Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
break;
case Instruction::NATIVEVOTE:
gas = GasCosts::voteGas;
// NATIVEVOTE reads two Solidity memory arrays. The stack length values are element
// counts, not byte lengths, so include the array length slot and 32 bytes per element.
gas += memoryGasForWordArray(-3, -2);
gas += memoryGasForWordArray(-1, 0);
break;
case Instruction::NATIVEWITHDRAWREWARD:
gas = GasCosts::withdrawGas;
Expand Down Expand Up @@ -293,6 +297,25 @@ GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosS
}));
}

GasMeter::GasConsumption GasMeter::memoryGasForWordArray(int _stackPosOffset, int _stackPosElementCount)
{
ExpressionClasses& classes = m_state->expressionClasses();
// The TVM reads and charges the 32-byte length slot even for empty arrays,
// so unlike memoryGas(int, int) there is no zero-size shortcut here.
ExpressionClasses::Id byteSize = classes.find(Instruction::MUL, {
m_state->relativeStackElement(_stackPosElementCount),
classes.find(u256(32))
});
ExpressionClasses::Id byteSizeWithLengthSlot = classes.find(Instruction::ADD, {
byteSize,
classes.find(u256(32))
});
return memoryGas(classes.find(Instruction::ADD, {
m_state->relativeStackElement(_stackPosOffset),
byteSizeWithLengthSlot
}));
}

unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVersion)
{
if (_instruction == Instruction::JUMPDEST)
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/GasMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ class GasMeter
/// @returns the memory gas for accessing the memory at a specific offset for a number of bytes
/// given as values on the stack at the given relative positions.
GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize);
/// @returns the memory gas for accessing a Solidity memory array whose element count is on the stack.
GasConsumption memoryGasForWordArray(int _stackPosOffset, int _stackPosElementCount);

std::shared_ptr<KnownState> m_state;
langutil::EVMVersion m_evmVersion;
Expand Down
6 changes: 3 additions & 3 deletions libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,10 @@ void ViewPureChecker::reportMutability(
m_errorReporter.typeError(
4006_error,
_location,
SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
SecondarySourceLocation().append("\"msg.value\", \"msg.tokenid\", \"msg.tokenvalue\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
m_currentFunction->isConstructor() ?
"This modifier uses \"msg.value\" or \"callvalue()\" and thus the constructor has to be payable."
: "This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
"This modifier uses \"msg.value\", \"msg.tokenid\", \"msg.tokenvalue\" or \"callvalue()\" and thus the constructor has to be payable."
: "This modifier uses \"msg.value\", \"msg.tokenid\", \"msg.tokenvalue\" or \"callvalue()\" and thus the function has to be payable or internal."
);
else
m_errorReporter.typeError(
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ detect_stray_source_files("${libsolutil_sources}" "libsolutil/")

set(libevmasm_sources
libevmasm/Assembler.cpp
libevmasm/GasMeter.cpp
libevmasm/Optimiser.cpp
)
detect_stray_source_files("${libevmasm_sources}" "libevmasm/")
Expand Down
99 changes: 99 additions & 0 deletions test/libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
This file is part of solidity.

solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Tests for gas estimation of TRON-specific instructions in libevmasm's GasMeter.
*/

#include <libevmasm/AssemblyItem.h>
#include <libevmasm/GasMeter.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/KnownState.h>

#include <test/Common.h>

#include <boost/test/unit_test.hpp>

#include <memory>
#include <optional>

using namespace solidity::evmasm;
using namespace solidity::langutil;

namespace solidity::frontend::test
{

namespace
{
/// Feeds the four NATIVEVOTE arguments as constants (or CALLVALUE for an unknown
/// element count) and returns the estimate for the NATIVEVOTE item itself.
GasMeter::GasConsumption estimateVote(
u256 const& _srOffset,
u256 const& _srCount,
u256 const& _tpOffset,
std::optional<u256> const& _tpCount
)
{
// KnownState keeps pointers into the fed items (feedItem's _copyItem defaults
// to false), so they have to outlive the meter, as they do in real assembly.
// NATIVEVOTE stack layout, top first: tpCount, tpOffset, srCount, srOffset.
AssemblyItems items{
_srOffset,
_srCount,
_tpOffset,
_tpCount ? AssemblyItem(*_tpCount) : AssemblyItem(Instruction::CALLVALUE),
Instruction::NATIVEVOTE
};
GasMeter meter(std::make_shared<KnownState>(), solidity::test::CommonOptions::get().evmVersion());
GasMeter::GasConsumption gas;
for (AssemblyItem const& item: items)
gas = meter.estimateMax(item);
return gas;
}
}

BOOST_AUTO_TEST_SUITE(EvmasmGasMeter)

BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays)
{
// java-tron (EnergyCost.getVoteWitnessCost2/3) charges per array
// memNeeded(offset, 32 * count + 32) and expands once to the larger end.
// Arrays at 128 and 256 with two elements each end at 224 and 352 bytes,
// i.e. 7 and 11 words: 3 * 11 = 33 on top of the flat vote cost.
GasMeter::GasConsumption gas = estimateVote(u256(128), u256(2), u256(256), u256(2));
BOOST_REQUIRE(!gas.isInfinite);
BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGas + 33));
}

BOOST_AUTO_TEST_CASE(nativevote_charges_length_slot_for_empty_arrays)
{
// The TVM reads and charges the 32-byte length slot even when count == 0:
// ends are 128+32 and 512+32 bytes, i.e. 5 and 17 words: 3 * 17 = 51.
GasMeter::GasConsumption gas = estimateVote(u256(128), u256(0), u256(512), u256(0));
BOOST_REQUIRE(!gas.isInfinite);
BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGas + 51));
}

BOOST_AUTO_TEST_CASE(nativevote_with_unknown_element_count_is_unbounded)
{
GasMeter::GasConsumption gas = estimateVote(u256(128), u256(2), u256(256), std::nullopt);
BOOST_CHECK(gas.isInfinite);
}

BOOST_AUTO_TEST_SUITE_END()

} // end namespaces
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
contract C {
modifier costs(uint _amount) { require(msg.value >= _amount); _; }
function f() costs(1 ether) public view {}
function f() costs(1 trx) public view {}
}
// ----
// TypeError 4006: (101-115): This modifier uses "msg.value" or "callvalue()" and thus the function has to be payable or internal.
// TypeError 4006: (101-113): This modifier uses "msg.value", "msg.tokenid", "msg.tokenvalue" or "callvalue()" and thus the function has to be payable or internal.