From 63453b52bbdd0c99747ff8b37dae4934c7fe76a8 Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 2 Jul 2026 11:19:24 +0800 Subject: [PATCH 1/2] fix(tron): account for NATIVEVOTE gas and modifier diagnostics --- libevmasm/GasMeter.cpp | 25 +++++++++++++++++++ libevmasm/GasMeter.h | 2 ++ libsolidity/analysis/ViewPureChecker.cpp | 6 ++--- .../memberLookup/msg_value_modifier_view.sol | 4 +-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index c6bd0737142d..b4429c3ddec2 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -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; @@ -293,6 +297,27 @@ GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosS })); } +GasMeter::GasConsumption GasMeter::memoryGasForWordArray(int _stackPosOffset, int _stackPosElementCount) +{ + ExpressionClasses& classes = m_state->expressionClasses(); + ExpressionClasses::Id elementCount = m_state->relativeStackElement(_stackPosElementCount); + if (classes.knownZero(elementCount)) + return GasConsumption(0); + + ExpressionClasses::Id byteSize = classes.find(Instruction::MUL, { + elementCount, + 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) diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 1372205af894..213fad69cc98 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -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 m_state; langutil::EVMVersion m_evmVersion; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 71c4020a9b44..88cc1fb49822 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -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( diff --git a/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol b/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol index 29e47138cd99..7e128966174b 100644 --- a/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol +++ b/test/libsolidity/syntaxTests/memberLookup/msg_value_modifier_view.sol @@ -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. From 688f895151e02f18353736e47e60716fbb685a5a Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 2 Jul 2026 21:45:30 +0800 Subject: [PATCH 2/2] fix(gasmeter): charge NATIVEVOTE length slot even for empty arrays --- libevmasm/GasMeter.cpp | 8 ++- test/CMakeLists.txt | 1 + test/libevmasm/GasMeter.cpp | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 test/libevmasm/GasMeter.cpp diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index b4429c3ddec2..81572d0e59e4 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -300,12 +300,10 @@ GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosS GasMeter::GasConsumption GasMeter::memoryGasForWordArray(int _stackPosOffset, int _stackPosElementCount) { ExpressionClasses& classes = m_state->expressionClasses(); - ExpressionClasses::Id elementCount = m_state->relativeStackElement(_stackPosElementCount); - if (classes.knownZero(elementCount)) - return GasConsumption(0); - + // 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, { - elementCount, + m_state->relativeStackElement(_stackPosElementCount), classes.find(u256(32)) }); ExpressionClasses::Id byteSizeWithLengthSlot = classes.find(Instruction::ADD, { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da115f0d4a5b..62ef14b30bfb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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/") diff --git a/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp new file mode 100644 index 000000000000..6d82f86552ab --- /dev/null +++ b/test/libevmasm/GasMeter.cpp @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Tests for gas estimation of TRON-specific instructions in libevmasm's GasMeter. + */ + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +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 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(), 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