diff --git a/lib/evmone/delegation.cpp b/lib/evmone/delegation.cpp index cb39733c9c..86a0adfb13 100644 --- a/lib/evmone/delegation.cpp +++ b/lib/evmone/delegation.cpp @@ -2,17 +2,17 @@ // 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. + // Load the code prefix one byte past the delegation designator 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)]; + // and returns the number of bytes copied, so code longer than the designator + // fills the extra byte and is rejected as ordinary code. + uint8_t designation_buffer[DELEGATION_DESIGNATOR_SIZE + 1]; const auto size = host.copy_code(addr, 0, designation_buffer, std::size(designation_buffer)); const bytes_view designation{designation_buffer, size}; @@ -21,8 +21,6 @@ std::optional get_delegate_address( // 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; } diff --git a/lib/evmone/delegation.hpp b/lib/evmone/delegation.hpp index 9c3799e66a..59f1df77ab 100644 --- a/lib/evmone/delegation.hpp +++ b/lib/evmone/delegation.hpp @@ -16,10 +16,15 @@ using evmc::bytes_view; 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 +/// Size of the EIP-7702 delegation designator: the magic followed by the delegate address. +constexpr size_t DELEGATION_DESIGNATOR_SIZE = std::size(DELEGATION_MAGIC) + sizeof(evmc::address); + +/// Check if code is an EIP-7702 delegation designator. +/// The designator is exactly the magic followed by the delegate address. +/// Code of any other size is ordinary code, even if it starts with the magic. constexpr bool is_code_delegated(bytes_view code) noexcept { - return code.starts_with(DELEGATION_MAGIC); + return code.size() == DELEGATION_DESIGNATOR_SIZE && code.starts_with(DELEGATION_MAGIC); } /// Get EIP-7702 delegate address from the code of addr, if it is delegated. diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 3bfc1410a4..28577afa8b 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources( evm_eip3860_initcode_test.cpp evm_eip4844_blobhash_test.cpp evm_eip7516_blobbasefee_test.cpp + evm_eip7702_test.cpp evm_eip7843_slotnum_test.cpp evm_eip7939_clz_test.cpp evm_eip8024_swapn_dupn_exchange_test.cpp diff --git a/test/unittests/evm_eip7702_test.cpp b/test/unittests/evm_eip7702_test.cpp new file mode 100644 index 0000000000..c329c6d8f8 --- /dev/null +++ b/test/unittests/evm_eip7702_test.cpp @@ -0,0 +1,78 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +/// This file contains EVM unit tests for EIP-7702 "Set EOA account code" +/// https://eips.ethereum.org/EIPS/eip-7702 + +#include "evm_fixture.hpp" +#include + +using namespace evmc::literals; +using namespace evmone::test; + +namespace +{ +constexpr auto callee = 0xca11ee_address; +constexpr auto delegate = 0xde1e_address; + +/// The delegation designator: the magic followed by the delegate address, 23 bytes. +const bytes designator = bytes{0xef, 0x01, 0x00} + bytes{delegate.bytes, sizeof(delegate.bytes)}; +} // namespace + +TEST_P(evm, eip7702_call_designator) +{ + rev = EVMC_PRAGUE; + ASSERT_EQ(designator.size(), 23); + host.accounts[callee].code = designator; + + execute(call(callee).gas(50'000)); + EXPECT_STATUS(EVMC_SUCCESS); + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls[0]; + EXPECT_EQ(call_msg.recipient, callee); + EXPECT_EQ(call_msg.code_address, delegate); + EXPECT_TRUE(call_msg.flags & EVMC_DELEGATED); +} + +TEST_P(evm, eip7702_call_designator_magic_only) +{ + rev = EVMC_PRAGUE; + host.accounts[callee].code = designator.substr(0, 3); + + execute(call(callee).gas(50'000)); + EXPECT_STATUS(EVMC_SUCCESS); + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls[0]; + EXPECT_EQ(call_msg.recipient, callee); + EXPECT_EQ(call_msg.code_address, callee); + EXPECT_FALSE(call_msg.flags & EVMC_DELEGATED); +} + +TEST_P(evm, eip7702_call_designator_too_short) +{ + rev = EVMC_PRAGUE; + host.accounts[callee].code = designator.substr(0, 22); + + execute(call(callee).gas(50'000)); + EXPECT_STATUS(EVMC_SUCCESS); + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls[0]; + EXPECT_EQ(call_msg.recipient, callee); + EXPECT_EQ(call_msg.code_address, callee); + EXPECT_FALSE(call_msg.flags & EVMC_DELEGATED); +} + +TEST_P(evm, eip7702_call_designator_too_long) +{ + rev = EVMC_PRAGUE; + host.accounts[callee].code = designator + bytes{0x00}; + + execute(call(callee).gas(50'000)); + EXPECT_STATUS(EVMC_SUCCESS); + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls[0]; + EXPECT_EQ(call_msg.recipient, callee); + EXPECT_EQ(call_msg.code_address, callee); + EXPECT_FALSE(call_msg.flags & EVMC_DELEGATED); +} diff --git a/test/utils/statetest_loader.cpp b/test/utils/statetest_loader.cpp index 2d282e54d7..fb0a37dd71 100644 --- a/test/utils/statetest_loader.cpp +++ b/test/utils/statetest_loader.cpp @@ -528,7 +528,9 @@ void validate_state(const TestState& state, evmc_revision rev) if (state::is_precompile(rev, addr) && !acc.code.empty()) throw std::invalid_argument("unexpected code at precompile address " + hex0x(addr)); - const bool allowedEF = (rev >= EVMC_PRAGUE && is_code_delegated(acc.code)) || + const bool has_delegation_magic = + rev >= EVMC_PRAGUE && acc.code.starts_with(DELEGATION_MAGIC); + const bool allowedEF = has_delegation_magic || // exceptions to EIP-3541 rule existing on Mainnet acc.code == "EF"_hex || acc.code == "EFF09f918bf09f9fa9"_hex; if (rev >= EVMC_LONDON && !allowedEF && !acc.code.empty() && acc.code[0] == 0xEF) @@ -538,8 +540,7 @@ void validate_state(const TestState& state, evmc_revision rev) !acc.storage.empty()) throw std::invalid_argument("empty account with non-empty storage at " + hex0x(addr)); - if (rev >= EVMC_PRAGUE && is_code_delegated(acc.code) && - acc.code.size() != std::size(DELEGATION_MAGIC) + sizeof(evmc::address)) + if (has_delegation_magic && !is_code_delegated(acc.code)) { throw std::invalid_argument( "EIP-7702 delegation designator at " + hex0x(addr) + " has invalid size");