Skip to content

Commit

Permalink
Implement EXT*CALL instructions (EIP-7069) in EOF (#630)
Browse files Browse the repository at this point in the history
Implements [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069),as part of
the [MegaEOF spec](https://github.com/ipsilon/eof/blob/main/spec/eof.md).
Adds implementation of EXTCALL, EXTDELEGATECALL and EXTSTATICCALL instructions.

Co-authored-by: pdobacz <[email protected]>
  • Loading branch information
2 people authored and chfast committed Mar 26, 2024
1 parent 8358ba0 commit d5e55fc
Show file tree
Hide file tree
Showing 16 changed files with 1,917 additions and 451 deletions.
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ jobs:
~/tests/EIPTests/BlockchainTests/
- download_execution_tests:
repo: ipsilon/tests
rev: eof-deprecate-ops-20240318
rev: eof-calls-20240321
legacy: false
- run:
name: "State tests (EOF)"
Expand Down
3 changes: 3 additions & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
table[OP_DATASIZE] = op_undefined;
table[OP_DATACOPY] = op_undefined;
table[OP_RETURNDATALOAD] = op_undefined;
table[OP_EXTCALL] = op_undefined;
table[OP_EXTSTATICCALL] = op_undefined;
table[OP_EXTDELEGATECALL] = op_undefined;
table[OP_JUMPF] = op_undefined;

table[OP_DUPN] = op_undefined;
Expand Down
6 changes: 6 additions & 0 deletions lib/evmone/baseline_instruction_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ constexpr auto legacy_cost_tables = []() noexcept {
tables[EVMC_PRAGUE][OP_SWAPN] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXCHANGE] = instr::undefined;
tables[EVMC_PRAGUE][OP_RETURNDATALOAD] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTSTATICCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTDELEGATECALL] = instr::undefined;
return tables;
}();

Expand All @@ -48,6 +51,9 @@ constexpr auto eof_cost_tables = []() noexcept {
tables[EVMC_PRAGUE][OP_PC] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALLCODE] = instr::undefined;
tables[EVMC_PRAGUE][OP_SELFDESTRUCT] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_STATICCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_DELEGATECALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_CREATE] = instr::undefined;
tables[EVMC_PRAGUE][OP_CREATE2] = instr::undefined;
tables[EVMC_PRAGUE][OP_CODESIZE] = instr::undefined;
Expand Down
6 changes: 6 additions & 0 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,12 @@ inline constexpr auto callcode = call_impl<OP_CALLCODE>;
inline constexpr auto delegatecall = call_impl<OP_DELEGATECALL>;
inline constexpr auto staticcall = call_impl<OP_STATICCALL>;

template <Opcode Op>
Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
inline constexpr auto extcall = extcall_impl<OP_EXTCALL>;
inline constexpr auto extdelegatecall = extcall_impl<OP_EXTDELEGATECALL>;
inline constexpr auto extstaticcall = extcall_impl<OP_EXTSTATICCALL>;

template <Opcode Op>
Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
inline constexpr auto create = create_impl<OP_CREATE>;
Expand Down
131 changes: 114 additions & 17 deletions lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
#include "eof.hpp"
#include "instructions.hpp"

constexpr int64_t MIN_RETAINED_GAS = 5000;
constexpr int64_t MIN_CALLEE_GAS = 2300;
constexpr int64_t CALL_VALUE_COST = 9000;
constexpr int64_t ACCOUNT_CREATION_COST = 25000;

constexpr auto EXTCALL_SUCCESS = 0;
constexpr auto EXTCALL_REVERT = 1;
constexpr auto EXTCALL_ABORT = 2;

namespace evmone::instr::core
{
template <Opcode Op>
Expand Down Expand Up @@ -61,15 +70,15 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
msg.input_size = input_size;
}

auto cost = has_value ? 9000 : 0;
auto cost = has_value ? CALL_VALUE_COST : 0;

if constexpr (Op == OP_CALL)
{
if (has_value && state.in_static_mode())
return {EVMC_STATIC_MODE_VIOLATION, gas_left};

if ((has_value || state.rev < EVMC_SPURIOUS_DRAGON) && !state.host.account_exists(dst))
cost += 25000;
cost += ACCOUNT_CREATION_COST;
}

if ((gas_left -= cost) < 0)
Expand All @@ -96,21 +105,6 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
if (has_value && intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < value)
return {EVMC_SUCCESS, gas_left}; // "Light" failure.

if constexpr (Op == OP_DELEGATECALL)
{
if (state.rev >= EVMC_PRAGUE && is_eof_container(state.original_code))
{
// The code targeted by DELEGATECALL must also be an EOF.
// This restriction has been added to EIP-3540 in
// https://github.com/ethereum/EIPs/pull/7131
uint8_t target_code_prefix[2];
const auto s = state.host.copy_code(
msg.code_address, 0, target_code_prefix, std::size(target_code_prefix));
if (!is_eof_container({target_code_prefix, s}))
return {EVMC_SUCCESS, gas_left};
}
}

const auto result = state.host.call(msg);
state.return_data.assign(result.output_data, result.output_size);
stack.top() = result.status_code == EVMC_SUCCESS;
Expand All @@ -133,6 +127,109 @@ template Result call_impl<OP_DELEGATECALL>(
template Result call_impl<OP_CALLCODE>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;

template <Opcode Op>
Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
{
static_assert(Op == OP_EXTCALL || Op == OP_EXTDELEGATECALL || Op == OP_EXTSTATICCALL);

const auto dst = intx::be::trunc<evmc::address>(stack.pop());
const auto input_offset_u256 = stack.pop();
const auto input_size_u256 = stack.pop();
const auto value = (Op == OP_EXTSTATICCALL || Op == OP_EXTDELEGATECALL) ? 0 : stack.pop();
const auto has_value = value != 0;

stack.push(EXTCALL_ABORT); // Assume (hard) failure.
state.return_data.clear();

if (state.host.access_account(dst) == EVMC_ACCESS_COLD)
{
if ((gas_left -= instr::additional_cold_account_access_cost) < 0)
return {EVMC_OUT_OF_GAS, gas_left};
}

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

const auto input_offset = static_cast<size_t>(input_offset_u256);
const auto input_size = static_cast<size_t>(input_size_u256);

auto msg = evmc_message{};
msg.kind = (Op == OP_EXTDELEGATECALL) ? EVMC_DELEGATECALL : EVMC_CALL;
msg.flags = (Op == OP_EXTSTATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags;
msg.depth = state.msg->depth + 1;
msg.recipient = (Op != OP_EXTDELEGATECALL) ? dst : state.msg->recipient;
msg.code_address = dst;
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);

if (input_size > 0)
{
// input_offset may be garbage if input_size == 0.
msg.input_data = &state.memory[input_offset];
msg.input_size = input_size;
}

auto cost = has_value ? CALL_VALUE_COST : 0;

if constexpr (Op == OP_EXTCALL)
{
if (has_value && state.in_static_mode())
return {EVMC_STATIC_MODE_VIOLATION, gas_left};

if (has_value && !state.host.account_exists(dst))
cost += ACCOUNT_CREATION_COST;
}

if ((gas_left -= cost) < 0)
return {EVMC_OUT_OF_GAS, gas_left};

msg.gas = gas_left - std::max(gas_left / 64, MIN_RETAINED_GAS);

if (msg.gas < MIN_CALLEE_GAS || state.msg->depth >= 1024 ||
(has_value &&
intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < value))
{
stack.top() = EXTCALL_REVERT;
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
}

if constexpr (Op == OP_EXTDELEGATECALL)
{
// The code targeted by EXTDELEGATECALL must also be an EOF.
// This restriction has been added to EIP-3540 in
// https://github.com/ethereum/EIPs/pull/7131
uint8_t target_code_prefix[2];
const auto s = state.host.copy_code(
msg.code_address, 0, target_code_prefix, std::size(target_code_prefix));
if (!is_eof_container({target_code_prefix, s}))
{
stack.top() = EXTCALL_REVERT;
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
}
}

const auto result = state.host.call(msg);
state.return_data.assign(result.output_data, result.output_size);
if (result.status_code == EVMC_SUCCESS)
stack.top() = EXTCALL_SUCCESS;
else if (result.status_code == EVMC_REVERT)
stack.top() = EXTCALL_REVERT;
else
stack.top() = EXTCALL_ABORT;

const auto gas_used = msg.gas - result.gas_left;
gas_left -= gas_used;
state.gas_refund += result.gas_refund;
return {EVMC_SUCCESS, gas_left};
}

template Result extcall_impl<OP_EXTCALL>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result extcall_impl<OP_EXTSTATICCALL>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result extcall_impl<OP_EXTDELEGATECALL>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;

template <Opcode Op>
Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
Expand Down
3 changes: 3 additions & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ enum Opcode : uint8_t
OP_CREATE2 = 0xf5,
OP_RETURNDATALOAD = 0xf7,

OP_EXTCALL = 0xf8,
OP_EXTDELEGATECALL = 0xf9,
OP_STATICCALL = 0xfa,
OP_EXTSTATICCALL = 0xfb,

OP_REVERT = 0xfd,
OP_INVALID = 0xfe,
Expand Down
6 changes: 6 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ constexpr inline GasCostTable gas_costs = []() noexcept {
table[EVMC_PRAGUE][OP_DATASIZE] = 2;
table[EVMC_PRAGUE][OP_DATACOPY] = 3;
table[EVMC_PRAGUE][OP_RETURNDATALOAD] = 3;
table[EVMC_PRAGUE][OP_EXTCALL] = warm_storage_read_cost;
table[EVMC_PRAGUE][OP_EXTDELEGATECALL] = warm_storage_read_cost;
table[EVMC_PRAGUE][OP_EXTSTATICCALL] = warm_storage_read_cost;

return table;
}();
Expand Down Expand Up @@ -405,7 +408,10 @@ constexpr inline std::array<Traits, 256> traits = []() noexcept {
table[OP_DELEGATECALL] = {"DELEGATECALL", 0, false, 6, -5, EVMC_HOMESTEAD};
table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE};
table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, EVMC_PRAGUE};
table[OP_EXTCALL] = {"EXTCALL", 0, false, 4, -3, EVMC_PRAGUE};
table[OP_EXTDELEGATECALL] = {"EXTDELEGATECALL", 0, false, 3, -2, EVMC_PRAGUE};
table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM};
table[OP_EXTSTATICCALL] = {"EXTSTATICCALL", 0, false, 3, -2, EVMC_PRAGUE};
table[OP_CALLF] = {"CALLF", 2, false, 0, 0, EVMC_PRAGUE};
table[OP_RETF] = {"RETF", 0, true, 0, 0, EVMC_PRAGUE};
table[OP_JUMPF] = {"JUMPF", 2, true, 0, 0, EVMC_PRAGUE};
Expand Down
Loading

0 comments on commit d5e55fc

Please sign in to comment.