diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 2bbd0925c1..484aa0d5b7 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -282,6 +282,7 @@ constexpr std::array instruction_implementations = []( table[OP_DUPN] = op_undefined; table[OP_SWAPN] = op_undefined; + table[OP_EXCHANGE] = op_undefined; return table; }(); diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index cefcd0a05b..36d78542e8 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -36,6 +36,7 @@ constexpr auto legacy_cost_tables = []() noexcept { tables[EVMC_PRAGUE][OP_DATACOPY] = instr::undefined; tables[EVMC_PRAGUE][OP_DUPN] = instr::undefined; tables[EVMC_PRAGUE][OP_SWAPN] = instr::undefined; + tables[EVMC_PRAGUE][OP_EXCHANGE] = instr::undefined; tables[EVMC_PRAGUE][OP_RETURNDATALOAD] = instr::undefined; return tables; }(); diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index de549073fb..8d0e39bdd6 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -434,6 +434,12 @@ std::variant validate_max_stack_height( stack_height_required = code[i + 1] + 1; else if (opcode == OP_SWAPN) stack_height_required = code[i + 1] + 2; + else if (opcode == OP_EXCHANGE) + { + const auto n = (code[i + 1] >> 4) + 1; + const auto m = (code[i + 1] & 0x0F) + 1; + stack_height_required = n + m + 1; + } if (stack_height.min < stack_height_required) return EOFValidationError::stack_underflow; diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 991184107a..efab94aa56 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -937,6 +937,15 @@ inline code_iterator swapn(StackTop stack, code_iterator pos) noexcept return pos + 2; } +inline code_iterator exchange(StackTop stack, code_iterator pos) noexcept +{ + const auto n = (pos[1] >> 4) + 1; + const auto m = (pos[1] & 0x0f) + 1; + // TODO: This may not be optimal, see instr::core::swap(). + std::swap(stack[n], stack[n + m]); + return pos + 2; +} + inline Result mcopy(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept { const auto& dst_u256 = stack.pop(); diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 0fbd48a6f6..977abd96fc 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -172,6 +172,7 @@ enum Opcode : uint8_t OP_DUPN = 0xe6, OP_SWAPN = 0xe7, + OP_EXCHANGE = 0xe8, OP_CREATE = 0xf0, OP_CALL = 0xf1, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 760ca86804..66289bca37 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -173,6 +173,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_PRAGUE] = table[EVMC_CANCUN]; table[EVMC_PRAGUE][OP_DUPN] = 3; table[EVMC_PRAGUE][OP_SWAPN] = 3; + table[EVMC_PRAGUE][OP_EXCHANGE] = 3; table[EVMC_PRAGUE][OP_RJUMP] = 2; table[EVMC_PRAGUE][OP_RJUMPI] = 4; table[EVMC_PRAGUE][OP_RJUMPV] = 4; @@ -390,6 +391,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_DUPN] = {"DUPN", 1, false, 0, 1, EVMC_PRAGUE}; table[OP_SWAPN] = {"SWAPN", 1, false, 0, 0, EVMC_PRAGUE}; + table[OP_EXCHANGE] = {"EXCHANGE", 1, false, 0, 0, EVMC_PRAGUE}; table[OP_MCOPY] = {"MCOPY", 0, false, 3, -3, EVMC_CANCUN}; table[OP_DATALOAD] = {"DATALOAD", 0, false, 1, 0, EVMC_PRAGUE}; table[OP_DATALOADN] = {"DATALOADN", 2, false, 0, 1, EVMC_PRAGUE}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index ce14eecfa7..6a77a818d3 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -278,7 +278,7 @@ ON_OPCODE_IDENTIFIER(OP_JUMPF, jumpf) \ ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \ ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \ - ON_OPCODE_UNDEFINED(0xe8) \ + ON_OPCODE_IDENTIFIER(OP_EXCHANGE, exchange) \ ON_OPCODE_UNDEFINED(0xe9) \ ON_OPCODE_UNDEFINED(0xea) \ ON_OPCODE_UNDEFINED(0xeb) \ diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 872099f16d..1271abbcd7 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources( evm_calls_test.cpp evm_control_flow_test.cpp evm_eip663_dupn_swapn_test.cpp + evm_eip663_exchange_test.cpp evm_eip2929_test.cpp evm_eip3198_basefee_test.cpp evm_eip3855_push0_test.cpp @@ -58,6 +59,7 @@ target_sources( state_transition_block_test.cpp state_transition_call_test.cpp state_transition_create_test.cpp + state_transition_eip663_test.cpp state_transition_extcode_test.cpp state_transition_selfdestruct_test.cpp state_transition_touch_test.cpp diff --git a/test/unittests/eof_validation_stack_test.cpp b/test/unittests/eof_validation_stack_test.cpp index 370d705832..6a6d407d95 100644 --- a/test/unittests/eof_validation_stack_test.cpp +++ b/test/unittests/eof_validation_stack_test.cpp @@ -1422,3 +1422,67 @@ TEST_F(eof_validation, swapn_stack_validation) add_test_case( eof_bytecode(pushes + OP_SWAPN + "ff" + OP_STOP, 20), EOFValidationError::stack_underflow); } + +TEST_F(eof_validation, exchange_stack_validation) +{ + const auto pushes = 10 * push(1); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "00" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "10" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "01" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "20" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "02" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "70" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "07" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "11" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "34" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "43" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "16" + OP_STOP, 10), EOFValidationError::success); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "61" + OP_STOP, 10), EOFValidationError::success); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "80" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "08" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "71" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "17" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "44" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "53" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "35" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "ee" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "ef" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "fe" + OP_STOP, 10), + EOFValidationError::stack_underflow); + add_test_case(eof_bytecode(pushes + OP_EXCHANGE + "ff" + OP_STOP, 10), + EOFValidationError::stack_underflow); +} + +TEST_F(eof_validation, exchange_deep_stack_validation) +{ + const auto pushes = 33 * push(1); + add_test_case( + eof_bytecode(pushes + OP_EXCHANGE + "ff" + OP_STOP, 33), EOFValidationError::success); +} + +TEST_F(eof_validation, exchange_empty_stack_validation) +{ + add_test_case(eof_bytecode(bytecode(OP_EXCHANGE) + "00" + OP_STOP, 0), + EOFValidationError::stack_underflow); +} diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index fcf2152747..a4d004b5cd 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -293,8 +293,9 @@ TEST_F(eof_validation, EOF1_undefined_opcodes) // PUSH*, DUPN, SWAPN, RJUMP*, CALLF, JUMPF 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_CALLF || - opcode == OP_RJUMPV || opcode == OP_DATALOADN || opcode == OP_JUMPF) + opcode == OP_SWAPN || opcode == OP_EXCHANGE || opcode == OP_RJUMP || + opcode == OP_RJUMPI || opcode == OP_CALLF || opcode == OP_RJUMPV || + opcode == OP_DATALOADN || opcode == OP_JUMPF) continue; // These opcodes are deprecated since Prague. // gas_cost table current implementation does not allow to undef instructions. diff --git a/test/unittests/evm_eip663_exchange_test.cpp b/test/unittests/evm_eip663_exchange_test.cpp new file mode 100644 index 0000000000..83c881f151 --- /dev/null +++ b/test/unittests/evm_eip663_exchange_test.cpp @@ -0,0 +1,115 @@ +// 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, exchange) +{ + // EXCHANGE is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_PRAGUE; + + auto pushes = bytecode{}; + for (uint64_t i = 1; i <= 20; ++i) + pushes += push(i); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "00" + ret_top(), 21)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "00" + OP_DUPN + "01" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(18); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "00" + OP_DUPN + "02" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(19); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "01" + ret_top(), 21)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "01" + OP_DUPN + "01" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(17); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "01" + OP_DUPN + "03" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(19); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "10" + ret_top(), 21)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "10" + OP_DUPN + "02" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(17); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "10" + OP_DUPN + "03" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(18); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "f2" + ret_top(), 21)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "f2" + OP_DUPN + "10" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "f2" + OP_DUPN + "13" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(4); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "0f" + ret_top(), 21)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(20); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "0f" + OP_DUPN + "01" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(3); + + execute(eof_bytecode(pushes + OP_EXCHANGE + "0f" + OP_DUPN + "11" + ret_top(), 22)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(19); +} + +TEST_P(evm, exchange_deep_stack) +{ + // EXCHANGE is not implemented in Advanced. + if (evm::is_advanced()) + return; + + rev = EVMC_PRAGUE; + auto full_stack_code = bytecode{}; + for (uint64_t i = 255; i >= 1; --i) + full_stack_code += push(i); + + execute(eof_bytecode(full_stack_code + OP_EXCHANGE + "ff" + ret_top(), 256)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + execute(eof_bytecode(full_stack_code + OP_EXCHANGE + "ff" + OP_DUPN + "10" + ret_top(), 257)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(33); + execute(eof_bytecode(full_stack_code + OP_EXCHANGE + "ff" + OP_DUPN + "20" + ret_top(), 257)); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(17); +} + +TEST_P(evm, exchange_undefined_in_legacy) +{ + rev = EVMC_PRAGUE; + + execute(push(1) + push(2) + push(3) + OP_EXCHANGE + "00"); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index 909df42897..a817ef40a7 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -61,7 +61,7 @@ constexpr void validate_traits_of() noexcept static_assert(tr.immediate_size == 2); else if constexpr (Op == OP_RJUMPV) static_assert(tr.immediate_size == 1); - else if constexpr (Op == OP_DUPN || Op == OP_SWAPN) + else if constexpr (Op == OP_DUPN || Op == OP_SWAPN || Op == OP_EXCHANGE) static_assert(tr.immediate_size == 1); else if constexpr (Op == OP_DATALOADN) static_assert(tr.immediate_size == 2); @@ -118,6 +118,7 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept case OP_JUMPF: case OP_DUPN: case OP_SWAPN: + case OP_EXCHANGE: case OP_MCOPY: case OP_DATALOAD: case OP_DATALOADN: diff --git a/test/unittests/state_transition_eip663_test.cpp b/test/unittests/state_transition_eip663_test.cpp new file mode 100644 index 0000000000..9c9ba59307 --- /dev/null +++ b/test/unittests/state_transition_eip663_test.cpp @@ -0,0 +1,49 @@ +// 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, dupn) +{ + rev = EVMC_PRAGUE; + tx.to = To; + pre.insert(*tx.to, + { + .code = eof_bytecode( + push(1) + 255 * push(2) + OP_DUPN + "ff" + sstore(0) + sstore(1) + OP_STOP, 258), + }); + expect.post[*tx.to].storage[0x00_bytes32] = 0x01_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; +} + +TEST_F(state_transition, swapn) +{ + rev = EVMC_PRAGUE; + tx.to = To; + pre.insert(*tx.to, + { + .code = eof_bytecode( + push(1) + 256 * push(2) + OP_SWAPN + "ff" + sstore(0) + sstore(1) + OP_STOP, 258), + }); + expect.post[*tx.to].storage[0x00_bytes32] = 0x01_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; +} + +TEST_F(state_transition, exchange) +{ + rev = EVMC_PRAGUE; + tx.to = To; + pre.insert(*tx.to, { + .code = eof_bytecode(push(1) + push(2) + push(3) + OP_EXCHANGE + "00" + + sstore(0) + sstore(1) + sstore(2) + OP_STOP, + 4), + }); + expect.post[*tx.to].storage[0x00_bytes32] = 0x03_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[*tx.to].storage[0x02_bytes32] = 0x02_bytes32; +} diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index a85bc13f82..1822fab935 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -399,6 +399,11 @@ inline bytecode sstore(bytecode index, bytecode value) return value + index + OP_SSTORE; } +inline bytecode sstore(bytecode index) +{ + return index + OP_SSTORE; +} + inline bytecode sload(bytecode index) { return index + OP_SLOAD;