diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 5ad8c9ce1c..93a47314f5 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -269,6 +269,7 @@ constexpr std::array 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; diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index 95405d436b..3781b8784c 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -34,6 +34,7 @@ constexpr auto legacy_cost_tables = []() noexcept { tables[EVMC_PRAGUE][OP_DATALOADN] = instr::undefined; tables[EVMC_PRAGUE][OP_DATASIZE] = instr::undefined; tables[EVMC_PRAGUE][OP_DATACOPY] = instr::undefined; + tables[EVMC_PRAGUE][OP_RETURNDATALOAD] = instr::undefined; return tables; }(); diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 03d9a813d4..6d412c3842 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -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(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(data); + return {EVMC_SUCCESS, gas_left}; + } +} + inline void returndatasize(StackTop stack, ExecutionState& state) noexcept { stack.push(state.return_data.size()); diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 0d5f8a3a05..55ba1461d6 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -178,6 +178,7 @@ enum Opcode : uint8_t OP_RETURN = 0xf3, OP_DELEGATECALL = 0xf4, OP_CREATE2 = 0xf5, + OP_RETURNDATALOAD = 0xf7, OP_STATICCALL = 0xfa, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 26b43b89f0..9bba5fe420 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -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; }(); @@ -401,6 +402,7 @@ constexpr inline std::array 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}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 17984355db..0b29791c1e 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -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) \ diff --git a/test/unittests/evm_calls_test.cpp b/test/unittests/evm_calls_test.cpp index e7d9753001..a5de2a7edc 100644 --- a/test/unittests/evm_calls_test.cpp +++ b/test/unittests/evm_calls_test.cpp @@ -741,6 +741,175 @@ 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(124, 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(124, eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + const auto max_uint256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32; + execute(124, eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(124, 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(124, eof_bytecode(staticcall(0) + returndataload(3) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + const auto max_uint256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32; + execute(124, eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(124, 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(124, eof_bytecode(staticcall(0) + returndataload(33) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + const auto max_uint256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32; + execute(124, eof_bytecode(staticcall(0) + returndataload(max_uint256) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + + execute(124, eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(31) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(32) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + execute(124, 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(124, eof_bytecode(staticcall(0) + returndataload(0) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + + execute(124, eof_bytecode(staticcall(0) + returndataload(1) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); + const auto max_uint256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32; + execute(124, 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(124, eof_bytecode(staticcall(0) + returndataload(highbits) + OP_STOP, 6)); + EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS); +} + TEST_P(evm, call_gas_refund_propagation) { rev = EVMC_LONDON; diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index 61a7395c20..909df42897 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -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; diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index d27e016c1f..40e293bf70 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -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;