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
1 change: 1 addition & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
table[OP_SWAPN] = op_undefined;
table[OP_EXCHANGE] = op_undefined;
table[OP_EOFCREATE] = op_undefined;
table[OP_TXCREATE] = op_undefined;
table[OP_RETURNCODE] = op_undefined;

return table;
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/eof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ enum class EOFValidationError

enum class ContainerKind : uint8_t
{
/// Container that uses RETURNCODE. Can be used by EOFCREATE/Creation transaction.
/// Container that uses RETURNCODE. Can be used by EOFCREATE/TXCREATE/Creation transaction.
initcode,
/// Container that uses STOP/RETURN. Can be returned by RETURNCODE.
runtime,
Expand Down
22 changes: 22 additions & 0 deletions lib/evmone/execution_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class ExecutionState

private:
evmc_tx_context m_tx = {};
std::optional<std::unordered_map<evmc::bytes32, bytes_view>> m_initcodes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it fly if we omit std::optional (empty map meaning no initcodes)?

Copy link
Member Author

@gumb0 gumb0 Apr 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is optimization to avoid calling get_tx_context().initcodes on every get_tx_initcode_by_hash().

So it will work without optional, but for no initcodes it will check tx_context.initcodes_count on every TXCREATE (will not query the host though, thanks to cached tx_context).


public:
/// Pointer to code analysis.
Expand Down Expand Up @@ -214,6 +215,7 @@ class ExecutionState
output_size = 0;
deploy_container = {};
m_tx = {};
m_initcodes.reset();
call_stack = {};
}

Expand All @@ -225,5 +227,25 @@ class ExecutionState
m_tx = host.get_tx_context();
return m_tx;
}

/// Get initcode by its hash from transaction initcodes.
///
/// Returns empty bytes_view if no such initcode was found.
[[nodiscard]] bytes_view get_tx_initcode_by_hash(const evmc_bytes32& hash) noexcept
{
if (!m_initcodes.has_value())
{
m_initcodes.emplace();
const auto& tx_context = get_tx_context();
for (size_t i = 0; i < tx_context.initcodes_count; ++i)
{
const auto& initcode = tx_context.initcodes[i];
m_initcodes->insert({initcode.hash, {initcode.code, initcode.code_size}});
}
}

const auto it = m_initcodes->find(hash);
return it != m_initcodes->end() ? it->second : bytes_view{};
}
};
} // namespace evmone
5 changes: 4 additions & 1 deletion lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1093,8 +1093,11 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex
inline constexpr auto create = create_impl<OP_CREATE>;
inline constexpr auto create2 = create_impl<OP_CREATE2>;

Result eofcreate(
template <Opcode Op>
Result create_eof_impl(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
inline constexpr auto eofcreate = create_eof_impl<OP_EOFCREATE>;
inline constexpr auto txcreate = create_eof_impl<OP_TXCREATE>;

inline code_iterator callf(StackTop stack, ExecutionState& state, code_iterator pos) noexcept
{
Expand Down
44 changes: 37 additions & 7 deletions lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ consteval evmc_call_kind to_call_kind(Opcode op) noexcept
case OP_CREATE2:
return EVMC_CREATE2;
case OP_EOFCREATE:
case OP_TXCREATE:
return EVMC_EOFCREATE;
default:
intx::unreachable();
Expand Down Expand Up @@ -381,12 +382,17 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex
return {EVMC_SUCCESS, gas_left};
}

Result eofcreate(
template <Opcode Op>
Result create_eof_impl(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept
{
static_assert(Op == OP_EOFCREATE || Op == OP_TXCREATE);

if (state.in_static_mode())
return {EVMC_STATIC_MODE_VIOLATION, gas_left};

const auto initcode_hash =
(Op == OP_TXCREATE) ? intx::be::store<evmc::bytes32>(stack.pop()) : evmc::bytes32{};
const auto salt = stack.pop();
const auto input_offset_u256 = stack.pop();
const auto input_size_u256 = stack.pop();
Expand All @@ -398,11 +404,24 @@ Result eofcreate(
if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird that memory check happens after we push 0 to the stack. Please check this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same as what CREATE* is doing (stack is not observable after OOG)

return {EVMC_OUT_OF_GAS, gas_left};

const auto initcontainer_index = pos[1];
pos += 2;
const auto& container = state.original_code;
const auto& eof_header = state.analysis.baseline->eof_header();
const auto initcontainer = eof_header.get_container(container, initcontainer_index);
bytes_view initcontainer;
if constexpr (Op == OP_EOFCREATE)
{
const auto initcontainer_index = pos[1];
pos += 2;
const auto& container = state.original_code;
const auto& eof_header = state.analysis.baseline->eof_header();
initcontainer = eof_header.get_container(container, initcontainer_index);
}
else
{
pos += 1;
initcontainer = state.get_tx_initcode_by_hash(initcode_hash);
// In case initcode was not found, empty bytes_view was returned.
// Transaction initcodes are not allowed to be empty.
if (initcontainer.empty())
return {EVMC_SUCCESS, gas_left}; // "Light" failure
}

const auto input_offset = static_cast<size_t>(input_offset_u256);
const auto input_size = static_cast<size_t>(input_size_u256);
Expand All @@ -414,7 +433,14 @@ Result eofcreate(
intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < endowment)
return {EVMC_SUCCESS, gas_left}; // "Light" failure.

evmc_message msg{.kind = EVMC_EOFCREATE};
if constexpr (Op == OP_TXCREATE)
{
const auto error_subcont = validate_eof(state.rev, ContainerKind::initcode, initcontainer);
if (error_subcont != EOFValidationError::success)
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
}

evmc_message msg{.kind = to_call_kind(Op)};
msg.gas = gas_left - gas_left / 64;
if (input_size > 0)
{
Expand Down Expand Up @@ -446,4 +472,8 @@ template Result create_impl<OP_CREATE>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result create_impl<OP_CREATE2>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result create_eof_impl<OP_EOFCREATE>(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
template Result create_eof_impl<OP_TXCREATE>(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
} // namespace evmone::instr::core
1 change: 1 addition & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ enum Opcode : uint8_t
OP_EXCHANGE = 0xe8,

OP_EOFCREATE = 0xec,
OP_TXCREATE = 0xed,
OP_RETURNCODE = 0xee,

OP_CREATE = 0xf0,
Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept {
table[EVMC_OSAKA][OP_EXTDELEGATECALL] = warm_storage_read_cost;
table[EVMC_OSAKA][OP_EXTSTATICCALL] = warm_storage_read_cost;
table[EVMC_OSAKA][OP_EOFCREATE] = 32000;
table[EVMC_OSAKA][OP_TXCREATE] = 32000;
table[EVMC_OSAKA][OP_RETURNCODE] = 0;

table[EVMC_EXPERIMENTAL] = table[EVMC_OSAKA];
Expand Down Expand Up @@ -422,6 +423,7 @@ constexpr inline std::array<Traits, 256> traits = []() noexcept {
table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE};
table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, {}, REV_EOF1};
table[OP_EOFCREATE] = {"EOFCREATE", 1, false, 4, -3, {}, REV_EOF1};
table[OP_TXCREATE] = {"TXCREATE", 0, false, 5, -4, EVMC_OSAKA, REV_EOF1};
table[OP_RETURNCODE] = {"RETURNCODE", 1, true, 2, -2, {}, REV_EOF1};
table[OP_EXTCALL] = {"EXTCALL", 0, false, 4, -3, {}, REV_EOF1};
table[OP_EXTDELEGATECALL] = {"EXTDELEGATECALL", 0, false, 3, -2, {}, REV_EOF1};
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/instructions_xmacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@
ON_OPCODE_UNDEFINED(0xea) \
ON_OPCODE_UNDEFINED(0xeb) \
ON_OPCODE_IDENTIFIER(OP_EOFCREATE, eofcreate) \
ON_OPCODE_UNDEFINED(0xed) \
ON_OPCODE_IDENTIFIER(OP_TXCREATE, txcreate) \
ON_OPCODE_IDENTIFIER(OP_RETURNCODE, returncode) \
ON_OPCODE_UNDEFINED(0xef) \
\
Expand Down
9 changes: 9 additions & 0 deletions test/state/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
BLOB_GAS_LIMIT_EXCEEDED,
CREATE_SET_CODE_TX,
EMPTY_AUTHORIZATION_LIST,
INIT_CODE_EMPTY,
INIT_CODE_COUNT_LIMIT_EXCEEDED,
INIT_CODE_COUNT_ZERO,
UNKNOWN_ERROR,
};

Expand Down Expand Up @@ -82,6 +85,12 @@
return "set code transaction must not be a create transaction";
case EMPTY_AUTHORIZATION_LIST:
return "empty authorization list";
case INIT_CODE_EMPTY:
return "initcode empty";
case INIT_CODE_COUNT_LIMIT_EXCEEDED:
return "max initcode count exceeded";
case INIT_CODE_COUNT_ZERO:
return "initcode list empty";

Check warning on line 93 in test/state/errors.hpp

View check run for this annotation

Codecov / codecov/patch

test/state/errors.hpp#L88-L93

Added lines #L88 - L93 were not covered by tests
case UNKNOWN_ERROR:
return "Unknown error";
default:
Expand Down
8 changes: 5 additions & 3 deletions test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ std::optional<evmc_message> Host::prepare_message(evmc_message msg) noexcept

msg.recipient = compute_create_address(msg.sender, creation_sender_nonce);
}
// EOFCREATE
// EOFCREATE or TXCREATE
else
msg.recipient = compute_eofcreate_address(msg.sender, msg.create2_salt);
}
Expand Down Expand Up @@ -395,8 +395,8 @@ evmc::Result Host::create(const evmc_message& msg) noexcept
{
if (m_rev >= EVMC_OSAKA)
{
// Only EOFCREATE/EOF-creation-tx is allowed to deploy code starting with EF.
// It must be valid EOF, which was validated before execution.
// Only EOFCREATE/TXCREATE/EOF-creation-tx is allowed to deploy code starting with
// EF. It must be valid EOF, which was validated before execution.
if (msg.kind != EVMC_EOFCREATE)
return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE};
assert(validate_eof(m_rev, ContainerKind::runtime, code) ==
Expand Down Expand Up @@ -513,6 +513,8 @@ evmc_tx_context Host::get_tx_context() const noexcept
intx::be::store<uint256be>(m_block.blob_base_fee.value_or(0)),
m_tx.blob_hashes.data(),
m_tx.blob_hashes.size(),
m_tx_initcodes.data(),
m_tx_initcodes.size(),
};
}

Expand Down
12 changes: 11 additions & 1 deletion test/state/host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,22 @@ class Host : public evmc::Host
const BlockHashes& m_block_hashes;
const Transaction& m_tx;
std::vector<Log> m_logs;
std::vector<evmc_tx_initcode> m_tx_initcodes;

public:
Host(evmc_revision rev, evmc::VM& vm, State& state, const BlockInfo& block,
const BlockHashes& block_hashes, const Transaction& tx) noexcept
: m_rev{rev}, m_vm{vm}, m_state{state}, m_block{block}, m_block_hashes{block_hashes}, m_tx{tx}
{}
{
if (tx.type == Transaction::Type::initcodes)
{
for (const auto& initcode : tx.initcodes)
{
const auto hash = keccak256({initcode.data(), initcode.size()});
m_tx_initcodes.push_back({hash, initcode.data(), initcode.size()});
}
}
}

[[nodiscard]] std::vector<Log>&& take_logs() noexcept { return std::move(m_logs); }

Expand Down
32 changes: 30 additions & 2 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ constexpr auto SECP256K1N_OVER_2 = evmmax::secp256k1::Order / 2;
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 auto MAX_INITCODE_COUNT = 256;

constexpr int64_t num_words(size_t size_in_bytes) noexcept
{
Expand All @@ -39,6 +41,14 @@ size_t compute_tx_data_tokens(evmc_revision rev, bytes_view data) noexcept
return (nonzero_byte_multiplier * num_nonzero_bytes) + num_zero_bytes;
}

size_t compute_tx_initcode_tokens(evmc_revision rev, std::span<const bytes> initcodes) noexcept
{
size_t sum = 0;
for (const auto& initcode : initcodes)
sum += compute_tx_data_tokens(rev, initcode);
return sum;
}

int64_t compute_access_list_cost(const AccessList& access_list) noexcept
{
static constexpr auto ADDRESS_COST = 2400;
Expand Down Expand Up @@ -70,7 +80,10 @@ TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction&
const auto create_cost = (is_create && rev >= EVMC_HOMESTEAD) ? TX_CREATE_COST : 0;

const auto num_data_tokens = static_cast<int64_t>(compute_tx_data_tokens(rev, tx.data));
const auto data_cost = num_data_tokens * DATA_TOKEN_COST;
const auto num_initcode_tokens =
static_cast<int64_t>(compute_tx_initcode_tokens(rev, tx.initcodes));
const auto num_tokens = num_data_tokens + num_initcode_tokens;
const auto data_cost = num_tokens * DATA_TOKEN_COST;

const auto access_list_cost = compute_access_list_cost(tx.access_list);

Expand All @@ -85,7 +98,7 @@ TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction&

// EIP-7623: Compute the minimum cost for the transaction by. If disabled, just use 0.
const auto min_cost =
rev >= EVMC_PRAGUE ? TX_BASE_COST + num_data_tokens * TOTAL_COST_FLOOR_PER_TOKEN : 0;
rev >= EVMC_PRAGUE ? TX_BASE_COST + num_tokens * TOTAL_COST_FLOOR_PER_TOKEN : 0;

return {intrinsic_cost, min_cost};
}
Expand Down Expand Up @@ -450,11 +463,26 @@ std::variant<TransactionProperties, std::error_code> validate_transaction(
return make_error_code(EMPTY_AUTHORIZATION_LIST);
break;

case Transaction::Type::initcodes:
if (rev < EVMC_OSAKA)
return make_error_code(TX_TYPE_NOT_SUPPORTED);
if (tx.initcodes.size() > MAX_INITCODE_COUNT)
return make_error_code(INIT_CODE_COUNT_LIMIT_EXCEEDED);
if (tx.initcodes.empty())
return make_error_code(INIT_CODE_COUNT_ZERO);
if (std::ranges::any_of(
tx.initcodes, [](const bytes& v) { return v.size() > MAX_INITCODE_SIZE; }))
return make_error_code(INIT_CODE_SIZE_LIMIT_EXCEEDED);
if (std::ranges::any_of(tx.initcodes, [](const bytes& v) { return v.empty(); }))
return make_error_code(INIT_CODE_EMPTY);
break;

default:;
}

switch (tx.type) // Validate the "regular" transaction type hierarchy.
{
case Transaction::Type::initcodes:
case Transaction::Type::set_code:
case Transaction::Type::blob:
case Transaction::Type::eip1559:
Expand Down
3 changes: 2 additions & 1 deletion test/state/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ namespace evmone::state

[[nodiscard]] bytes rlp_encode(const Transaction& tx)
{
assert(tx.type <= Transaction::Type::set_code);
// There is no Transaction::Type 5 - reserved for Authorization List encoding in EIP-7702.
assert(tx.type <= Transaction::Type::initcodes && stdx::to_underlying(tx.type) != 5);

// TODO: Refactor this function. For all type of transactions most of the code is similar.
if (tx.type == Transaction::Type::legacy)
Expand Down
5 changes: 5 additions & 0 deletions test/state/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ struct Transaction
/// The typed set code transaction (with authorization list).
/// Introduced by EIP-7702 https://eips.ethereum.org/EIPS/eip-7702.
set_code = 4,

/// The typed transaction with initcode list.
/// Introduced by EIP-7873 https://eips.ethereum.org/EIPS/eip-7873.
initcodes = 6,
};

/// Returns amount of blob gas used by this transaction
Expand All @@ -80,6 +84,7 @@ struct Transaction
intx::uint256 s;
uint8_t v = 0;
AuthorizationList authorization_list;
std::vector<bytes> initcodes;
};

/// Transaction properties computed during the validation needed for the execution.
Expand Down
6 changes: 6 additions & 0 deletions test/statetest/statetest_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ namespace evmone::test
return "SenderNotEOA";
case INIT_CODE_SIZE_LIMIT_EXCEEDED:
return "TR_InitCodeLimitExceeded";
case INIT_CODE_EMPTY:
return "TR_InitCodeEmpty";
case INIT_CODE_COUNT_LIMIT_EXCEEDED:
return "TR_InitCodeCountLimitExceeded";
case INIT_CODE_COUNT_ZERO:
return "TR_InitCodeCountZero";
case CREATE_BLOB_TX:
return "TR_BLOBCREATE";
case EMPTY_BLOB_HASHES_LIST:
Expand Down
6 changes: 6 additions & 0 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
o.type = state::Transaction::Type::set_code;
o.authorization_list = from_json<state::AuthorizationList>(*au_it);
}
else if (const auto it_initcodes = j.find("initcodes"); it_initcodes != j.end())
{
o.type = state::Transaction::Type::initcodes;
for (const auto& initcode : *it_initcodes)
o.initcodes.push_back(from_json<bytes>(initcode));
}
}

template <>
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ target_sources(
state_transition_eip7702_test.cpp
state_transition_eof_calls_test.cpp
state_transition_eof_create_test.cpp
state_transition_txcreate_test.cpp
state_transition_extcode_test.cpp
state_transition_selfdestruct_test.cpp
state_transition_touch_test.cpp
Expand Down
Loading