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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions lib/evmone/delegation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#include "delegation.hpp"
#include <cassert>

namespace evmone
{
std::optional<evmc::address> 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};

Expand All @@ -21,8 +21,6 @@ std::optional<evmc::address> 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;
}
Expand Down
9 changes: 7 additions & 2 deletions lib/evmone/delegation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 78 additions & 0 deletions test/unittests/evm_eip7702_test.cpp
Original file line number Diff line number Diff line change
@@ -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 <evmone/delegation.hpp>

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);
}
7 changes: 4 additions & 3 deletions test/utils/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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");
Expand Down