From 3971776788238af01a2eb90fd8f164d58666495c Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 15:57:03 +0100 Subject: [PATCH 01/33] Validate EIP-7702 set-code transactions and process authorization list --- lib/evmone/CMakeLists.txt | 1 + lib/evmone/delegation.hpp | 23 ++++++++ test/state/errors.hpp | 6 +++ test/state/state.cpp | 106 ++++++++++++++++++++++++++++++++++++- test/state/transaction.hpp | 15 ++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 lib/evmone/delegation.hpp diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index 777c0800f2..75daf16d0c 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(evmone baseline_instruction_table.cpp baseline_instruction_table.hpp constants.hpp + delegation.hpp eof.cpp eof.hpp instructions.hpp diff --git a/lib/evmone/delegation.hpp b/lib/evmone/delegation.hpp new file mode 100644 index 0000000000..b49cc544a3 --- /dev/null +++ b/lib/evmone/delegation.hpp @@ -0,0 +1,23 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +namespace evmone +{ +using evmc::bytes_view; + +/// Prefix of code for delegated accounts +/// defined by [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) +constexpr uint8_t DELEGATION_MAGIC_BYTES[] = {0xef, 0x01, 0x00}; +constexpr bytes_view DELEGATION_MAGIC{DELEGATION_MAGIC_BYTES, std::size(DELEGATION_MAGIC_BYTES)}; + +/// Check if code contains EIP-7702 delegation designator +inline constexpr bool is_code_delegated(bytes_view code) noexcept +{ + return code.starts_with(DELEGATION_MAGIC); +} +} // namespace evmone diff --git a/test/state/errors.hpp b/test/state/errors.hpp index 5d9f95d6b1..ac2f575001 100644 --- a/test/state/errors.hpp +++ b/test/state/errors.hpp @@ -27,6 +27,8 @@ enum ErrorCode : int EMPTY_BLOB_HASHES_LIST, INVALID_BLOB_HASH_VERSION, BLOB_GAS_LIMIT_EXCEEDED, + CREATE_SET_CODE_TX, + EMPTY_AUTHORIZATION_LIST, UNKNOWN_ERROR, }; @@ -73,6 +75,10 @@ inline const std::error_category& evmone_category() noexcept return "invalid blob hash version"; case BLOB_GAS_LIMIT_EXCEEDED: return "blob gas limit exceeded"; + case CREATE_SET_CODE_TX: + return "set code transaction must not be a create transaction"; + case EMPTY_AUTHORIZATION_LIST: + return "empty authorization list"; case UNKNOWN_ERROR: return "Unknown error"; default: diff --git a/test/state/state.cpp b/test/state/state.cpp index 8d19f798d5..98e460368a 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -7,13 +7,24 @@ #include "host.hpp" #include "state_view.hpp" #include +#include #include +#include #include +using namespace intx; + namespace evmone::state { namespace { +/// Secp256k1's N/2 is the upper bound of the signature's s value. +constexpr auto SECP256K1N_OVER_2 = evmmax::secp256k1::Order / 2; +/// EIP-7702: The cost of authorization that sets delegation to an account that didn't exist before. +constexpr auto AUTHORIZATION_EMPTY_ACCOUNT_COST = 25000; +/// EIP-7702: The cost of authorization that sets delegation to an account that already exists. +constexpr auto AUTHORIZATION_BASE_COST = 12500; + constexpr int64_t num_words(size_t size_in_bytes) noexcept { return static_cast((size_in_bytes + 31) / 32); @@ -63,11 +74,14 @@ TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& const auto access_list_cost = compute_access_list_cost(tx.access_list); + const auto auth_list_cost = + static_cast(tx.authorization_list.size()) * AUTHORIZATION_EMPTY_ACCOUNT_COST; + const auto initcode_cost = (is_create && rev >= EVMC_SHANGHAI) ? INITCODE_WORD_COST * num_words(tx.data.size()) : 0; const auto intrinsic_cost = - TX_BASE_COST + create_cost + data_cost + access_list_cost + initcode_cost; + TX_BASE_COST + create_cost + data_cost + access_list_cost + auth_list_cost + initcode_cost; // EIP-7623: Compute the minimum cost for the transaction by. If disabled, just use 0. const auto min_cost = @@ -344,6 +358,15 @@ std::variant validate_transaction( return make_error_code(BLOB_GAS_LIMIT_EXCEEDED); break; + case Transaction::Type::set_code: + if (rev < EVMC_PRAGUE) + return make_error_code(TX_TYPE_NOT_SUPPORTED); + if (!tx.to.has_value()) + return make_error_code(CREATE_SET_CODE_TX); + if (tx.authorization_list.empty()) + return make_error_code(EMPTY_AUTHORIZATION_LIST); + break; + default:; } @@ -460,6 +483,85 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc assert(sender_acc.nonce < Account::NonceMax); // Required for valid tx. ++sender_acc.nonce; // Bump sender nonce. + auto delegation_refund = 0; + + for (const auto& auth : tx.authorization_list) + { + if (auth.chain_id != 0 && auth.chain_id != tx.chain_id) + continue; + + if (auth.nonce == Account::NonceMax) + continue; + + // Check if signer could be ecrecovered. + if (!auth.signer.has_value()) + continue; + + if (auth.s > SECP256K1N_OVER_2) + continue; + + // Check if authority exists. + auto* authority_ptr = state.find(*auth.signer); + if (authority_ptr != nullptr && !authority_ptr->is_empty()) + { + authority_ptr->access_status = EVMC_ACCESS_WARM; + + // Skip if authority has non-delegated code. + if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && + !is_code_delegated(state.get_code(*auth.signer))) + continue; + + // Skip if authorization nonce is incorrect. + if (auth.nonce != authority_ptr->nonce) + continue; + } + else + { + // Create authority account. + // It is still considered empty at this point until nonce increment, but already warm. + authority_ptr = &state.get_or_insert( + *auth.signer, {.erase_if_empty = true, .access_status = EVMC_ACCESS_WARM}); + + // Skip if authorization nonce is incorrect. + if (auth.nonce != 0) + continue; + } + + // Refund if authority account creation is not needed, + // i.e. if it had non-empty balance, nonce, code or storage. + if (authority_ptr != nullptr && + (!authority_ptr->is_empty() || authority_ptr->has_initial_storage)) + { + static constexpr int64_t EXISTING_AUTHORITY_REFUND = + AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; + delegation_refund += EXISTING_AUTHORITY_REFUND; + } + + if (auth.addr == address{}) + { + if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH) + authority_ptr->code_changed = true; + authority_ptr->code.clear(); + authority_ptr->code_hash = Account::EMPTY_CODE_HASH; + } + else + { + bytes new_code(bytes(DELEGATION_MAGIC) + bytes(auth.addr)); + if (authority_ptr->code != new_code) + authority_ptr->code_changed = true; + authority_ptr->code = std::move(new_code); + + // TODO The hash of delegated accounts is not used anywhere, + // it is only important that it is not a hash of empty. + // We could avoid this hashing, if we found a way to not rely only on code_hash for + // emptiness checks. + // (e.g for emptiness check code_hash == EMPTY_CODE_HASH && !code_changed) + authority_ptr->code_hash = keccak256(authority_ptr->code); + } + + ++authority_ptr->nonce; + } + const auto base_fee = (rev >= EVMC_LONDON) ? block.base_fee : 0; assert(tx.max_gas_price >= base_fee); // Required for valid tx. assert(tx.max_gas_price >= tx.max_priority_gas_price); // Required for valid tx. @@ -506,7 +608,7 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc const auto max_refund_quotient = rev >= EVMC_LONDON ? 5 : 2; const auto refund_limit = gas_used / max_refund_quotient; - const auto refund = std::min(result.gas_refund, refund_limit); + const auto refund = std::min(delegation_refund + result.gas_refund, refund_limit); gas_used -= refund; assert(gas_used > 0); diff --git a/test/state/transaction.hpp b/test/state/transaction.hpp index 0932b70373..ef83a2b150 100644 --- a/test/state/transaction.hpp +++ b/test/state/transaction.hpp @@ -14,6 +14,20 @@ namespace evmone::state { using AccessList = std::vector>>; +struct Authorization +{ + intx::uint256 chain_id; + address addr; + uint64_t nonce = 0; + /// Signer is empty if cannot be ecrecovered from r, s, v. + std::optional
signer; + intx::uint256 r; + intx::uint256 s; + intx::uint256 v; +}; + +using AuthorizationList = std::vector; + struct Transaction { /// The type of the transaction. @@ -65,6 +79,7 @@ struct Transaction intx::uint256 r; intx::uint256 s; uint8_t v = 0; + AuthorizationList authorization_list; }; /// Transaction properties computed during the validation needed for the execution. From ca8bfed9f153b07ecb11dd0171644d43687adc99 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 16:10:38 +0100 Subject: [PATCH 02/33] Support code delegation in instructions implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- lib/evmone/CMakeLists.txt | 1 + lib/evmone/delegation.cpp | 27 ++++++++++++++++ lib/evmone/delegation.hpp | 4 +++ lib/evmone/instructions_calls.cpp | 45 +++++++++++++++++++++++++-- test/unittests/evm_eof_calls_test.cpp | 18 ++++++----- 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 lib/evmone/delegation.cpp diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index 75daf16d0c..c4fa519091 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(evmone baseline_instruction_table.cpp baseline_instruction_table.hpp constants.hpp + delegation.cpp delegation.hpp eof.cpp eof.hpp diff --git a/lib/evmone/delegation.cpp b/lib/evmone/delegation.cpp new file mode 100644 index 0000000000..512be760f6 --- /dev/null +++ b/lib/evmone/delegation.cpp @@ -0,0 +1,27 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#include "delegation.hpp" +#include + +namespace evmone +{ +std::optional get_delegate_address( + const evmc::HostInterface& host, const evmc::address& addr) noexcept +{ + // Load the code prefix up to the delegation designation size. + uint8_t designation_buffer[std::size(DELEGATION_MAGIC) + sizeof(evmc::address)]; + const auto size = host.copy_code(addr, 0, designation_buffer, std::size(designation_buffer)); + const bytes_view designation{designation_buffer, size}; + + if (!is_code_delegated(designation)) + return {}; + + // Copy the delegate address from the designation buffer. + evmc::address delegate_address; + // Assume the designation with the valid magic has also valid length. + assert(designation.size() == std::size(designation_buffer)); + std::ranges::copy(designation.substr(std::size(DELEGATION_MAGIC)), delegate_address.bytes); + return delegate_address; +} +} // namespace evmone diff --git a/lib/evmone/delegation.hpp b/lib/evmone/delegation.hpp index b49cc544a3..06364c0fcf 100644 --- a/lib/evmone/delegation.hpp +++ b/lib/evmone/delegation.hpp @@ -20,4 +20,8 @@ inline constexpr bool is_code_delegated(bytes_view code) noexcept { return code.starts_with(DELEGATION_MAGIC); } + +/// Get EIP-7702 delegate address from the code of addr, if it is delegated. +std::optional get_delegate_address( + const evmc::HostInterface& host, const evmc::address& addr) noexcept; } // namespace evmone diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index a97000debb..059dbba62a 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -2,6 +2,7 @@ // Copyright 2019 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "delegation.hpp" #include "eof.hpp" #include "instructions.hpp" @@ -16,6 +17,34 @@ constexpr auto EXTCALL_ABORT = 2; namespace evmone::instr::core { +namespace +{ +/// Get target address of a code executing instruction. +/// +/// Returns EIP-7702 delegate address if addr is delegated, or addr itself otherwise. +/// Applies gas charge for accessing delegate account and may fail with out of gas. +inline std::variant get_target_address( + const evmc::address& addr, int64_t& gas_left, ExecutionState& state) noexcept +{ + if (state.rev < EVMC_PRAGUE) + return addr; + + const auto delegate_addr = get_delegate_address(state.host, addr); + if (!delegate_addr) + return addr; + + const auto delegate_account_access_cost = + (state.host.access_account(*delegate_addr) == EVMC_ACCESS_COLD ? + instr::cold_account_access_cost : + instr::warm_storage_read_cost); + + if ((gas_left -= delegate_account_access_cost) < 0) + return Result{EVMC_OUT_OF_GAS, gas_left}; + + return *delegate_addr; +} +} // namespace + /// Converts an opcode to matching EVMC call kind. /// NOLINTNEXTLINE(misc-use-internal-linkage) fixed in clang-tidy 20. consteval evmc_call_kind to_call_kind(Opcode op) noexcept @@ -67,6 +96,12 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce return {EVMC_OUT_OF_GAS, gas_left}; } + const auto target_addr_or_result = get_target_address(dst, gas_left, state); + if (const auto* result = std::get_if(&target_addr_or_result)) + return *result; + + const auto& code_addr = std::get(target_addr_or_result); + if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256)) return {EVMC_OUT_OF_GAS, gas_left}; @@ -82,7 +117,7 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce msg.flags = (Op == OP_STATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags; msg.depth = state.msg->depth + 1; msg.recipient = (Op == OP_CALL || Op == OP_STATICCALL) ? dst : state.msg->recipient; - msg.code_address = dst; + msg.code_address = code_addr; msg.sender = (Op == OP_DELEGATECALL) ? state.msg->sender : state.msg->recipient; msg.value = (Op == OP_DELEGATECALL) ? state.msg->value : intx::be::store(value); @@ -178,6 +213,12 @@ Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noe return {EVMC_OUT_OF_GAS, gas_left}; } + const auto target_addr_or_result = get_target_address(dst, gas_left, state); + if (const auto* result = std::get_if(&target_addr_or_result)) + return *result; + + const auto& code_addr = std::get(target_addr_or_result); + if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256)) return {EVMC_OUT_OF_GAS, gas_left}; @@ -188,7 +229,7 @@ Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noe msg.flags = (Op == OP_EXTSTATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags; msg.depth = state.msg->depth + 1; msg.recipient = (Op != OP_EXTDELEGATECALL) ? dst : state.msg->recipient; - msg.code_address = dst; + msg.code_address = code_addr; msg.sender = (Op == OP_EXTDELEGATECALL) ? state.msg->sender : state.msg->recipient; msg.value = (Op == OP_EXTDELEGATECALL) ? state.msg->value : intx::be::store(value); diff --git a/test/unittests/evm_eof_calls_test.cpp b/test/unittests/evm_eof_calls_test.cpp index d3f40ae118..50cf3ae896 100644 --- a/test/unittests/evm_eof_calls_test.cpp +++ b/test/unittests/evm_eof_calls_test.cpp @@ -203,11 +203,12 @@ TEST_P(evm, extcall_new_account_creation_cost) EXPECT_EQ(call_msg.value.bytes[31], 0); EXPECT_EQ(call_msg.input_size, 0); EXPECT_GAS_USED(EVMC_SUCCESS, gas_before_call + call_msg.gas + 3 + 3 + 3 + 3 + 3); - ASSERT_EQ(host.recorded_account_accesses.size(), 4); + ASSERT_EQ(host.recorded_account_accesses.size(), 5); EXPECT_EQ(host.recorded_account_accesses[0], 0x00_address); // EIP-2929 tweak EXPECT_EQ(host.recorded_account_accesses[1], msg.recipient); // EIP-2929 tweak - EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // ? - EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // Call. + EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // EIP-2929 warm up call_dst + EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // EIP-7702 delegation check + EXPECT_EQ(host.recorded_account_accesses[4], call_dst); // Call. host.recorded_account_accesses.clear(); host.recorded_calls.clear(); } @@ -227,13 +228,14 @@ TEST_P(evm, extcall_new_account_creation_cost) EXPECT_EQ(call_msg.value.bytes[31], 1); EXPECT_EQ(call_msg.input_size, 0); EXPECT_GAS_USED(EVMC_SUCCESS, gas_before_call + call_msg.gas + 3 + 3 + 3 + 3 + 3); - ASSERT_EQ(host.recorded_account_accesses.size(), 6); + ASSERT_EQ(host.recorded_account_accesses.size(), 7); EXPECT_EQ(host.recorded_account_accesses[0], 0x00_address); // EIP-2929 tweak EXPECT_EQ(host.recorded_account_accesses[1], msg.recipient); // EIP-2929 tweak - EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // ? - EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // Account exist?. - EXPECT_EQ(host.recorded_account_accesses[4], msg.recipient); // Balance. - EXPECT_EQ(host.recorded_account_accesses[5], call_dst); // Call. + EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // EIP-2929 warm up call_dst + EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // EIP-7702 delegation check + EXPECT_EQ(host.recorded_account_accesses[4], call_dst); // Account exist?. + EXPECT_EQ(host.recorded_account_accesses[5], msg.recipient); // Balance. + EXPECT_EQ(host.recorded_account_accesses[6], call_dst); // Call. host.recorded_account_accesses.clear(); host.recorded_calls.clear(); } From 3ff3a713bcfdd7344d96fd8d00a136a6d5ce2798 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 7 Feb 2025 15:37:07 +0100 Subject: [PATCH 03/33] Allow account with delegated code to be originator of transactions --- test/state/state.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 98e460368a..b00c2bd8ca 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -407,7 +407,8 @@ std::variant validate_transaction( const auto sender_acc = state_view.get_account(tx.sender).value_or( StateView::Account{.code_hash = Account::EMPTY_CODE_HASH}); - if (sender_acc.code_hash != Account::EMPTY_CODE_HASH) + if (sender_acc.code_hash != Account::EMPTY_CODE_HASH && + !is_code_delegated(state_view.get_account_code(tx.sender))) return make_error_code(SENDER_NOT_EOA); // Origin must not be a contract (EIP-3607). if (sender_acc.nonce == Account::NonceMax) // Nonce value limit (EIP-2681). From c1fdd9d9d5f065d47503b87f73c5d2c675a84822 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 16:06:55 +0100 Subject: [PATCH 04/33] Support delegation for transaction recipient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- lib/evmone/delegation.cpp | 2 ++ lib/evmone/delegation.hpp | 5 +++-- test/state/state.cpp | 13 ++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/evmone/delegation.cpp b/lib/evmone/delegation.cpp index 512be760f6..cb39733c9c 100644 --- a/lib/evmone/delegation.cpp +++ b/lib/evmone/delegation.cpp @@ -10,6 +10,8 @@ std::optional get_delegate_address( const evmc::HostInterface& host, const evmc::address& addr) noexcept { // Load the code prefix up to the delegation designation size. + // The HostInterface::copy_code() copies up to the addr's code size + // and returns the number of bytes copied. uint8_t designation_buffer[std::size(DELEGATION_MAGIC) + sizeof(evmc::address)]; const auto size = host.copy_code(addr, 0, designation_buffer, std::size(designation_buffer)); const bytes_view designation{designation_buffer, size}; diff --git a/lib/evmone/delegation.hpp b/lib/evmone/delegation.hpp index 06364c0fcf..9c3799e66a 100644 --- a/lib/evmone/delegation.hpp +++ b/lib/evmone/delegation.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace evmone { @@ -16,12 +17,12 @@ constexpr uint8_t DELEGATION_MAGIC_BYTES[] = {0xef, 0x01, 0x00}; constexpr bytes_view DELEGATION_MAGIC{DELEGATION_MAGIC_BYTES, std::size(DELEGATION_MAGIC_BYTES)}; /// Check if code contains EIP-7702 delegation designator -inline constexpr bool is_code_delegated(bytes_view code) noexcept +constexpr bool is_code_delegated(bytes_view code) noexcept { return code.starts_with(DELEGATION_MAGIC); } /// Get EIP-7702 delegate address from the code of addr, if it is delegated. -std::optional get_delegate_address( +EVMC_EXPORT std::optional get_delegate_address( const evmc::HostInterface& host, const evmc::address& addr) noexcept; } // namespace evmone diff --git a/test/state/state.cpp b/test/state/state.cpp index b00c2bd8ca..0b7288711a 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -603,7 +603,18 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (rev >= EVMC_SHANGHAI) host.access_account(block.coinbase); - const auto result = host.call(build_message(tx, tx_props.execution_gas_limit, rev)); + auto message = build_message(tx, tx_props.execution_gas_limit, rev); + if (tx.to.has_value()) + { + if (const auto delegate = get_delegate_address(host, *tx.to)) + { + message.code_address = *delegate; + message.flags |= EVMC_DELEGATED; + host.access_account(message.code_address); + } + } + + const auto result = host.call(message); auto gas_used = tx.gas_limit - result.gas_left; From 1e0f7ab2c222593c2f75c5bac08f88b919736d17 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 15:59:04 +0100 Subject: [PATCH 05/33] Do not execute precompile when calling account delegated to precompile --- lib/evmone/instructions_calls.cpp | 8 ++++++++ test/state/host.cpp | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index 059dbba62a..67736066a7 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -115,6 +115,10 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce evmc_message msg{.kind = to_call_kind(Op)}; msg.flags = (Op == OP_STATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags; + if (dst != code_addr) + msg.flags |= EVMC_DELEGATED; + else + msg.flags &= ~std::underlying_type_t{EVMC_DELEGATED}; msg.depth = state.msg->depth + 1; msg.recipient = (Op == OP_CALL || Op == OP_STATICCALL) ? dst : state.msg->recipient; msg.code_address = code_addr; @@ -227,6 +231,10 @@ Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noe evmc_message msg{.kind = to_call_kind(Op)}; msg.flags = (Op == OP_EXTSTATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags; + if (dst != code_addr) + msg.flags |= EVMC_DELEGATED; + else + msg.flags &= ~std::underlying_type_t{EVMC_DELEGATED}; msg.depth = state.msg->depth + 1; msg.recipient = (Op != OP_EXTDELEGATECALL) ? dst : state.msg->recipient; msg.code_address = code_addr; diff --git a/test/state/host.cpp b/test/state/host.cpp index 4f51d1982a..99ed70b438 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -440,7 +440,7 @@ evmc::Result Host::execute_message(const evmc_message& msg) noexcept } } - if (is_precompile(m_rev, msg.code_address)) + if ((msg.flags & EVMC_DELEGATED) == 0 && is_precompile(m_rev, msg.code_address)) return call_precompile(m_rev, msg); // TODO: get_code() performs the account lookup. Add a way to get an account with code? From 865cc13ef72f1b500eeb226408dc23f8a0cb685b Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 15:44:41 +0100 Subject: [PATCH 06/33] State transition tests for EIP-7702 Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> --- test/unittests/CMakeLists.txt | 1 + .../state_transition_eip7702_test.cpp | 305 ++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 test/unittests/state_transition_eip7702_test.cpp diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 03014aaa43..d86803d71c 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -68,6 +68,7 @@ target_sources( state_transition_call_test.cpp state_transition_create_test.cpp state_transition_eip663_test.cpp + state_transition_eip7702_test.cpp state_transition_eof_calls_test.cpp state_transition_eof_create_test.cpp state_transition_extcode_test.cpp diff --git a/test/unittests/state_transition_eip7702_test.cpp b/test/unittests/state_transition_eip7702_test.cpp new file mode 100644 index 0000000000..f74a1f4cf5 --- /dev/null +++ b/test/unittests/state_transition_eip7702_test.cpp @@ -0,0 +1,305 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "../utils/bytecode.hpp" +#include "state_transition.hpp" + +using namespace evmc::literals; +using namespace evmone::test; + +TEST_F(state_transition, eip7702_set_code_transaction) +{ + rev = EVMC_PRAGUE; + + constexpr auto authority = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[delegate] = {.code = bytecode{OP_STOP}}; + tx.to = To; + tx.type = Transaction::Type::set_code; + tx.authorization_list = {{.addr = delegate, .nonce = 0, .signer = authority}}; + pre[To] = {.code = ret(0)}; + + expect.post[To].exists = true; + expect.post[delegate].exists = true; + expect.post[authority].nonce = 1; + expect.post[authority].code = bytes{0xef, 0x01, 0x00} + hex(delegate); +} + +TEST_F(state_transition, eip7702_set_code_transaction_authority_is_sender) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + pre[delegate] = {.code = bytecode{OP_STOP}}; + tx.to = To; + tx.type = Transaction::Type::set_code; + // Sender nonce is 1 in prestate, it is bumped once for tx and then another time for delegation + tx.authorization_list = {{.addr = delegate, .nonce = 2, .signer = Sender}}; + pre[To] = {.code = ret(0)}; + + expect.post[Sender].nonce = 3; + expect.post[Sender].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + expect.post[To].exists = true; + expect.post[delegate].exists = true; +} + +TEST_F(state_transition, eip7702_set_code_transaction_authority_is_to) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + pre[delegate] = {.code = bytecode{OP_STOP}}; + tx.to = To; + tx.type = Transaction::Type::set_code; + tx.authorization_list = {{.addr = delegate, .nonce = 0, .signer = To}}; + + expect.post[delegate].exists = true; + expect.post[To].nonce = pre[To].nonce + 1; + expect.post[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); +} + +TEST_F(state_transition, eip7702_extcodesize) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + pre[delegate] = {.code = 1024 * OP_JUMPDEST}; + tx.to = To; + pre[To] = {.code = sstore(1, push(callee) + OP_EXTCODESIZE)}; + + expect.post[callee].exists = true; + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x17_bytes32; +} + +TEST_F(state_transition, eip7702_extcodehash_delegation_to_empty) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + tx.to = To; + pre[To] = {.code = sstore(0, push(callee) + OP_EXTCODEHASH) + sstore(1, 1)}; + + expect.post[callee].exists = true; + expect.post[delegate].exists = false; + expect.post[To].storage[0x00_bytes32] = keccak256(bytes{0xef, 0x01, 0x00} + hex(delegate)); + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, eip7702_extcodecopy) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + tx.to = To; + pre[To] = {.code = push(10) + push0() + push0() + push(callee) + OP_EXTCODECOPY + + sstore(0, mload(0)) + sstore(1, 1)}; + + expect.post[callee].exists = true; + expect.post[delegate].exists = false; + expect.post[To].storage[0x00_bytes32] = + 0xef01000000000000000000000000000000000000000000000000000000000000_bytes32; + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, eip7702_call) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + pre[delegate] = {.code = sstore(0, 0x11)}; + tx.to = To; + pre[To] = {.code = sstore(1, call(callee).gas(50'000))}; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].storage[0x00_bytes32] = 0x11_bytes32; +} + +TEST_F(state_transition, eip7702_call_with_value) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + pre[delegate] = {.code = sstore(0, 0x11)}; + tx.to = To; + pre[To] = {.balance = 10, .code = sstore(1, call(callee).gas(50'000).value(10))}; + + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[To].balance = 0; + expect.post[callee].storage[0x00_bytes32] = 0x11_bytes32; + expect.post[callee].balance = 10; + expect.post[delegate].balance = 0; +} + +TEST_F(state_transition, eip7702_call_warms_up_delegate) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + constexpr auto delegate = 0xde1e_address; + pre[callee] = {.nonce = 1, .code = bytes{0xef, 0x01, 0x00} + hex(delegate)}; + pre[delegate] = {.code = bytecode{OP_STOP}}; + tx.to = To; + pre[To] = {.code = sstore(1, call(callee).gas(50'000)) + OP_GAS + call(delegate).gas(50'000) + + OP_GAS + OP_SWAP1 + push(2) + OP_SSTORE + OP_SWAP1 + OP_SUB + push(3) + + OP_SSTORE}; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[To].storage[0x02_bytes32] = 0x01_bytes32; + // 100 gas for warm call + 7 * 3 for argument pushes + 2 for GAS = 123 = 0x7b + expect.post[To].storage[0x03_bytes32] = 0x7b_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, eip7702_transaction_from_delegated_account) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + pre[Sender].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + pre[delegate] = {.code = 1024 * OP_JUMPDEST}; + + tx.to = To; + pre[To] = {.code = sstore(1, OP_CALLER)}; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = to_bytes32(Sender); +} + +TEST_F(state_transition, eip7702_transaction_to_delegated_account) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + pre[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + + pre[delegate] = {.code = sstore(1, 1)}; + tx.to = To; + pre[To] = {.code = sstore(1, OP_CALLER)}; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = to_bytes32(Sender); +} + +TEST_F(state_transition, eip7702_transaction_to_delegation_to_precompile) +{ + rev = EVMC_PRAGUE; + + constexpr auto ecadd_precompile = 0x06_address; // reverts on invalid input + pre[To].code = bytes{0xef, 0x01, 0x00} + hex(ecadd_precompile); + + tx.to = To; + tx.data = "01"_hex; + + expect.status = EVMC_SUCCESS; + expect.post[To].exists = true; +} + +TEST_F(state_transition, eip7702_transaction_to_delegation_to_empty) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + pre[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + + tx.to = To; + + expect.status = EVMC_SUCCESS; + expect.post[To].exists = true; + expect.post[delegate].exists = false; +} + +TEST_F(state_transition, eip7702_delegated_mode_propagation_call) +{ + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + constexpr auto identity_precompile = 0x04_address; + pre[delegate] = { + .code = call(identity_precompile).input(0, 10).gas(OP_GAS) + sstore(1, returndatasize())}; + pre[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + + tx.to = To; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x0a_bytes32; +} + +TEST_F(state_transition, eip7702_delegated_mode_propagation_extcall) +{ + rev = EVMC_OSAKA; + + constexpr auto delegate = 0xde1e_address; + constexpr auto identity_precompile = 0x04_address; + pre[delegate] = { + .code = eof_bytecode( + extcall(identity_precompile).input(0, 10) + sstore(1, returndatasize()) + OP_STOP, 4)}; + pre[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + + tx.to = To; + + expect.post[delegate].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x0a_bytes32; +} + +TEST_F(state_transition, eip7702_selfdestruct) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + constexpr bytes32 salt{0xff}; + + const auto deploy_code = bytecode{selfdestruct(0x00_address)}; + const auto initcode = + mstore(0, push(deploy_code)) + ret(32 - deploy_code.size(), deploy_code.size()); + const auto deployed_address = compute_create2_address(To, salt, initcode); + + pre[To].code = mstore(0, push(initcode)) + + sstore(0, create2().input(32 - initcode.size(), initcode.size()).salt(salt)) + + sstore(1, call(callee).gas(OP_GAS)); + pre[callee].code = bytes{0xef, 0x01, 0x00} + hex(deployed_address); + + tx.to = To; + + expect.post[deployed_address].code = deploy_code; + expect.post[To].storage[0x00_bytes32] = to_bytes32(deployed_address); + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].code = bytes{0xef, 0x01, 0x00} + hex(deployed_address); +} + +TEST_F(state_transition, eip7702_set_code_transaction_with_selfdestruct) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + constexpr bytes32 salt{0xff}; + + const auto deploy_code = bytecode{selfdestruct(0x00_address)}; + const auto initcode = + mstore(0, push(deploy_code)) + ret(32 - deploy_code.size(), deploy_code.size()); + const auto deployed_address = compute_create2_address(To, salt, initcode); + + pre[To].code = mstore(0, push(initcode)) + + sstore(0, create2().input(32 - initcode.size(), initcode.size()).salt(salt)) + + sstore(1, call(callee).gas(OP_GAS)); + + tx.to = To; + tx.type = Transaction::Type::set_code; + tx.authorization_list = {{.addr = deployed_address, .nonce = 0, .signer = callee}}; + + expect.post[deployed_address].code = deploy_code; + expect.post[To].storage[0x00_bytes32] = to_bytes32(deployed_address); + expect.post[To].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].code = bytes{0xef, 0x01, 0x00} + hex(deployed_address); +} From e7832d21700e4b6fe2ca36721d85ad7be4392d01 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 7 Aug 2024 16:46:58 +0200 Subject: [PATCH 07/33] Support set-code transactions in exporting state transition tests --- test/unittests/state_transition.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unittests/state_transition.cpp b/test/unittests/state_transition.cpp index f1991feae7..3d2c42859e 100644 --- a/test/unittests/state_transition.cpp +++ b/test/unittests/state_transition.cpp @@ -232,6 +232,25 @@ void state_transition::export_state_test( } } + if (!tx.authorization_list.empty()) + { + auto& ja = jtx["authorizationList"]; + for (const auto& [chain_id, addr, nonce, signer, r, s, y_parity] : tx.authorization_list) + { + json::json je; + je["chainId"] = hex0x(chain_id); + je["address"] = hex0x(addr); + je["nonce"] = hex0x(nonce); + je["v"] = hex0x(y_parity); + je["r"] = hex0x(r); + je["s"] = hex0x(s); + if (signer.has_value()) + je["signer"] = hex0x(*signer); + ja.emplace_back(std::move(je)); + } + } + + auto& jpost = jt["post"][to_test_fork_name(rev)][0]; jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}}; jpost["hash"] = hex0x(mpt_hash(post)); From a2ef2d7813f7993bdab0abe7f0a611cf9b71218d Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:46:01 +0200 Subject: [PATCH 08/33] Support set-code transaction in RLP encoding --- test/state/transaction.cpp | 21 +++++++++++++++++++-- test/state/transaction.hpp | 3 +++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/state/transaction.cpp b/test/state/transaction.cpp index e992e6f5c1..35ed4c7814 100644 --- a/test/state/transaction.cpp +++ b/test/state/transaction.cpp @@ -16,7 +16,7 @@ namespace evmone::state [[nodiscard]] bytes rlp_encode(const Transaction& tx) { - assert(tx.type <= Transaction::Type::blob); + assert(tx.type <= Transaction::Type::set_code); // TODO: Refactor this function. For all type of transactions most of the code is similar. if (tx.type == Transaction::Type::legacy) @@ -46,7 +46,7 @@ namespace evmone::state tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.access_list, tx.v, tx.r, tx.s); } - else // Transaction::Type::blob + else if (tx.type == Transaction::Type::blob) { // tx_type + // rlp [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, @@ -57,6 +57,17 @@ namespace evmone::state tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.access_list, tx.max_blob_gas_price, tx.blob_hashes, tx.v, tx.r, tx.s); } + else // Transaction::Type::set_code + { + // tx_type + + // rlp [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, + // data, access_list, authorization_list, sig_parity, r, s]; + return bytes{0x04} + // Transaction type (set_code type == 4) + rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_priority_gas_price, tx.max_gas_price, + static_cast(tx.gas_limit), + tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, + tx.access_list, tx.authorization_list, tx.v, tx.r, tx.s); + } } [[nodiscard]] bytes rlp_encode(const TransactionReceipt& receipt) @@ -80,4 +91,10 @@ namespace evmone::state bytes_view(receipt.logs_bloom_filter), receipt.logs); } } + +[[nodiscard]] bytes rlp_encode(const Authorization& authorization) +{ + return rlp::encode_tuple(authorization.chain_id, authorization.addr, authorization.nonce, + authorization.v, authorization.r, authorization.s); +} } // namespace evmone::state diff --git a/test/state/transaction.hpp b/test/state/transaction.hpp index ef83a2b150..270435536a 100644 --- a/test/state/transaction.hpp +++ b/test/state/transaction.hpp @@ -134,4 +134,7 @@ struct TransactionReceipt /// Defines how to RLP-encode a Log. [[nodiscard]] bytes rlp_encode(const Log& log); + +/// Defines how to RLP-encode an Authorization (EIP-7702). +[[nodiscard]] bytes rlp_encode(const Authorization& authorization); } // namespace evmone::state From 85ccedddedf386cd49ddfb98aac61f25fc145b0e Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 13 Jan 2025 17:58:33 +0100 Subject: [PATCH 09/33] Support set-code transaction in state test loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> Co-authored-by: Paweł Bylica --- test/statetest/statetest_loader.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index f5d003f7f5..df88c7ab97 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -121,6 +121,26 @@ state::AccessList from_json(const json::json& j) return o; } +template <> +state::AuthorizationList from_json(const json::json& j) +{ + state::AuthorizationList o; + for (const auto& a : j) + { + state::Authorization authorization{}; + authorization.chain_id = from_json(a.at("chainId")); + authorization.addr = from_json
(a.at("address")); + authorization.nonce = from_json(a.at("nonce")); + if (a.contains("signer")) + authorization.signer = from_json
(a["signer"]); + authorization.r = from_json(a.at("r")); + authorization.s = from_json(a.at("s")); + authorization.v = from_json(a.at("v")); + o.emplace_back(authorization); + } + return o; +} + // Based on calculateEIP1559BaseFee from ethereum/retesteth static uint64_t calculate_current_base_fee_eip1559( uint64_t parent_gas_used, uint64_t parent_gas_limit, uint64_t parent_base_fee) @@ -343,6 +363,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o) else if (const auto au_it = j.find("authorizationList"); au_it != j.end()) { o.type = state::Transaction::Type::set_code; + o.authorization_list = from_json(*au_it); } } From 4495548ba8c09cc1e4163575f7bc121a97bb10ca Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 22 Jan 2025 12:33:37 +0100 Subject: [PATCH 10/33] Run EEST tests for EIP-7702 on CI Add required MODEXP stub value --- circle.yml | 3 +-- test/state/precompiles_stubs.cpp | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 316111ea59..962008a51e 100644 --- a/circle.yml +++ b/circle.yml @@ -373,14 +373,13 @@ jobs: working_directory: ~/spec-tests/fixtures/state_tests command: > ~/build/bin/evmone-statetest ~/spec-tests/fixtures/state_tests - --gtest_filter='-*eip7702*' - run: name: "Execution spec tests (develop, blockchain_tests)" # Tests for in-development EVM revision currently passing. working_directory: ~/spec-tests/fixtures/blockchain_tests command: > ~/build/bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests - --gtest_filter='-*block_hashes.block_hashes_history:*eip7702*:*eip7623*' + --gtest_filter='-*block_hashes.block_hashes_history' - collect_coverage_gcc - upload_coverage: flags: execution_spec_tests diff --git a/test/state/precompiles_stubs.cpp b/test/state/precompiles_stubs.cpp index 74f3c54fee..37626fe444 100644 --- a/test/state/precompiles_stubs.cpp +++ b/test/state/precompiles_stubs.cpp @@ -321,6 +321,7 @@ ExecutionResult expmod_stub( "0000000000000000000000000000000000000000000000000000000000000000"_hex}, {0xd09419104ce1c64b6a06bcf063e98c2c91ad9e1beaf98b21c9d4734b4a3c9956_bytes32, "0000000000000000000000000000000000000000000000000000000000000000"_hex}, + {0xd397b3b043d87fcd6fad1291ff0bfd16401c274896d8c63a923727f077b8e0b5_bytes32, ""_hex}, {0xd6c0c03ec1f713b63be3d39b4fa8ef082b3407adc29baf74669fd2a574c638ac_bytes32, "01"_hex}, {0xd837f9dcf93155fe558c02c7a660edc0cd238a8b8f95ee6b68e4a5c6a41fc70a_bytes32, "0000000000000000000000000000000000000000000000000000000000000001"_hex}, From c82f016fa21dcbc479185592eed0fb3cb7504edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 24 Jan 2025 15:29:03 +0100 Subject: [PATCH 11/33] statetest: Don't skip tests with 7702 transactions --- test/statetest/statetest_runner.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/statetest/statetest_runner.cpp b/test/statetest/statetest_runner.cpp index 9ea557f363..b4f3abb7ec 100644 --- a/test/statetest/statetest_runner.cpp +++ b/test/statetest/statetest_runner.cpp @@ -23,13 +23,6 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm, bool trace_su // if (case_index != 3) // continue; - if (test.multi_tx.type == state::Transaction::Type::set_code) - { - // FIXME: Remove this once EIP-7702 is implemented. - std::cout << "WARNING: test case with set_code transaction (type 4) skipped\n"; - continue; - } - const auto& expected = cases[case_index]; const auto tx = test.multi_tx.get(expected.indexes); auto state = test.pre_state; From 03c0d96484a77e47520add7fba9b60ad27257b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 18:06:22 +0100 Subject: [PATCH 12/33] fixup! transaction validation --- test/state/state.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 0b7288711a..ea6dfd33c6 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -338,7 +338,7 @@ std::variant validate_transaction( const StateView& state_view, const BlockInfo& block, const Transaction& tx, evmc_revision rev, int64_t block_gas_left, int64_t blob_gas_left) noexcept { - switch (tx.type) + switch (tx.type) // Validate "special" transaction types. { case Transaction::Type::blob: if (rev < EVMC_CANCUN) @@ -370,13 +370,9 @@ std::variant validate_transaction( default:; } - switch (tx.type) + switch (tx.type) // Validate the "regular" transaction type hierarchy. { case Transaction::Type::set_code: - if (rev < EVMC_PRAGUE) - return make_error_code(TX_TYPE_NOT_SUPPORTED); - [[fallthrough]]; - case Transaction::Type::blob: case Transaction::Type::eip1559: if (rev < EVMC_LONDON) @@ -533,12 +529,12 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (authority_ptr != nullptr && (!authority_ptr->is_empty() || authority_ptr->has_initial_storage)) { - static constexpr int64_t EXISTING_AUTHORITY_REFUND = + static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; delegation_refund += EXISTING_AUTHORITY_REFUND; } - if (auth.addr == address{}) + if (is_zero(auth.addr)) { if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH) authority_ptr->code_changed = true; From 8e280d1877889a35fcb9113245c0105e1981d5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 20:36:40 +0100 Subject: [PATCH 13/33] typo --- test/state/transaction.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/state/transaction.hpp b/test/state/transaction.hpp index 270435536a..80b6c23709 100644 --- a/test/state/transaction.hpp +++ b/test/state/transaction.hpp @@ -19,7 +19,7 @@ struct Authorization intx::uint256 chain_id; address addr; uint64_t nonce = 0; - /// Signer is empty if cannot be ecrecovered from r, s, v. + /// Signer is empty if it cannot be ecrecovered from r, s, v. std::optional
signer; intx::uint256 r; intx::uint256 s; From 705456d2ca7f940458dc6dd3e6aa933381415a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 20:36:53 +0100 Subject: [PATCH 14/33] improve authority insertion --- test/state/state.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index ea6dfd33c6..96e566307b 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -516,8 +516,9 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc { // Create authority account. // It is still considered empty at this point until nonce increment, but already warm. - authority_ptr = &state.get_or_insert( - *auth.signer, {.erase_if_empty = true, .access_status = EVMC_ACCESS_WARM}); + if (authority_ptr == nullptr) + authority_ptr = &state.insert( + *auth.signer, {.erase_if_empty = true, .access_status = EVMC_ACCESS_WARM}); // Skip if authorization nonce is incorrect. if (auth.nonce != 0) From 39eb0c822ccdd23c62460b79462f6ce7e9942af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 20:41:23 +0100 Subject: [PATCH 15/33] hoist nonce check --- test/state/state.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 96e566307b..4570c0130c 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -507,10 +507,6 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && !is_code_delegated(state.get_code(*auth.signer))) continue; - - // Skip if authorization nonce is incorrect. - if (auth.nonce != authority_ptr->nonce) - continue; } else { @@ -519,12 +515,12 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (authority_ptr == nullptr) authority_ptr = &state.insert( *auth.signer, {.erase_if_empty = true, .access_status = EVMC_ACCESS_WARM}); - - // Skip if authorization nonce is incorrect. - if (auth.nonce != 0) - continue; } + // Skip if authorization nonce is incorrect. + if (auth.nonce != authority_ptr->nonce) + continue; + // Refund if authority account creation is not needed, // i.e. if it had non-empty balance, nonce, code or storage. if (authority_ptr != nullptr && From bca95af5134ccd8d6ceb2c07a75da8b2b31c4c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 20:49:11 +0100 Subject: [PATCH 16/33] remove redundant check --- test/state/state.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 4570c0130c..d5e9965647 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -523,8 +523,7 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Refund if authority account creation is not needed, // i.e. if it had non-empty balance, nonce, code or storage. - if (authority_ptr != nullptr && - (!authority_ptr->is_empty() || authority_ptr->has_initial_storage)) + if (!authority_ptr->is_empty() || authority_ptr->has_initial_storage) { static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; From c70c66df854accadcfbdfdecb9959e892e1823f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 20:53:39 +0100 Subject: [PATCH 17/33] add FIXME with potential test case missing --- test/state/state.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/state/state.cpp b/test/state/state.cpp index d5e9965647..facef19677 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -510,6 +510,8 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc } else { + // FIXME: If authority exists, but is empty, it will not be warm. + // Create authority account. // It is still considered empty at this point until nonce increment, but already warm. if (authority_ptr == nullptr) From ecc45268ca38c475f95d61cd03a9d43c1c514895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 21:01:45 +0100 Subject: [PATCH 18/33] hoist warming --- test/state/state.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index facef19677..88632eb72d 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -501,8 +501,6 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc auto* authority_ptr = state.find(*auth.signer); if (authority_ptr != nullptr && !authority_ptr->is_empty()) { - authority_ptr->access_status = EVMC_ACCESS_WARM; - // Skip if authority has non-delegated code. if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && !is_code_delegated(state.get_code(*auth.signer))) @@ -515,10 +513,11 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Create authority account. // It is still considered empty at this point until nonce increment, but already warm. if (authority_ptr == nullptr) - authority_ptr = &state.insert( - *auth.signer, {.erase_if_empty = true, .access_status = EVMC_ACCESS_WARM}); + authority_ptr = &state.insert(*auth.signer, {.erase_if_empty = true}); } + authority_ptr->access_status = EVMC_ACCESS_WARM; + // Skip if authorization nonce is incorrect. if (auth.nonce != authority_ptr->nonce) continue; From 7ce66e0e298aba74fe9e0035ec4e73a5270c95c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 21:09:47 +0100 Subject: [PATCH 19/33] Fancy check for "authority exists" --- test/state/state.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 88632eb72d..ef8e234e3f 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -499,6 +499,9 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Check if authority exists. auto* authority_ptr = state.find(*auth.signer); + + const auto authority_exists = authority_ptr != nullptr; + if (authority_ptr != nullptr && !authority_ptr->is_empty()) { // Skip if authority has non-delegated code. @@ -524,7 +527,8 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Refund if authority account creation is not needed, // i.e. if it had non-empty balance, nonce, code or storage. - if (!authority_ptr->is_empty() || authority_ptr->has_initial_storage) + // FIXME: Audit this fancy check. + if (authority_exists && !(authority_ptr->erase_if_empty && authority_ptr->is_empty())) { static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; From 919d5690bbd32e0ac13feff344c109b8cc3d20cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 23:16:02 +0100 Subject: [PATCH 20/33] fix it by checking code after warming --- test/state/state.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index ef8e234e3f..4cdb1dc403 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -504,10 +504,6 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (authority_ptr != nullptr && !authority_ptr->is_empty()) { - // Skip if authority has non-delegated code. - if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && - !is_code_delegated(state.get_code(*auth.signer))) - continue; } else { @@ -521,6 +517,11 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc authority_ptr->access_status = EVMC_ACCESS_WARM; + // Skip if authority has non-delegated code. + if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && + !is_code_delegated(state.get_code(*auth.signer))) + continue; + // Skip if authorization nonce is incorrect. if (auth.nonce != authority_ptr->nonce) continue; From 87c4c1086d27ce4fc6d4a831a9a73050f507fd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 23:23:02 +0100 Subject: [PATCH 21/33] simplify conditions --- test/state/state.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 4cdb1dc403..4f4d31ef42 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -497,23 +497,16 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (auth.s > SECP256K1N_OVER_2) continue; + // FIXME: In previous impl: If authority exists, but is empty, it will not be warm. + // Check if authority exists. auto* authority_ptr = state.find(*auth.signer); - const auto authority_exists = authority_ptr != nullptr; - if (authority_ptr != nullptr && !authority_ptr->is_empty()) - { - } - else - { - // FIXME: If authority exists, but is empty, it will not be warm. - - // Create authority account. - // It is still considered empty at this point until nonce increment, but already warm. - if (authority_ptr == nullptr) - authority_ptr = &state.insert(*auth.signer, {.erase_if_empty = true}); - } + // Create authority account. + // It is still considered empty at this point until nonce increment, but already warm. + if (!authority_exists) + authority_ptr = &state.insert(*auth.signer, {.erase_if_empty = true}); authority_ptr->access_status = EVMC_ACCESS_WARM; From 670bd5c87f919adce640d3523d42579104f46357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 23:24:58 +0100 Subject: [PATCH 22/33] simplify refund check (what does it mean now?) --- test/state/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 4f4d31ef42..ad8ee73b49 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -522,7 +522,7 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Refund if authority account creation is not needed, // i.e. if it had non-empty balance, nonce, code or storage. // FIXME: Audit this fancy check. - if (authority_exists && !(authority_ptr->erase_if_empty && authority_ptr->is_empty())) + if (!authority_ptr->erase_if_empty || !authority_ptr->is_empty()) { static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; From 8c24ac26ae72d275c9a6cb60a829838ffb328210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 23:26:47 +0100 Subject: [PATCH 23/33] combine find+insert --- test/state/state.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index ad8ee73b49..7ab834eba6 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -500,13 +500,9 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // FIXME: In previous impl: If authority exists, but is empty, it will not be warm. // Check if authority exists. - auto* authority_ptr = state.find(*auth.signer); - const auto authority_exists = authority_ptr != nullptr; - // Create authority account. // It is still considered empty at this point until nonce increment, but already warm. - if (!authority_exists) - authority_ptr = &state.insert(*auth.signer, {.erase_if_empty = true}); + auto* authority_ptr = &state.get_or_insert(*auth.signer, {.erase_if_empty = true}); authority_ptr->access_status = EVMC_ACCESS_WARM; From 6310b887c80ec7a625c3cbfcd12f45f52bde99f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 10 Feb 2025 23:30:07 +0100 Subject: [PATCH 24/33] =?UTF-8?q?ptr=20=E2=86=92=20ref?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/state/state.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 7ab834eba6..8659ae6d74 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -502,23 +502,23 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // Check if authority exists. // Create authority account. // It is still considered empty at this point until nonce increment, but already warm. - auto* authority_ptr = &state.get_or_insert(*auth.signer, {.erase_if_empty = true}); + auto& authority = state.get_or_insert(*auth.signer, {.erase_if_empty = true}); - authority_ptr->access_status = EVMC_ACCESS_WARM; + authority.access_status = EVMC_ACCESS_WARM; // Skip if authority has non-delegated code. - if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH && + if (authority.code_hash != Account::EMPTY_CODE_HASH && !is_code_delegated(state.get_code(*auth.signer))) continue; // Skip if authorization nonce is incorrect. - if (auth.nonce != authority_ptr->nonce) + if (auth.nonce != authority.nonce) continue; // Refund if authority account creation is not needed, // i.e. if it had non-empty balance, nonce, code or storage. // FIXME: Audit this fancy check. - if (!authority_ptr->erase_if_empty || !authority_ptr->is_empty()) + if (!authority.erase_if_empty || !authority.is_empty()) { static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; @@ -527,27 +527,27 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (is_zero(auth.addr)) { - if (authority_ptr->code_hash != Account::EMPTY_CODE_HASH) - authority_ptr->code_changed = true; - authority_ptr->code.clear(); - authority_ptr->code_hash = Account::EMPTY_CODE_HASH; + if (authority.code_hash != Account::EMPTY_CODE_HASH) + authority.code_changed = true; + authority.code.clear(); + authority.code_hash = Account::EMPTY_CODE_HASH; } else { bytes new_code(bytes(DELEGATION_MAGIC) + bytes(auth.addr)); - if (authority_ptr->code != new_code) - authority_ptr->code_changed = true; - authority_ptr->code = std::move(new_code); + if (authority.code != new_code) + authority.code_changed = true; + authority.code = std::move(new_code); // TODO The hash of delegated accounts is not used anywhere, // it is only important that it is not a hash of empty. // We could avoid this hashing, if we found a way to not rely only on code_hash for // emptiness checks. // (e.g for emptiness check code_hash == EMPTY_CODE_HASH && !code_changed) - authority_ptr->code_hash = keccak256(authority_ptr->code); + authority.code_hash = keccak256(authority.code); } - ++authority_ptr->nonce; + ++authority.nonce; } const auto base_fee = (rev >= EVMC_LONDON) ? block.base_fee : 0; From ff6e1ed383a0a5d8fbf0e708a9edaa4f057a0328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 12:00:39 +0100 Subject: [PATCH 25/33] describe the gas refund check --- test/state/state.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 8659ae6d74..2076d5f24f 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -515,11 +515,16 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (auth.nonce != authority.nonce) continue; - // Refund if authority account creation is not needed, - // i.e. if it had non-empty balance, nonce, code or storage. - // FIXME: Audit this fancy check. - if (!authority.erase_if_empty || !authority.is_empty()) + // 7. Add PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas to the global refund counter + // if authority exists in the trie. + // Fully empty accounts (no storage) may not exist in the trie at the beginning of the + // transaction so we are looking for new accounts created by processing this authorisation + // list that remain empty because of a failed authorisation. At this point we are going to + // bump its nonce, and it will remain in the trie. Therefore, no refund should be applied. + if (!(authority.erase_if_empty && authority.is_empty())) { + // FIXME: Maybe it's better to count number of refunds and compute the total amount + // in the end. This way we can avoid overflow. static constexpr auto EXISTING_AUTHORITY_REFUND = AUTHORIZATION_EMPTY_ACCOUNT_COST - AUTHORIZATION_BASE_COST; delegation_refund += EXISTING_AUTHORITY_REFUND; From 862912d73ea25ca820aed49d1cebaac49409f905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 12:10:18 +0100 Subject: [PATCH 26/33] nice description of the steps. --- test/state/state.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 2076d5f24f..a6e0a98c13 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -484,16 +484,21 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc for (const auto& auth : tx.authorization_list) { + // 1. Verify the chain id is either 0 or the chain’s current ID. + // FIXME: Transaction chain id is not the chain id. Can it be 0? if (auth.chain_id != 0 && auth.chain_id != tx.chain_id) continue; + // 2. Verify the nonce is less than 2**64 - 1. if (auth.nonce == Account::NonceMax) continue; - // Check if signer could be ecrecovered. + // 3. Verify if the signer has been successfully recovered from the signature. + // authority = ecrecover(...) if (!auth.signer.has_value()) continue; + // s value must be less than or equal to secp256k1n/2, as specified in EIP-2. if (auth.s > SECP256K1N_OVER_2) continue; @@ -504,14 +509,16 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // It is still considered empty at this point until nonce increment, but already warm. auto& authority = state.get_or_insert(*auth.signer, {.erase_if_empty = true}); + // 4. Add authority to accessed_addresses (as defined in EIP-2929.) authority.access_status = EVMC_ACCESS_WARM; - // Skip if authority has non-delegated code. + // 5. Verify the code of authority is either empty or already delegated. if (authority.code_hash != Account::EMPTY_CODE_HASH && !is_code_delegated(state.get_code(*auth.signer))) continue; - // Skip if authorization nonce is incorrect. + // 6. Verify the nonce of authority is equal to nonce. + // In case authority does not exist in the trie, verify that nonce is equal to 0. if (auth.nonce != authority.nonce) continue; From a3677168a83fe4824bf4e562a5f7de5f0e8fc10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 12:20:09 +0100 Subject: [PATCH 27/33] optimize and simplify code update --- test/state/state.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index a6e0a98c13..48b24d28cc 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -544,18 +544,11 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc authority.code.clear(); authority.code_hash = Account::EMPTY_CODE_HASH; } - else + else if (auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); + authority.code != new_code) { - bytes new_code(bytes(DELEGATION_MAGIC) + bytes(auth.addr)); - if (authority.code != new_code) - authority.code_changed = true; + authority.code_changed = true; authority.code = std::move(new_code); - - // TODO The hash of delegated accounts is not used anywhere, - // it is only important that it is not a hash of empty. - // We could avoid this hashing, if we found a way to not rely only on code_hash for - // emptiness checks. - // (e.g for emptiness check code_hash == EMPTY_CODE_HASH && !code_changed) authority.code_hash = keccak256(authority.code); } From f6298fec1c963b1204054a998b03b253ac9a1106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 12:25:33 +0100 Subject: [PATCH 28/33] more description --- test/state/state.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/state/state.cpp b/test/state/state.cpp index 48b24d28cc..15c6865e0e 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -537,6 +537,8 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc delegation_refund += EXISTING_AUTHORITY_REFUND; } + // As a special case, if address is 0 do not write the designation. + // Clear the account’s code and reset the account’s code hash to the empty hash. if (is_zero(auth.addr)) { if (authority.code_hash != Account::EMPTY_CODE_HASH) @@ -544,14 +546,17 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc authority.code.clear(); authority.code_hash = Account::EMPTY_CODE_HASH; } + // 8. Set the code of authority to be 0xef0100 || address. This is a delegation designation. else if (auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); authority.code != new_code) { + // We are doing this only if the code is different to make the state diff precise. authority.code_changed = true; authority.code = std::move(new_code); authority.code_hash = keccak256(authority.code); } + // 9. Increase the nonce of authority by one. ++authority.nonce; } From 019f862d2d9986cdc967988ad16f03fe8a330220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 16:48:16 +0100 Subject: [PATCH 29/33] lazily clear the code --- test/state/state.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 15c6865e0e..8f92d9cbf1 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -542,14 +542,17 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (is_zero(auth.addr)) { if (authority.code_hash != Account::EMPTY_CODE_HASH) + { authority.code_changed = true; - authority.code.clear(); - authority.code_hash = Account::EMPTY_CODE_HASH; + authority.code.clear(); + authority.code_hash = Account::EMPTY_CODE_HASH; + } } // 8. Set the code of authority to be 0xef0100 || address. This is a delegation designation. else if (auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); authority.code != new_code) { + // TODO: Split "else if" to make it symmetric to "if if". // We are doing this only if the code is different to make the state diff precise. authority.code_changed = true; authority.code = std::move(new_code); From 8223d2b130ee6b7b8e441cb514f058ebb7998342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 18:54:39 +0100 Subject: [PATCH 30/33] improve code nesting --- test/state/state.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 8f92d9cbf1..98a6a4e6ce 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -549,14 +549,16 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc } } // 8. Set the code of authority to be 0xef0100 || address. This is a delegation designation. - else if (auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); - authority.code != new_code) + else { - // TODO: Split "else if" to make it symmetric to "if if". - // We are doing this only if the code is different to make the state diff precise. - authority.code_changed = true; - authority.code = std::move(new_code); - authority.code_hash = keccak256(authority.code); + auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); + if (authority.code != new_code) + { + // We are doing this only if the code is different to make the state diff precise. + authority.code_changed = true; + authority.code = std::move(new_code); + authority.code_hash = keccak256(authority.code); + } } // 9. Increase the nonce of authority by one. From d386377327b891acbb7d9342c69a116c7bad9f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 19:10:11 +0100 Subject: [PATCH 31/33] rework gas refund condition again --- test/state/state.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 98a6a4e6ce..7945a13e47 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -524,11 +524,12 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc // 7. Add PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas to the global refund counter // if authority exists in the trie. - // Fully empty accounts (no storage) may not exist in the trie at the beginning of the - // transaction so we are looking for new accounts created by processing this authorisation - // list that remain empty because of a failed authorisation. At this point we are going to - // bump its nonce, and it will remain in the trie. Therefore, no refund should be applied. - if (!(authority.erase_if_empty && authority.is_empty())) + // We are interested in _empty_ accounts (EIP-161) because _empty_ implies they don't exist + // in the state (EIP-7523). Successful authorisation validation makes an account non-empty. + // We apply the refund only if the account has been non-empty before. + // TODO: The additional condition (erase_if_empty) is to handle existent-but-empty which + // are against EIP-7523 and may happen only in testing environments. + if (!authority.is_empty() || !authority.erase_if_empty) { // FIXME: Maybe it's better to count number of refunds and compute the total amount // in the end. This way we can avoid overflow. From 7a7f8561257699363802ef2546b3653a903c627c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 19:21:20 +0100 Subject: [PATCH 32/33] change delegation_refund type to int64_t --- test/state/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 7945a13e47..76ac8b619c 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -480,7 +480,7 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc assert(sender_acc.nonce < Account::NonceMax); // Required for valid tx. ++sender_acc.nonce; // Bump sender nonce. - auto delegation_refund = 0; + int64_t delegation_refund = 0; for (const auto& auth : tx.authorization_list) { From 16c6bc9856dd1689ceb21694369941b0b3987762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Feb 2025 19:23:34 +0100 Subject: [PATCH 33/33] rework comments --- test/state/state.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/state/state.cpp b/test/state/state.cpp index 76ac8b619c..22cc492ebb 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -502,11 +502,8 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (auth.s > SECP256K1N_OVER_2) continue; - // FIXME: In previous impl: If authority exists, but is empty, it will not be warm. - - // Check if authority exists. - // Create authority account. - // It is still considered empty at this point until nonce increment, but already warm. + // Get or create the authority account. + // It is still empty at this point until nonce bump followed by successful authorization. auto& authority = state.get_or_insert(*auth.signer, {.erase_if_empty = true}); // 4. Add authority to accessed_addresses (as defined in EIP-2929.)