From 7464c5531ae5446ad248045eea53342c9b9a2fe1 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 24 Jun 2024 12:39:40 +0200 Subject: [PATCH 1/8] Add validation of subcontainer kinds --- lib/evmone/eof.cpp | 61 +++++++++++++++++++++++++++---- lib/evmone/eof.hpp | 2 + test/unittests/eof_validation.cpp | 4 ++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index b2d44265ac..319ae4448b 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -244,6 +244,17 @@ std::variant, EOFValidationError> validate_types( return types; } +enum class ContainerKind : uint8_t +{ + /// Container that uses RETURNCONTRACT. Can be used by EOFCREATE/TXCREATE/Creation transaction. + initcode, + /// Container that uses STOP/RETURN. Can be returned by RETURNCONTRACT. + runtime, + /// Container that uses only REVERT/INVALID or does not terminate execution. + /// Can be used in any context. + initcode_runtime, +}; + /// Result of validating instructions in a code section. struct InstructionValidationResult { @@ -255,7 +266,8 @@ struct InstructionValidationResult }; std::variant validate_instructions( - evmc_revision rev, const EOF1Header& header, size_t code_idx, bytes_view container) noexcept + evmc_revision rev, const EOF1Header& header, ContainerKind kind, size_t code_idx, + bytes_view container) noexcept { const bytes_view code{header.get_code(container, code_idx)}; assert(!code.empty()); // guaranteed by EOF headers validation @@ -323,9 +335,20 @@ std::variant validate_instructi if (container_idx >= header.container_sizes.size()) return EOFValidationError::invalid_container_section_index; - subcontainer_references.emplace_back(container_idx, Opcode{op}); + if (op == OP_RETURNCONTRACT) + { + if (kind == ContainerKind::runtime || kind == ContainerKind::initcode_runtime) + return EOFValidationError::incompatible_container_kind; + } + + subcontainer_references.push_back({container_idx, Opcode{op}}); ++i; } + else if (op == OP_RETURN || op == OP_STOP) + { + if (kind == ContainerKind::initcode || kind == ContainerKind::initcode_runtime) + return EOFValidationError::incompatible_container_kind; + } else i += instr::traits[op].immediate_size; } @@ -577,15 +600,16 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n struct ContainerValidation { bytes_view bytes; - bool referenced_by_eofcreate = false; + ContainerKind kind; }; // Queue of containers left to process std::queue container_queue; - container_queue.push({main_container, false}); + + container_queue.push({main_container, ContainerKind::initcode_runtime}); while (!container_queue.empty()) { - const auto& [container, referenced_by_eofcreate] = container_queue.front(); + const auto& [container, container_kind] = container_queue.front(); // Validate header auto error_or_header = validate_header(rev, container); @@ -603,6 +627,7 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n const auto subcontainer_count = header.container_sizes.size(); std::vector subcontainer_referenced_by_eofcreate(subcontainer_count, false); + std::vector subcontainer_referenced_by_returncontract(subcontainer_count, false); while (!code_sections_queue.empty()) { @@ -616,7 +641,7 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n // Validate instructions const auto instr_validation_result_or_error = - validate_instructions(rev, header, code_idx, container); + validate_instructions(rev, header, container_kind, code_idx, container); if (const auto* error = std::get_if(&instr_validation_result_or_error)) return *error; @@ -629,6 +654,10 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n { if (opcode == OP_EOFCREATE) subcontainer_referenced_by_eofcreate[index] = true; + else if (opcode == OP_RETURNCONTRACT) + subcontainer_referenced_by_returncontract[index] = true; + else + intx::unreachable(); } // TODO(C++23): can use push_range() @@ -657,7 +686,8 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n { if (main_container == container) return EOFValidationError::toplevel_container_truncated; - if (referenced_by_eofcreate) + if (container_kind == ContainerKind::initcode || + container_kind == ContainerKind::initcode_runtime) return EOFValidationError::eofcreate_with_truncated_container; } @@ -666,7 +696,18 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n { const bytes_view subcontainer{header.get_container(container, subcont_idx)}; - container_queue.push({subcontainer, subcontainer_referenced_by_eofcreate[subcont_idx]}); + const bool eofcreate = subcontainer_referenced_by_eofcreate[subcont_idx]; + const bool returncontract = subcontainer_referenced_by_returncontract[subcont_idx]; + + // TODO Validate whether subcontainer was referenced by any instruction + + auto subcontainer_kind = ContainerKind::initcode_runtime; + if (!eofcreate) + subcontainer_kind = ContainerKind::runtime; + else if (!returncontract) + subcontainer_kind = ContainerKind::initcode; + + container_queue.push({subcontainer, subcontainer_kind}); } container_queue.pop(); @@ -935,6 +976,10 @@ std::string_view get_error_message(EOFValidationError err) noexcept return "eofcreate_with_truncated_container"; case EOFValidationError::toplevel_container_truncated: return "toplevel_container_truncated"; + case EOFValidationError::ambiguous_container_kind: + return "ambiguous_container_kind"; + case EOFValidationError::incompatible_container_kind: + return "incompatible_container_kind"; case EOFValidationError::impossible: return "impossible"; } diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index 2ea969ad72..0ff35444b7 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -139,6 +139,8 @@ enum class EOFValidationError invalid_container_section_index, eofcreate_with_truncated_container, toplevel_container_truncated, + ambiguous_container_kind, + incompatible_container_kind, impossible, }; diff --git a/test/unittests/eof_validation.cpp b/test/unittests/eof_validation.cpp index 6bed831c2a..f439eca86c 100644 --- a/test/unittests/eof_validation.cpp +++ b/test/unittests/eof_validation.cpp @@ -89,6 +89,10 @@ std::string_view get_tests_error_message(EOFValidationError err) noexcept return "EOF_EofCreateWithTruncatedContainer"; case EOFValidationError::toplevel_container_truncated: return "EOF_ToplevelContainerTruncated"; + case EOFValidationError::ambiguous_container_kind: + return "EOF_AmbiguousContainerKind"; + case EOFValidationError::incompatible_container_kind: + return "EOF_IncompatibleContainerKind"; case EOFValidationError::impossible: return "impossible"; } From 1a6c7acbbf20d9e6e449472819cb0beb45b3dd69 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 29 Apr 2024 18:02:59 +0200 Subject: [PATCH 2/8] Remove initcode mode runtime checks from RETURNCONTRACT, RETURN, STOP --- lib/evmone/instructions.hpp | 20 ++------------------ test/unittests/evm_eof_test.cpp | 15 --------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 33fbb1183a..6d16508c9f 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -144,15 +144,9 @@ inline constexpr auto pop = noop; inline constexpr auto jumpdest = noop; template -inline TermResult stop_impl(StackTop /*stack*/, int64_t gas_left, ExecutionState& state) noexcept +inline TermResult stop_impl( + StackTop /*stack*/, int64_t gas_left, ExecutionState& /*state*/) noexcept { - // STOP is forbidden inside EOFCREATE context - if constexpr (Status == EVMC_SUCCESS) - { - if (state.msg->kind == EVMC_EOFCREATE) - return {EVMC_UNDEFINED_INSTRUCTION, gas_left}; - } - return {Status, gas_left}; } inline constexpr auto stop = stop_impl; @@ -1160,13 +1154,6 @@ inline code_iterator jumpf(StackTop stack, ExecutionState& state, code_iterator template inline TermResult return_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept { - // RETURN is forbidden inside EOFCREATE context - if constexpr (StatusCode == EVMC_SUCCESS) - { - if (state.msg->kind == EVMC_EOFCREATE) - return {EVMC_UNDEFINED_INSTRUCTION, gas_left}; - } - const auto& offset = stack[0]; const auto& size = stack[1]; @@ -1187,9 +1174,6 @@ inline TermResult returncontract( const auto& offset = stack[0]; const auto& size = stack[1]; - if (state.msg->kind != EVMC_EOFCREATE) - return {EVMC_UNDEFINED_INSTRUCTION, gas_left}; - if (!check_memory(gas_left, state.memory, offset, size)) return {EVMC_OUT_OF_GAS, gas_left}; diff --git a/test/unittests/evm_eof_test.cpp b/test/unittests/evm_eof_test.cpp index c7c203ff25..55f06b1731 100644 --- a/test/unittests/evm_eof_test.cpp +++ b/test/unittests/evm_eof_test.cpp @@ -315,21 +315,6 @@ TEST_P(evm, returncontract_undefined_in_legacy) EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } -TEST_P(evm, returncontract_not_in_initcode) -{ - if (is_advanced()) - return; - - rev = EVMC_PRAGUE; - const auto code = eof_bytecode( - calldatacopy(0, 0, OP_CALLDATASIZE) + OP_CALLDATASIZE + 0 + OP_RETURNCONTRACT + Opcode{0}, - 3) - .container(eof_bytecode(OP_INVALID)); - - execute(code); - EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); -} - TEST_P(evm, eofcreate_staticmode) { if (is_advanced()) From c3df71559e1bd72be25c8c3d0a3d6fc74f9e6b29 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 29 Apr 2024 18:06:42 +0200 Subject: [PATCH 3/8] Fix validation tests --- test/unittests/eof_validation_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 04e38254a3..ea3f879708 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -1182,12 +1182,12 @@ TEST_F(eof_validation, EOF1_returncontract_invalid) EOFValidationError::invalid_container_section_index); // Unreachable code after RETURNCONTRACT - add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0} + OP_STOP, 2) + add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0} + revert(0, 0), 2) .container(embedded), EOFValidationError::unreachable_instructions); } -TEST_F(eof_validation, EOF1_eofcreate_returncontract_return_mix_valid) +TEST_F(eof_validation, EOF1_eofcreate_returncontract_return_mix) { // This test ensures that we _do not_ have a validation rule preventing EOFCREATE, // RETURNCONTRACT and RETURN mixing. @@ -1202,7 +1202,7 @@ TEST_F(eof_validation, EOF1_eofcreate_returncontract_return_mix_valid) // This top level container mixes all combinations of EOFCREATE/RETURNCONTRACT/RETURN. add_test_case(eof_bytecode(mixing_initcode, 4).container(mixing_initcontainer), - EOFValidationError::success); + EOFValidationError::ambiguous_container_kind); } TEST_F(eof_validation, EOF1_unreferenced_subcontainer_valid) From baf2db0f691d932f5b715f0ffe7301d42df67655 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 4 Jun 2024 13:02:31 +0200 Subject: [PATCH 4/8] Check that container is initcontainer in creation transaction/TXCREATE Refactor validation API to include a parameter for required container kind of the top-level container. --- lib/evmone/baseline.cpp | 4 +- lib/evmone/eof.cpp | 23 ++-- lib/evmone/eof.hpp | 13 ++- lib/evmone/instructions_calls.cpp | 2 +- test/eofparse/eofparse.cpp | 2 +- test/eofparsefuzz/eofparsefuzz.cpp | 3 +- test/eoftest/eoftest_runner.cpp | 4 +- test/state/host.cpp | 5 +- test/statetest/statetest_loader.cpp | 2 +- test/unittests/eof_test.cpp | 4 +- test/unittests/eof_validation.cpp | 4 +- test/unittests/eof_validation_test.cpp | 3 +- test/unittests/evm_eof_function_test.cpp | 3 +- test/unittests/evm_eof_test.cpp | 9 -- test/unittests/evm_fixture.hpp | 2 +- .../state_transition_eof_create_test.cpp | 108 +----------------- 16 files changed, 48 insertions(+), 143 deletions(-) diff --git a/lib/evmone/baseline.cpp b/lib/evmone/baseline.cpp index 94a79bdc66..130070dee4 100644 --- a/lib/evmone/baseline.cpp +++ b/lib/evmone/baseline.cpp @@ -403,7 +403,9 @@ evmc_result execute(evmc_vm* c_vm, const evmc_host_interface* host, evmc_host_co if (vm->validate_eof && rev >= EVMC_PRAGUE && is_eof_container(container)) { - if (validate_eof(rev, container) != EOFValidationError::success) + const auto container_kind = + (msg->depth == 0 ? ContainerKind::initcode : ContainerKind::runtime); + if (validate_eof(rev, container_kind, container) != EOFValidationError::success) return evmc_make_result(EVMC_CONTRACT_VALIDATION_FAILURE, 0, 0, nullptr, 0); } diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index 319ae4448b..5afb055051 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -244,17 +244,6 @@ std::variant, EOFValidationError> validate_types( return types; } -enum class ContainerKind : uint8_t -{ - /// Container that uses RETURNCONTRACT. Can be used by EOFCREATE/TXCREATE/Creation transaction. - initcode, - /// Container that uses STOP/RETURN. Can be returned by RETURNCONTRACT. - runtime, - /// Container that uses only REVERT/INVALID or does not terminate execution. - /// Can be used in any context. - initcode_runtime, -}; - /// Result of validating instructions in a code section. struct InstructionValidationResult { @@ -341,7 +330,7 @@ std::variant validate_instructi return EOFValidationError::incompatible_container_kind; } - subcontainer_references.push_back({container_idx, Opcode{op}}); + subcontainer_references.emplace_back(container_idx, Opcode{op}); ++i; } else if (op == OP_RETURN || op == OP_STOP) @@ -595,7 +584,8 @@ std::variant validate_max_stack_height( return max_stack_height_it->max; } -EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) noexcept +EOFValidationError validate_eof1( + evmc_revision rev, ContainerKind main_container_kind, bytes_view main_container) noexcept { struct ContainerValidation { @@ -605,7 +595,7 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n // Queue of containers left to process std::queue container_queue; - container_queue.push({main_container, ContainerKind::initcode_runtime}); + container_queue.push({main_container, main_container_kind}); while (!container_queue.empty()) { @@ -893,9 +883,10 @@ uint8_t get_eof_version(bytes_view container) noexcept 0; } -EOFValidationError validate_eof(evmc_revision rev, bytes_view container) noexcept +EOFValidationError validate_eof( + evmc_revision rev, ContainerKind kind, bytes_view container) noexcept { - return validate_eof1(rev, container); + return validate_eof1(rev, kind, container); } std::string_view get_error_message(EOFValidationError err) noexcept diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index 0ff35444b7..61af8592df 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -145,6 +145,17 @@ enum class EOFValidationError impossible, }; +enum class ContainerKind : uint8_t +{ + /// Container that uses RETURNCONTRACT. Can be used by EOFCREATE/TXCREATE/Creation transaction. + initcode, + /// Container that uses STOP/RETURN. Can be returned by RETURNCONTRACT. + runtime, + /// Container that uses only REVERT/INVALID or does not terminate execution. + /// Can be used in any context. + initcode_runtime, +}; + /// Determines the EOF version of the container by inspecting container's EOF prefix. /// If the prefix is missing or invalid, 0 is returned meaning legacy code. [[nodiscard]] uint8_t get_eof_version(bytes_view container) noexcept; @@ -155,7 +166,7 @@ enum class EOFValidationError /// Validates whether given container is a valid EOF according to the rules of given revision. [[nodiscard]] EVMC_EXPORT EOFValidationError validate_eof( - evmc_revision rev, bytes_view container) noexcept; + evmc_revision rev, ContainerKind kind, bytes_view container) noexcept; /// Returns the error message corresponding to an error code. [[nodiscard]] EVMC_EXPORT std::string_view get_error_message(EOFValidationError err) noexcept; diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index 5e81e742fc..a58e046d67 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -399,7 +399,7 @@ Result create_eof_impl( if constexpr (Op == OP_TXCREATE) { - const auto error_subcont = validate_eof(state.rev, initcontainer); + const auto error_subcont = validate_eof(state.rev, ContainerKind::initcode, initcontainer); if (error_subcont != EOFValidationError::success) return {EVMC_SUCCESS, gas_left}; // "Light" failure. } diff --git a/test/eofparse/eofparse.cpp b/test/eofparse/eofparse.cpp index 7765240cfb..b9a768f751 100644 --- a/test/eofparse/eofparse.cpp +++ b/test/eofparse/eofparse.cpp @@ -54,7 +54,7 @@ int main() } const auto& eof = *o; - const auto err = evmone::validate_eof(EVMC_PRAGUE, eof); + const auto err = evmone::validate_eof(EVMC_PRAGUE, evmone::ContainerKind::runtime, eof); if (err != evmone::EOFValidationError::success) { std::cout << "err: " << evmone::get_error_message(err) << "\n"; diff --git a/test/eofparsefuzz/eofparsefuzz.cpp b/test/eofparsefuzz/eofparsefuzz.cpp index 95477912c7..139200e9ce 100644 --- a/test/eofparsefuzz/eofparsefuzz.cpp +++ b/test/eofparsefuzz/eofparsefuzz.cpp @@ -7,7 +7,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) noexcept { const evmone::bytes_view eof{data, data_size}; - if (evmone::validate_eof(EVMC_PRAGUE, eof) == evmone::EOFValidationError::success) + if (evmone::validate_eof(EVMC_PRAGUE, evmone::ContainerKind::runtime, eof) == + evmone::EOFValidationError::success) (void)evmone::read_valid_eof1_header(eof); return 0; } diff --git a/test/eoftest/eoftest_runner.cpp b/test/eoftest/eoftest_runner.cpp index 9d9b092938..dab423402c 100644 --- a/test/eoftest/eoftest_runner.cpp +++ b/test/eoftest/eoftest_runner.cpp @@ -67,7 +67,9 @@ void run_eof_test(std::istream& input) { for (const auto& expectation : cases.expectations) { - const auto result = evmone::validate_eof(expectation.rev, cases.code); + // TODO read requested container kind from the test + const auto result = + evmone::validate_eof(expectation.rev, ContainerKind::runtime, cases.code); const bool b_result = (result == EOFValidationError::success); EXPECT_EQ(b_result, expectation.result) << name << " " << expectation.rev << " " << hex(cases.code); diff --git a/test/state/host.cpp b/test/state/host.cpp index 4b6ed3f856..a15433be39 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -270,7 +270,7 @@ std::optional Host::prepare_message(evmc_message msg) noexcept msg.input_data = msg.input_data + container_size; msg.input_size = msg.input_size - container_size; - if (validate_eof(m_rev, {msg.code, msg.code_size}) != + if (validate_eof(m_rev, ContainerKind::initcode, {msg.code, msg.code_size}) != EOFValidationError::success) return {}; // Light early exception. @@ -367,7 +367,8 @@ evmc::Result Host::create(const evmc_message& msg) noexcept // It must be valid EOF, which was validated before execution. if (msg.kind != EVMC_EOFCREATE) return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; - assert(validate_eof(m_rev, code) == EOFValidationError::success); + assert( + validate_eof(m_rev, ContainerKind::runtime, code) == EOFValidationError::success); } else if (m_rev >= EVMC_LONDON) { diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index e81f7e69a6..51663f4012 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -470,7 +470,7 @@ void validate_state(const TestState& state, evmc_revision rev) { if (rev >= EVMC_PRAGUE) { - if (const auto result = validate_eof(rev, acc.code); + if (const auto result = validate_eof(rev, ContainerKind::runtime, acc.code); result != EOFValidationError::success) { throw std::invalid_argument( diff --git a/test/unittests/eof_test.cpp b/test/unittests/eof_test.cpp index e31bbd59d8..4043e385bb 100644 --- a/test/unittests/eof_test.cpp +++ b/test/unittests/eof_test.cpp @@ -84,7 +84,9 @@ TEST(eof, read_valid_eof1_header) for (const auto& test_case : test_cases) { const auto code = from_spaced_hex(test_case.code).value(); - EXPECT_EQ(validate_eof(EVMC_PRAGUE, code), EOFValidationError::success) << test_case.code; + EXPECT_EQ( + validate_eof(EVMC_PRAGUE, ContainerKind::runtime, code), EOFValidationError::success) + << test_case.code; const auto header = read_valid_eof1_header(code); EXPECT_EQ(header.code_sizes, test_case.code_sizes) << test_case.code; diff --git a/test/unittests/eof_validation.cpp b/test/unittests/eof_validation.cpp index f439eca86c..4176edf99c 100644 --- a/test/unittests/eof_validation.cpp +++ b/test/unittests/eof_validation.cpp @@ -105,7 +105,9 @@ void eof_validation::TearDown() for (size_t i = 0; i < test_cases.size(); ++i) { const auto& test_case = test_cases[i]; - EXPECT_EQ(evmone::validate_eof(rev, test_case.container), test_case.error) + // TODO make kind configurable + EXPECT_EQ( + evmone::validate_eof(rev, ContainerKind::runtime, test_case.container), test_case.error) << "test case " << i << " " << test_case.name << "\n" << hex(test_case.container); } diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index ea3f879708..4600ea52d2 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -14,7 +14,8 @@ using namespace evmone::test; TEST_F(eof_validation, before_activation) { - ASSERT_EQ(evmone::validate_eof(EVMC_CANCUN, bytes(eof_bytecode(OP_STOP))), + ASSERT_EQ( + evmone::validate_eof(EVMC_CANCUN, ContainerKind::runtime, bytes(eof_bytecode(OP_STOP))), EOFValidationError::eof_version_unknown); } diff --git a/test/unittests/evm_eof_function_test.cpp b/test/unittests/evm_eof_function_test.cpp index 95e3f371f8..be59930acb 100644 --- a/test/unittests/evm_eof_function_test.cpp +++ b/test/unittests/evm_eof_function_test.cpp @@ -249,7 +249,8 @@ TEST_P(evm, jumpf_with_inputs_stack_overflow) .code(push0() + OP_JUMPF + "0002", 3, 0x80, 4) .code(push0() + OP_STOP, 3, 0x80, 4); - ASSERT_EQ(evmone::validate_eof(rev, code), evmone::EOFValidationError::success); + ASSERT_EQ(evmone::validate_eof(rev, evmone::ContainerKind::runtime, code), + evmone::EOFValidationError::success); execute(code); EXPECT_STATUS(EVMC_STACK_OVERFLOW); } diff --git a/test/unittests/evm_eof_test.cpp b/test/unittests/evm_eof_test.cpp index 55f06b1731..4b6105b1d4 100644 --- a/test/unittests/evm_eof_test.cpp +++ b/test/unittests/evm_eof_test.cpp @@ -284,15 +284,6 @@ TEST_P(evm, eof_eofcreate) ASSERT_EQ(result.output_size, 32); EXPECT_EQ(output, "000000000000000000000000cc010203040506070809010203040506070809ce"_hex); - - // test executing initcontainer - msg.kind = EVMC_EOFCREATE; - execute(init_container, aux_data); - EXPECT_STATUS(EVMC_SUCCESS); - const bytecode deployed_container = - eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data); - ASSERT_EQ(result.output_size, deployed_container.size()); - EXPECT_EQ(output, deployed_container); } TEST_P(evm, eofcreate_undefined_in_legacy) diff --git a/test/unittests/evm_fixture.hpp b/test/unittests/evm_fixture.hpp index 226eed6a16..e85dddad6e 100644 --- a/test/unittests/evm_fixture.hpp +++ b/test/unittests/evm_fixture.hpp @@ -82,7 +82,7 @@ class evm : public testing::TestWithParam if (rev >= EVMC_PRAGUE && is_eof_container(code)) { - ASSERT_EQ(get_error_message(validate_eof(rev, code)), + ASSERT_EQ(get_error_message(validate_eof(rev, ContainerKind::runtime, code)), get_error_message(EOFValidationError::success)); } diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 9c0b8175bd..1d3edaa621 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -195,37 +195,6 @@ TEST_F(state_transition, eofcreate_empty_auxdata) expect.post[create_address].nonce = 1; } -TEST_F(state_transition, eofcreate_extcall_returncontract) -{ - rev = EVMC_PRAGUE; - constexpr auto callee = 0xca11ee_address; - const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); - - pre.insert( - callee, { - .code = eof_bytecode(returncontract(0, 0, 0), 2).container(deploy_container), - }); - - - const auto init_code = mstore(0, extcall(callee)) + revert(0, 32); - const bytecode init_container = eof_bytecode(init_code, 4); - - const auto factory_code = - sstore(0, eofcreate().container(0).salt(Salt)) + sstore(1, returndataload(0)) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); - - tx.to = To; - - pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; - // No new address returned from EOFCREATE. - expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; - // Internal EXTCALL returned 2 (abort). - expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; - expect.post[callee].exists = true; -} - TEST_F(state_transition, eofcreate_auxdata_equal_to_declared) { rev = EVMC_PRAGUE; @@ -447,42 +416,6 @@ TEST_F(state_transition, eofcreate_initcontainer_aborts) expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; } -TEST_F(state_transition, eofcreate_initcontainer_return) -{ - rev = EVMC_PRAGUE; - const auto init_code = bytecode{0xaa + ret_top()}; - const auto init_container = eof_bytecode(init_code, 2); - - const auto factory_code = - calldatacopy(0, 0, OP_CALLDATASIZE) + - sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt)) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); - - tx.to = To; - pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; - expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; -} - -TEST_F(state_transition, eofcreate_initcontainer_stop) -{ - rev = EVMC_PRAGUE; - const auto init_code = bytecode{Opcode{OP_STOP}}; - const auto init_container = eof_bytecode(init_code, 0); - - const auto factory_code = - calldatacopy(0, 0, OP_CALLDATASIZE) + - sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt)) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); - - tx.to = To; - pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; - expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; -} - TEST_F(state_transition, eofcreate_deploy_container_max_size) { rev = EVMC_PRAGUE; @@ -1045,7 +978,7 @@ TEST_F(state_transition, creation_tx_initcontainer_return) tx.data = init_container; expect.post[Sender].nonce = pre.get(Sender).nonce + 1; - expect.status = EVMC_UNDEFINED_INSTRUCTION; + expect.status = EVMC_FAILURE; } TEST_F(state_transition, creation_tx_initcontainer_stop) @@ -1057,7 +990,7 @@ TEST_F(state_transition, creation_tx_initcontainer_stop) tx.data = init_container; expect.post[Sender].nonce = pre.get(Sender).nonce + 1; - expect.status = EVMC_UNDEFINED_INSTRUCTION; + expect.status = EVMC_FAILURE; } TEST_F(state_transition, creation_tx_initcontainer_max_size) @@ -1291,39 +1224,6 @@ TEST_F(state_transition, txcreate_empty_auxdata) expect.post[create_address].nonce = 1; } -TEST_F(state_transition, txcreate_extcall_returncontract) -{ - rev = EVMC_OSAKA; - constexpr auto callee = 0xca11ee_address; - const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); - - pre.insert( - callee, { - .code = eof_bytecode(returncontract(0, 0, 0), 2).container(deploy_container), - }); - - const auto init_code = mstore(0, extcall(callee)) + revert(0, 32); - const bytecode init_container = eof_bytecode(init_code, 4); - - tx.type = Transaction::Type::initcodes; - tx.initcodes.push_back(init_container); - - const auto factory_code = sstore(0, txcreate().initcode(keccak256(init_container)).salt(Salt)) + - sstore(1, returndataload(0)) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 5); - - tx.to = To; - - pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; - // No new address returned from TXCREATE. - expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; - // Internal EXTCALL returned 2 (abort). - expect.post[*tx.to].storage[0x01_bytes32] = 0x02_bytes32; - expect.post[callee].exists = true; -} - TEST_F(state_transition, txcreate_auxdata_equal_to_declared) { rev = EVMC_OSAKA; @@ -1574,7 +1474,7 @@ TEST_F(state_transition, txcreate_initcontainer_return) tx.to = To; pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; } @@ -1597,7 +1497,7 @@ TEST_F(state_transition, txcreate_initcontainer_stop) tx.to = To; pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); - expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; } From b294d38d488af7661247e8ddb57830b274ab0048 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 18 Jun 2024 17:17:53 +0200 Subject: [PATCH 5/8] Fix tests that use initcode in toplevel container Make container kind configurable in validation tests. --- test/unittests/eof_validation.cpp | 4 +- test/unittests/eof_validation.hpp | 11 ++++- test/unittests/eof_validation_test.cpp | 44 ++++++------------- .../state_transition_eof_create_test.cpp | 4 +- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/test/unittests/eof_validation.cpp b/test/unittests/eof_validation.cpp index 4176edf99c..25164f9d08 100644 --- a/test/unittests/eof_validation.cpp +++ b/test/unittests/eof_validation.cpp @@ -105,9 +105,7 @@ void eof_validation::TearDown() for (size_t i = 0; i < test_cases.size(); ++i) { const auto& test_case = test_cases[i]; - // TODO make kind configurable - EXPECT_EQ( - evmone::validate_eof(rev, ContainerKind::runtime, test_case.container), test_case.error) + EXPECT_EQ(evmone::validate_eof(rev, test_case.kind, test_case.container), test_case.error) << "test case " << i << " " << test_case.name << "\n" << hex(test_case.container); } diff --git a/test/unittests/eof_validation.hpp b/test/unittests/eof_validation.hpp index a32312ec8d..d467a46ea5 100644 --- a/test/unittests/eof_validation.hpp +++ b/test/unittests/eof_validation.hpp @@ -25,6 +25,8 @@ class eof_validation : public ExportableFixture { /// Container to be validated. bytes container; + /// Expected container kind + ContainerKind kind = ContainerKind::runtime; /// Expected error if container is expected to be invalid, /// or EOFValidationError::success if it is expected to be valid. EOFValidationError error = EOFValidationError::success; @@ -42,7 +44,14 @@ class eof_validation : public ExportableFixture /// or add_test_case(bytes_view cont, error, name). void add_test_case(bytecode container, EOFValidationError error, std::string name = {}) { - test_cases.push_back({std::move(container), error, std::move(name)}); + test_cases.push_back( + {std::move(container), ContainerKind::runtime, error, std::move(name)}); + } + + void add_test_case( + bytecode container, ContainerKind kind, EOFValidationError error, std::string name = {}) + { + test_cases.push_back({std::move(container), kind, error, std::move(name)}); } /// The test runner. diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 4600ea52d2..09b4f7b763 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -189,10 +189,10 @@ TEST_F(eof_validation, EOF1_truncated_section) // Data section may be truncated in runtime subcontainer add_test_case( eof_bytecode(returncontract(0, 0, 2), 2).container(eof_bytecode(OP_INVALID).data("", 2)), - EOFValidationError::success); + ContainerKind::initcode, EOFValidationError::success); add_test_case( eof_bytecode(returncontract(0, 0, 1), 2).container(eof_bytecode(OP_INVALID).data("aa", 2)), - EOFValidationError::success); + ContainerKind::initcode, EOFValidationError::success); // Data section may not be truncated in toplevel container add_test_case( @@ -517,7 +517,7 @@ TEST_F(eof_validation, EOF1_rjump_invalid_destination) // To RETURNCONTRACT immediate add_test_case( eof_bytecode(rjump(5) + 0 + 0 + OP_RETURNCONTRACT + Opcode{0}, 2).container(embedded), - EOFValidationError::invalid_rjump_destination); + ContainerKind::initcode, EOFValidationError::invalid_rjump_destination); } TEST_F(eof_validation, EOF1_rjumpi_invalid_destination) @@ -557,7 +557,7 @@ TEST_F(eof_validation, EOF1_rjumpi_invalid_destination) // To RETURNCONTRACT immediate add_test_case( eof_bytecode(rjumpi(5, 0) + 0 + 0 + OP_RETURNCONTRACT + Opcode{0}, 2).container(embedded), - EOFValidationError::invalid_rjump_destination); + ContainerKind::initcode, EOFValidationError::invalid_rjump_destination); } TEST_F(eof_validation, EOF1_rjumpv_invalid_destination) @@ -614,7 +614,7 @@ TEST_F(eof_validation, EOF1_rjumpv_invalid_destination) // To RETURNCONTRACT immediate add_test_case( eof_bytecode(rjumpv({5}, 0) + 0 + 0 + OP_RETURNCONTRACT + Opcode{0}, 2).container(embedded), - EOFValidationError::invalid_rjump_destination); + ContainerKind::initcode, EOFValidationError::invalid_rjump_destination); } TEST_F(eof_validation, EOF1_section_order) @@ -1031,7 +1031,7 @@ TEST_F(eof_validation, EOF1_embedded_container) // data section is allowed to be truncated in runtime subcontainer add_test_case( eof_bytecode(returncontract(0, 0, 2), 2).container(eof_bytecode(OP_INVALID).data("", 2)), - EOFValidationError::success); + ContainerKind::initcode, EOFValidationError::success); // with data section add_test_case( @@ -1152,19 +1152,19 @@ TEST_F(eof_validation, EOF1_returncontract_valid) const auto embedded = eof_bytecode(bytecode{OP_INVALID}); add_test_case( eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0}, 2).container(embedded), - EOFValidationError::success); + ContainerKind::initcode, EOFValidationError::success); // initcontainer_index = 1 add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{1}, 2) .container(embedded) .container(embedded), - EOFValidationError::success); + ContainerKind::initcode, EOFValidationError::success); // initcontainer_index = 255 auto cont = eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{255}, 2); for (auto i = 0; i < 256; ++i) cont.container(embedded); - add_test_case(cont, EOFValidationError::success); + add_test_case(cont, ContainerKind::initcode, EOFValidationError::success); } TEST_F(eof_validation, EOF1_returncontract_invalid) @@ -1172,38 +1172,20 @@ TEST_F(eof_validation, EOF1_returncontract_invalid) // truncated immediate const auto embedded = eof_bytecode(bytecode{OP_INVALID}); add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT, 4).container(embedded), - EOFValidationError::truncated_instruction); + ContainerKind::initcode, EOFValidationError::truncated_instruction); // referring to non-existent container section add_test_case( eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{1}, 4).container(embedded), - EOFValidationError::invalid_container_section_index); + ContainerKind::initcode, EOFValidationError::invalid_container_section_index); add_test_case( eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0xff}, 4).container(embedded), - EOFValidationError::invalid_container_section_index); + ContainerKind::initcode, EOFValidationError::invalid_container_section_index); // Unreachable code after RETURNCONTRACT add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0} + revert(0, 0), 2) .container(embedded), - EOFValidationError::unreachable_instructions); -} - -TEST_F(eof_validation, EOF1_eofcreate_returncontract_return_mix) -{ - // This test ensures that we _do not_ have a validation rule preventing EOFCREATE, - // RETURNCONTRACT and RETURN mixing. - const auto embedded = eof_bytecode(bytecode{OP_INVALID}); - - // This contains both RETURNCONTRACT and RETURN, as well as references same - // container from both EOFCREATE and RETURNCONTRACT - const auto mixing_initcode = - eofcreate().container(0) + rjumpi(6, 1) + returncontract(0, 0, 0) + ret_top(); - - const auto mixing_initcontainer = eof_bytecode(mixing_initcode, 4).container(embedded); - - // This top level container mixes all combinations of EOFCREATE/RETURNCONTRACT/RETURN. - add_test_case(eof_bytecode(mixing_initcode, 4).container(mixing_initcontainer), - EOFValidationError::ambiguous_container_kind); + ContainerKind::initcode, EOFValidationError::unreachable_instructions); } TEST_F(eof_validation, EOF1_unreferenced_subcontainer_valid) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 1d3edaa621..20e4e649f9 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -1989,7 +1989,7 @@ TEST_F(state_transition, txcreate_failure_after_txcreate_success) sstore(0, txcreate().initcode(keccak256(init_container)).salt(Salt)) + sstore(1, txcreate().initcode(keccak256(init_container)).salt(Salt)) + // address collision sstore(2, returndatasize()) + sstore(3, 1) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 5).container(init_container); + const auto factory_container = eof_bytecode(factory_code, 5); tx.to = To; @@ -2229,7 +2229,7 @@ TEST_F(state_transition, txcreate_call_created_contract) OP_POP + sstore(2, returndataload(0)) + mstore8(31, 2) + extcall(create_address).input(0, 32) + // calldata 2 OP_POP + sstore(3, returndataload(0)) + sstore(4, 1) + OP_STOP; - const auto factory_container = eof_bytecode(factory_code, 5).container(init_container); + const auto factory_container = eof_bytecode(factory_code, 5); tx.to = To; From 96700b5746fc9e55be28d045dd3469015fb282f9 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 4 Jun 2024 13:05:17 +0200 Subject: [PATCH 6/8] Container kind validation tests --- test/unittests/eof_validation_test.cpp | 236 +++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 09b4f7b763..fa0f77c29c 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -1220,3 +1220,239 @@ TEST_F(eof_validation, max_nested_containers) } add_test_case(code, EOFValidationError::success); } + +// Summary of validity of combinations of referencing instructions with instructions inside +// referenced containers. +// Rows are instructions referencing subcontainers or rules for top-level container. +// Columns are instructions inside referenced subcontainer. +// +// | | STOP | RETURN | REVERT | RETURNCONTRACT | +// | ---------------------------- | ------ | ------ | ------ | -------------- | +// | top-level initcode | - | - | + | + | +// | EOFCREATE | - | - | + | + | +// | TXCREATE | - | - | + | + | +// | top-level runtime | + | + | + | - | +// | RETURNCONTRACT | + | + | + | - | +// | EOFCREATE and RETURNCONTRACT | - | - | + | - | + +TEST_F(eof_validation, initcode_container_stop) +{ + const auto initcode = bytecode{OP_STOP}; + const auto initcontainer = eof_bytecode(initcode, 0); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_container_return) +{ + const auto initcode = ret(0, 0); + const bytecode initcontainer = eof_bytecode(initcode, 2); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_container_revert) +{ + const auto initcode = revert(0, 0); + const auto initcontainer = eof_bytecode(initcode, 2); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, initcode_container_returncontract) +{ + const auto initcode = returncontract(0, 0, 0); + const auto initcontainer = eof_bytecode(initcode, 2).container(eof_bytecode(OP_INVALID)); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, runtime_container_stop) +{ + const auto runtime_container = eof_bytecode(OP_STOP); + + add_test_case(runtime_container, ContainerKind::runtime, EOFValidationError::success); + + const auto initcontainer = + eof_bytecode(returncontract(0, 0, 0), 2).container(runtime_container); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, runtime_container_return) +{ + const auto runtime_container = eof_bytecode(ret(0, 0), 2); + + add_test_case(runtime_container, ContainerKind::runtime, EOFValidationError::success); + + const auto initcontainer = + eof_bytecode(returncontract(0, 0, 0), 2).container(runtime_container); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, runtime_container_revert) +{ + const auto runtime_container = eof_bytecode(revert(0, 0), 2); + + add_test_case(runtime_container, ContainerKind::runtime, EOFValidationError::success); + + const auto initcontainer = + eof_bytecode(returncontract(0, 0, 0), 2).container(runtime_container); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, runtime_container_returncontract) +{ + const auto runtime_container = + eof_bytecode(returncontract(0, 0, 0), 2).container(eof_bytecode(OP_INVALID)); + + add_test_case( + runtime_container, ContainerKind::runtime, EOFValidationError::incompatible_container_kind); + + const auto initcontainer = + eof_bytecode(returncontract(0, 0, 0), 2).container(runtime_container); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_runtime_container_stop) +{ + const auto runtime_container = eof_bytecode(OP_STOP); + + add_test_case(runtime_container, ContainerKind::initcode_runtime, + EOFValidationError::incompatible_container_kind); + + const auto initcontainer = + eof_bytecode(eofcreate() + returncontract(0, 0, 0), 4).container(runtime_container); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_runtime_container_return) +{ + const auto runtime_container = eof_bytecode(ret(0, 0), 2); + + add_test_case(runtime_container, ContainerKind::initcode_runtime, + EOFValidationError::incompatible_container_kind); + + const auto initcontainer = + eof_bytecode(eofcreate() + returncontract(0, 0, 0), 4).container(runtime_container); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_runtime_container_revert) +{ + const auto runtime_container = + eof_bytecode(returncontract(0, 0, 0), 2).container(eof_bytecode(OP_INVALID)); + + add_test_case(runtime_container, ContainerKind::initcode_runtime, + EOFValidationError::incompatible_container_kind); + + const auto initcontainer = + eof_bytecode(eofcreate() + returncontract(0, 0, 0), 4).container(runtime_container); + + add_test_case( + initcontainer, ContainerKind::initcode, EOFValidationError::incompatible_container_kind); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, initcode_runtime_container_returncontract) +{ + const auto runtime_container = eof_bytecode(revert(0, 0), 2); + + add_test_case(runtime_container, ContainerKind::initcode_runtime, EOFValidationError::success); + + const auto initcontainer = + eof_bytecode(eofcreate() + returncontract(0, 0, 0), 4).container(runtime_container); + + add_test_case(initcontainer, ContainerKind::initcode, EOFValidationError::success); + + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::success); +} + +TEST_F(eof_validation, eofcreate_stop_and_returncontract) +{ + const auto runtime_container = eof_bytecode(OP_INVALID); + const auto initcode = rjumpi(1, 0) + OP_STOP + returncontract(0, 0, 0); + const auto initcontainer = eof_bytecode(initcode, 2).container(runtime_container); + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} + +TEST_F(eof_validation, eofcreate_return_and_returncontract) +{ + const auto runtime_container = eof_bytecode(OP_INVALID); + const auto initcode = rjumpi(5, 0) + ret(0, 0) + returncontract(0, 0, 0); + const auto initcontainer = eof_bytecode(initcode, 2).container(runtime_container); + const auto factory_code = eofcreate() + OP_STOP; + const bytecode factory_container = eof_bytecode(factory_code, 4).container(initcontainer); + + add_test_case(factory_container, EOFValidationError::incompatible_container_kind); +} From 843878f018bb7ac75f21328b549873e2b849a5ee Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 3 Jun 2024 15:17:31 +0200 Subject: [PATCH 7/8] Support container kind field in json EOF validation tests --- test/eoftest/eoftest_runner.cpp | 24 +++++++++++++++++++----- test/unittests/eof_validation.cpp | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/test/eoftest/eoftest_runner.cpp b/test/eoftest/eoftest_runner.cpp index dab423402c..56e202c298 100644 --- a/test/eoftest/eoftest_runner.cpp +++ b/test/eoftest/eoftest_runner.cpp @@ -22,16 +22,29 @@ struct EOFValidationTest { struct Expectation { - evmc_revision rev; - bool result; + evmc_revision rev = EVMC_PRAGUE; + bool result = false; }; std::string name; evmc::bytes code; + ContainerKind kind = ContainerKind::runtime; std::vector expectations; }; std::unordered_map cases; }; +ContainerKind container_kind_from_string(std::string_view s) +{ + if (s == "runtime") + return ContainerKind::runtime; + else if (s == "initcode") + return ContainerKind::initcode; + else if (s == "initcode_runtime") + return ContainerKind::initcode_runtime; + else + throw std::invalid_argument{"unknown container kind"}; +} + void from_json(const json::json& j, EOFValidationTest::Case& o) { const auto op_code = evmc::from_hex(j.at("code").get()); @@ -39,6 +52,9 @@ void from_json(const json::json& j, EOFValidationTest::Case& o) throw std::invalid_argument{"code is invalid hex string"}; o.code = *op_code; + if (const auto it_kind = j.find("kind"); it_kind != j.end()) + o.kind = container_kind_from_string(it_kind->get()); + for (const auto& [rev, result] : j.at("results").items()) { o.expectations.push_back({to_rev(rev), result.at("result").get()}); @@ -67,9 +83,7 @@ void run_eof_test(std::istream& input) { for (const auto& expectation : cases.expectations) { - // TODO read requested container kind from the test - const auto result = - evmone::validate_eof(expectation.rev, ContainerKind::runtime, cases.code); + const auto result = evmone::validate_eof(expectation.rev, cases.kind, cases.code); const bool b_result = (result == EOFValidationError::success); EXPECT_EQ(b_result, expectation.result) << name << " " << expectation.rev << " " << hex(cases.code); diff --git a/test/unittests/eof_validation.cpp b/test/unittests/eof_validation.cpp index 25164f9d08..f8d0583c21 100644 --- a/test/unittests/eof_validation.cpp +++ b/test/unittests/eof_validation.cpp @@ -98,6 +98,21 @@ std::string_view get_tests_error_message(EOFValidationError err) noexcept } return ""; } + +std::string_view to_string(ContainerKind container_kind) noexcept +{ + switch (container_kind) + { + case (ContainerKind::runtime): + return "runtime"; + case (ContainerKind::initcode): + return "initcode"; + case (ContainerKind::initcode_runtime): + return "initcode_runtime"; + } + return ""; +} + } // namespace void eof_validation::TearDown() @@ -129,6 +144,7 @@ void eof_validation::export_eof_validation_test() auto& jcase = jvectors[case_name]; jcase["code"] = hex0x(test_case.container); + jcase["kind"] = to_string(test_case.kind); auto& jresults = jcase["results"][evmc::to_string(rev)]; if (test_case.error == EOFValidationError::success) From 37ed90256c5f52f09ede81bb05a8186e51ca3ea7 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 19 Jun 2024 12:52:56 +0200 Subject: [PATCH 8/8] Update tests --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index e87fbd17a0..a0df698d4c 100644 --- a/circle.yml +++ b/circle.yml @@ -457,7 +457,7 @@ jobs: ~/tests/EIPTests/BlockchainTests/ - download_execution_tests: repo: ipsilon/tests - rev: eof-toplevel + rev: eof-initcode-tests legacy: false - run: name: "State tests (EOF)"