From 7feccf8170661638fa0cb860614b40f1ad5f5dab Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 28 Feb 2023 20:29:29 +0100 Subject: [PATCH] eof: Add CALLF & RETF instructions Implement CALLF and RETF EOF instructions specified by EIP-4750 EOF Functions (https://eips.ethereum.org/EIPS/eip-4750). Co-authored-by: Andrei Maiboroda --- lib/evmone/advanced_instructions.cpp | 2 + lib/evmone/baseline_instruction_table.cpp | 2 + lib/evmone/execution_state.hpp | 2 + lib/evmone/instructions.hpp | 16 ++++++ lib/evmone/instructions_opcodes.hpp | 3 ++ lib/evmone/instructions_traits.hpp | 4 ++ lib/evmone/instructions_xmacro.hpp | 4 +- test/unittests/CMakeLists.txt | 1 + test/unittests/eof_validation_test.cpp | 7 +-- test/unittests/evm_eof_function_test.cpp | 59 +++++++++++++++++++++++ test/unittests/instructions_test.cpp | 5 +- 11 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 test/unittests/evm_eof_function_test.cpp diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 10df67d592..936411fee4 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -248,6 +248,8 @@ constexpr std::array instruction_implementations = []( table[OP_RJUMP] = op_undefined; table[OP_RJUMPI] = op_undefined; table[OP_RJUMPV] = op_undefined; + table[OP_CALLF] = op_undefined; + table[OP_RETF] = op_undefined; table[OP_DUPN] = op_undefined; table[OP_SWAPN] = op_undefined; diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index cf07f2a187..2fd6b4b673 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -27,6 +27,8 @@ constexpr auto legacy_cost_tables = []() noexcept { tables[EVMC_CANCUN][OP_RJUMP] = instr::undefined; tables[EVMC_CANCUN][OP_RJUMPI] = instr::undefined; tables[EVMC_CANCUN][OP_RJUMPV] = instr::undefined; + tables[EVMC_CANCUN][OP_CALLF] = instr::undefined; + tables[EVMC_CANCUN][OP_RETF] = instr::undefined; return tables; }(); diff --git a/lib/evmone/execution_state.hpp b/lib/evmone/execution_state.hpp index c212da8809..8aa54353ca 100644 --- a/lib/evmone/execution_state.hpp +++ b/lib/evmone/execution_state.hpp @@ -154,6 +154,8 @@ class ExecutionState const advanced::AdvancedCodeAnalysis* advanced; } analysis{}; + std::vector call_stack; + /// Stack space allocation. /// /// This is the last field to make other fields' offsets of reasonable values. diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index d68c095bb6..6a95e48662 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -933,6 +933,22 @@ evmc_status_code create_impl(StackTop stack, ExecutionState& state) noexcept; inline constexpr auto create = create_impl; inline constexpr auto create2 = create_impl; +inline code_iterator callf(StackTop /*stack*/, ExecutionState& state, code_iterator pos) noexcept +{ + const auto index = read_uint16_be(&pos[1]); + state.call_stack.push_back(pos + 3); + const auto offset = state.analysis.baseline->code_offsets[index]; + auto code = state.analysis.baseline->executable_code; + return code.data() + offset; +} + +inline code_iterator retf(StackTop /*stack*/, ExecutionState& state, code_iterator /*pos*/) noexcept +{ + const auto p = state.call_stack.back(); + state.call_stack.pop_back(); + return p; +} + template inline StopToken return_impl(StackTop stack, ExecutionState& state) noexcept { diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 4a919c3352..9b430ce47a 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -157,6 +157,9 @@ enum Opcode : uint8_t OP_LOG3 = 0xa3, OP_LOG4 = 0xa4, + OP_CALLF = 0xb0, + OP_RETF = 0xb1, + OP_DUPN = 0xb5, OP_SWAPN = 0xb6, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 99e6db1d8d..fc4bb85776 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -169,6 +169,8 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_CANCUN][OP_RJUMP] = 2; table[EVMC_CANCUN][OP_RJUMPI] = 4; table[EVMC_CANCUN][OP_RJUMPV] = 4; + table[EVMC_CANCUN][OP_CALLF] = 5; + table[EVMC_CANCUN][OP_RETF] = 3; table[EVMC_PRAGUE] = table[EVMC_CANCUN]; @@ -381,6 +383,8 @@ 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_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM}; + table[OP_CALLF] = {"CALLF", 2, false, 0, 0, EVMC_CANCUN}; + table[OP_RETF] = {"RETF", 0, true, 0, 0, EVMC_CANCUN}; table[OP_REVERT] = {"REVERT", 0, true, 2, -2, EVMC_BYZANTIUM}; table[OP_INVALID] = {"INVALID", 0, true, 0, 0, EVMC_FRONTIER}; table[OP_SELFDESTRUCT] = {"SELFDESTRUCT", 0, true, 1, -1, EVMC_FRONTIER}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 378f5516fa..7c81d7f135 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -219,8 +219,8 @@ ON_OPCODE_UNDEFINED(0xae) \ ON_OPCODE_UNDEFINED(0xaf) \ \ - ON_OPCODE_UNDEFINED(0xb0) \ - ON_OPCODE_UNDEFINED(0xb1) \ + ON_OPCODE_IDENTIFIER(OP_CALLF, callf) \ + ON_OPCODE_IDENTIFIER(OP_RETF, retf) \ ON_OPCODE_UNDEFINED(0xb2) \ ON_OPCODE_UNDEFINED(0xb3) \ ON_OPCODE_UNDEFINED(0xb4) \ diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index fd1561001f..9f9d5d62e0 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources( evm_eip3855_push0_test.cpp evm_eip3860_initcode_test.cpp evm_eof_test.cpp + evm_eof_function_test.cpp evm_eof_rjump_test.cpp evm_memory_test.cpp evm_state_test.cpp diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 7b58fd52f9..9a8aef3bab 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -319,10 +319,11 @@ TEST(eof_validation, EOF1_undefined_opcodes) for (uint16_t opcode = 0; opcode <= 0xff; ++opcode) { - // PUSH*, DUPN, SWAPN, RJUMP* require immediate argument to be valid, checked in - // a separate test + // PUSH*, DUPN, SWAPN, RJUMP*, CALLF require immediate argument to be valid, + // checked in a separate test. if ((opcode >= OP_PUSH1 && opcode <= OP_PUSH32) || opcode == OP_DUPN || - opcode == OP_SWAPN || opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_RJUMPV) + opcode == OP_SWAPN || opcode == OP_RJUMP || opcode == OP_RJUMPI || + opcode == OP_RJUMPV || opcode == OP_CALLF) continue; // These opcodes are deprecated since Cancun. // gas_cost table current implementation does not allow to undef instructions. diff --git a/test/unittests/evm_eof_function_test.cpp b/test/unittests/evm_eof_function_test.cpp new file mode 100644 index 0000000000..df185c6672 --- /dev/null +++ b/test/unittests/evm_eof_function_test.cpp @@ -0,0 +1,59 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2022 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "evm_fixture.hpp" +#include "evmone/eof.hpp" + +using evmone::test::evm; + +TEST_P(evm, eof_function_example1) +{ + // Relative jumps are not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_CANCUN; + const auto code = + "EF00 01 010008 020002 000f 0002 030000 00" + "00000002 02010002" + "6001 6008 b00001 " + + ret_top() + "03b1"; + + ASSERT_EQ((int)evmone::validate_eof(rev, code), (int)evmone::EOFValidationError{}); + + execute(code); + EXPECT_GAS_USED(EVMC_SUCCESS, 32); + EXPECT_OUTPUT_INT(7); +} + +TEST_P(evm, eof_function_example2) +{ + // Relative jumps are not implemented in Advanced. + if (is_advanced()) + return; + + rev = EVMC_CANCUN; + const auto code = + "ef0001 01000c 020003 003b 0017 001d 030000 00 00000004 01010003 01010004" + "60043560003560e01c63c766526781145d001c63c6c2ea1781145d00065050600080fd50b00002600052602060" + "00f350b0000160005260206000f3" + "600181115d0004506001b160018103b0000181029050b1" + "600281115d0004506001b160028103b0000260018203b00002019050b1"_hex; + + ASSERT_EQ((int)evmone::validate_eof(rev, code), (int)evmone::EOFValidationError{}); + + // Call fac(5) + const auto calldata_fac = + "c76652670000000000000000000000000000000000000000000000000000000000000005"_hex; + execute(bytecode{code}, calldata_fac); + EXPECT_GAS_USED(EVMC_SUCCESS, 246); + EXPECT_EQ(output, "0000000000000000000000000000000000000000000000000000000000000078"_hex); + + // Call fib(15) + const auto calldata_fib = + "c6c2ea17000000000000000000000000000000000000000000000000000000000000000f"_hex; + execute(bytecode{code}, calldata_fib); + EXPECT_GAS_USED(EVMC_SUCCESS, 44544); + EXPECT_EQ(output, "0000000000000000000000000000000000000000000000000000000000000262"_hex); +} diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index f9fd7f7904..2e13a3799d 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -38,6 +38,7 @@ constexpr bool is_terminating(Opcode op) noexcept { case OP_STOP: case OP_RETURN: + case OP_RETF: case OP_REVERT: case OP_INVALID: case OP_SELFDESTRUCT: @@ -55,7 +56,7 @@ constexpr void validate_traits_of() noexcept // immediate_size if constexpr (Op >= OP_PUSH1 && Op <= OP_PUSH32) static_assert(tr.immediate_size == Op - OP_PUSH1 + 1); - else if constexpr (Op == OP_RJUMP || Op == OP_RJUMPI) + else if constexpr (Op == OP_RJUMP || Op == OP_RJUMPI || Op == OP_CALLF) static_assert(tr.immediate_size == 2); else if constexpr (Op == OP_DUPN || Op == OP_SWAPN) static_assert(tr.immediate_size == 1); @@ -105,6 +106,8 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept case OP_RJUMP: case OP_RJUMPI: case OP_RJUMPV: + case OP_CALLF: + case OP_RETF: case OP_DUPN: case OP_SWAPN: return true;