Skip to content

Commit

Permalink
Implement RETURNDATALOAD (EOF)
Browse files Browse the repository at this point in the history
  • Loading branch information
pdobacz committed Jan 29, 2024
1 parent 951b88d commit 5f61632
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
table[OP_DATALOADN] = op_undefined;
table[OP_DATASIZE] = op_undefined;
table[OP_DATACOPY] = op_undefined;
table[OP_RETURNDATALOAD] = op_undefined;
table[OP_JUMPF] = op_undefined;

table[OP_DUPN] = op_undefined;
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_RETURNDATALOAD] = instr::undefined;
return tables;
}();

Expand Down
22 changes: 22 additions & 0 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,28 @@ inline Result extcodecopy(StackTop stack, int64_t gas_left, ExecutionState& stat
return {EVMC_SUCCESS, gas_left};
}

inline Result returndataload(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
{
auto& index = stack.top();

if (state.return_data.size() < index)
return {EVMC_INVALID_MEMORY_ACCESS, gas_left};
else
{
const auto begin = static_cast<size_t>(index);
const auto end = begin + 32;
if (state.return_data.size() < end)
return {EVMC_INVALID_MEMORY_ACCESS, gas_left};

uint8_t data[32] = {};
for (size_t i = 0; i < (end - begin); ++i)
data[i] = state.return_data[begin + i];

index = intx::be::unsafe::load<uint256>(data);
return {EVMC_SUCCESS, gas_left};
}
}

inline void returndatasize(StackTop stack, ExecutionState& state) noexcept
{
stack.push(state.return_data.size());
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 @@ -179,6 +179,7 @@ enum Opcode : uint8_t
OP_RETURN = 0xf3,
OP_DELEGATECALL = 0xf4,
OP_CREATE2 = 0xf5,
OP_RETURNDATALOAD = 0xf7,

OP_STATICCALL = 0xfa,

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 @@ -183,6 +183,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept {
table[EVMC_PRAGUE][OP_DATALOADN] = 3;
table[EVMC_PRAGUE][OP_DATASIZE] = 2;
table[EVMC_PRAGUE][OP_DATACOPY] = 3;
table[EVMC_PRAGUE][OP_RETURNDATALOAD] = 3;

return table;
}();
Expand Down Expand Up @@ -401,6 +402,7 @@ constexpr inline std::array<Traits, 256> traits = []() noexcept {
table[OP_RETURN] = {"RETURN", 0, true, 2, -2, EVMC_FRONTIER};
table[OP_DELEGATECALL] = {"DELEGATECALL", 0, false, 6, -5, EVMC_HOMESTEAD};
table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE};
table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, EVMC_PRAGUE};
table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM};
table[OP_CALLF] = {"CALLF", 2, false, 0, 0, EVMC_PRAGUE};
table[OP_RETF] = {"RETF", 0, true, 0, 0, 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 @@ -294,7 +294,7 @@
ON_OPCODE_IDENTIFIER(OP_DELEGATECALL, delegatecall) \
ON_OPCODE_IDENTIFIER(OP_CREATE2, create2) \
ON_OPCODE_UNDEFINED(0xf6) \
ON_OPCODE_UNDEFINED(0xf7) \
ON_OPCODE_IDENTIFIER(OP_RETURNDATALOAD, returndataload) \
ON_OPCODE_UNDEFINED(0xf8) \
ON_OPCODE_UNDEFINED(0xf9) \
ON_OPCODE_IDENTIFIER(OP_STATICCALL, staticcall) \
Expand Down
171 changes: 171 additions & 0 deletions test/unittests/evm_calls_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
using namespace evmc::literals;
using evmone::test::evm;

inline constexpr auto max_uint256 =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32;

TEST_P(evm, delegatecall)
{
auto code = bytecode{};
Expand Down Expand Up @@ -741,6 +744,174 @@ TEST_P(evm, returndatacopy_outofrange)
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
}

TEST_P(evm, returndatacopy_outofrange_highbits)
{
const uint8_t call_output[2]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

// Covers an incorrect cast of RETURNDATALOAD arg to `size_t` ignoring the high bits.
const auto highbits =
0x1000000000000000000000000000000000000000000000000000000000000000_bytes32;
execute(735, staticcall(0) + returndatacopy(0, highbits, 0));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);
}

TEST_P(evm, returndataload)
{
// Not implemented in Advanced.
if (is_advanced())
return;

rev = EVMC_PRAGUE;
const auto call_output =
0x497f3c9f61479c1cfa53f0373d39d2bf4e5f73f71411da62f1d6b85c03a60735_bytes32;
host.call_result.output_data = std::data(call_output.bytes);
host.call_result.output_size = std::size(call_output.bytes);

const auto code = eof_bytecode(staticcall(0) + returndataload(0) + ret_top(), 6);
execute(code);
EXPECT_GAS_USED(EVMC_SUCCESS, 139);
EXPECT_EQ(bytes_view(result.output_data, result.output_size), call_output);
}

TEST_P(evm, returndataload_cost)
{
// Not implemented in Advanced.
if (is_advanced())
return;

rev = EVMC_PRAGUE;
const uint8_t call_output[32]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

const auto code = eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6);
execute(124, code);
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
execute(123, code);
EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS);
}

TEST_P(evm, returndataload_outofrange)
{
// Not implemented in Advanced.
if (is_advanced())
return;

rev = EVMC_PRAGUE;
{
const uint8_t call_output[31]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);
}
{
const uint8_t call_output[32]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
}
{
const uint8_t call_output[34]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

execute(eof_bytecode(staticcall(0) + returndataload(3) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);

execute(eof_bytecode(staticcall(0) + returndataload(2) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
}
{
const uint8_t call_output[64]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

execute(eof_bytecode(staticcall(0) + returndataload(33) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);


execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);

execute(eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);

execute(eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_SUCCESS);
}
}

TEST_P(evm, returndataload_empty)
{
// Not implemented in Advanced.
if (is_advanced())
return;

rev = EVMC_PRAGUE;
execute(eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);

execute(eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);
}

TEST_P(evm, returndataload_outofrange_highbits)
{
// Not implemented in Advanced.
if (is_advanced())
return;

rev = EVMC_PRAGUE;
const uint8_t call_output[34]{};
host.call_result.output_data = std::data(call_output);
host.call_result.output_size = std::size(call_output);

// Covers an incorrect cast of RETURNDATALOAD arg to `size_t` ignoring the high bits.
const auto highbits =
0x1000000000000000000000000000000000000000000000000000000000000000_bytes32;
execute(eof_bytecode(staticcall(0) + returndataload(highbits) + OP_STOP, 6));
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);
}

TEST_P(evm, returndataload_undefined_in_legacy)
{
rev = EVMC_PRAGUE;
execute(staticcall(0) + returndataload(0));
EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION);
}

TEST_P(evm, call_gas_refund_propagation)
{
rev = EVMC_LONDON;
Expand Down
1 change: 1 addition & 0 deletions test/unittests/instructions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept
case OP_DATALOADN:
case OP_DATASIZE:
case OP_DATACOPY:
case OP_RETURNDATALOAD:
case OP_TLOAD:
case OP_TSTORE:
return true;
Expand Down
5 changes: 5 additions & 0 deletions test/utils/bytecode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ inline bytecode calldatacopy(bytecode dst, bytecode src, bytecode size)
return std::move(size) + std::move(src) + std::move(dst) + OP_CALLDATACOPY;
}

inline bytecode returndataload(bytecode index)
{
return index + OP_RETURNDATALOAD;
}

inline bytecode returndatasize()
{
return OP_RETURNDATASIZE;
Expand Down

0 comments on commit 5f61632

Please sign in to comment.