From d5e55fc4fc1a6fe94160424e91b443847c13927a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 26 Mar 2024 16:05:47 +0100 Subject: [PATCH] Implement EXT*CALL instructions (EIP-7069) in EOF (#630) 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 <5735525+pdobacz@users.noreply.github.com> --- circle.yml | 2 +- lib/evmone/advanced_instructions.cpp | 3 + lib/evmone/baseline_instruction_table.cpp | 6 + lib/evmone/instructions.hpp | 6 + lib/evmone/instructions_calls.cpp | 131 ++- lib/evmone/instructions_opcodes.hpp | 3 + lib/evmone/instructions_traits.hpp | 6 + lib/evmone/instructions_xmacro.hpp | 542 +++++----- test/unittests/CMakeLists.txt | 2 + test/unittests/eof_validation_test.cpp | 8 +- test/unittests/evm_calls_test.cpp | 151 +-- test/unittests/evm_eof_calls_test.cpp | 486 ++++++++- test/unittests/instructions_test.cpp | 3 + test/unittests/state_transition_call_test.cpp | 35 + .../state_transition_eof_calls_test.cpp | 931 ++++++++++++++++++ test/utils/bytecode.hpp | 53 + 16 files changed, 1917 insertions(+), 451 deletions(-) create mode 100644 test/unittests/state_transition_eof_calls_test.cpp diff --git a/circle.yml b/circle.yml index c2ee667745..1ce521b4ab 100644 --- a/circle.yml +++ b/circle.yml @@ -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)" diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 484aa0d5b7..7afcbedd29 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -278,6 +278,9 @@ constexpr std::array 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; diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index 36d78542e8..1709974011 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -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; }(); @@ -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; diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index efab94aa56..315525fe14 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1066,6 +1066,12 @@ inline constexpr auto callcode = call_impl; inline constexpr auto delegatecall = call_impl; inline constexpr auto staticcall = call_impl; +template +Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; +inline constexpr auto extcall = extcall_impl; +inline constexpr auto extdelegatecall = extcall_impl; +inline constexpr auto extstaticcall = extcall_impl; + template Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; inline constexpr auto create = create_impl; diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index df2eaaa440..f517a7647d 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -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 @@ -61,7 +70,7 @@ 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) { @@ -69,7 +78,7 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce 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) @@ -96,21 +105,6 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce if (has_value && intx::be::load(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; @@ -133,6 +127,109 @@ template Result call_impl( template Result call_impl( StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; +template +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(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(input_offset_u256); + const auto input_size = static_cast(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(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(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( + StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; +template Result extcall_impl( + StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; +template Result extcall_impl( + StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; template Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 977abd96fc..98f63be1cb 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -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, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 66289bca37..3d37cc7418 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -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; }(); @@ -405,7 +408,10 @@ constexpr inline std::array 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}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 6a77a818d3..b1f4cbf17e 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -31,275 +31,275 @@ /// undef it and restore the alias after usage. /// /// See for more about X Macros: https://en.wikipedia.org/wiki/X_Macro. -#define MAP_OPCODES \ - ON_OPCODE_IDENTIFIER(OP_STOP, stop) \ - ON_OPCODE_IDENTIFIER(OP_ADD, add) \ - ON_OPCODE_IDENTIFIER(OP_MUL, mul) \ - ON_OPCODE_IDENTIFIER(OP_SUB, sub) \ - ON_OPCODE_IDENTIFIER(OP_DIV, div) \ - ON_OPCODE_IDENTIFIER(OP_SDIV, sdiv) \ - ON_OPCODE_IDENTIFIER(OP_MOD, mod) \ - ON_OPCODE_IDENTIFIER(OP_SMOD, smod) \ - ON_OPCODE_IDENTIFIER(OP_ADDMOD, addmod) \ - ON_OPCODE_IDENTIFIER(OP_MULMOD, mulmod) \ - ON_OPCODE_IDENTIFIER(OP_EXP, exp) \ - ON_OPCODE_IDENTIFIER(OP_SIGNEXTEND, signextend) \ - ON_OPCODE_UNDEFINED(0x0c) \ - ON_OPCODE_UNDEFINED(0x0d) \ - ON_OPCODE_UNDEFINED(0x0e) \ - ON_OPCODE_UNDEFINED(0x0f) \ - \ - ON_OPCODE_IDENTIFIER(OP_LT, lt) \ - ON_OPCODE_IDENTIFIER(OP_GT, gt) \ - ON_OPCODE_IDENTIFIER(OP_SLT, slt) \ - ON_OPCODE_IDENTIFIER(OP_SGT, sgt) \ - ON_OPCODE_IDENTIFIER(OP_EQ, eq) \ - ON_OPCODE_IDENTIFIER(OP_ISZERO, iszero) \ - ON_OPCODE_IDENTIFIER(OP_AND, and_) \ - ON_OPCODE_IDENTIFIER(OP_OR, or_) \ - ON_OPCODE_IDENTIFIER(OP_XOR, xor_) \ - ON_OPCODE_IDENTIFIER(OP_NOT, not_) \ - ON_OPCODE_IDENTIFIER(OP_BYTE, byte) \ - ON_OPCODE_IDENTIFIER(OP_SHL, shl) \ - ON_OPCODE_IDENTIFIER(OP_SHR, shr) \ - ON_OPCODE_IDENTIFIER(OP_SAR, sar) \ - ON_OPCODE_UNDEFINED(0x1e) \ - ON_OPCODE_UNDEFINED(0x1f) \ - \ - ON_OPCODE_IDENTIFIER(OP_KECCAK256, keccak256) \ - ON_OPCODE_UNDEFINED(0x21) \ - ON_OPCODE_UNDEFINED(0x22) \ - ON_OPCODE_UNDEFINED(0x23) \ - ON_OPCODE_UNDEFINED(0x24) \ - ON_OPCODE_UNDEFINED(0x25) \ - ON_OPCODE_UNDEFINED(0x26) \ - ON_OPCODE_UNDEFINED(0x27) \ - ON_OPCODE_UNDEFINED(0x28) \ - ON_OPCODE_UNDEFINED(0x29) \ - ON_OPCODE_UNDEFINED(0x2a) \ - ON_OPCODE_UNDEFINED(0x2b) \ - ON_OPCODE_UNDEFINED(0x2c) \ - ON_OPCODE_UNDEFINED(0x2d) \ - ON_OPCODE_UNDEFINED(0x2e) \ - ON_OPCODE_UNDEFINED(0x2f) \ - \ - ON_OPCODE_IDENTIFIER(OP_ADDRESS, address) \ - ON_OPCODE_IDENTIFIER(OP_BALANCE, balance) \ - ON_OPCODE_IDENTIFIER(OP_ORIGIN, origin) \ - ON_OPCODE_IDENTIFIER(OP_CALLER, caller) \ - ON_OPCODE_IDENTIFIER(OP_CALLVALUE, callvalue) \ - ON_OPCODE_IDENTIFIER(OP_CALLDATALOAD, calldataload) \ - ON_OPCODE_IDENTIFIER(OP_CALLDATASIZE, calldatasize) \ - ON_OPCODE_IDENTIFIER(OP_CALLDATACOPY, calldatacopy) \ - ON_OPCODE_IDENTIFIER(OP_CODESIZE, codesize) \ - ON_OPCODE_IDENTIFIER(OP_CODECOPY, codecopy) \ - ON_OPCODE_IDENTIFIER(OP_GASPRICE, gasprice) \ - ON_OPCODE_IDENTIFIER(OP_EXTCODESIZE, extcodesize) \ - ON_OPCODE_IDENTIFIER(OP_EXTCODECOPY, extcodecopy) \ - ON_OPCODE_IDENTIFIER(OP_RETURNDATASIZE, returndatasize) \ - ON_OPCODE_IDENTIFIER(OP_RETURNDATACOPY, returndatacopy) \ - ON_OPCODE_IDENTIFIER(OP_EXTCODEHASH, extcodehash) \ - \ - ON_OPCODE_IDENTIFIER(OP_BLOCKHASH, blockhash) \ - ON_OPCODE_IDENTIFIER(OP_COINBASE, coinbase) \ - ON_OPCODE_IDENTIFIER(OP_TIMESTAMP, timestamp) \ - ON_OPCODE_IDENTIFIER(OP_NUMBER, number) \ - ON_OPCODE_IDENTIFIER(OP_PREVRANDAO, prevrandao) \ - ON_OPCODE_IDENTIFIER(OP_GASLIMIT, gaslimit) \ - ON_OPCODE_IDENTIFIER(OP_CHAINID, chainid) \ - ON_OPCODE_IDENTIFIER(OP_SELFBALANCE, selfbalance) \ - ON_OPCODE_IDENTIFIER(OP_BASEFEE, basefee) \ - ON_OPCODE_IDENTIFIER(OP_BLOBHASH, blobhash) \ - ON_OPCODE_IDENTIFIER(OP_BLOBBASEFEE, blobbasefee) \ - ON_OPCODE_UNDEFINED(0x4b) \ - ON_OPCODE_UNDEFINED(0x4c) \ - ON_OPCODE_UNDEFINED(0x4d) \ - ON_OPCODE_UNDEFINED(0x4e) \ - ON_OPCODE_UNDEFINED(0x4f) \ - \ - ON_OPCODE_IDENTIFIER(OP_POP, pop) \ - ON_OPCODE_IDENTIFIER(OP_MLOAD, mload) \ - ON_OPCODE_IDENTIFIER(OP_MSTORE, mstore) \ - ON_OPCODE_IDENTIFIER(OP_MSTORE8, mstore8) \ - ON_OPCODE_IDENTIFIER(OP_SLOAD, sload) \ - ON_OPCODE_IDENTIFIER(OP_SSTORE, sstore) \ - ON_OPCODE_IDENTIFIER(OP_JUMP, jump) \ - ON_OPCODE_IDENTIFIER(OP_JUMPI, jumpi) \ - ON_OPCODE_IDENTIFIER(OP_PC, pc) \ - ON_OPCODE_IDENTIFIER(OP_MSIZE, msize) \ - ON_OPCODE_IDENTIFIER(OP_GAS, gas) \ - ON_OPCODE_IDENTIFIER(OP_JUMPDEST, jumpdest) \ - ON_OPCODE_IDENTIFIER(OP_TLOAD, tload) \ - ON_OPCODE_IDENTIFIER(OP_TSTORE, tstore) \ - ON_OPCODE_IDENTIFIER(OP_MCOPY, mcopy) \ - ON_OPCODE_IDENTIFIER(OP_PUSH0, push0) \ - \ - ON_OPCODE_IDENTIFIER(OP_PUSH1, push<1>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH2, push<2>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH3, push<3>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH4, push<4>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH5, push<5>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH6, push<6>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH7, push<7>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH8, push<8>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH9, push<9>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH10, push<10>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH11, push<11>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH12, push<12>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH13, push<13>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH14, push<14>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH15, push<15>) \ - \ - ON_OPCODE_IDENTIFIER(OP_PUSH16, push<16>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH17, push<17>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH18, push<18>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH19, push<19>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH20, push<20>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH21, push<21>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH22, push<22>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH23, push<23>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH24, push<24>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH25, push<25>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH26, push<26>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH27, push<27>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH28, push<28>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH29, push<29>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH30, push<30>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH31, push<31>) \ - ON_OPCODE_IDENTIFIER(OP_PUSH32, push<32>) \ - \ - ON_OPCODE_IDENTIFIER(OP_DUP1, dup<1>) \ - ON_OPCODE_IDENTIFIER(OP_DUP2, dup<2>) \ - ON_OPCODE_IDENTIFIER(OP_DUP3, dup<3>) \ - ON_OPCODE_IDENTIFIER(OP_DUP4, dup<4>) \ - ON_OPCODE_IDENTIFIER(OP_DUP5, dup<5>) \ - ON_OPCODE_IDENTIFIER(OP_DUP6, dup<6>) \ - ON_OPCODE_IDENTIFIER(OP_DUP7, dup<7>) \ - ON_OPCODE_IDENTIFIER(OP_DUP8, dup<8>) \ - ON_OPCODE_IDENTIFIER(OP_DUP9, dup<9>) \ - ON_OPCODE_IDENTIFIER(OP_DUP10, dup<10>) \ - ON_OPCODE_IDENTIFIER(OP_DUP11, dup<11>) \ - ON_OPCODE_IDENTIFIER(OP_DUP12, dup<12>) \ - ON_OPCODE_IDENTIFIER(OP_DUP13, dup<13>) \ - ON_OPCODE_IDENTIFIER(OP_DUP14, dup<14>) \ - ON_OPCODE_IDENTIFIER(OP_DUP15, dup<15>) \ - ON_OPCODE_IDENTIFIER(OP_DUP16, dup<16>) \ - \ - ON_OPCODE_IDENTIFIER(OP_SWAP1, swap<1>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP2, swap<2>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP3, swap<3>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP4, swap<4>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP5, swap<5>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP6, swap<6>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP7, swap<7>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP8, swap<8>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP9, swap<9>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP10, swap<10>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP11, swap<11>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP12, swap<12>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP13, swap<13>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP14, swap<14>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP15, swap<15>) \ - ON_OPCODE_IDENTIFIER(OP_SWAP16, swap<16>) \ - \ - ON_OPCODE_IDENTIFIER(OP_LOG0, log<0>) \ - ON_OPCODE_IDENTIFIER(OP_LOG1, log<1>) \ - ON_OPCODE_IDENTIFIER(OP_LOG2, log<2>) \ - ON_OPCODE_IDENTIFIER(OP_LOG3, log<3>) \ - ON_OPCODE_IDENTIFIER(OP_LOG4, log<4>) \ - ON_OPCODE_UNDEFINED(0xa5) \ - ON_OPCODE_UNDEFINED(0xa6) \ - ON_OPCODE_UNDEFINED(0xa7) \ - ON_OPCODE_UNDEFINED(0xa8) \ - ON_OPCODE_UNDEFINED(0xa9) \ - ON_OPCODE_UNDEFINED(0xaa) \ - ON_OPCODE_UNDEFINED(0xab) \ - ON_OPCODE_UNDEFINED(0xac) \ - ON_OPCODE_UNDEFINED(0xad) \ - ON_OPCODE_UNDEFINED(0xae) \ - ON_OPCODE_UNDEFINED(0xaf) \ - \ - ON_OPCODE_UNDEFINED(0xb0) \ - ON_OPCODE_UNDEFINED(0xb1) \ - ON_OPCODE_UNDEFINED(0xb2) \ - ON_OPCODE_UNDEFINED(0xb3) \ - ON_OPCODE_UNDEFINED(0xb4) \ - ON_OPCODE_UNDEFINED(0xb5) \ - ON_OPCODE_UNDEFINED(0xb6) \ - ON_OPCODE_UNDEFINED(0xb7) \ - ON_OPCODE_UNDEFINED(0xb8) \ - ON_OPCODE_UNDEFINED(0xb9) \ - ON_OPCODE_UNDEFINED(0xba) \ - ON_OPCODE_UNDEFINED(0xbb) \ - ON_OPCODE_UNDEFINED(0xbc) \ - ON_OPCODE_UNDEFINED(0xbd) \ - ON_OPCODE_UNDEFINED(0xbe) \ - ON_OPCODE_UNDEFINED(0xbf) \ - \ - ON_OPCODE_UNDEFINED(0xc0) \ - ON_OPCODE_UNDEFINED(0xc1) \ - ON_OPCODE_UNDEFINED(0xc2) \ - ON_OPCODE_UNDEFINED(0xc3) \ - ON_OPCODE_UNDEFINED(0xc4) \ - ON_OPCODE_UNDEFINED(0xc5) \ - ON_OPCODE_UNDEFINED(0xc6) \ - ON_OPCODE_UNDEFINED(0xc7) \ - ON_OPCODE_UNDEFINED(0xc8) \ - ON_OPCODE_UNDEFINED(0xc9) \ - ON_OPCODE_UNDEFINED(0xca) \ - ON_OPCODE_UNDEFINED(0xcb) \ - ON_OPCODE_UNDEFINED(0xcc) \ - ON_OPCODE_UNDEFINED(0xcd) \ - ON_OPCODE_UNDEFINED(0xce) \ - ON_OPCODE_UNDEFINED(0xcf) \ - \ - ON_OPCODE_IDENTIFIER(OP_DATALOAD, dataload) \ - ON_OPCODE_IDENTIFIER(OP_DATALOADN, dataloadn) \ - ON_OPCODE_IDENTIFIER(OP_DATASIZE, datasize) \ - ON_OPCODE_IDENTIFIER(OP_DATACOPY, datacopy) \ - ON_OPCODE_UNDEFINED(0xd4) \ - ON_OPCODE_UNDEFINED(0xd5) \ - ON_OPCODE_UNDEFINED(0xd6) \ - ON_OPCODE_UNDEFINED(0xd7) \ - ON_OPCODE_UNDEFINED(0xd8) \ - ON_OPCODE_UNDEFINED(0xd9) \ - ON_OPCODE_UNDEFINED(0xda) \ - ON_OPCODE_UNDEFINED(0xdb) \ - ON_OPCODE_UNDEFINED(0xdc) \ - ON_OPCODE_UNDEFINED(0xdd) \ - ON_OPCODE_UNDEFINED(0xde) \ - ON_OPCODE_UNDEFINED(0xdf) \ - \ - ON_OPCODE_IDENTIFIER(OP_RJUMP, rjump) \ - ON_OPCODE_IDENTIFIER(OP_RJUMPI, rjumpi) \ - ON_OPCODE_IDENTIFIER(OP_RJUMPV, rjumpv) \ - ON_OPCODE_IDENTIFIER(OP_CALLF, callf) \ - ON_OPCODE_IDENTIFIER(OP_RETF, retf) \ - ON_OPCODE_IDENTIFIER(OP_JUMPF, jumpf) \ - ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \ - ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \ - ON_OPCODE_IDENTIFIER(OP_EXCHANGE, exchange) \ - ON_OPCODE_UNDEFINED(0xe9) \ - ON_OPCODE_UNDEFINED(0xea) \ - ON_OPCODE_UNDEFINED(0xeb) \ - ON_OPCODE_UNDEFINED(0xec) \ - ON_OPCODE_UNDEFINED(0xed) \ - ON_OPCODE_UNDEFINED(0xee) \ - ON_OPCODE_UNDEFINED(0xef) \ - \ - ON_OPCODE_IDENTIFIER(OP_CREATE, create) \ - ON_OPCODE_IDENTIFIER(OP_CALL, call) \ - ON_OPCODE_IDENTIFIER(OP_CALLCODE, callcode) \ - ON_OPCODE_IDENTIFIER(OP_RETURN, return_) \ - ON_OPCODE_IDENTIFIER(OP_DELEGATECALL, delegatecall) \ - ON_OPCODE_IDENTIFIER(OP_CREATE2, create2) \ - ON_OPCODE_UNDEFINED(0xf6) \ - ON_OPCODE_IDENTIFIER(OP_RETURNDATALOAD, returndataload) \ - ON_OPCODE_UNDEFINED(0xf8) \ - ON_OPCODE_UNDEFINED(0xf9) \ - ON_OPCODE_IDENTIFIER(OP_STATICCALL, staticcall) \ - ON_OPCODE_UNDEFINED(0xfb) \ - ON_OPCODE_UNDEFINED(0xfc) \ - ON_OPCODE_IDENTIFIER(OP_REVERT, revert) \ - ON_OPCODE_IDENTIFIER(OP_INVALID, invalid) \ +#define MAP_OPCODES \ + ON_OPCODE_IDENTIFIER(OP_STOP, stop) \ + ON_OPCODE_IDENTIFIER(OP_ADD, add) \ + ON_OPCODE_IDENTIFIER(OP_MUL, mul) \ + ON_OPCODE_IDENTIFIER(OP_SUB, sub) \ + ON_OPCODE_IDENTIFIER(OP_DIV, div) \ + ON_OPCODE_IDENTIFIER(OP_SDIV, sdiv) \ + ON_OPCODE_IDENTIFIER(OP_MOD, mod) \ + ON_OPCODE_IDENTIFIER(OP_SMOD, smod) \ + ON_OPCODE_IDENTIFIER(OP_ADDMOD, addmod) \ + ON_OPCODE_IDENTIFIER(OP_MULMOD, mulmod) \ + ON_OPCODE_IDENTIFIER(OP_EXP, exp) \ + ON_OPCODE_IDENTIFIER(OP_SIGNEXTEND, signextend) \ + ON_OPCODE_UNDEFINED(0x0c) \ + ON_OPCODE_UNDEFINED(0x0d) \ + ON_OPCODE_UNDEFINED(0x0e) \ + ON_OPCODE_UNDEFINED(0x0f) \ + \ + ON_OPCODE_IDENTIFIER(OP_LT, lt) \ + ON_OPCODE_IDENTIFIER(OP_GT, gt) \ + ON_OPCODE_IDENTIFIER(OP_SLT, slt) \ + ON_OPCODE_IDENTIFIER(OP_SGT, sgt) \ + ON_OPCODE_IDENTIFIER(OP_EQ, eq) \ + ON_OPCODE_IDENTIFIER(OP_ISZERO, iszero) \ + ON_OPCODE_IDENTIFIER(OP_AND, and_) \ + ON_OPCODE_IDENTIFIER(OP_OR, or_) \ + ON_OPCODE_IDENTIFIER(OP_XOR, xor_) \ + ON_OPCODE_IDENTIFIER(OP_NOT, not_) \ + ON_OPCODE_IDENTIFIER(OP_BYTE, byte) \ + ON_OPCODE_IDENTIFIER(OP_SHL, shl) \ + ON_OPCODE_IDENTIFIER(OP_SHR, shr) \ + ON_OPCODE_IDENTIFIER(OP_SAR, sar) \ + ON_OPCODE_UNDEFINED(0x1e) \ + ON_OPCODE_UNDEFINED(0x1f) \ + \ + ON_OPCODE_IDENTIFIER(OP_KECCAK256, keccak256) \ + ON_OPCODE_UNDEFINED(0x21) \ + ON_OPCODE_UNDEFINED(0x22) \ + ON_OPCODE_UNDEFINED(0x23) \ + ON_OPCODE_UNDEFINED(0x24) \ + ON_OPCODE_UNDEFINED(0x25) \ + ON_OPCODE_UNDEFINED(0x26) \ + ON_OPCODE_UNDEFINED(0x27) \ + ON_OPCODE_UNDEFINED(0x28) \ + ON_OPCODE_UNDEFINED(0x29) \ + ON_OPCODE_UNDEFINED(0x2a) \ + ON_OPCODE_UNDEFINED(0x2b) \ + ON_OPCODE_UNDEFINED(0x2c) \ + ON_OPCODE_UNDEFINED(0x2d) \ + ON_OPCODE_UNDEFINED(0x2e) \ + ON_OPCODE_UNDEFINED(0x2f) \ + \ + ON_OPCODE_IDENTIFIER(OP_ADDRESS, address) \ + ON_OPCODE_IDENTIFIER(OP_BALANCE, balance) \ + ON_OPCODE_IDENTIFIER(OP_ORIGIN, origin) \ + ON_OPCODE_IDENTIFIER(OP_CALLER, caller) \ + ON_OPCODE_IDENTIFIER(OP_CALLVALUE, callvalue) \ + ON_OPCODE_IDENTIFIER(OP_CALLDATALOAD, calldataload) \ + ON_OPCODE_IDENTIFIER(OP_CALLDATASIZE, calldatasize) \ + ON_OPCODE_IDENTIFIER(OP_CALLDATACOPY, calldatacopy) \ + ON_OPCODE_IDENTIFIER(OP_CODESIZE, codesize) \ + ON_OPCODE_IDENTIFIER(OP_CODECOPY, codecopy) \ + ON_OPCODE_IDENTIFIER(OP_GASPRICE, gasprice) \ + ON_OPCODE_IDENTIFIER(OP_EXTCODESIZE, extcodesize) \ + ON_OPCODE_IDENTIFIER(OP_EXTCODECOPY, extcodecopy) \ + ON_OPCODE_IDENTIFIER(OP_RETURNDATASIZE, returndatasize) \ + ON_OPCODE_IDENTIFIER(OP_RETURNDATACOPY, returndatacopy) \ + ON_OPCODE_IDENTIFIER(OP_EXTCODEHASH, extcodehash) \ + \ + ON_OPCODE_IDENTIFIER(OP_BLOCKHASH, blockhash) \ + ON_OPCODE_IDENTIFIER(OP_COINBASE, coinbase) \ + ON_OPCODE_IDENTIFIER(OP_TIMESTAMP, timestamp) \ + ON_OPCODE_IDENTIFIER(OP_NUMBER, number) \ + ON_OPCODE_IDENTIFIER(OP_PREVRANDAO, prevrandao) \ + ON_OPCODE_IDENTIFIER(OP_GASLIMIT, gaslimit) \ + ON_OPCODE_IDENTIFIER(OP_CHAINID, chainid) \ + ON_OPCODE_IDENTIFIER(OP_SELFBALANCE, selfbalance) \ + ON_OPCODE_IDENTIFIER(OP_BASEFEE, basefee) \ + ON_OPCODE_IDENTIFIER(OP_BLOBHASH, blobhash) \ + ON_OPCODE_IDENTIFIER(OP_BLOBBASEFEE, blobbasefee) \ + ON_OPCODE_UNDEFINED(0x4b) \ + ON_OPCODE_UNDEFINED(0x4c) \ + ON_OPCODE_UNDEFINED(0x4d) \ + ON_OPCODE_UNDEFINED(0x4e) \ + ON_OPCODE_UNDEFINED(0x4f) \ + \ + ON_OPCODE_IDENTIFIER(OP_POP, pop) \ + ON_OPCODE_IDENTIFIER(OP_MLOAD, mload) \ + ON_OPCODE_IDENTIFIER(OP_MSTORE, mstore) \ + ON_OPCODE_IDENTIFIER(OP_MSTORE8, mstore8) \ + ON_OPCODE_IDENTIFIER(OP_SLOAD, sload) \ + ON_OPCODE_IDENTIFIER(OP_SSTORE, sstore) \ + ON_OPCODE_IDENTIFIER(OP_JUMP, jump) \ + ON_OPCODE_IDENTIFIER(OP_JUMPI, jumpi) \ + ON_OPCODE_IDENTIFIER(OP_PC, pc) \ + ON_OPCODE_IDENTIFIER(OP_MSIZE, msize) \ + ON_OPCODE_IDENTIFIER(OP_GAS, gas) \ + ON_OPCODE_IDENTIFIER(OP_JUMPDEST, jumpdest) \ + ON_OPCODE_IDENTIFIER(OP_TLOAD, tload) \ + ON_OPCODE_IDENTIFIER(OP_TSTORE, tstore) \ + ON_OPCODE_IDENTIFIER(OP_MCOPY, mcopy) \ + ON_OPCODE_IDENTIFIER(OP_PUSH0, push0) \ + \ + ON_OPCODE_IDENTIFIER(OP_PUSH1, push<1>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH2, push<2>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH3, push<3>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH4, push<4>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH5, push<5>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH6, push<6>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH7, push<7>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH8, push<8>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH9, push<9>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH10, push<10>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH11, push<11>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH12, push<12>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH13, push<13>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH14, push<14>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH15, push<15>) \ + \ + ON_OPCODE_IDENTIFIER(OP_PUSH16, push<16>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH17, push<17>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH18, push<18>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH19, push<19>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH20, push<20>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH21, push<21>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH22, push<22>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH23, push<23>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH24, push<24>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH25, push<25>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH26, push<26>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH27, push<27>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH28, push<28>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH29, push<29>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH30, push<30>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH31, push<31>) \ + ON_OPCODE_IDENTIFIER(OP_PUSH32, push<32>) \ + \ + ON_OPCODE_IDENTIFIER(OP_DUP1, dup<1>) \ + ON_OPCODE_IDENTIFIER(OP_DUP2, dup<2>) \ + ON_OPCODE_IDENTIFIER(OP_DUP3, dup<3>) \ + ON_OPCODE_IDENTIFIER(OP_DUP4, dup<4>) \ + ON_OPCODE_IDENTIFIER(OP_DUP5, dup<5>) \ + ON_OPCODE_IDENTIFIER(OP_DUP6, dup<6>) \ + ON_OPCODE_IDENTIFIER(OP_DUP7, dup<7>) \ + ON_OPCODE_IDENTIFIER(OP_DUP8, dup<8>) \ + ON_OPCODE_IDENTIFIER(OP_DUP9, dup<9>) \ + ON_OPCODE_IDENTIFIER(OP_DUP10, dup<10>) \ + ON_OPCODE_IDENTIFIER(OP_DUP11, dup<11>) \ + ON_OPCODE_IDENTIFIER(OP_DUP12, dup<12>) \ + ON_OPCODE_IDENTIFIER(OP_DUP13, dup<13>) \ + ON_OPCODE_IDENTIFIER(OP_DUP14, dup<14>) \ + ON_OPCODE_IDENTIFIER(OP_DUP15, dup<15>) \ + ON_OPCODE_IDENTIFIER(OP_DUP16, dup<16>) \ + \ + ON_OPCODE_IDENTIFIER(OP_SWAP1, swap<1>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP2, swap<2>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP3, swap<3>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP4, swap<4>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP5, swap<5>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP6, swap<6>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP7, swap<7>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP8, swap<8>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP9, swap<9>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP10, swap<10>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP11, swap<11>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP12, swap<12>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP13, swap<13>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP14, swap<14>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP15, swap<15>) \ + ON_OPCODE_IDENTIFIER(OP_SWAP16, swap<16>) \ + \ + ON_OPCODE_IDENTIFIER(OP_LOG0, log<0>) \ + ON_OPCODE_IDENTIFIER(OP_LOG1, log<1>) \ + ON_OPCODE_IDENTIFIER(OP_LOG2, log<2>) \ + ON_OPCODE_IDENTIFIER(OP_LOG3, log<3>) \ + ON_OPCODE_IDENTIFIER(OP_LOG4, log<4>) \ + ON_OPCODE_UNDEFINED(0xa5) \ + ON_OPCODE_UNDEFINED(0xa6) \ + ON_OPCODE_UNDEFINED(0xa7) \ + ON_OPCODE_UNDEFINED(0xa8) \ + ON_OPCODE_UNDEFINED(0xa9) \ + ON_OPCODE_UNDEFINED(0xaa) \ + ON_OPCODE_UNDEFINED(0xab) \ + ON_OPCODE_UNDEFINED(0xac) \ + ON_OPCODE_UNDEFINED(0xad) \ + ON_OPCODE_UNDEFINED(0xae) \ + ON_OPCODE_UNDEFINED(0xaf) \ + \ + ON_OPCODE_UNDEFINED(0xb0) \ + ON_OPCODE_UNDEFINED(0xb1) \ + ON_OPCODE_UNDEFINED(0xb2) \ + ON_OPCODE_UNDEFINED(0xb3) \ + ON_OPCODE_UNDEFINED(0xb4) \ + ON_OPCODE_UNDEFINED(0xb5) \ + ON_OPCODE_UNDEFINED(0xb6) \ + ON_OPCODE_UNDEFINED(0xb7) \ + ON_OPCODE_UNDEFINED(0xb8) \ + ON_OPCODE_UNDEFINED(0xb9) \ + ON_OPCODE_UNDEFINED(0xba) \ + ON_OPCODE_UNDEFINED(0xbb) \ + ON_OPCODE_UNDEFINED(0xbc) \ + ON_OPCODE_UNDEFINED(0xbd) \ + ON_OPCODE_UNDEFINED(0xbe) \ + ON_OPCODE_UNDEFINED(0xbf) \ + \ + ON_OPCODE_UNDEFINED(0xc0) \ + ON_OPCODE_UNDEFINED(0xc1) \ + ON_OPCODE_UNDEFINED(0xc2) \ + ON_OPCODE_UNDEFINED(0xc3) \ + ON_OPCODE_UNDEFINED(0xc4) \ + ON_OPCODE_UNDEFINED(0xc5) \ + ON_OPCODE_UNDEFINED(0xc6) \ + ON_OPCODE_UNDEFINED(0xc7) \ + ON_OPCODE_UNDEFINED(0xc8) \ + ON_OPCODE_UNDEFINED(0xc9) \ + ON_OPCODE_UNDEFINED(0xca) \ + ON_OPCODE_UNDEFINED(0xcb) \ + ON_OPCODE_UNDEFINED(0xcc) \ + ON_OPCODE_UNDEFINED(0xcd) \ + ON_OPCODE_UNDEFINED(0xce) \ + ON_OPCODE_UNDEFINED(0xcf) \ + \ + ON_OPCODE_IDENTIFIER(OP_DATALOAD, dataload) \ + ON_OPCODE_IDENTIFIER(OP_DATALOADN, dataloadn) \ + ON_OPCODE_IDENTIFIER(OP_DATASIZE, datasize) \ + ON_OPCODE_IDENTIFIER(OP_DATACOPY, datacopy) \ + ON_OPCODE_UNDEFINED(0xd4) \ + ON_OPCODE_UNDEFINED(0xd5) \ + ON_OPCODE_UNDEFINED(0xd6) \ + ON_OPCODE_UNDEFINED(0xd7) \ + ON_OPCODE_UNDEFINED(0xd8) \ + ON_OPCODE_UNDEFINED(0xd9) \ + ON_OPCODE_UNDEFINED(0xda) \ + ON_OPCODE_UNDEFINED(0xdb) \ + ON_OPCODE_UNDEFINED(0xdc) \ + ON_OPCODE_UNDEFINED(0xdd) \ + ON_OPCODE_UNDEFINED(0xde) \ + ON_OPCODE_UNDEFINED(0xdf) \ + \ + ON_OPCODE_IDENTIFIER(OP_RJUMP, rjump) \ + ON_OPCODE_IDENTIFIER(OP_RJUMPI, rjumpi) \ + ON_OPCODE_IDENTIFIER(OP_RJUMPV, rjumpv) \ + ON_OPCODE_IDENTIFIER(OP_CALLF, callf) \ + ON_OPCODE_IDENTIFIER(OP_RETF, retf) \ + ON_OPCODE_IDENTIFIER(OP_JUMPF, jumpf) \ + ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \ + ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \ + ON_OPCODE_IDENTIFIER(OP_EXCHANGE, exchange) \ + ON_OPCODE_UNDEFINED(0xe9) \ + ON_OPCODE_UNDEFINED(0xea) \ + ON_OPCODE_UNDEFINED(0xeb) \ + ON_OPCODE_UNDEFINED(0xec) \ + ON_OPCODE_UNDEFINED(0xed) \ + ON_OPCODE_UNDEFINED(0xee) \ + ON_OPCODE_UNDEFINED(0xef) \ + \ + ON_OPCODE_IDENTIFIER(OP_CREATE, create) \ + ON_OPCODE_IDENTIFIER(OP_CALL, call) \ + ON_OPCODE_IDENTIFIER(OP_CALLCODE, callcode) \ + ON_OPCODE_IDENTIFIER(OP_RETURN, return_) \ + ON_OPCODE_IDENTIFIER(OP_DELEGATECALL, delegatecall) \ + ON_OPCODE_IDENTIFIER(OP_CREATE2, create2) \ + ON_OPCODE_UNDEFINED(0xf6) \ + ON_OPCODE_IDENTIFIER(OP_RETURNDATALOAD, returndataload) \ + ON_OPCODE_IDENTIFIER(OP_EXTCALL, extcall) \ + ON_OPCODE_IDENTIFIER(OP_EXTDELEGATECALL, extdelegatecall) \ + ON_OPCODE_IDENTIFIER(OP_STATICCALL, staticcall) \ + ON_OPCODE_IDENTIFIER(OP_EXTSTATICCALL, extstaticcall) \ + ON_OPCODE_UNDEFINED(0xfc) \ + ON_OPCODE_IDENTIFIER(OP_REVERT, revert) \ + ON_OPCODE_IDENTIFIER(OP_INVALID, invalid) \ ON_OPCODE_IDENTIFIER(OP_SELFDESTRUCT, selfdestruct) diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 1271abbcd7..3ff6233665 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -60,6 +60,8 @@ target_sources( state_transition_call_test.cpp state_transition_create_test.cpp state_transition_eip663_test.cpp + state_transition_eof_calls_test.cpp + state_transition_eof_test.cpp state_transition_extcode_test.cpp state_transition_selfdestruct_test.cpp state_transition_touch_test.cpp diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index a4d004b5cd..d7bd6f654d 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -300,7 +300,8 @@ TEST_F(eof_validation, EOF1_undefined_opcodes) // These opcodes are deprecated since Prague. // gas_cost table current implementation does not allow to undef instructions. if (opcode == OP_JUMP || opcode == OP_JUMPI || opcode == OP_PC || opcode == OP_CALLCODE || - opcode == OP_SELFDESTRUCT || opcode == OP_CREATE || opcode == OP_CREATE2 || + opcode == OP_SELFDESTRUCT || opcode == OP_CALL || opcode == OP_STATICCALL || + opcode == OP_DELEGATECALL || opcode == OP_CREATE || opcode == OP_CREATE2 || opcode == OP_CODESIZE || opcode == OP_CODECOPY || opcode == OP_EXTCODESIZE || opcode == OP_EXTCODECOPY || opcode == OP_EXTCODEHASH || opcode == OP_GAS) continue; @@ -577,8 +578,9 @@ TEST_F(eof_validation, EOF1_section_order) TEST_F(eof_validation, deprecated_instructions) { - for (auto op : {OP_CALLCODE, OP_SELFDESTRUCT, OP_JUMP, OP_JUMPI, OP_PC, OP_CREATE, OP_CREATE2, - OP_CODESIZE, OP_CODECOPY, OP_EXTCODESIZE, OP_EXTCODECOPY, OP_EXTCODEHASH, OP_GAS}) + for (auto op : {OP_CALLCODE, OP_SELFDESTRUCT, OP_JUMP, OP_JUMPI, OP_PC, OP_CALL, OP_STATICCALL, + OP_DELEGATECALL, OP_CREATE, OP_CREATE2, OP_CODESIZE, OP_CODECOPY, OP_EXTCODESIZE, + OP_EXTCODECOPY, OP_EXTCODEHASH, OP_GAS}) add_test_case(eof_bytecode(op), EOFValidationError::undefined_instruction); } diff --git a/test/unittests/evm_calls_test.cpp b/test/unittests/evm_calls_test.cpp index c3ce9e4787..71bac8671f 100644 --- a/test/unittests/evm_calls_test.cpp +++ b/test/unittests/evm_calls_test.cpp @@ -750,165 +750,38 @@ TEST_P(evm, returndatacopy_outofrange_highbits) host.call_result.output_data = std::data(call_output); host.call_result.output_size = std::size(call_output); - // Covers an incorrect cast of RETURNDATALOAD arg to `size_t` ignoring the high bits. + // Covers an incorrect cast of RETURNDATACOPY arg to `size_t` ignoring the high bits. const auto highbits = 0x1000000000000000000000000000000000000000000000000000000000000000_bytes32; execute(735, staticcall(0) + returndatacopy(0, highbits, 0)); EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); } -TEST_P(evm, returndataload) -{ - // Not implemented in Advanced. - if (is_advanced()) - return; - - rev = EVMC_PRAGUE; - const auto call_output = - 0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32; - host.call_result.output_data = std::data(call_output.bytes); - host.call_result.output_size = std::size(call_output.bytes); - - const auto code = eof_bytecode(staticcall(0) + returndataload(0) + ret_top(), 6); - execute(code); - EXPECT_GAS_USED(EVMC_SUCCESS, 139); - EXPECT_EQ(bytes_view(result.output_data, result.output_size), call_output); -} - -TEST_P(evm, returndataload_cost) -{ - // Not implemented in Advanced. - if (is_advanced()) - return; - - rev = EVMC_PRAGUE; - const uint8_t call_output[32]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - const auto code = eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6); - execute(124, code); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - execute(123, code); - EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); -} - -TEST_P(evm, returndataload_outofrange) +TEST_P(evm, returndataload_undefined_in_legacy) { - // Not implemented in Advanced. - if (is_advanced()) - return; - rev = EVMC_PRAGUE; - { - const uint8_t call_output[31]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - } - { - const uint8_t call_output[32]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - } - { - const uint8_t call_output[34]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - execute(eof_bytecode(staticcall(0) + returndataload(3) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(2) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - } - { - const uint8_t call_output[64]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - execute(eof_bytecode(staticcall(0) + returndataload(33) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - - execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - } + execute(staticcall(0) + returndataload(0)); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } -TEST_P(evm, returndataload_empty) +TEST_P(evm, extcall_undefined_in_legacy) { - // Not implemented in Advanced. - if (is_advanced()) - return; - rev = EVMC_PRAGUE; - execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); - - execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + execute(extcall(0)); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } -TEST_P(evm, returndataload_outofrange_highbits) +TEST_P(evm, extdelegatecall_undefined_in_legacy) { - // Not implemented in Advanced. - if (is_advanced()) - return; - rev = EVMC_PRAGUE; - const uint8_t call_output[34]{}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - - // Covers an incorrect cast of RETURNDATALOAD arg to `size_t` ignoring the high bits. - const auto highbits = - 0x1000000000000000000000000000000000000000000000000000000000000000_bytes32; - execute(eof_bytecode(staticcall(0) + returndataload(highbits) + OP_STOP, 6)); - EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + execute(extdelegatecall(0)); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } -TEST_P(evm, returndataload_undefined_in_legacy) +TEST_P(evm, extstaticcall_undefined_in_legacy) { rev = EVMC_PRAGUE; - execute(staticcall(0) + returndataload(0)); + execute(extstaticcall(0)); EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } diff --git a/test/unittests/evm_eof_calls_test.cpp b/test/unittests/evm_eof_calls_test.cpp index f6aae9f6ac..c6081243c0 100644 --- a/test/unittests/evm_eof_calls_test.cpp +++ b/test/unittests/evm_eof_calls_test.cpp @@ -4,45 +4,491 @@ #include "evm_fixture.hpp" #include "evmone/eof.hpp" +#include "test/utils/bytecode.hpp" using evmone::test::evm; using namespace evmc::literals; -TEST_P(evm, eof1_delegatecall_eof1) +inline constexpr auto max_uint256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32; + +// Controlled amount of gas crossing the minimum callee/retained gas thresholds. +inline constexpr auto safe_call_gas = 400000; + +TEST_P(evm, extdelegatecall) { + // Not implemented in Advanced. + if (is_advanced()) + return; + rev = EVMC_PRAGUE; constexpr auto callee = 0xca11ee_address; - host.accounts[callee].code = eof_bytecode(OP_STOP); - bytes call_output{0x01, 0x02, 0x03}; - host.call_result.output_data = std::data(call_output); - host.call_result.output_size = std::size(call_output); - host.call_result.gas_left = 100; - host.call_result.status_code = EVMC_SUCCESS; + host.access_account(callee); + host.accounts[callee].code = "EF00"_hex; - const auto code = eof_bytecode(delegatecall(callee) + OP_RETURNDATASIZE + OP_PUSH0 + OP_PUSH0 + - OP_RETURNDATACOPY + ret(0, evmone::OP_RETURNDATASIZE), - 6); + auto code = eof_bytecode(mstore(0, push(1) + push0() + OP_SUB) + + extdelegatecall(callee).input(0x2, 0x3) + + returndatacopy(0x4, 0x0, 0x5) + ret(0, 8), + 4); - execute(code); - EXPECT_STATUS(EVMC_SUCCESS); - EXPECT_EQ(hex({result.output_data, result.output_size}), "010203"); + auto call_output = bytes{0xa, 0xb, 0xc, 0xd, 0xe}; + host.call_result.output_data = call_output.data(); + host.call_result.output_size = call_output.size(); + host.call_result.gas_left = 1; + + msg.value.bytes[17] = 0xfe; + + execute(safe_call_gas, code); + + auto gas_before_call = 3 + 2 + 3 + 3 + 6 + 3 * 3 + 100; + auto gas_left = safe_call_gas - gas_before_call; + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls.back(); + EXPECT_EQ(call_msg.gas, gas_left - gas_left / 64); + EXPECT_EQ(call_msg.input_size, 3); + EXPECT_EQ(call_msg.value.bytes[17], 0xfe); + + ASSERT_EQ(result.output_size, 8); + EXPECT_EQ(output, (bytes{0xff, 0xff, 0xff, 0xff, 0xa, 0xb, 0xc, 0xd})); + + EXPECT_GAS_USED(EVMC_SUCCESS, + gas_before_call + call_msg.gas - host.call_result.gas_left + 3 + 3 + 3 + 3 + 3 + 3 + 3); } -TEST_P(evm, eof1_delegatecall_legacy) +TEST_P(evm, extdelegatecall_oog_depth_limit) { + // Not implemented in Advanced. + if (is_advanced()) + return; + rev = EVMC_PRAGUE; constexpr auto callee = 0xca11ee_address; host.access_account(callee); + host.accounts[callee].code = "EF00"_hex; - for (const auto& target_code : {""_hex, "EF"_hex, "EF01"_hex, "000000"_hex}) - { - SCOPED_TRACE("target code: " + hex(target_code)); - host.accounts[callee].code = target_code; + msg.depth = 1024; + const auto code = eof_bytecode(extdelegatecall(callee) + ret_top(), 3); + + execute(safe_call_gas, code); + EXPECT_EQ(host.recorded_calls.size(), 0); + auto expected_gas_used = 3 * 3 + 100 + 3 + 3 + 3 + 3 + 3; + EXPECT_GAS_USED(EVMC_SUCCESS, expected_gas_used); + EXPECT_OUTPUT_INT(1); - const auto code = eof_bytecode(delegatecall(callee) + ret_top(), 6); + execute(expected_gas_used, code); + EXPECT_STATUS(EVMC_SUCCESS); // MIN_CALLEE_GAS failure is light failure as well. + EXPECT_OUTPUT_INT(1); +} + +TEST_P(evm, extcall_failing_with_value) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + host.access_account(callee); + host.accounts[callee] = {}; + + const auto code = eof_bytecode(extcall(callee).input(0x0, 0xff).value(0x1) + OP_STOP, 4); + + // Fails on balance check. + execute(safe_call_gas, code); + EXPECT_GAS_USED(EVMC_SUCCESS, 4 * 3 + 100 + 8 * 3 + 9000); + EXPECT_EQ(host.recorded_calls.size(), 0); // There was no call(). + + // Fails on value transfer additional cost - minimum gas limit that triggers this + execute(4 * 3 + 100 + 8 * 3, code); + EXPECT_STATUS(EVMC_OUT_OF_GAS); + EXPECT_EQ(host.recorded_calls.size(), 0); // There was no call(). + + // Fails on value transfer additional cost - maximum gas limit that triggers this + execute(4 * 3 + 100 + 8 * 3 + 9000 - 1, code); + EXPECT_STATUS(EVMC_OUT_OF_GAS); + EXPECT_EQ(host.recorded_calls.size(), 0); // There was no call(). +} + +TEST_P(evm, extcall_with_value_depth_limit) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + + constexpr auto call_dst = 0x00000000000000000000000000000000000000aa_address; + host.accounts[call_dst] = {}; + + msg.depth = 1024; + execute(eof_bytecode(extcall(call_dst).input(0x0, 0xff).value(0x1) + OP_STOP, 4)); + + EXPECT_EQ(gas_used, 4 * 3 + 2600 + 8 * 3 + 9000); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + EXPECT_EQ(host.recorded_calls.size(), 0); +} + +TEST_P(evm, extcall_depth_limit) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + host.access_account(callee); + host.accounts[callee].code = "EF00"_hex; + msg.depth = 1024; + for (auto op : {OP_EXTCALL, OP_EXTDELEGATECALL, OP_EXTSTATICCALL}) + { + const auto code = eof_bytecode(push(callee) + 3 * push0() + op + ret_top(), 4); execute(code); - EXPECT_GAS_USED(EVMC_SUCCESS, 133); // Low gas usage because DELEGATECALL fails lightly. + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + EXPECT_EQ(host.recorded_calls.size(), 0); + EXPECT_OUTPUT_INT(1); + } +} + +TEST_P(evm, extcall_value_zero_to_nonexistent_account) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + host.call_result.gas_left = 1000; + + const auto code = eof_bytecode(extcall(0xaa).input(0, 0x40) + OP_STOP, 4); + + execute(safe_call_gas, code); + auto gas_before_call = 4 * 3 + 2 * 3 + 2600; + auto gas_left = safe_call_gas - gas_before_call; + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls.back(); + EXPECT_EQ(call_msg.kind, EVMC_CALL); + EXPECT_EQ(call_msg.depth, 1); + EXPECT_EQ(call_msg.gas, gas_left - gas_left / 64); + EXPECT_EQ(call_msg.input_size, 64); + EXPECT_EQ(call_msg.recipient, 0x00000000000000000000000000000000000000aa_address); + EXPECT_EQ(call_msg.value.bytes[31], 0); + + EXPECT_GAS_USED(EVMC_SUCCESS, gas_before_call + call_msg.gas - host.call_result.gas_left); +} + +TEST_P(evm, extcall_new_account_creation_cost) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + constexpr auto call_dst = 0x00000000000000000000000000000000000000ad_address; + constexpr auto msg_dst = 0x0000000000000000000000000000000000000003_address; + const auto code = + eof_bytecode(extcall(call_dst).value(calldataload(0)).input(0, 0) + ret_top(), 4); + + msg.recipient = msg_dst; + + rev = EVMC_PRAGUE; + { + auto gas_before_call = 3 * 3 + 3 + 3 + 2600; + auto gas_left = safe_call_gas - gas_before_call; + + host.accounts[msg.recipient].set_balance(0); + execute(safe_call_gas, code, "00"_hex); + EXPECT_OUTPUT_INT(0); + ASSERT_EQ(host.recorded_calls.size(), 1); + auto& call_msg = host.recorded_calls.back(); + EXPECT_EQ(call_msg.recipient, call_dst); + EXPECT_EQ(call_msg.gas, gas_left - gas_left / 64); + EXPECT_EQ(call_msg.sender, msg_dst); + EXPECT_EQ(call_msg.value.bytes[31], 0); + EXPECT_EQ(call_msg.input_size, 0); + EXPECT_GAS_USED(EVMC_SUCCESS, gas_before_call + call_msg.gas + 3 + 3 + 3 + 3 + 3); + ASSERT_EQ(host.recorded_account_accesses.size(), 4); + EXPECT_EQ(host.recorded_account_accesses[0], 0x00_address); // EIP-2929 tweak + EXPECT_EQ(host.recorded_account_accesses[1], msg.recipient); // EIP-2929 tweak + EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // ? + EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // Call. + host.recorded_account_accesses.clear(); + host.recorded_calls.clear(); + } + { + auto gas_before_call = 3 * 3 + 3 + 3 + 2600 + 25000 + 9000; + auto gas_left = safe_call_gas - gas_before_call; + + host.accounts[msg.recipient].set_balance(1); + execute(safe_call_gas, code, + "0000000000000000000000000000000000000000000000000000000000000001"_hex); EXPECT_OUTPUT_INT(0); + ASSERT_EQ(host.recorded_calls.size(), 1); + auto& call_msg = host.recorded_calls.back(); + EXPECT_EQ(call_msg.recipient, call_dst); + EXPECT_EQ(call_msg.gas, gas_left - gas_left / 64); + EXPECT_EQ(call_msg.sender, msg_dst); + EXPECT_EQ(call_msg.value.bytes[31], 1); + EXPECT_EQ(call_msg.input_size, 0); + EXPECT_GAS_USED(EVMC_SUCCESS, gas_before_call + call_msg.gas + 3 + 3 + 3 + 3 + 3); + ASSERT_EQ(host.recorded_account_accesses.size(), 6); + EXPECT_EQ(host.recorded_account_accesses[0], 0x00_address); // EIP-2929 tweak + EXPECT_EQ(host.recorded_account_accesses[1], msg.recipient); // EIP-2929 tweak + EXPECT_EQ(host.recorded_account_accesses[2], call_dst); // ? + EXPECT_EQ(host.recorded_account_accesses[3], call_dst); // Account exist?. + EXPECT_EQ(host.recorded_account_accesses[4], msg.recipient); // Balance. + EXPECT_EQ(host.recorded_account_accesses[5], call_dst); // Call. + host.recorded_account_accesses.clear(); + host.recorded_calls.clear(); } } + +TEST_P(evm, extcall_oog_after_balance_check) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + // Create the call destination account. + host.accounts[0x0000000000000000000000000000000000000000_address] = {}; + auto code = eof_bytecode(extcall(0).value(1) + OP_POP + OP_STOP, 4); + execute(9112, code); + EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); +} + +TEST_P(evm, extcall_oog_after_depth_check) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + // Create the call recipient account. + host.accounts[0x0000000000000000000000000000000000000000_address] = {}; + msg.depth = 1024; + + auto code = eof_bytecode(extcall(0).value(1) + OP_POP + OP_STOP, 4); + execute(9112, code); + EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); +} + +TEST_P(evm, returndataload) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + const auto call_output = + 0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32; + host.call_result.output_data = std::data(call_output.bytes); + host.call_result.output_size = std::size(call_output.bytes); + + const auto code = eof_bytecode(extstaticcall(0) + returndataload(0) + ret_top(), 3); + + execute(code); + EXPECT_EQ(bytes_view(result.output_data, result.output_size), call_output); +} + +TEST_P(evm, returndataload_cost) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + const uint8_t call_output[32]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + host.call_result.gas_left = 0; + + execute(eof_bytecode(extstaticcall(0) + returndataload(0) + OP_STOP, 3)); + const auto gas_with = gas_used; + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + execute(eof_bytecode(extstaticcall(0) + push(0) + OP_STOP, 3)); + EXPECT_GAS_USED(EVMC_SUCCESS, gas_with - 3); +} + +TEST_P(evm, returndataload_oog) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + const uint8_t call_output[32]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + host.call_result.gas_left = 0; + + constexpr auto retained_gas = 5000; + constexpr auto gas = 3 * 3 + 100 + retained_gas * 64; + // Uses OP_JUMPDEST to burn gas retained by the caller. + execute(gas, eof_bytecode(extstaticcall(0) + (retained_gas - 3 - 3) * OP_JUMPDEST + + returndataload(0) + OP_STOP, + 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(gas, eof_bytecode(extstaticcall(0) + (retained_gas - 3 - 2) * OP_JUMPDEST + + returndataload(0) + OP_STOP, + 3)); + EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); +} + +TEST_P(evm, returndataload_outofrange) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + { + const uint8_t call_output[31]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + + execute(eof_bytecode(extstaticcall(0) + returndataload(0) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + } + { + const uint8_t call_output[32]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + + execute(eof_bytecode(extstaticcall(0) + returndataload(1) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(31) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(32) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(max_uint256) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(0) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + } + { + const uint8_t call_output[34]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + + execute(eof_bytecode(extstaticcall(0) + returndataload(3) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(max_uint256) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(1) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(2) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + } + { + const uint8_t call_output[64]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + + execute(eof_bytecode(extstaticcall(0) + returndataload(33) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(max_uint256) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + + execute(eof_bytecode(extstaticcall(0) + returndataload(1) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(31) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(32) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + execute(eof_bytecode(extstaticcall(0) + returndataload(0) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + } +} + +TEST_P(evm, returndataload_empty) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + execute(eof_bytecode(extstaticcall(0) + returndataload(0) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(1) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(eof_bytecode(extstaticcall(0) + returndataload(max_uint256) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); +} + +TEST_P(evm, returndataload_outofrange_highbits) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + const uint8_t call_output[34]{}; + host.call_result.output_data = std::data(call_output); + host.call_result.output_size = std::size(call_output); + + // Covers an incorrect cast of RETURNDATALOAD arg to `size_t` ignoring the high bits. + const auto highbits = + 0x1000000000000000000000000000000000000000000000000000000000000000_bytes32; + execute(eof_bytecode(extstaticcall(0) + returndataload(highbits) + OP_STOP, 3)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); +} + +TEST_P(evm, extcall_gas_refund_aggregation_different_calls) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + host.access_account(callee); + host.accounts[callee].code = "EF00"_hex; + host.accounts[msg.recipient].set_balance(1); + host.call_result.status_code = EVMC_SUCCESS; + host.call_result.gas_refund = 1; + + const auto code = eof_bytecode( + extcall(callee) + extdelegatecall(callee) + extstaticcall(callee) + OP_STOP, 5); + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(result.gas_refund, 3); +} + +TEST_P(evm, extcall_gas_refund_aggregation_same_calls) +{ + // Not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; + constexpr auto callee = 0xaa_address; + host.access_account(callee); + host.accounts[callee].code = "EF00"_hex; + host.accounts[msg.recipient].set_balance(2); + host.call_result.status_code = EVMC_SUCCESS; + host.call_result.gas_refund = 1; + + execute(eof_bytecode(2 * extcall(callee).value(1).input(1, 1) + OP_STOP, 5)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(result.gas_refund, 2); + + execute(eof_bytecode(2 * extdelegatecall(callee).input(1, 1) + OP_STOP, 4)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(result.gas_refund, 2); + + execute(eof_bytecode(2 * extstaticcall(callee).input(1, 1) + OP_STOP, 4)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(result.gas_refund, 2); +} diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index a817ef40a7..eca1725dac 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -127,6 +127,9 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept case OP_RETURNDATALOAD: case OP_TLOAD: case OP_TSTORE: + case OP_EXTCALL: + case OP_EXTDELEGATECALL: + case OP_EXTSTATICCALL: return true; default: return false; diff --git a/test/unittests/state_transition_call_test.cpp b/test/unittests/state_transition_call_test.cpp index 7ed7667fb8..a22be33397 100644 --- a/test/unittests/state_transition_call_test.cpp +++ b/test/unittests/state_transition_call_test.cpp @@ -19,3 +19,38 @@ TEST_F(state_transition, call_value_to_empty) expect.post[To].balance = 0; expect.post[BENEFICIARY].balance = 1; } + +TEST_F(state_transition, delegatecall_static_legacy) +{ + rev = EVMC_PRAGUE; + // Checks if DELEGATECALL forwards the "static" flag. + constexpr auto callee1 = 0xca11ee01_address; + constexpr auto callee2 = 0xca11ee02_address; + pre.insert(callee2, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = sstore(1, 0xcc_bytes32), + }); + pre.insert(callee1, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = ret(delegatecall(callee2).gas(100'000)), + }); + + tx.to = To; + pre.insert( + *tx.to, { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}, + {0x02_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = sstore(1, staticcall(callee1).gas(200'000)) + + sstore(2, returndatacopy(0, 0, returndatasize()) + mload(0)), + }); + expect.gas_used = 131480; + // Outer call - success. + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + // Inner call - no success. + expect.post[*tx.to].storage[0x02_bytes32] = 0x00_bytes32; + // SSTORE failed. + expect.post[callee1].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee2].storage[0x01_bytes32] = 0xdd_bytes32; +} diff --git a/test/unittests/state_transition_eof_calls_test.cpp b/test/unittests/state_transition_eof_calls_test.cpp new file mode 100644 index 0000000000..5ca5b107b5 --- /dev/null +++ b/test/unittests/state_transition_eof_calls_test.cpp @@ -0,0 +1,931 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "../utils/bytecode.hpp" +#include "state_transition.hpp" + +using namespace evmc::literals; +using namespace evmone::test; + +TEST_F(state_transition, eof1_extdelegatecall_eof1) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + pre.insert(callee, + { + .storage = {{0x02_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(sstore(2, 0xcc_bytes32) + mstore(0, 0x010203_bytes32) + ret(0, 32), 2), + }); + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x02_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(extdelegatecall(callee) + sstore(1, returndataload(0)) + OP_STOP, 3), + }); + + expect.gas_used = 50742; + expect.post[*tx.to].storage[0x01_bytes32] = 0x010203_bytes32; + // SSTORE executed on caller. + expect.post[callee].storage[0x02_bytes32] = 0xdd_bytes32; + expect.post[*tx.to].storage[0x02_bytes32] = 0xcc_bytes32; +} + +TEST_F(state_transition, eof1_extdelegatecall_legacy) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee = 0xca11ee_address; + pre.insert(callee, { + .code = sstore(3, 0xcc_bytes32), + }); + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}, + {0x02_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + sstore(1, extdelegatecall(callee)) + sstore(2, returndatasize()) + OP_STOP, 3), + }); + + expect.gas_used = 28817; // Low gas usage because EXTDELEGATECALL fails lightly + // Call - light failure. + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + // Returndata empty. + expect.post[*tx.to].storage[0x02_bytes32] = 0x00_bytes32; + // SSTORE not executed. + expect.post[callee].storage[0x03_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x03_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, extdelegatecall_static) +{ + rev = EVMC_PRAGUE; + // Checks if EXTDELEGATECALL forwards the "static" flag. + constexpr auto callee1 = 0xca11ee01_address; + constexpr auto callee2 = 0xca11ee02_address; + pre.insert(callee2, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + pre.insert(callee1, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(ret(extdelegatecall(callee2)), 3), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}, + {0x02_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + sstore(1, extstaticcall(callee1)) + sstore(2, returndataload(0)) + OP_STOP, 3), + }); + expect.gas_used = 974995; + // Outer call - success. + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + // Inner call - abort. + expect.post[*tx.to].storage[0x02_bytes32] = 0x02_bytes32; + // SSTORE failed. + expect.post[callee1].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee2].storage[0x01_bytes32] = 0xdd_bytes32; +} + +TEST_F(state_transition, extcall_static_with_value) +{ + rev = EVMC_PRAGUE; + + constexpr auto callee1 = 0xca11ee01_address; + constexpr auto callee2 = 0xca11ee02_address; + pre.insert(callee1, { + .code = eof_bytecode(extcall(callee2).value(1) + OP_STOP, 4), + }); + + tx.to = To; + pre.insert( + *tx.to, { + .storage = + { + {0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}, + }, + .code = eof_bytecode(sstore(1, extstaticcall(callee1)) + OP_STOP, 3), + }); + expect.gas_used = 989747; + // Outer call - abort in callee. + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; + expect.post[callee1].exists = true; +} + +TEST_F(state_transition, extcall_failing_with_value_balance_check) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(sstore(1, extcall(callee).input(0x0, 0xff).value(0x1)) + OP_STOP, 4), + }); + + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].storage[0x01_bytes32] = 0xdd_bytes32; +} + +TEST_F(state_transition, extcall_failing_with_value_additional_cost) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(sstore(1, extcall(callee).input(0x0, 0xff).value(0x1)) + OP_STOP, 4), + }); + // Fails on value transfer additional cost - maximum gas limit that triggers this + tx.gas_limit = 37639 - 1; + + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee].storage[0x01_bytes32] = 0xdd_bytes32; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extcall_with_value) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .balance = 0x01, + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(sstore(1, extcall(callee).input(0x0, 0xff).value(0x1)) + OP_STOP, 4), + }); + expect.gas_used = 37845; + + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[callee].storage[0x01_bytes32] = 0xcc_bytes32; +} + +TEST_F(state_transition, extcall_min_callee_gas_failure_mode) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_STOP, 0), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extcall(callee)) + OP_STOP, 4), + }); + // Just short of what the caller needs + MIN_RETAINED_GAS + MIN_CALLEE_GAS + tx.gas_limit = 21000 + 4 * 3 + 2600 + 5000 + 2300 - 1; + + // Light failure + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_output) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(0x0a0b_bytes32), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + extcall(callee) + sstore(1, returndatacopy(31, 30, 1) + mload(0)) + OP_STOP, 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x000a_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extdelegatecall_output) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(0x0a0b_bytes32), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + extdelegatecall(callee) + sstore(1, returndatacopy(31, 30, 1) + mload(0)) + OP_STOP, + 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x000a_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extstaticcall_output) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(0x0a0b_bytes32), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + extstaticcall(callee) + sstore(1, returndatacopy(31, 30, 1) + mload(0)) + OP_STOP, + 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x000a_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_memory) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extcall(callee).input(0, 0xFFFFFFFF) + OP_STOP, 4), + }); + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extdelegatecall_memory) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert( + *tx.to, { + .code = eof_bytecode(extdelegatecall(callee).input(0, 0xFFFFFFFF) + OP_STOP, 3), + }); + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extstaticcall_memory) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert( + *tx.to, { + .code = eof_bytecode(extstaticcall(callee).input(0, 0xFFFFFFFF) + OP_STOP, 3), + }); + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extcall_cold_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extcall(callee) + OP_STOP, 4), + }); + tx.gas_limit = 21000 + 4 * 3 + 100 + 2500 - 1; + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extdelegatecall_cold_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extdelegatecall(callee) + OP_STOP, 3), + }); + tx.gas_limit = 21000 + 3 * 3 + 100 + 2500 - 1; + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extstaticcall_cold_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extstaticcall(callee) + OP_STOP, 3), + }); + tx.gas_limit = 21000 + 3 * 3 + 100 + 2500 - 1; + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extcall_value_zero_to_nonexistent_account) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extcall(callee).value(0x0)) + OP_STOP, 4), + }); + tx.gas_limit = 21000 + 4 * 3 + 5000 + 2300 + 2600 + 2; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, extcall_then_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + extcall(callee) + rjump(-3), 4), + }); + // Enough to SSTORE and complete EXTCALL, OOG is sure to be in the infinite loop. + tx.gas_limit = 1'000'000; + + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee].storage[0x01_bytes32] = 0xdd_bytes32; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extdelegatecall_then_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + extdelegatecall(callee) + rjump(-3), 3), + }); + // Enough to complete EXTDELEGATECALL, OOG in infinite loop. + tx.gas_limit = 35000; + + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee].storage[0x01_bytes32] = 0xdd_bytes32; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extstaticcall_then_oog) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_STOP), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0xcc_bytes32) + extstaticcall(callee) + rjump(-3), 3), + }); + // Enough to complete EXTSTATICCALL, OOG in infinite loop. + tx.gas_limit = 35000; + + expect.gas_used = tx.gas_limit; + expect.post[*tx.to].storage[0x01_bytes32] = 0xdd_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_OUT_OF_GAS; +} + +TEST_F(state_transition, extcall_callee_revert) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(revert(0, 0), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extcall(callee)) + OP_STOP, 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extdelegatecall_callee_revert) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(revert(0, 0), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extdelegatecall(callee)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extstaticcall_callee_revert) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(revert(0, 0), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extstaticcall(callee)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extcall_callee_abort) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_INVALID), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extcall(callee)) + OP_STOP, 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extdelegatecall_callee_abort) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_INVALID), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extdelegatecall(callee)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extstaticcall_callee_abort) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_INVALID), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, extstaticcall(callee)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; + expect.post[callee].exists = true; + expect.status = EVMC_SUCCESS; +} + +TEST_F(state_transition, extcall_input) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(eq(calldataload(0), 0x010203)), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(mstore(0, 0x010203) + extcall(callee).input(0, 32) + + sstore(1, returndataload(0)) + OP_STOP, + 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extdelegatecall_input) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(eq(calldataload(0), 0x010203)), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(mstore(0, 0x010203) + extdelegatecall(callee).input(0, 32) + + sstore(1, returndataload(0)) + OP_STOP, + 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extstaticcall_input) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(eq(calldataload(0), 0x010203)), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(mstore(0, 0x010203) + extstaticcall(callee).input(0, 32) + + sstore(1, returndataload(0)) + OP_STOP, + 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_with_value_enough_gas) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, {.balance = 0x1}); + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extcall(callee).value(1) + OP_POP + OP_STOP, 4), + }); + + constexpr auto callee_and_retained = 5000 + 2300; + // Just enough to ensure callee and retained gas. + tx.gas_limit = 21000 + 4 * 3 + callee_and_retained + 9000 + 2600 + 2; + // Callee and retained gas aren't used + expect.gas_used = tx.gas_limit - callee_and_retained; + expect.post[*tx.to].exists = true; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_with_value_low_gas) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, {.balance = 0x1}); + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extcall(callee).value(1) + OP_POP + OP_STOP, 4), + }); + + // Not enough to ensure MIN_CALLEE_GAS. + tx.gas_limit = 21000 + 4 * 3 + 9000 + 2600 + 2 - 1; + expect.gas_used = tx.gas_limit; + expect.status = EVMC_OUT_OF_GAS; + expect.post[*tx.to].exists = true; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_recipient_and_code_address) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .code = eof_bytecode(ret(eq(OP_ADDRESS, callee) + eq(OP_CALLER, To) + OP_AND), 3), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(extcall(callee) + sstore(1, returndataload(0)) + OP_STOP, 4), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extdelegatecall_recipient_and_code_address) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .code = eof_bytecode(ret(eq(OP_ADDRESS, To) + eq(OP_CALLER, tx.sender) + OP_AND), 3), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(extdelegatecall(callee) + sstore(1, returndataload(0)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extstaticcall_recipient_and_code_address) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .code = eof_bytecode(ret(eq(OP_ADDRESS, callee) + eq(OP_CALLER, To) + OP_AND), 3), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(extstaticcall(callee) + sstore(1, returndataload(0)) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[callee].exists = true; +} + + +TEST_F(state_transition, extcall_value) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .balance = 0x0, + .code = eof_bytecode(OP_STOP), + }); + + tx.to = To; + pre.insert(*tx.to, { + .balance = 0x1, + .code = eof_bytecode(extcall(callee).value(0x1) + OP_STOP, 4), + }); + expect.post[*tx.to].exists = true; + expect.post[callee].balance = 1; +} + +TEST_F(state_transition, returndatasize_before_extcall) +{ + rev = EVMC_PRAGUE; + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, returndatasize()) + OP_STOP, 2), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, extdelegatecall_returndatasize) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(ret(0, 0x13), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(extdelegatecall(callee) + sstore(1, returndatasize()) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x13_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extdelegatecall_returndatasize_abort) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(OP_INVALID), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(extdelegatecall(callee) + sstore(1, returndatasize()) + OP_STOP, 3), + }); + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, returndatacopy) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + const auto call_output = + 0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32; + + pre.insert(callee, { + .code = eof_bytecode(ret(call_output), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + extdelegatecall(callee) + sstore(1, returndatacopy(0, 0, 32) + mload(0)) + OP_STOP, + 4), + }); + expect.gas_used = 28654; + expect.post[*tx.to].storage[0x01_bytes32] = call_output; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, returndataload) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + const auto call_output = + 0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32; + + pre.insert(callee, { + .code = eof_bytecode(ret(call_output), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = + eof_bytecode(extdelegatecall(callee) + sstore(1, returndataload(0)) + OP_STOP, 3), + }); + expect.gas_used = 28636; + expect.post[*tx.to].storage[0x01_bytes32] = call_output; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_clears_returndata) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + const auto call_output = + 0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32; + + pre.insert(callee, { + .code = eof_bytecode(ret(call_output), 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode( + extcall(callee) + extcall(callee).value(1) + sstore(1, returndatasize()) + OP_STOP, + 5), + }); + + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[callee].exists = true; +} + +TEST_F(state_transition, extcall_gas_refund_propagation) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(sstore(1, 0x00) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(extcall(callee) + OP_STOP, 4), + }); + expect.gas_used = 21000 + 2600 + 5000 + 6 * 3 - 4800; + expect.post[*tx.to].exists = true; + expect.post[callee].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, extdelegatecall_gas_refund_propagation) +{ + rev = EVMC_PRAGUE; + constexpr auto callee = 0xca11ee_address; + + pre.insert(callee, { + .code = eof_bytecode(sstore(1, 0x00) + OP_STOP, 2), + }); + + tx.to = To; + pre.insert(*tx.to, + { + .storage = {{0x01_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(extdelegatecall(callee) + OP_STOP, 3), + }); + expect.gas_used = 21000 + 2600 + 5000 + 5 * 3 - 4800; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[callee].exists = true; +} diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index 1822fab935..ccfe2333bc 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -503,6 +503,59 @@ inline call_instruction callcode(bytecode address) return call_instruction{std::move(address)}; } +template +struct extcall_instruction +{ +private: + bytecode m_address = 0; + bytecode m_value = 0; + bytecode m_input = 0; + bytecode m_input_size = 0; + +public: + explicit extcall_instruction(bytecode address) : m_address{std::move(address)} {} + + + template + requires requires { k == OP_EXTCALL; } + extcall_instruction& value(bytecode v) + { + m_value = std::move(v); + return *this; + } + + auto& input(bytecode index, bytecode size) + { + m_input = std::move(index); + m_input_size = std::move(size); + return *this; + } + + operator bytecode() const + { + auto code = bytecode{}; + if constexpr (kind == OP_EXTCALL) + code += m_value; + code += m_input_size + m_input + m_address + kind; + return code; + } +}; + +inline extcall_instruction extdelegatecall(bytecode address) +{ + return extcall_instruction{std::move(address)}; +} + +inline extcall_instruction extstaticcall(bytecode address) +{ + return extcall_instruction{std::move(address)}; +} + +inline extcall_instruction extcall(bytecode address) +{ + return extcall_instruction{std::move(address)}; +} + template struct create_instruction