From 5f3c8e1eb74b3270652dae9a255fda2cd8b24959 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 15 Apr 2025 15:14:15 +0200 Subject: [PATCH 1/3] Add test case for calling TXCREATE in a loop --- .../state_transition_txcreate_test.cpp | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/test/unittests/state_transition_txcreate_test.cpp b/test/unittests/state_transition_txcreate_test.cpp index c7666ae19a..96c6eba6e6 100644 --- a/test/unittests/state_transition_txcreate_test.cpp +++ b/test/unittests/state_transition_txcreate_test.cpp @@ -1170,6 +1170,70 @@ TEST_F(state_transition, txcreate_from_blob_tx) expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; } +TEST_F(state_transition, txcreate_loop_valid) +{ + rev = EVMC_OSAKA; + block.gas_limit = 30'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncode(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + constexpr auto iterations = 800; + const auto initcode_hash = keccak256(init_container); + const auto factory_code = + push0() + // initial salt + push(1) + OP_ADD + // increment salt + OP_DUP1 + push(iterations + 1) + rjumpi(45, OP_EQ) + // if (salt == iterations + 1) break; + txcreate().value(0).initcode(initcode_hash).input(0, 0).salt(OP_DUP4) + OP_POP + + rjump(-56) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 6); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + iterations; + for (uint16_t salt = 1; salt <= iterations; ++salt) + { + const auto create_address = compute_eofcreate_address(*tx.to, bytes32{salt}); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; + } +} + +TEST_F(state_transition, txcreate_loop_invalid) +{ + rev = EVMC_OSAKA; + block.gas_limit = 30'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const bytecode init_container = OP_STOP; // invalid EOF + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + constexpr auto iterations = 900; + const auto initcode_hash = keccak256(init_container); + const auto factory_code = + push0() + // initial salt + push(1) + OP_ADD + // increment salt + OP_DUP1 + push(iterations + 1) + rjumpi(45, OP_EQ) + // if (salt == iterations + 1) break; + txcreate().value(0).initcode(initcode_hash).input(0, 0).salt(OP_DUP4) + OP_POP + + rjump(-56) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 6); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; +} TEST_F(state_transition, legacy_txcreate_empty_auxdata) { @@ -2290,3 +2354,67 @@ TEST_F(state_transition, legacy_txcreate_from_blob_tx) expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; // CREATE must fail expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; } + +TEST_F(state_transition, legacy_txcreate_loop_valid) +{ + rev = EVMC_OSAKA; + block.gas_limit = 30'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncode(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + constexpr auto iterations = 800; + const auto initcode_hash = keccak256(init_container); + const auto factory_code = + push0() + // initial salt + OP_JUMPDEST + push(1) + OP_ADD + // increment salt + OP_DUP1 + push(iterations + 1) + jumpi(58, OP_EQ) + // if (salt == iterations + 1) break; + txcreate().value(0).initcode(initcode_hash).input(0, 0).salt(OP_DUP4) + OP_POP + jump(1) + + OP_JUMPDEST + OP_STOP; + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_code}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + iterations; + for (uint16_t salt = 1; salt <= iterations; ++salt) + { + const auto create_address = compute_eofcreate_address(*tx.to, bytes32{salt}); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; + } +} + +TEST_F(state_transition, legacy_txcreate_loop_invalid) +{ + rev = EVMC_OSAKA; + block.gas_limit = 30'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const bytecode init_container = OP_STOP; // invalid EOF + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + constexpr auto iterations = 900; + const auto initcode_hash = keccak256(init_container); + const auto factory_code = + push0() + // initial salt + push(1) + OP_ADD + // increment salt + OP_DUP1 + push(iterations + 1) + rjumpi(45, OP_EQ) + // if (salt == iterations + 1) break; + txcreate().value(0).initcode(initcode_hash).input(0, 0).salt(OP_DUP4) + OP_POP + + rjump(-56) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 6); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; +} From 556074da6eaccab7d84bd19d3310bd5fcb5d6038 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 15 Apr 2025 18:03:09 +0200 Subject: [PATCH 2/3] Reorder TXCREATE checks --- lib/evmone/instructions_calls.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index d33baae3f9..33728bed85 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -404,42 +404,40 @@ Result create_eof_impl( if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256)) return {EVMC_OUT_OF_GAS, gas_left}; + constexpr auto pos_advance = (Op == OP_EOFCREATE ? 2 : 1); + pos += pos_advance; + + if (state.msg->depth >= 1024) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + + if (endowment != 0 && + intx::be::load(state.host.get_balance(state.msg->recipient)) < endowment) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + bytes_view initcontainer; if constexpr (Op == OP_EOFCREATE) { - const auto initcontainer_index = pos[1]; - pos += 2; + const auto initcontainer_index = pos[-1]; const auto& container = state.original_code; const auto& eof_header = state.analysis.baseline->eof_header(); initcontainer = eof_header.get_container(container, initcontainer_index); } else { - pos += 1; initcontainer = state.get_tx_initcode_by_hash(initcode_hash); // In case initcode was not found, empty bytes_view was returned. // Transaction initcodes are not allowed to be empty. if (initcontainer.empty()) return {EVMC_SUCCESS, gas_left}; // "Light" failure - } - - const auto input_offset = static_cast(input_offset_u256); - const auto input_size = static_cast(input_size_u256); - if (state.msg->depth >= 1024) - return {EVMC_SUCCESS, gas_left}; // "Light" failure. - - if (endowment != 0 && - intx::be::load(state.host.get_balance(state.msg->recipient)) < endowment) - return {EVMC_SUCCESS, gas_left}; // "Light" failure. - - if constexpr (Op == OP_TXCREATE) - { const auto error_subcont = validate_eof(state.rev, ContainerKind::initcode, initcontainer); if (error_subcont != EOFValidationError::success) return {EVMC_SUCCESS, gas_left}; // "Light" failure. } + const auto input_offset = static_cast(input_offset_u256); + const auto input_size = static_cast(input_size_u256); + evmc_message msg{.kind = to_call_kind(Op)}; msg.gas = gas_left - gas_left / 64; if (input_size > 0) From d1bc27588925f2a88135f1b035be4502534bf66c Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 11 Apr 2025 18:04:24 +0200 Subject: [PATCH 3/3] eof: Add caching of TXCREATE validation results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- lib/evmone/execution_state.hpp | 20 +++++++++++++++----- lib/evmone/instructions_calls.cpp | 18 ++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/evmone/execution_state.hpp b/lib/evmone/execution_state.hpp index b9af355c16..1f84bdc0bf 100644 --- a/lib/evmone/execution_state.hpp +++ b/lib/evmone/execution_state.hpp @@ -145,6 +145,15 @@ class Memory void clear() noexcept { m_size = 0; } }; +/// Initcode read from Initcode Transaction (EIP-7873). +struct TransactionInitcode +{ + /// Initcode bytes. + bytes_view code; + /// Result of initcode validation, if it was validated. + /// std::nullopt if initcode was not validated yet. + std::optional is_valid; +}; /// Generic execution state for generic instructions implementations. // NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) @@ -172,7 +181,7 @@ class ExecutionState private: evmc_tx_context m_tx = {}; - std::optional> m_initcodes; + std::optional> m_initcodes; public: /// Pointer to code analysis. @@ -230,8 +239,8 @@ class ExecutionState /// Get initcode by its hash from transaction initcodes. /// - /// Returns empty bytes_view if no such initcode was found. - [[nodiscard]] bytes_view get_tx_initcode_by_hash(const evmc_bytes32& hash) noexcept + /// Returns nullptr if no such initcode was found. + [[nodiscard]] TransactionInitcode* get_tx_initcode_by_hash(const evmc_bytes32& hash) { if (!m_initcodes.has_value()) { @@ -240,12 +249,13 @@ class ExecutionState for (size_t i = 0; i < tx_context.initcodes_count; ++i) { const auto& initcode = tx_context.initcodes[i]; - m_initcodes->insert({initcode.hash, {initcode.code, initcode.code_size}}); + m_initcodes->insert({initcode.hash, + {.code = {initcode.code, initcode.code_size}, .is_valid = std::nullopt}}); } } const auto it = m_initcodes->find(hash); - return it != m_initcodes->end() ? it->second : bytes_view{}; + return it != m_initcodes->end() ? &it->second : nullptr; } }; } // namespace evmone diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index 33728bed85..5edf4fd7d9 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -424,14 +424,20 @@ Result create_eof_impl( } else { - initcontainer = state.get_tx_initcode_by_hash(initcode_hash); - // In case initcode was not found, empty bytes_view was returned. - // Transaction initcodes are not allowed to be empty. - if (initcontainer.empty()) + auto* tx_initcode = state.get_tx_initcode_by_hash(initcode_hash); + // In case initcode was not found, nullptr was returned. + if (tx_initcode == nullptr) return {EVMC_SUCCESS, gas_left}; // "Light" failure + initcontainer = tx_initcode->code; - const auto error_subcont = validate_eof(state.rev, ContainerKind::initcode, initcontainer); - if (error_subcont != EOFValidationError::success) + if (!tx_initcode->is_valid.has_value()) + { + const auto error_subcont = + validate_eof(state.rev, ContainerKind::initcode, initcontainer); + tx_initcode->is_valid = (error_subcont == EOFValidationError::success); + } + + if (!*tx_initcode->is_valid) return {EVMC_SUCCESS, gas_left}; // "Light" failure. }