Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP-663: Unlimited SWAP and DUP instructions #529

Merged
merged 3 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
table[OP_CREATE2] = op_create<OP_CREATE2>;
table[OP_STATICCALL] = op_call<OP_STATICCALL>;

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

return table;
}();
} // namespace
Expand Down
35 changes: 35 additions & 0 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <size_t NumTopics>
inline evmc_status_code log(StackTop stack, ExecutionState& state) noexcept
{
Expand Down
3 changes: 3 additions & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}();
Expand Down Expand Up @@ -360,6 +362,9 @@ constexpr inline std::array<Traits, 256> 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};
Expand Down
4 changes: 2 additions & 2 deletions lib/evmone/instructions_xmacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t>(opcode) + bytes(op_traits.immediate_size, 0)};
Expand Down
190 changes: 190 additions & 0 deletions test/unittests/evm_eip663_dupn_swapn_test.cpp
Original file line number Diff line number Diff line change
@@ -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 <numeric>

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);
}
5 changes: 5 additions & 0 deletions test/unittests/evm_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ const char* print_vm_name(const testing::TestParamInfo<evmc::VM*>& 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
3 changes: 3 additions & 0 deletions test/unittests/evm_fixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ namespace evmone::test
class evm : public testing::TestWithParam<evmc::VM*>
{
protected:
/// Reports if execution is done by evmone/Advanced.
static bool is_advanced() noexcept;

/// The VM handle.
evmc::VM& vm;

Expand Down
16 changes: 16 additions & 0 deletions test/unittests/instructions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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]);
}
}