Skip to content
Merged
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
3 changes: 1 addition & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ add_library(evmone
baseline_instruction_table.cpp
baseline_instruction_table.hpp
constants.hpp
delegation.cpp
delegation.hpp
eof.cpp
eof.hpp
instructions.hpp
Expand Down
29 changes: 29 additions & 0 deletions lib/evmone/delegation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// evmone: Fast Ethereum Virtual Machine implementation
// 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.
// 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};

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
28 changes: 28 additions & 0 deletions lib/evmone/delegation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <evmc/bytes.hpp>
#include <evmc/evmc.hpp>
#include <evmc/utils.h>

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
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.
EVMC_EXPORT std::optional<evmc::address> get_delegate_address(
const evmc::HostInterface& host, const evmc::address& addr) noexcept;
} // namespace evmone
53 changes: 51 additions & 2 deletions lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2019 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "delegation.hpp"
#include "eof.hpp"
#include "instructions.hpp"

Expand All @@ -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<evmc::address, Result> 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
Expand Down Expand Up @@ -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<Result>(&target_addr_or_result))
return *result;

const auto& code_addr = std::get<evmc::address>(target_addr_or_result);

if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256))
return {EVMC_OUT_OF_GAS, gas_left};

Expand All @@ -80,9 +115,13 @@ 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_flags>{EVMC_DELEGATED};
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<evmc::uint256be>(value);
Expand Down Expand Up @@ -178,6 +217,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<Result>(&target_addr_or_result))
return *result;

const auto& code_addr = std::get<evmc::address>(target_addr_or_result);

if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256))
return {EVMC_OUT_OF_GAS, gas_left};

Expand All @@ -186,9 +231,13 @@ 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_flags>{EVMC_DELEGATED};
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<evmc::uint256be>(value);
Expand Down
6 changes: 6 additions & 0 deletions test/state/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ evmc::Result Host::execute_message(const evmc_message& msg) noexcept
}
}

if (is_precompile(m_rev, msg.code_address))
// Calls to precompile address via EIP-7702 delegation execute empty code instead of precompile.
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?
Expand Down
1 change: 1 addition & 0 deletions test/state/precompiles_stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ ExecutionResult expmod_stub(
"0000000000000000000000000000000000000000000000000000000000000000"_hex},
{0xd09419104ce1c64b6a06bcf063e98c2c91ad9e1beaf98b21c9d4734b4a3c9956_bytes32,
"0000000000000000000000000000000000000000000000000000000000000000"_hex},
{0xd397b3b043d87fcd6fad1291ff0bfd16401c274896d8c63a923727f077b8e0b5_bytes32, ""_hex},
{0xd6c0c03ec1f713b63be3d39b4fa8ef082b3407adc29baf74669fd2a574c638ac_bytes32, "01"_hex},
{0xd837f9dcf93155fe558c02c7a660edc0cd238a8b8f95ee6b68e4a5c6a41fc70a_bytes32,
"0000000000000000000000000000000000000000000000000000000000000001"_hex},
Expand Down
Loading