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
26 changes: 25 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 @@ -392,6 +397,16 @@ struct evmc_result;
*/
typedef void (*evmc_release_result_fn)(const struct evmc_result* result);

/** The state-gas counters of an execution (EIP-8037). */
struct evmc_state_gas
{
/** The amount of state gas left. */
int64_t left = 0;

/** The portion of the consumed state gas taken from evmc_result::gas_left. */
int64_t spilled = 0;
Comment on lines +404 to +407
};

/** The EVM code execution result. */
struct evmc_result
{
Expand All @@ -414,6 +429,15 @@ struct evmc_result
*/
int64_t gas_refund;

/**
* The state-gas counters after execution (EIP-8037).
*
* If evmc_result::status_code is other than ::EVMC_SUCCESS, evmc_state_gas::left MUST equal
* ::evmc_message::state_gas and evmc_state_gas::spilled MUST be 0. The VM returns the spill
* to evmc_result::gas_left for ::EVMC_REVERT; any other failure consumes it with gas_left.
*/
struct evmc_state_gas state_gas;

/**
* The reference to output data.
*
Expand Down
26 changes: 23 additions & 3 deletions evmc/include/evmc/evmc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ inline const char* to_string(evmc_revision rev) noexcept
/// Alias for evmc_make_result().
constexpr auto make_result = evmc_make_result;

/// @copydoc evmc_state_gas
using StateGas = evmc_state_gas;

/// @copydoc evmc_result
///
/// This is a RAII wrapper for evmc_result and objects of this type
Expand All @@ -332,6 +335,7 @@ 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;
using evmc_result::status_code;

/// Creates the result from the provided arguments.
Expand All @@ -344,23 +348,39 @@ class Result : private evmc_result
/// @param _gas_refund The amount of refunded gas.
/// @param _output_data The pointer to the output.
/// @param _output_size The output size.
/// @param _state_gas The state-gas fields.
explicit Result(evmc_status_code _status_code,
int64_t _gas_left,
int64_t _gas_refund,
const uint8_t* _output_data,
size_t _output_size) noexcept
size_t _output_size,
StateGas _state_gas = {}) noexcept
: evmc_result{make_result(_status_code, _gas_left, _gas_refund, _output_data, _output_size)}
{}
{
state_gas = _state_gas;
}

/// Creates the result without output.
///
/// @param _status_code The status code.
/// @param _gas_left The amount of gas left.
/// @param _gas_refund The amount of refunded gas.
/// @param _state_gas The state-gas fields.
explicit Result(evmc_status_code _status_code = EVMC_INTERNAL_ERROR,
int64_t _gas_left = 0,
int64_t _gas_refund = 0) noexcept
int64_t _gas_refund = 0,
StateGas _state_gas = {}) noexcept
: evmc_result{make_result(_status_code, _gas_left, _gas_refund, nullptr, 0)}
{
state_gas = _state_gas;
}

/// Creates the result without output and without gas left.
///
/// @param _status_code The status code.
/// @param _state_gas The state-gas fields.
explicit Result(evmc_status_code _status_code, StateGas _state_gas) noexcept
: Result{_status_code, 0, 0, _state_gas}
{}

/// Converting constructor from raw evmc_result.
Expand Down
3 changes: 1 addition & 2 deletions evmc/include/evmc/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ static inline struct evmc_result evmc_make_result(enum evmc_status_code status_c
const uint8_t* output_data,
size_t output_size)
{
struct evmc_result result;
memset(&result, 0, sizeof(result));
struct evmc_result result = {};

if (output_size != 0)
{
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
25 changes: 22 additions & 3 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,9 @@ class ExecutionState
const advanced::AdvancedCodeAnalysis* advanced;
} analysis{};

/// The frame's state-gas counters (EIP-8037).
StateGas state_gas;

/// Stack space allocation.
///
/// This is the last field to make other fields' offsets of reasonable values.
Expand All @@ -164,7 +168,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 +181,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 +211,23 @@ 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,
state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size);
return evmc::Result{state.status, gas_left, gas_refund,
state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size,
state.state_gas}
.release_raw();
}
} // 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
58 changes: 57 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,11 +143,18 @@ 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 +166,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 +203,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 +224,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 +301,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 +323,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
15 changes: 14 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 @@ -138,6 +139,18 @@ Result sstore(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
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)
{
// The refill part can be done here because gas_left check always succeeds in this case.
static_assert(cold_sload_cost + warm_storage_read_cost <= CALL_STIPEND);
if (status == EVMC_STORAGE_ADDED_DELETED)
state.state_gas.refill(gas_left, STORAGE_SET_STATE_GAS);
else if (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
Loading