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
17 changes: 16 additions & 1 deletion evmc/include/evmc/evmc.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum
*
* @see @ref versioning
*/
EVMC_ABI_VERSION = 18
EVMC_ABI_VERSION = 19
};


Expand Down Expand Up @@ -120,6 +120,11 @@ struct evmc_message
*/
int64_t gas;

/**
* The amount of state gas available (EIP-8037).
*/
int64_t state_gas;

/**
* The recipient of the message.
*
Expand Down Expand Up @@ -414,6 +419,16 @@ struct evmc_result
*/
int64_t gas_refund;

/**
* The amount of state gas left after execution (EIP-8037).
*/
int64_t state_gas_left;

/**
* The portion of consumed state gas taken from gas_left (EIP-8037).
*/
int64_t state_gas_spilled;

/**
* The reference to output data.
*
Expand Down
2 changes: 2 additions & 0 deletions evmc/include/evmc/evmc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ class Result : private evmc_result
using evmc_result::gas_refund;
using evmc_result::output_data;
using evmc_result::output_size;
using evmc_result::state_gas_left;
using evmc_result::state_gas_spilled;
using evmc_result::status_code;

/// Creates the result from the provided arguments.
Expand Down
9 changes: 9 additions & 0 deletions lib/evmone/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ constexpr auto MAX_NONCE = 0xffff'ffff'ffff'ffff;

/// The gas given back to a value-transferring CALL, the Yellow Paper's G_callstipend.
constexpr auto CALL_STIPEND = 2300;

/// The fixed cost per state byte (EIP-8037).
constexpr auto COST_PER_STATE_BYTE = 1530;

/// State-gas cost of creating a new account (EIP-8037).
constexpr auto NEW_ACCOUNT_STATE_GAS = 120 * COST_PER_STATE_BYTE;

/// State-gas cost of allocating a storage slot (EIP-8037).
constexpr auto STORAGE_SET_STATE_GAS = 64 * COST_PER_STATE_BYTE;
} // namespace evmone
27 changes: 25 additions & 2 deletions lib/evmone/execution_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include "state_gas.hpp"
#include <evmc/evmc.hpp>
#include <intx/intx.hpp>
#include <cassert>
Expand Down Expand Up @@ -154,6 +155,11 @@ class ExecutionState
const advanced::AdvancedCodeAnalysis* advanced;
} analysis{};

/// The frame's state-gas counters (EIP-8037).
///
/// Kept in the cold tail so `status` and `host` are accessed with shorted instructions.
StateGas state_gas;

/// Stack space allocation.
///
/// This is the last field to make other fields' offsets of reasonable values.
Expand All @@ -164,7 +170,11 @@ class ExecutionState
ExecutionState(const evmc_message& message, evmc_revision revision,
const evmc_host_interface& host_interface, evmc_host_context* host_ctx,
bytes_view _code) noexcept
: msg{&message}, host{host_interface, host_ctx}, rev{revision}, original_code{_code}
: msg{&message},
host{host_interface, host_ctx},
rev{revision},
original_code{_code},
state_gas{.left = message.state_gas}
{}

/// Resets the contents of the ExecutionState so that it could be reused.
Expand All @@ -173,6 +183,7 @@ class ExecutionState
bytes_view _code) noexcept
{
gas_refund = 0;
state_gas = {.left = message.state_gas};
memory.clear();
msg = &message;
host = {host_interface, host_ctx};
Expand Down Expand Up @@ -202,13 +213,25 @@ class ExecutionState
/// success, and the output is the memory range recorded in the state.
inline evmc_result make_execution_result(ExecutionState& state, int64_t gas_left) noexcept
{
if (state.rev >= EVMC_AMSTERDAM && state.status != EVMC_SUCCESS)
{
// Unsuccessful frame doesn't commit any state changes, roll-back all state-gas costs.
gas_left += state.state_gas.spilled;
state.state_gas.left = state.msg->state_gas;
state.state_gas.spilled = 0;
}

// An exceptional halt consumes all gas; only a success or revert keeps gas_left.
if (state.status != EVMC_SUCCESS && state.status != EVMC_REVERT)
gas_left = 0;
const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0;

assert(state.output_size != 0 || state.output_offset == 0);
return evmc::make_result(state.status, gas_left, gas_refund,
// TODO: Simplify result creation.
auto result = evmc::make_result(state.status, gas_left, gas_refund,
state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size);
result.state_gas_left = state.state_gas.left;
result.state_gas_spilled = state.state_gas.spilled;
return result;
}
} // namespace evmone
13 changes: 11 additions & 2 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include "baseline.hpp"
#include "constants.hpp"
#include "execution_state.hpp"
#include "instructions_traits.hpp"
#include "instructions_xmacro.hpp"
Expand Down Expand Up @@ -1081,8 +1082,16 @@ inline TermResult selfdestruct(StackTop stack, int64_t gas_left, ExecutionState&
// sending value to a non-existing account.
if (!state.host.account_exists(beneficiary))
{
if ((gas_left -= 25000) < 0)
return {EVMC_OUT_OF_GAS, gas_left};
if (state.rev >= EVMC_AMSTERDAM)
{
if (!state.state_gas.charge(gas_left, NEW_ACCOUNT_STATE_GAS))
return {EVMC_OUT_OF_GAS, gas_left};
}
else
{
if ((gas_left -= 25000) < 0)
return {EVMC_OUT_OF_GAS, gas_left};
}
}
}
}
Expand Down
60 changes: 59 additions & 1 deletion lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ inline std::variant<evmc::address, Result> get_target_address(

return *delegate_addr;
}

/// Absorbs a child's state-gas back to the parent (EIP-8037).
inline void absorb_child_state_gas(
int64_t& gas_left, ExecutionState& state, const evmc::Result& result) noexcept
{
assert(result.state_gas_left >= 0);
assert(result.state_gas_spilled >= 0);

// At most one of the two pools is ever non-empty.
assert(state.state_gas.left == 0 || state.state_gas.spilled == 0);
assert(result.state_gas_left == 0 || result.state_gas_spilled == 0);

// In a non-successful result, all is returned back.
assert(result.status_code == EVMC_SUCCESS ||
(result.state_gas_left == state.state_gas.left && result.state_gas_spilled == 0));

// Accumulate the spilled state-gas.
state.state_gas.spilled += result.state_gas_spilled;

// Rebalance the state-gas refills: the caller must move callee's refills to gas_left up to the
// caller's spilled counter. Do this by refilling all returned state-gas to zeroed `left`.
state.state_gas.left = 0;
state.state_gas.refill(gas_left, result.state_gas_left);
}
} // namespace

/// Converts an opcode to matching EVMC call kind.
Expand Down Expand Up @@ -119,12 +143,21 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce

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

bool new_account_charged = false; // NOLINT(*-const-correctness)
if constexpr (Op == OP_CALL)
{
if ((has_value || state.rev < EVMC_SPURIOUS_DRAGON) && !state.host.account_exists(dst))
{
if ((gas_left -= ACCOUNT_CREATION_COST) < 0)
if (state.rev >= EVMC_AMSTERDAM)
{
if (!state.state_gas.charge(gas_left, NEW_ACCOUNT_STATE_GAS))
return {EVMC_OUT_OF_GAS, gas_left};
new_account_charged = true;
}
else if ((gas_left -= ACCOUNT_CREATION_COST) < 0)
{
return {EVMC_OUT_OF_GAS, gas_left};
}
}
}

Expand All @@ -135,6 +168,7 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
else
msg.flags &= ~std::underlying_type_t<evmc_flags>{EVMC_DELEGATED};
msg.depth = state.msg->depth + 1;
msg.state_gas = state.state_gas.left;
msg.recipient = (Op == OP_CALL || Op == OP_STATICCALL) ? dst : state.msg->recipient;
msg.code_address = code_addr;
msg.sender = (Op == OP_DELEGATECALL) ? state.msg->sender : state.msg->recipient;
Expand Down Expand Up @@ -171,7 +205,11 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
msg.gas += CALL_STIPEND;
gas_left += CALL_STIPEND;
if (intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < value)
{
if (new_account_charged)
state.state_gas.refill(gas_left, NEW_ACCOUNT_STATE_GAS);
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
}
}
}

Expand All @@ -188,6 +226,14 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
const auto gas_used = msg.gas - result.gas_left;
gas_left -= gas_used;
state.gas_refund += result.gas_refund;
absorb_child_state_gas(gas_left, state, result);

if constexpr (Op == OP_CALL)
{
if (new_account_charged && result.status_code != EVMC_SUCCESS)
state.state_gas.refill(gas_left, NEW_ACCOUNT_STATE_GAS);
}

return {EVMC_SUCCESS, gas_left};
}

Expand Down Expand Up @@ -257,10 +303,19 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex
if (state.rev >= EVMC_BERLIN)
state.host.access_account(msg.recipient);

bool new_account_charged = false;
if (state.rev >= EVMC_AMSTERDAM && !state.host.account_exists(msg.recipient))
{
if (!state.state_gas.charge(gas_left, NEW_ACCOUNT_STATE_GAS))
return {EVMC_OUT_OF_GAS, gas_left};
new_account_charged = true;
}

msg.gas = gas_left;
if (state.rev >= EVMC_TANGERINE_WHISTLE)
msg.gas -= msg.gas / 64;

msg.state_gas = state.state_gas.left;
msg.input_data = init_code.data();
msg.input_size = init_code.size();
msg.sender = sender;
Expand All @@ -270,6 +325,9 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex
const auto result = state.host.call(msg);
gas_left -= msg.gas - result.gas_left;
state.gas_refund += result.gas_refund;
absorb_child_state_gas(gas_left, state, result);
if (new_account_charged && result.status_code != EVMC_SUCCESS)
state.state_gas.refill(gas_left, NEW_ACCOUNT_STATE_GAS);

state.return_data.assign(result.output_data, result.output_size);
if (result.status_code == EVMC_SUCCESS)
Expand Down
11 changes: 10 additions & 1 deletion lib/evmone/instructions_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ constexpr auto storage_cost_spec = []() noexcept {
tbl[EVMC_PRAGUE] = tbl[EVMC_LONDON];
tbl[EVMC_OSAKA] = tbl[EVMC_LONDON];
tbl[EVMC_AMSTERDAM] = tbl[EVMC_LONDON];
tbl[EVMC_EXPERIMENTAL] = tbl[EVMC_LONDON];
tbl[EVMC_AMSTERDAM].set = tbl[EVMC_AMSTERDAM].reset; // Only execution cost (EIP-8037).
tbl[EVMC_EXPERIMENTAL] = tbl[EVMC_AMSTERDAM];
return tbl;
}();

Expand Down Expand Up @@ -134,10 +135,18 @@ Result sstore(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
0;
const auto status = state.host.set_storage(state.msg->recipient, key, value);

if (state.rev >= EVMC_AMSTERDAM && status == EVMC_STORAGE_ADDED_DELETED)
state.state_gas.refill(gas_left, STORAGE_SET_STATE_GAS);

const auto [gas_cost_warm, gas_refund] = sstore_costs[state.rev][status];
const auto gas_cost = gas_cost_warm + gas_cost_cold;
if ((gas_left -= gas_cost) < 0)
return {EVMC_OUT_OF_GAS, gas_left};

if (state.rev >= EVMC_AMSTERDAM && status == EVMC_STORAGE_ADDED &&
!state.state_gas.charge(gas_left, STORAGE_SET_STATE_GAS))
return {EVMC_OUT_OF_GAS, gas_left};

state.gas_refund += gas_refund;
return {EVMC_SUCCESS, gas_left};
}
Expand Down
51 changes: 51 additions & 0 deletions lib/evmone/state_gas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2026 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <algorithm>
#include <cassert>
#include <cstdint>

namespace evmone
{
/// A frame's state-gas as a (left, spilled) pair, independent from the execution gas (EIP-8037).
struct StateGas
{
/// Remaining state-gas reservoir.
int64_t left = 0;

/// Consumed state-gas taken from `gas_left` (happens when `left` is empty).
int64_t spilled = 0;

/// Charges `cost`, first from `left`, then from `gas_left` (recorded in `spilled`).
[[nodiscard]] bool charge(int64_t& gas_left, int64_t cost) noexcept
{
assert(cost >= 0); // 0 charge happens in code deployment.
if (left >= cost)
{
left -= cost;
return true;
}
const auto spill = cost - left;
if (gas_left < spill)
return false;
gas_left -= spill;
spilled += spill;
left = 0;
return true;
}

/// Refund state-gas.
///
/// Give the `cost` to `gas_left` (up to `spilled`) and `left` (whatever remains).
void refill(int64_t& gas_left, int64_t cost) noexcept
{
assert(cost >= 0); // 0 refill happens in absorb.
const auto to_gas_left = std::min(cost, spilled);
gas_left += to_gas_left;
spilled -= to_gas_left;
left += cost - to_gas_left;
}
};
} // namespace evmone
Loading