From 581ce39da14bd7020176f1fd166831cb36d17077 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 10 Mar 2022 14:08:15 +0100 Subject: [PATCH 1/3] test: Helper to check if current VM is Advanced --- test/unittests/evm_fixture.cpp | 5 +++++ test/unittests/evm_fixture.hpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/test/unittests/evm_fixture.cpp b/test/unittests/evm_fixture.cpp index 173dccfd85..22342e9602 100644 --- a/test/unittests/evm_fixture.cpp +++ b/test/unittests/evm_fixture.cpp @@ -27,4 +27,9 @@ const char* print_vm_name(const testing::TestParamInfo& info) noexcep INSTANTIATE_TEST_SUITE_P( evmone, evm, testing::Values(&advanced_vm, &baseline_vm, &bnocgoto_vm), print_vm_name); + +bool evm::is_advanced() noexcept +{ + return GetParam() == &advanced_vm; +} } // namespace evmone::test diff --git a/test/unittests/evm_fixture.hpp b/test/unittests/evm_fixture.hpp index 1309f85999..7452c245d8 100644 --- a/test/unittests/evm_fixture.hpp +++ b/test/unittests/evm_fixture.hpp @@ -32,6 +32,9 @@ namespace evmone::test class evm : public testing::TestWithParam { protected: + /// Reports if execution is done by evmone/Advanced. + static bool is_advanced() noexcept; + /// The VM handle. evmc::VM& vm; From 4b1dad498247275264061362736a20657da5bc9a Mon Sep 17 00:00:00 2001 From: rodiazet Date: Wed, 30 Nov 2022 18:56:37 +0100 Subject: [PATCH 2/3] baseline: Implement EIP-663 DUPN SWAPN --- lib/evmone/advanced_instructions.cpp | 3 +++ lib/evmone/instructions.hpp | 35 ++++++++++++++++++++++++++++ lib/evmone/instructions_opcodes.hpp | 3 +++ lib/evmone/instructions_traits.hpp | 5 ++++ lib/evmone/instructions_xmacro.hpp | 4 ++-- 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 2a6469d02c..b484912f2d 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -245,6 +245,9 @@ constexpr std::array instruction_implementations = []( table[OP_CREATE2] = op_create; table[OP_STATICCALL] = op_call; + table[OP_DUPN] = op_undefined; + table[OP_SWAPN] = op_undefined; + return table; }(); } // namespace diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 982ae4a1e8..e525387d83 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -820,6 +820,41 @@ inline void swap(StackTop stack) noexcept a[3] = t3; } +inline code_iterator dupn(StackTop stack, ExecutionState& state, code_iterator pos) noexcept +{ + const auto n = pos[1] + 1; + + const auto stack_size = &stack.top() - state.stack_space.bottom(); + + if (stack_size < n) + { + state.status = EVMC_STACK_UNDERFLOW; + return nullptr; + } + + stack.push(stack[n - 1]); + + return pos + 2; +} + +inline code_iterator swapn(StackTop stack, ExecutionState& state, code_iterator pos) noexcept +{ + const auto n = pos[1] + 1; + + const auto stack_size = &stack.top() - state.stack_space.bottom(); + + if (stack_size <= n) + { + state.status = EVMC_STACK_UNDERFLOW; + return nullptr; + } + + // TODO: This may not be optimal, see instr::core::swap(). + std::swap(stack.top(), stack[n]); + + return pos + 2; +} + template inline evmc_status_code log(StackTop stack, ExecutionState& state) noexcept { diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 0846b1e097..8385c7120d 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -152,6 +152,9 @@ enum Opcode OP_LOG3 = 0xa3, OP_LOG4 = 0xa4, + OP_DUPN = 0xb5, + OP_SWAPN = 0xb6, + OP_CREATE = 0xf0, OP_CALL = 0xf1, OP_CALLCODE = 0xf2, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index d5e486e8b6..ccea01b2fb 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -164,6 +164,8 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_SHANGHAI][OP_PUSH0] = 2; table[EVMC_CANCUN] = table[EVMC_SHANGHAI]; + table[EVMC_CANCUN][OP_DUPN] = 3; + table[EVMC_CANCUN][OP_SWAPN] = 3; return table; }(); @@ -360,6 +362,9 @@ constexpr inline std::array traits = []() noexcept { table[OP_LOG3] = {"LOG3", 0, false, 5, -5, EVMC_FRONTIER}; table[OP_LOG4] = {"LOG4", 0, false, 6, -6, EVMC_FRONTIER}; + table[OP_DUPN] = {"DUPN", 1, false, 0, 1, EVMC_CANCUN}; + table[OP_SWAPN] = {"SWAPN", 1, false, 0, 0, EVMC_CANCUN}; + table[OP_CREATE] = {"CREATE", 0, false, 3, -2, EVMC_FRONTIER}; table[OP_CALL] = {"CALL", 0, false, 7, -6, EVMC_FRONTIER}; table[OP_CALLCODE] = {"CALLCODE", 0, false, 7, -6, EVMC_FRONTIER}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 1f7b135caf..d69b5c13f5 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -224,8 +224,8 @@ ON_OPCODE_UNDEFINED(0xb2) \ ON_OPCODE_UNDEFINED(0xb3) \ ON_OPCODE_UNDEFINED(0xb4) \ - ON_OPCODE_UNDEFINED(0xb5) \ - ON_OPCODE_UNDEFINED(0xb6) \ + ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \ + ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \ ON_OPCODE_UNDEFINED(0xb7) \ ON_OPCODE_UNDEFINED(0xb8) \ ON_OPCODE_UNDEFINED(0xb9) \ From 3e0ab0ec741c99354c46a9b4fd2c8d720c70c901 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Mon, 28 Nov 2022 12:22:23 +0100 Subject: [PATCH 3/3] test: Add EIP-663 DUPN SWAPN unit tests --- test/unittests/CMakeLists.txt | 1 + test/unittests/eof_validation_test.cpp | 3 +- test/unittests/evm_eip663_dupn_swapn_test.cpp | 190 ++++++++++++++++++ test/unittests/instructions_test.cpp | 16 ++ 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 test/unittests/evm_eip663_dupn_swapn_test.cpp diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index e58cd1d33f..33f6e44b84 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(evmone-unittests evm_test.cpp evm_calls_test.cpp evm_control_flow_test.cpp + evm_eip663_dupn_swapn_test.cpp evm_eip2929_test.cpp evm_eip3198_basefee_test.cpp evm_eip3855_push0_test.cpp diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index e55ee49a0a..54925bef1e 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -219,7 +219,8 @@ TEST(eof_validation, EOF1_terminating_instructions) { const auto& op_traits = traits[opcode]; // Skip undefined opcodes. - if (op_traits.name == nullptr) + // TODO: iterate over all EOF revisions. + if (op_traits.name == nullptr || op_traits.since == EVMC_CANCUN) continue; bytes code{static_cast(opcode) + bytes(op_traits.immediate_size, 0)}; diff --git a/test/unittests/evm_eip663_dupn_swapn_test.cpp b/test/unittests/evm_eip663_dupn_swapn_test.cpp new file mode 100644 index 0000000000..acc77911f4 --- /dev/null +++ b/test/unittests/evm_eip663_dupn_swapn_test.cpp @@ -0,0 +1,190 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2022 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "evm_fixture.hpp" +#include + +using namespace evmc::literals; +using namespace evmone; +using namespace intx; +using evmone::test::evm; + +TEST_P(evm, dupn) +{ + // DUPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + + auto pushes = bytecode{}; + for (uint64_t i = 1; i <= 20; ++i) + pushes += push(i); + + execute(pushes + OP_DUPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(pushes + OP_DUPN + "02" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(18); + + execute(pushes + OP_DUPN + "13" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + execute(pushes + OP_DUPN + "14" + ret_top()); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} + +TEST_P(evm, swapn) +{ + // SWAPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + + auto pushes = bytecode{}; + for (uint64_t i = 1; i <= 20; ++i) + pushes += push(i); + + execute(pushes + OP_SWAPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(19); + + execute(pushes + OP_SWAPN + "00" + OP_DUPN + "01" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(pushes + OP_SWAPN + "01" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(18); + + execute(pushes + OP_SWAPN + "01" + OP_DUPN + "02" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(pushes + OP_SWAPN + "12" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + execute(pushes + OP_SWAPN + "12" + OP_DUPN + "13" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(pushes + OP_SWAPN + "13" + ret_top()); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} + +TEST_P(evm, dupn_full_stack) +{ + // DUPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + auto full_stack_code = bytecode{}; + for (uint64_t i = 1023; i >= 1; --i) + full_stack_code += push(i); + + execute(full_stack_code + OP_POP + OP_DUPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(2); + + execute(full_stack_code + OP_POP + OP_DUPN + "ff" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(257); + + execute(full_stack_code + OP_POP + OP_DUPN + "fe" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(256); + + execute(full_stack_code + OP_DUPN + "fe" + OP_DUPN + "ff"); + EXPECT_STATUS(EVMC_STACK_OVERFLOW); +} + +TEST_P(evm, swapn_full_stack) +{ + // SWAPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + auto full_stack_code = bytecode{}; + for (uint64_t i = 1024; i >= 1; --i) + full_stack_code += push(i); + + execute(full_stack_code + OP_POP + OP_SWAPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(3); + + execute(full_stack_code + OP_POP + OP_SWAPN + "ff" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(255 + 3); + + execute(full_stack_code + OP_POP + OP_SWAPN + "fe" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(254 + 3); + + execute(full_stack_code + OP_SWAPN + "ff" + OP_SWAPN + "00" + OP_RETURN); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(result.output_size, 255 + 2); +} + +TEST_P(evm, dupn_dup_consistency) +{ + // DUPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + auto pushes = bytecode{}; + for (uint64_t i = 32; i >= 1; --i) + pushes += push(i); + + execute(pushes + OP_DUP1 + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + execute(pushes + OP_DUPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + execute(pushes + OP_DUP16 + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(16); + + execute(pushes + OP_DUPN + "0f" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(16); +} + +TEST_P(evm, swapn_swap_consistency) +{ + // DUPN is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_CANCUN; + auto pushes = bytecode{}; + for (uint64_t i = 32; i >= 1; --i) + pushes += push(i); + + execute(pushes + OP_SWAP1 + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(2); + + execute(pushes + OP_SWAPN + "00" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(2); + + execute(pushes + OP_SWAP16 + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(17); + + execute(pushes + OP_SWAPN + "0f" + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(17); +} diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index 1a36632287..e86550724b 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -55,6 +55,8 @@ 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_DUPN || Op == OP_SWAPN) + static_assert(tr.immediate_size == 1); else static_assert(tr.immediate_size == 0); @@ -101,6 +103,10 @@ TEST(instructions, compare_with_evmc_instruction_tables) for (size_t i = 0; i < evmone_tbl.size(); ++i) { + // Skip DUPN and SWAPN for Cancun. They are not defined in evmc + // TODO: Define DUPN and SWAPN in evmc + if (r == EVMC_CANCUN && (Opcode(i) == OP_DUPN || Opcode(i) == OP_SWAPN)) + continue; const auto gas_cost = (instr_tbl[i] != instr::undefined) ? instr_tbl[i] : 0; const auto& metrics = evmone_tbl[i]; const auto& ref_metrics = evmc_tbl[i]; @@ -129,7 +135,13 @@ TEST(instructions, compare_undefined_instructions) const auto* evmc_names_tbl = evmc_get_instruction_names_table(rev); for (size_t i = 0; i < instr_tbl.size(); ++i) + { + // Skip DUPN and SWAPN. They are not defined in evmc + // TODO: Define DUPN and SWAPN in evmc + if (Opcode(i) == OP_DUPN || Opcode(i) == OP_SWAPN) + continue; EXPECT_EQ(instr_tbl[i] == instr::undefined, evmc_names_tbl[i] == nullptr) << i; + } } } @@ -138,6 +150,10 @@ TEST(instructions, compare_with_evmc_instruction_names) const auto* evmc_tbl = evmc_get_instruction_names_table(EVMC_MAX_REVISION); for (size_t i = 0; i < instr::traits.size(); ++i) { + // Skip DUPN and SWAPN. They are not defined in evmc + // TODO: Define DUPN and SWAPN in evmc + if (Opcode(i) == OP_DUPN || Opcode(i) == OP_SWAPN) + continue; EXPECT_STREQ(instr::traits[i].name, evmc_tbl[i]); } }