Skip to content

Commit

Permalink
Implement EXCHANGE from EIP-663
Browse files Browse the repository at this point in the history
  • Loading branch information
pdobacz committed Mar 18, 2024
1 parent 0abe773 commit 898c393
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 4 deletions.
1 change: 1 addition & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](

table[OP_DUPN] = op_undefined;
table[OP_SWAPN] = op_undefined;
table[OP_EXCHANGE] = op_undefined;

return table;
}();
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/baseline_instruction_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}();
Expand Down
6 changes: 6 additions & 0 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,12 @@ std::variant<EOFValidationError, int32_t> 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;
Expand Down
9 changes: 9 additions & 0 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ enum Opcode : uint8_t

OP_DUPN = 0xe6,
OP_SWAPN = 0xe7,
OP_EXCHANGE = 0xe8,

OP_CREATE = 0xf0,
OP_CALL = 0xf1,
Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -390,6 +391,7 @@ constexpr inline std::array<Traits, 256> 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};
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/instructions_xmacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
2 changes: 2 additions & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions test/unittests/eof_validation_stack_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 3 additions & 2 deletions test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
115 changes: 115 additions & 0 deletions test/unittests/evm_eip663_exchange_test.cpp
Original file line number Diff line number Diff line change
@@ -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 <numeric>

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);
}
3 changes: 2 additions & 1 deletion test/unittests/instructions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
49 changes: 49 additions & 0 deletions test/unittests/state_transition_eip663_test.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions test/utils/bytecode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 898c393

Please sign in to comment.