From 06241066ccacbadca6e6e77a57f4332ce854f0e6 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 16 May 2024 11:40:27 +0200 Subject: [PATCH 1/5] Legacy EOF create (squashed) --- lib/evmone/eof.cpp | 288 +++++++------- lib/evmone/eof.hpp | 17 +- test/state/errors.hpp | 3 - test/state/host.cpp | 45 ++- test/state/state.cpp | 22 +- test/unittests/eof_validation_test.cpp | 14 +- .../state_transition_eof_create_test.cpp | 362 +++++++++++++++++- test/unittests/state_tx_test.cpp | 10 +- 8 files changed, 574 insertions(+), 187 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index ec086f4f4c..61b5d80bcf 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -18,7 +17,6 @@ #include #include #include -#include #include namespace evmone @@ -26,12 +24,6 @@ namespace evmone namespace { constexpr uint8_t MAGIC[] = {0xef, 0x00}; -constexpr uint8_t TERMINATOR = 0x00; -constexpr uint8_t TYPE_SECTION = 0x01; -constexpr uint8_t CODE_SECTION = 0x02; -constexpr uint8_t CONTAINER_SECTION = 0x03; -constexpr uint8_t DATA_SECTION = 0x04; -constexpr uint8_t MAX_SECTION = DATA_SECTION; constexpr auto CODE_SECTION_NUMBER_LIMIT = 1024; constexpr auto CONTAINER_SECTION_NUMBER_LIMIT = 256; constexpr auto MAX_STACK_HEIGHT = 0x03FF; @@ -40,8 +32,6 @@ constexpr auto REL_OFFSET_SIZE = sizeof(int16_t); constexpr auto STACK_SIZE_LIMIT = 1024; constexpr uint8_t NON_RETURNING_FUNCTION = 0x80; -using EOFSectionHeaders = std::array, MAX_SECTION + 1>; - size_t eof_header_size(const EOFSectionHeaders& headers) noexcept { const auto non_code_section_count = 2; // type section and data section @@ -80,143 +70,6 @@ EOFValidationError get_section_missing_error(uint8_t section_id) noexcept } } -std::variant validate_section_headers(bytes_view container) -{ - enum class State - { - section_id, - section_size, - terminated - }; - - auto state = State::section_id; - uint8_t section_id = 0; - uint16_t section_num = 0; - EOFSectionHeaders section_headers{}; - const auto container_end = container.end(); - auto it = container.begin() + std::size(MAGIC) + 1; // MAGIC + VERSION - uint8_t expected_section_id = TYPE_SECTION; - while (it != container_end && state != State::terminated) - { - switch (state) - { - case State::section_id: - { - section_id = *it++; - - // If DATA_SECTION is expected, CONTAINER_SECTION is also allowed, because - // container section is optional. - if (section_id != expected_section_id && - (expected_section_id != DATA_SECTION || section_id != CONTAINER_SECTION)) - return get_section_missing_error(expected_section_id); - - switch (section_id) - { - case TERMINATOR: - state = State::terminated; - break; - case TYPE_SECTION: - expected_section_id = CODE_SECTION; - state = State::section_size; - break; - case CODE_SECTION: - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_number; - section_num = read_uint16_be(it); - it += 2; - if (section_num == 0) - return EOFValidationError::zero_section_size; - if (section_num > CODE_SECTION_NUMBER_LIMIT) - return EOFValidationError::too_many_code_sections; - expected_section_id = DATA_SECTION; - state = State::section_size; - break; - } - case DATA_SECTION: - expected_section_id = TERMINATOR; - state = State::section_size; - break; - case CONTAINER_SECTION: - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_number; - section_num = read_uint16_be(it); - it += 2; - if (section_num == 0) - return EOFValidationError::zero_section_size; - if (section_num > CONTAINER_SECTION_NUMBER_LIMIT) - return EOFValidationError::too_many_container_sections; - expected_section_id = DATA_SECTION; - state = State::section_size; - break; - } - default: - assert(false); - } - break; - } - case State::section_size: - { - if (section_id == CODE_SECTION || section_id == CONTAINER_SECTION) - { - assert(section_num > 0); // Guaranteed by previous validation step. - for (size_t i = 0; i < section_num; ++i) - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_size; - const auto section_size = read_uint16_be(it); - it += 2; - if (section_size == 0) - return EOFValidationError::zero_section_size; - - section_headers[section_id].emplace_back(section_size); - } - } - else // TYPES_SECTION or DATA_SECTION - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_size; - const auto section_size = read_uint16_be(it); - it += 2; - if (section_size == 0 && section_id != DATA_SECTION) - return EOFValidationError::zero_section_size; - - section_headers[section_id].emplace_back(section_size); - } - - state = State::section_id; - break; - } - case State::terminated: - return EOFValidationError::impossible; - } - } - - if (state != State::terminated) - return EOFValidationError::section_headers_not_terminated; - - const auto section_bodies_without_data = - section_headers[TYPE_SECTION].front() + - std::accumulate( - section_headers[CODE_SECTION].begin(), section_headers[CODE_SECTION].end(), 0) + - std::accumulate(section_headers[CONTAINER_SECTION].begin(), - section_headers[CONTAINER_SECTION].end(), 0); - const auto remaining_container_size = container_end - it; - // Only data section may be truncated, so remaining_container size must be in - // [declared_size_without_data, declared_size_without_data + declared_data_size] - if (remaining_container_size < section_bodies_without_data) - return EOFValidationError::invalid_section_bodies_size; - if (remaining_container_size > - section_bodies_without_data + section_headers[DATA_SECTION].front()) - return EOFValidationError::invalid_section_bodies_size; - - if (section_headers[TYPE_SECTION][0] != section_headers[CODE_SECTION].size() * 4) - return EOFValidationError::invalid_type_section_size; - - return section_headers; -} - std::variant, EOFValidationError> validate_types( bytes_view container, size_t header_size, uint16_t type_section_size) noexcept { @@ -298,6 +151,9 @@ std::variant validate_header( } const auto data_offset = static_cast(offset); + if (container.size() > offset + data_size) + return EOFValidationError::invalid_section_bodies_size; + return EOF1Header{ .version = container[2], .code_sizes = code_sizes, @@ -751,6 +607,141 @@ bool is_eof_container(bytes_view container) noexcept return container.size() > 1 && container[0] == MAGIC[0] && container[1] == MAGIC[1]; } +std::variant validate_section_headers( + bytes_view container) noexcept +{ + enum class State + { + section_id, + section_size, + terminated + }; + + auto state = State::section_id; + uint8_t section_id = 0; + uint16_t section_num = 0; + EOFSectionHeaders section_headers{}; + const auto container_end = container.end(); + auto it = container.begin() + std::size(MAGIC) + 1; // MAGIC + VERSION + uint8_t expected_section_id = TYPE_SECTION; + while (it != container_end && state != State::terminated) + { + switch (state) + { + case State::section_id: + { + section_id = *it++; + + // If DATA_SECTION is expected, CONTAINER_SECTION is also allowed, because + // container section is optional. + if (section_id != expected_section_id && + (expected_section_id != DATA_SECTION || section_id != CONTAINER_SECTION)) + return get_section_missing_error(expected_section_id); + + switch (section_id) + { + case TERMINATOR: + state = State::terminated; + break; + case TYPE_SECTION: + expected_section_id = CODE_SECTION; + state = State::section_size; + break; + case CODE_SECTION: + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_number; + section_num = read_uint16_be(it); + it += 2; + if (section_num == 0) + return EOFValidationError::zero_section_size; + if (section_num > CODE_SECTION_NUMBER_LIMIT) + return EOFValidationError::too_many_code_sections; + expected_section_id = DATA_SECTION; + state = State::section_size; + break; + } + case DATA_SECTION: + expected_section_id = TERMINATOR; + state = State::section_size; + break; + case CONTAINER_SECTION: + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_number; + section_num = read_uint16_be(it); + it += 2; + if (section_num == 0) + return EOFValidationError::zero_section_size; + if (section_num > CONTAINER_SECTION_NUMBER_LIMIT) + return EOFValidationError::too_many_container_sections; + expected_section_id = DATA_SECTION; + state = State::section_size; + break; + } + default: + assert(false); + } + break; + } + case State::section_size: + { + if (section_id == CODE_SECTION || section_id == CONTAINER_SECTION) + { + assert(section_num > 0); // Guaranteed by previous validation step. + for (size_t i = 0; i < section_num; ++i) + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_size; + const auto section_size = read_uint16_be(it); + it += 2; + if (section_size == 0) + return EOFValidationError::zero_section_size; + + section_headers[section_id].emplace_back(section_size); + } + } + else // TYPES_SECTION or DATA_SECTION + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_size; + const auto section_size = read_uint16_be(it); + it += 2; + if (section_size == 0 && section_id != DATA_SECTION) + return EOFValidationError::zero_section_size; + + section_headers[section_id].emplace_back(section_size); + } + + state = State::section_id; + break; + } + case State::terminated: + return EOFValidationError::impossible; + } + } + + if (state != State::terminated) + return EOFValidationError::section_headers_not_terminated; + + const auto section_bodies_without_data = + section_headers[TYPE_SECTION].front() + + std::accumulate( + section_headers[CODE_SECTION].begin(), section_headers[CODE_SECTION].end(), 0) + + std::accumulate(section_headers[CONTAINER_SECTION].begin(), + section_headers[CONTAINER_SECTION].end(), 0); + const auto remaining_container_size = container_end - it; + // Only data section may be truncated, so remaining_container size must be at least + // declared_size_without_data + if (remaining_container_size < section_bodies_without_data) + return EOFValidationError::invalid_section_bodies_size; + + if (section_headers[TYPE_SECTION][0] != section_headers[CODE_SECTION].size() * 4) + return EOFValidationError::invalid_type_section_size; + + return section_headers; +} + /// This function expects the prefix and version to be valid, as it ignores it. EOF1Header read_valid_eof1_header(bytes_view container) { @@ -818,6 +809,9 @@ bool append_data_section(bytes& container, bytes_view aux_data) { const auto header = read_valid_eof1_header(container); + // Assert we don't need to trim off the bytes beyond the declared data section first. + assert(container.size() <= header.data_offset + header.data_size); + const auto new_data_size = container.size() - header.data_offset + aux_data.size(); if (new_data_size > std::numeric_limits::max()) return false; diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index ddcec16480..1579e40b16 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -5,17 +5,28 @@ #include #include +#include #include #include #include #include +#include #include namespace evmone { +constexpr uint8_t TERMINATOR = 0x00; +constexpr uint8_t TYPE_SECTION = 0x01; +constexpr uint8_t CODE_SECTION = 0x02; +constexpr uint8_t CONTAINER_SECTION = 0x03; +constexpr uint8_t DATA_SECTION = 0x04; +constexpr uint8_t MAX_SECTION = DATA_SECTION; + using bytes = std::basic_string; using bytes_view = std::basic_string_view; +using EOFSectionHeaders = std::array, MAX_SECTION + 1>; + struct EOFCodeType { uint8_t inputs; ///< Number of code inputs. @@ -70,7 +81,7 @@ struct EOF1Header [[nodiscard]] bool can_init(size_t container_size) const noexcept { // Containers with truncated data section cannot be initcontainers. - const auto truncated_data = static_cast(data_offset + data_size) != container_size; + const auto truncated_data = static_cast(data_offset + data_size) > container_size; return !truncated_data; } @@ -144,6 +155,10 @@ enum class EOFValidationError /// If the prefix is missing or invalid, 0 is returned meaning legacy code. [[nodiscard]] uint8_t get_eof_version(bytes_view container) noexcept; +/// Validates only the section headers and returns their contents if successful. +[[nodiscard]] EVMC_EXPORT std::variant +validate_section_headers(bytes_view container) noexcept; + /// 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; diff --git a/test/state/errors.hpp b/test/state/errors.hpp index b93e5ba493..f42a0bc61e 100644 --- a/test/state/errors.hpp +++ b/test/state/errors.hpp @@ -30,7 +30,6 @@ enum ErrorCode : int EMPTY_BLOB_HASHES_LIST, INVALID_BLOB_HASH_VERSION, BLOB_GAS_LIMIT_EXCEEDED, - EOF_CREATION_TRANSACTION, UNKNOWN_ERROR, }; @@ -83,8 +82,6 @@ inline const std::error_category& evmone_category() noexcept return "invalid blob hash version"; case BLOB_GAS_LIMIT_EXCEEDED: return "blob gas limit exceeded"; - case EOF_CREATION_TRANSACTION: - return "EOF initcode in creation transaction"; case UNKNOWN_ERROR: return "Unknown error"; default: diff --git a/test/state/host.cpp b/test/state/host.cpp index b7aad698d7..a7c483d88c 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -244,9 +244,46 @@ std::optional Host::prepare_message(evmc_message msg) else { assert(msg.kind == EVMC_EOFCREATE); - const bytes_view initcontainer{msg.code, msg.code_size}; - msg.recipient = - compute_eofcreate_address(msg.sender, msg.create2_salt, initcontainer); + const bytes_view input = {msg.input_data, msg.input_size}; + + // Indicator of an EOF creation tx - EVMC_EOFCREATE at depth 0. + if (msg.depth == 0) + { + // Assert this is a legacy EOF creation tx. + assert(is_eof_container(input)); + + const auto section_headers_or_error = validate_section_headers(input); + if (holds_alternative(section_headers_or_error)) + return {}; // Light early exception. + + const auto header = read_valid_eof1_header(input); + + if (!header.can_init(msg.input_size)) + return {}; // Light early exception. + + const auto container_size = + static_cast(header.data_offset + header.data_size); + // Follows from the header.can_init condition above. + assert(container_size <= msg.input_size); + + msg.code = msg.input_data; + msg.code_size = container_size; + 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}) != + EOFValidationError::success) + return {}; // Light early exception. + + msg.recipient = compute_create_address(msg.sender, sender_nonce); + } + // EOFCREATE or TXCREATE + else + { + const bytes_view initcontainer{msg.code, msg.code_size}; + msg.recipient = + compute_eofcreate_address(msg.sender, msg.create2_salt, initcontainer); + } } // By EIP-2929, the access to new created address is never reverted. @@ -327,7 +364,7 @@ evmc::Result Host::create(const evmc_message& msg) noexcept { if (m_rev >= EVMC_PRAGUE) { - // Only EOFCREATE/TXCREATE is allowed to deploy code starting with EF. + // Only EOFCREATE/TXCREATE/EOF-creation-tx is allowed to deploy code starting with EF. // It must be valid EOF, which was validated before execution. if (msg.kind != EVMC_EOFCREATE) return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; diff --git a/test/state/state.cpp b/test/state/state.cpp index a839602f20..55648fc5e4 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -55,7 +55,7 @@ int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noex static constexpr auto call_tx_cost = 21000; static constexpr auto create_tx_cost = 53000; static constexpr auto initcode_word_cost = 2; - const auto is_create = !tx.to.has_value(); + const auto is_create = !tx.to.has_value(); // Covers also EOF creation txs. const auto initcode_cost = is_create && rev >= EVMC_SHANGHAI ? initcode_word_cost * num_words(tx.data.size()) : 0; const auto tx_cost = is_create && rev >= EVMC_HOMESTEAD ? create_tx_cost : call_tx_cost; @@ -63,12 +63,19 @@ int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noex compute_initcode_list_cost(rev, tx.initcodes) + initcode_cost; } -evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) noexcept +evmc_message build_message( + const Transaction& tx, int64_t execution_gas_limit, evmc_revision rev) noexcept { const auto recipient = tx.to.has_value() ? *tx.to : evmc::address{}; - return {tx.to.has_value() ? EVMC_CALL : EVMC_CREATE, 0, 0, execution_gas_limit, recipient, - tx.sender, tx.data.data(), tx.data.size(), intx::be::store(tx.value), {}, - recipient, nullptr, 0}; + + const auto is_legacy_eof_create = + rev >= EVMC_PRAGUE && !tx.to.has_value() && is_eof_container(tx.data); + + return {is_legacy_eof_create ? EVMC_EOFCREATE : + tx.to.has_value() ? EVMC_CALL : + EVMC_CREATE, + 0, 0, execution_gas_limit, recipient, tx.sender, tx.data.data(), tx.data.size(), + intx::be::store(tx.value), {}, recipient, nullptr, 0}; } } // namespace @@ -326,9 +333,6 @@ std::variant validate_transaction(const Account& sende if (rev >= EVMC_SHANGHAI && !tx.to.has_value() && tx.data.size() > max_initcode_size) return make_error_code(INIT_CODE_SIZE_LIMIT_EXCEEDED); - if (rev >= EVMC_PRAGUE && !tx.to.has_value() && is_eof_container(tx.data)) - return make_error_code(EOF_CREATION_TRANSACTION); - // Compute and check if sender has enough balance for the theoretical maximum transaction cost. // Note this is different from tx_max_cost computed with effective gas price later. // The computation cannot overflow if done with 512-bit precision. @@ -485,7 +489,7 @@ std::variant transition(State& state, const if (rev >= EVMC_SHANGHAI) host.access_account(block.coinbase); - const auto result = host.call(build_message(tx, execution_gas_limit)); + const auto result = host.call(build_message(tx, execution_gas_limit, rev)); auto gas_used = tx.gas_limit - result.gas_left; diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 1342aae4e5..d37853cf3e 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -204,7 +204,19 @@ TEST_F(eof_validation, EOF1_code_section_offset) EXPECT_EQ(header.code_offsets[1], 28); } -TEST_F(eof_validation, EOF1_trailing_bytes) +TEST_F(eof_validation, EOF1_trailing_bytes_in_subcontainer) +{ + add_test_case( + "EF0001 010004 0200010001 0300010018 040000 00 00800000 FE EF0001 010004 0200010001 040000 " + "00 00800000 FE DEADBEEF", + EOFValidationError::invalid_section_bodies_size); + add_test_case( + "EF0001 010004 0200010001 030001001a 040000 00 00800000 FE EF0001 010004 0200010001 040002 " + "00 00800000 FE AABB DEADBEEF", + EOFValidationError::invalid_section_bodies_size); +} + +TEST_F(eof_validation, EOF1_trailing_bytes_top_level) { add_test_case("EF0001 010004 0200010001 040000 00 00800000 FE DEADBEEF", EOFValidationError::invalid_section_bodies_size); diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index eaa726e167..145525daea 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -13,17 +13,6 @@ namespace constexpr bytes32 Salt{0xff}; } -TEST_F(state_transition, create_tx_with_eof_initcode) -{ - rev = EVMC_PRAGUE; - - const bytecode init_container = eof_bytecode(ret(0, 1)); - - tx.data = init_container; - - expect.tx_error = EOF_CREATION_TRANSACTION; -} - TEST_F(state_transition, create_with_eof_initcode) { rev = EVMC_PRAGUE; @@ -69,7 +58,7 @@ TEST_F(state_transition, create2_with_eof_initcode) expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; } -TEST_F(state_transition, create_tx_deploying_eof) +TEST_F(state_transition, creation_tx_deploying_eof) { rev = EVMC_PRAGUE; @@ -880,6 +869,327 @@ TEST_F(state_transition, eofcreate_call_created_contract) expect.post[create_address].nonce = 1; } +TEST_F(state_transition, creation_tx) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_deploy_data) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_auxdata_in_calldata) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto aux_data = "aabbccddeeff"_hex; + const auto deploy_data_size = static_cast(deploy_data.size()); + const auto deploy_container = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data, deploy_data_size); + + const auto init_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + returncontract(0, 0, OP_CALLDATASIZE); + const bytecode init_container = eof_bytecode(init_code, 3).container(deploy_container); + + tx.data = init_container + bytecode(aux_data); + const auto expected_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data); + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_dataloadn_referring_to_auxdata) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = bytes(64, 0x01); + const auto aux_data = bytes(32, 0x03); + const auto deploy_data_size = static_cast(deploy_data.size() + aux_data.size()); + // DATALOADN{64} - referring to data that will be appended as aux_data + const auto deploy_code = bytecode(OP_DATALOADN) + "0040" + ret_top(); + const auto deploy_container = eof_bytecode(deploy_code, 2).data(deploy_data, deploy_data_size); + + const auto init_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + returncontract(0, 0, OP_CALLDATASIZE); + const bytecode init_container = eof_bytecode(init_code, 3).container(deploy_container); + + tx.data = init_container + bytecode(aux_data); + + const auto expected_container = eof_bytecode(deploy_code, 2).data(deploy_data + aux_data); + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_initcontainer_aborts) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{Opcode{OP_INVALID}}; + const bytecode init_container = eof_bytecode(init_code, 0); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_INVALID_INSTRUCTION; +} + +TEST_F(state_transition, creation_tx_initcontainer_return) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{0xaa + ret_top()}; + const bytecode init_container = eof_bytecode(init_code, 2); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_UNDEFINED_INSTRUCTION; +} + +TEST_F(state_transition, creation_tx_initcontainer_stop) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{Opcode{OP_STOP}}; + const bytecode init_container = eof_bytecode(init_code, 0); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_UNDEFINED_INSTRUCTION; +} + +TEST_F(state_transition, creation_tx_initcontainer_max_size) +{ + rev = EVMC_PRAGUE; + block.gas_limit = 10'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container_no_data = eof_bytecode(init_code, 2).container(deploy_container); + const auto data_size = 0xc000 - init_container_no_data.size(); + const bytecode init_container = + eof_bytecode(init_code, 2).container(deploy_container).data(bytes(data_size, 0)); + EXPECT_EQ(init_container.size(), 0xc000); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_initcontainer_too_large) +{ + rev = EVMC_PRAGUE; + block.gas_limit = 10'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container_no_data = eof_bytecode(init_code, 2).container(deploy_container); + const auto data_size = 0xc001 - init_container_no_data.size(); + const bytecode init_container = + eof_bytecode(init_code, 2).container(deploy_container).data(bytes(data_size, 0)); + EXPECT_EQ(init_container.size(), 0xc001); + + tx.data = init_container; + + expect.tx_error = INIT_CODE_SIZE_LIMIT_EXCEEDED; +} + +TEST_F(state_transition, creation_tx_deploy_container_max_size) +{ + rev = EVMC_PRAGUE; + block.gas_limit = 10'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto eof_header_size = + static_cast(bytecode{eof_bytecode(Opcode{OP_INVALID})}.size() - 1); + const auto deploy_code = (0x5fff - eof_header_size) * bytecode{Opcode{OP_JUMPDEST}} + OP_STOP; + const bytecode deploy_container = eof_bytecode(deploy_code); + EXPECT_EQ(deploy_container.size(), 0x6000); + + // no aux data + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, creation_tx_deploy_container_too_large) +{ + rev = EVMC_PRAGUE; + block.gas_limit = 10'000'000; + tx.gas_limit = block.gas_limit; + pre.get(tx.sender).balance = tx.gas_limit * tx.max_gas_price + tx.value + 1; + + const auto eof_header_size = + static_cast(bytecode{eof_bytecode(Opcode{OP_INVALID})}.size() - 1); + const auto deploy_code = (0x6000 - eof_header_size) * bytecode{Opcode{OP_JUMPDEST}} + OP_STOP; + const bytecode deploy_container = eof_bytecode(deploy_code); + EXPECT_EQ(deploy_container.size(), 0x6001); + + // no aux data + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; +} + +TEST_F(state_transition, creation_tx_nested_eofcreate) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data); + + const auto deploy_data_nested = "ffffff"_hex; + const auto deploy_container_nested = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data_nested); + + const auto init_code_nested = returncontract(0, 0, 0); + const bytecode init_container_nested = + eof_bytecode(init_code_nested, 2).container(deploy_container_nested); + + const auto init_code = sstore(0, eofcreate().container(1).salt(Salt)) + returncontract(0, 0, 0); + const bytecode init_container = + eof_bytecode(init_code, 4).container(deploy_container).container(init_container_nested); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 2; + const auto create_address_nested = + compute_eofcreate_address(create_address, Salt, init_container_nested); + expect.post[create_address].storage[0x00_bytes32] = to_bytes32(create_address_nested); + expect.post[create_address_nested].code = deploy_container_nested; + expect.post[create_address_nested].nonce = 1; +} + +TEST_F(state_transition, creation_tx_invalid_initcode_header) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + bytes init_container = eof_bytecode(init_code, 2).container(deploy_container); + + assert(init_container[3] == 0x01); + init_container[3] = 0x04; // Data section as first section in the header invalid. + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; + expect.gas_used = 53516; +} + +TEST_F(state_transition, creation_tx_invalid_initcode) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const bytes init_container = + eof_bytecode(init_code, 123).container(deploy_container); // Invalid EOF + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; + expect.gas_used = 53516; +} + +TEST_F(state_transition, creation_tx_truncated_data_initcode) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const bytes init_container = + eof_bytecode(init_code, 2).data("", 1).container(deploy_container); // Truncated data + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; + expect.gas_used = 53528; +} + +TEST_F(state_transition, creation_tx_invalid_deploycode) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID), 123); // Invalid EOF + + const auto init_code = returncontract(0, 0, 0); + const bytes init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; + expect.gas_used = 53528; +} + +TEST_F(state_transition, creation_tx_invalid_eof_version) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + bytes init_container = eof_bytecode(init_code, 2).container(deploy_container); + + assert(init_container[2] == 0x01); + init_container[2] = 0x02; + + tx.data = init_container; + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + expect.status = EVMC_FAILURE; + expect.gas_used = 53516; +} + TEST_F(state_transition, txcreate_empty_auxdata) { rev = EVMC_PRAGUE; @@ -1035,7 +1345,33 @@ TEST_F(state_transition, txcreate_auxdata_shorter_than_declared) 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, txcreate_stray_data_initcontainer) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + const auto init_code = returncontract(0, 0, 0); + const auto stray_data = "abcdef"_hex; + const bytecode init_container = + eof_bytecode(init_code, 2).container(deploy_container) + stray_data; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt) + + OP_DUP1 + push(1) + OP_SSTORE + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[tx.sender].nonce = pre.get(tx.sender).nonce + 1; + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; // CREATE caller's nonce must not be bumped + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; // CREATE must fail } TEST_F(state_transition, txcreate_dataloadn_referring_to_auxdata) diff --git a/test/unittests/state_tx_test.cpp b/test/unittests/state_tx_test.cpp index a56f2abcc1..47cad6eeee 100644 --- a/test/unittests/state_tx_test.cpp +++ b/test/unittests/state_tx_test.cpp @@ -166,7 +166,7 @@ TEST(state_tx, validate_data) .base_fee = 0x0a, .withdrawals = {}}; const Account acc{.nonce = 1, .balance = 0xe8d4a51000}; - Transaction tx{ + const Transaction tx{ .data = "EF00 01 010004 0200010001 030004 00 00000000 00 AABBCCDD"_hex, .gas_limit = 60000, .max_gas_price = bi.base_fee, @@ -183,14 +183,6 @@ TEST(state_tx, validate_data) ASSERT_FALSE(holds_alternative( validate_transaction(acc, bi, tx, EVMC_CANCUN, 60000, BlockInfo::MAX_BLOB_GAS_PER_BLOCK))); - - EXPECT_EQ(std::get(validate_transaction(acc, bi, tx, EVMC_PRAGUE, 60000, - BlockInfo::MAX_BLOB_GAS_PER_BLOCK)) - .message(), - "EOF initcode in creation transaction"); - - tx.data = "00"_hex; - ASSERT_FALSE(holds_alternative( validate_transaction(acc, bi, tx, EVMC_PRAGUE, 60000, BlockInfo::MAX_BLOB_GAS_PER_BLOCK))); } From 0cea63fe28d05fc07157df48b77bbb52b6f4fbc4 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 15 May 2024 18:23:22 +0200 Subject: [PATCH 2/5] Assert against overflow in header.data_offset (cherry picked from commit 963c7f0f97b141bdffd12311df032692022873db) --- lib/evmone/eof.cpp | 2 ++ test/unittests/eof_validation_test.cpp | 11 +++++++---- test/unittests/state_transition_eof_create_test.cpp | 12 ++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index 61b5d80bcf..05d0c48b31 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -149,6 +149,8 @@ std::variant validate_header( container_offsets.emplace_back(static_cast(offset)); offset += container_size; } + // NOTE: assertion always satisfied only as long as initcode limits apply (48K). + assert(offset <= static_cast(std::numeric_limits::max())); const auto data_offset = static_cast(offset); if (container.size() > offset + data_size) diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index d37853cf3e..b0b9843ec9 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -1217,9 +1217,12 @@ TEST_F(eof_validation, EOF1_subcontainer_containing_unreachable_code_sections) TEST_F(eof_validation, max_nested_containers) { - bytecode code = eof_bytecode(OP_INVALID); - while (code.size() <= std::numeric_limits::max()) - code = eof_bytecode(OP_INVALID).container(code); - + bytecode code{}; + bytecode nextcode = eof_bytecode(OP_INVALID); + while (nextcode.size() <= std::numeric_limits::max()) + { + code = nextcode; + nextcode = eof_bytecode(OP_INVALID).container(nextcode); + } add_test_case(code, EOFValidationError::success); } diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 145525daea..c50c943bcb 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -659,18 +659,18 @@ TEST_F(state_transition, eofcreate_not_enough_gas_for_initcode_charge) const auto init_code = returncontract(0, 0, 0); auto init_container = eof_bytecode(init_code, 2).container(deploy_container); - // add max size data - const auto init_data = - bytes(std::numeric_limits::max() - bytecode(init_container).size(), 0); - init_container.data(init_data); - EXPECT_EQ(bytecode(init_container).size(), std::numeric_limits::max()); + const uint16_t init_data_size = std::numeric_limits::max() / 2 - + static_cast(bytecode(init_container).size()); + const auto init_data = bytes(init_data_size, 0); + init_container.data(init_data, init_data_size); + EXPECT_EQ(bytecode(init_container).size(), std::numeric_limits::max() / 2); const auto factory_code = sstore(0, eofcreate().container(0).salt(Salt)) + OP_STOP; const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); tx.to = To; // tx intrinsic cost + EOFCREATE cost + initcode charge - not enough for pushes before EOFCREATE - tx.gas_limit = 21'000 + 32'000 + (std::numeric_limits::max() + 31) / 32 * 6; + tx.gas_limit = 21'000 + 32'000 + (std::numeric_limits::max() / 2 + 31) / 32 * 6; pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); From 11e70a19ba1ee38fb03a78109f01bc19d8c460bd Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 15 May 2024 18:38:16 +0200 Subject: [PATCH 3/5] Avoid expansion of eof.hpp and reparsing the header --- lib/evmone/eof.cpp | 384 ++++++++++++++++++++++---------------------- lib/evmone/eof.hpp | 16 +- test/state/host.cpp | 6 +- 3 files changed, 202 insertions(+), 204 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index 05d0c48b31..2b7b5b8a5c 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,12 @@ namespace evmone namespace { constexpr uint8_t MAGIC[] = {0xef, 0x00}; +constexpr uint8_t TERMINATOR = 0x00; +constexpr uint8_t TYPE_SECTION = 0x01; +constexpr uint8_t CODE_SECTION = 0x02; +constexpr uint8_t CONTAINER_SECTION = 0x03; +constexpr uint8_t DATA_SECTION = 0x04; +constexpr uint8_t MAX_SECTION = DATA_SECTION; constexpr auto CODE_SECTION_NUMBER_LIMIT = 1024; constexpr auto CONTAINER_SECTION_NUMBER_LIMIT = 256; constexpr auto MAX_STACK_HEIGHT = 0x03FF; @@ -32,6 +39,8 @@ constexpr auto REL_OFFSET_SIZE = sizeof(int16_t); constexpr auto STACK_SIZE_LIMIT = 1024; constexpr uint8_t NON_RETURNING_FUNCTION = 0x80; +using EOFSectionHeaders = std::array, MAX_SECTION + 1>; + size_t eof_header_size(const EOFSectionHeaders& headers) noexcept { const auto non_code_section_count = 2; // type section and data section @@ -70,6 +79,140 @@ EOFValidationError get_section_missing_error(uint8_t section_id) noexcept } } +std::variant validate_section_headers(bytes_view container) +{ + enum class State + { + section_id, + section_size, + terminated + }; + + auto state = State::section_id; + uint8_t section_id = 0; + uint16_t section_num = 0; + EOFSectionHeaders section_headers{}; + const auto container_end = container.end(); + auto it = container.begin() + std::size(MAGIC) + 1; // MAGIC + VERSION + uint8_t expected_section_id = TYPE_SECTION; + while (it != container_end && state != State::terminated) + { + switch (state) + { + case State::section_id: + { + section_id = *it++; + + // If DATA_SECTION is expected, CONTAINER_SECTION is also allowed, because + // container section is optional. + if (section_id != expected_section_id && + (expected_section_id != DATA_SECTION || section_id != CONTAINER_SECTION)) + return get_section_missing_error(expected_section_id); + + switch (section_id) + { + case TERMINATOR: + state = State::terminated; + break; + case TYPE_SECTION: + expected_section_id = CODE_SECTION; + state = State::section_size; + break; + case CODE_SECTION: + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_number; + section_num = read_uint16_be(it); + it += 2; + if (section_num == 0) + return EOFValidationError::zero_section_size; + if (section_num > CODE_SECTION_NUMBER_LIMIT) + return EOFValidationError::too_many_code_sections; + expected_section_id = DATA_SECTION; + state = State::section_size; + break; + } + case DATA_SECTION: + expected_section_id = TERMINATOR; + state = State::section_size; + break; + case CONTAINER_SECTION: + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_number; + section_num = read_uint16_be(it); + it += 2; + if (section_num == 0) + return EOFValidationError::zero_section_size; + if (section_num > CONTAINER_SECTION_NUMBER_LIMIT) + return EOFValidationError::too_many_container_sections; + expected_section_id = DATA_SECTION; + state = State::section_size; + break; + } + default: + assert(false); + } + break; + } + case State::section_size: + { + if (section_id == CODE_SECTION || section_id == CONTAINER_SECTION) + { + assert(section_num > 0); // Guaranteed by previous validation step. + for (size_t i = 0; i < section_num; ++i) + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_size; + const auto section_size = read_uint16_be(it); + it += 2; + if (section_size == 0) + return EOFValidationError::zero_section_size; + + section_headers[section_id].emplace_back(section_size); + } + } + else // TYPES_SECTION or DATA_SECTION + { + if (it >= container_end - 1) + return EOFValidationError::incomplete_section_size; + const auto section_size = read_uint16_be(it); + it += 2; + if (section_size == 0 && section_id != DATA_SECTION) + return EOFValidationError::zero_section_size; + + section_headers[section_id].emplace_back(section_size); + } + + state = State::section_id; + break; + } + case State::terminated: + return EOFValidationError::impossible; + } + } + + if (state != State::terminated) + return EOFValidationError::section_headers_not_terminated; + + const auto section_bodies_without_data = + section_headers[TYPE_SECTION].front() + + std::accumulate( + section_headers[CODE_SECTION].begin(), section_headers[CODE_SECTION].end(), 0) + + std::accumulate(section_headers[CONTAINER_SECTION].begin(), + section_headers[CONTAINER_SECTION].end(), 0); + const auto remaining_container_size = container_end - it; + // Only data section may be truncated, so remaining_container size must be at least + // declared_size_without_data + if (remaining_container_size < section_bodies_without_data) + return EOFValidationError::invalid_section_bodies_size; + + if (section_headers[TYPE_SECTION][0] != section_headers[CODE_SECTION].size() * 4) + return EOFValidationError::invalid_type_section_size; + + return section_headers; +} + std::variant, EOFValidationError> validate_types( bytes_view container, size_t header_size, uint16_t type_section_size) noexcept { @@ -103,71 +246,6 @@ std::variant, EOFValidationError> validate_types( return types; } -std::variant validate_header( - evmc_revision rev, bytes_view container) noexcept -{ - if (!is_eof_container(container)) - return EOFValidationError::invalid_prefix; - - const auto version = get_eof_version(container); - if (version != 1) - return EOFValidationError::eof_version_unknown; - - if (rev < EVMC_PRAGUE) - return EOFValidationError::eof_version_unknown; - - const auto section_headers_or_error = validate_section_headers(container); - if (const auto* error = std::get_if(§ion_headers_or_error)) - return *error; - - const auto& section_headers = std::get(section_headers_or_error); - const auto& code_sizes = section_headers[CODE_SECTION]; - const auto data_size = section_headers[DATA_SECTION][0]; - - const auto header_size = eof_header_size(section_headers); - - const auto types_or_error = - validate_types(container, header_size, section_headers[TYPE_SECTION].front()); - if (const auto* error = std::get_if(&types_or_error)) - return *error; - const auto& types = std::get>(types_or_error); - - std::vector code_offsets; - const auto type_section_size = section_headers[TYPE_SECTION][0]; - auto offset = header_size + type_section_size; - for (const auto code_size : code_sizes) - { - assert(offset <= std::numeric_limits::max()); - code_offsets.emplace_back(static_cast(offset)); - offset += code_size; - } - - const auto& container_sizes = section_headers[CONTAINER_SECTION]; - std::vector container_offsets; - for (const auto container_size : container_sizes) - { - container_offsets.emplace_back(static_cast(offset)); - offset += container_size; - } - // NOTE: assertion always satisfied only as long as initcode limits apply (48K). - assert(offset <= static_cast(std::numeric_limits::max())); - const auto data_offset = static_cast(offset); - - if (container.size() > offset + data_size) - return EOFValidationError::invalid_section_bodies_size; - - return EOF1Header{ - .version = container[2], - .code_sizes = code_sizes, - .code_offsets = code_offsets, - .data_size = data_size, - .data_offset = data_offset, - .container_sizes = container_sizes, - .container_offsets = container_offsets, - .types = types, - }; -} - /// Result of validating instructions in a code section. struct InstructionValidationResult { @@ -518,6 +596,9 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n auto& header = std::get(error_or_header); + if (container.size() > static_cast(header.data_offset) + header.data_size) + return EOFValidationError::invalid_section_bodies_size; + // Validate code sections std::vector visited_code_sections(header.code_sizes.size()); std::queue code_sections_queue({0}); @@ -609,139 +690,66 @@ bool is_eof_container(bytes_view container) noexcept return container.size() > 1 && container[0] == MAGIC[0] && container[1] == MAGIC[1]; } -std::variant validate_section_headers( - bytes_view container) noexcept +std::variant validate_header( + evmc_revision rev, bytes_view container) noexcept { - enum class State - { - section_id, - section_size, - terminated - }; - - auto state = State::section_id; - uint8_t section_id = 0; - uint16_t section_num = 0; - EOFSectionHeaders section_headers{}; - const auto container_end = container.end(); - auto it = container.begin() + std::size(MAGIC) + 1; // MAGIC + VERSION - uint8_t expected_section_id = TYPE_SECTION; - while (it != container_end && state != State::terminated) - { - switch (state) - { - case State::section_id: - { - section_id = *it++; + if (!is_eof_container(container)) + return EOFValidationError::invalid_prefix; - // If DATA_SECTION is expected, CONTAINER_SECTION is also allowed, because - // container section is optional. - if (section_id != expected_section_id && - (expected_section_id != DATA_SECTION || section_id != CONTAINER_SECTION)) - return get_section_missing_error(expected_section_id); + const auto version = get_eof_version(container); + if (version != 1) + return EOFValidationError::eof_version_unknown; - switch (section_id) - { - case TERMINATOR: - state = State::terminated; - break; - case TYPE_SECTION: - expected_section_id = CODE_SECTION; - state = State::section_size; - break; - case CODE_SECTION: - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_number; - section_num = read_uint16_be(it); - it += 2; - if (section_num == 0) - return EOFValidationError::zero_section_size; - if (section_num > CODE_SECTION_NUMBER_LIMIT) - return EOFValidationError::too_many_code_sections; - expected_section_id = DATA_SECTION; - state = State::section_size; - break; - } - case DATA_SECTION: - expected_section_id = TERMINATOR; - state = State::section_size; - break; - case CONTAINER_SECTION: - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_number; - section_num = read_uint16_be(it); - it += 2; - if (section_num == 0) - return EOFValidationError::zero_section_size; - if (section_num > CONTAINER_SECTION_NUMBER_LIMIT) - return EOFValidationError::too_many_container_sections; - expected_section_id = DATA_SECTION; - state = State::section_size; - break; - } - default: - assert(false); - } - break; - } - case State::section_size: - { - if (section_id == CODE_SECTION || section_id == CONTAINER_SECTION) - { - assert(section_num > 0); // Guaranteed by previous validation step. - for (size_t i = 0; i < section_num; ++i) - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_size; - const auto section_size = read_uint16_be(it); - it += 2; - if (section_size == 0) - return EOFValidationError::zero_section_size; + if (rev < EVMC_PRAGUE) + return EOFValidationError::eof_version_unknown; - section_headers[section_id].emplace_back(section_size); - } - } - else // TYPES_SECTION or DATA_SECTION - { - if (it >= container_end - 1) - return EOFValidationError::incomplete_section_size; - const auto section_size = read_uint16_be(it); - it += 2; - if (section_size == 0 && section_id != DATA_SECTION) - return EOFValidationError::zero_section_size; + const auto section_headers_or_error = validate_section_headers(container); + if (const auto* error = std::get_if(§ion_headers_or_error)) + return *error; - section_headers[section_id].emplace_back(section_size); - } + const auto& section_headers = std::get(section_headers_or_error); + const auto& code_sizes = section_headers[CODE_SECTION]; + const auto data_size = section_headers[DATA_SECTION][0]; - state = State::section_id; - break; - } - case State::terminated: - return EOFValidationError::impossible; - } - } + const auto header_size = eof_header_size(section_headers); - if (state != State::terminated) - return EOFValidationError::section_headers_not_terminated; + const auto types_or_error = + validate_types(container, header_size, section_headers[TYPE_SECTION].front()); + if (const auto* error = std::get_if(&types_or_error)) + return *error; + const auto& types = std::get>(types_or_error); - const auto section_bodies_without_data = - section_headers[TYPE_SECTION].front() + - std::accumulate( - section_headers[CODE_SECTION].begin(), section_headers[CODE_SECTION].end(), 0) + - std::accumulate(section_headers[CONTAINER_SECTION].begin(), - section_headers[CONTAINER_SECTION].end(), 0); - const auto remaining_container_size = container_end - it; - // Only data section may be truncated, so remaining_container size must be at least - // declared_size_without_data - if (remaining_container_size < section_bodies_without_data) - return EOFValidationError::invalid_section_bodies_size; + std::vector code_offsets; + const auto type_section_size = section_headers[TYPE_SECTION][0]; + auto offset = header_size + type_section_size; + for (const auto code_size : code_sizes) + { + assert(offset <= std::numeric_limits::max()); + code_offsets.emplace_back(static_cast(offset)); + offset += code_size; + } - if (section_headers[TYPE_SECTION][0] != section_headers[CODE_SECTION].size() * 4) - return EOFValidationError::invalid_type_section_size; + const auto& container_sizes = section_headers[CONTAINER_SECTION]; + std::vector container_offsets; + for (const auto container_size : container_sizes) + { + container_offsets.emplace_back(static_cast(offset)); + offset += container_size; + } + // NOTE: assertion always satisfied only as long as initcode limits apply (48K). + assert(offset <= static_cast(std::numeric_limits::max())); + const auto data_offset = static_cast(offset); - return section_headers; + return EOF1Header{ + .version = container[2], + .code_sizes = code_sizes, + .code_offsets = code_offsets, + .data_size = data_size, + .data_offset = data_offset, + .container_sizes = container_sizes, + .container_offsets = container_offsets, + .types = types, + }; } /// This function expects the prefix and version to be valid, as it ignores it. diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index 1579e40b16..e1a6056b29 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -15,18 +14,9 @@ namespace evmone { -constexpr uint8_t TERMINATOR = 0x00; -constexpr uint8_t TYPE_SECTION = 0x01; -constexpr uint8_t CODE_SECTION = 0x02; -constexpr uint8_t CONTAINER_SECTION = 0x03; -constexpr uint8_t DATA_SECTION = 0x04; -constexpr uint8_t MAX_SECTION = DATA_SECTION; - using bytes = std::basic_string; using bytes_view = std::basic_string_view; -using EOFSectionHeaders = std::array, MAX_SECTION + 1>; - struct EOFCodeType { uint8_t inputs; ///< Number of code inputs. @@ -155,9 +145,9 @@ enum class EOFValidationError /// If the prefix is missing or invalid, 0 is returned meaning legacy code. [[nodiscard]] uint8_t get_eof_version(bytes_view container) noexcept; -/// Validates only the section headers and returns their contents if successful. -[[nodiscard]] EVMC_EXPORT std::variant -validate_section_headers(bytes_view container) noexcept; +/// Validates the header and returns its representation if successful. +[[nodiscard]] EVMC_EXPORT std::variant validate_header( + evmc_revision rev, bytes_view container) noexcept; /// Validates whether given container is a valid EOF according to the rules of given revision. [[nodiscard]] EVMC_EXPORT EOFValidationError validate_eof( diff --git a/test/state/host.cpp b/test/state/host.cpp index a7c483d88c..8a512bf80c 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -252,11 +252,11 @@ std::optional Host::prepare_message(evmc_message msg) // Assert this is a legacy EOF creation tx. assert(is_eof_container(input)); - const auto section_headers_or_error = validate_section_headers(input); - if (holds_alternative(section_headers_or_error)) + const auto header_or_error = validate_header(m_rev, input); + if (holds_alternative(header_or_error)) return {}; // Light early exception. - const auto header = read_valid_eof1_header(input); + const auto header = std::get(header_or_error); if (!header.can_init(msg.input_size)) return {}; // Light early exception. From 0c61309617e91cee2e0c3b4e240551fb49fcd6a4 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 27 May 2024 11:15:18 +0200 Subject: [PATCH 4/5] Update tests and examples --- test/unittests/eof_example_test.cpp | 40 +++++++++++++++++++ .../state_transition_eof_create_test.cpp | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/test/unittests/eof_example_test.cpp b/test/unittests/eof_example_test.cpp index 6786d0265a..7e25d537b4 100644 --- a/test/unittests/eof_example_test.cpp +++ b/test/unittests/eof_example_test.cpp @@ -119,6 +119,46 @@ TEST_F(state_transition, eof_examples_callf) expect.post[*tx.to].exists = true; } +TEST_F(state_transition, eof_examples_creation_tx) +{ + // # Example 4 + // + // A creation transaction used to create a new EOF contract. + + rev = EVMC_PRAGUE; + + const auto initcontainer = bytecode( + ////////////////// + // Initcontainer + // Code section: PUSH0 [aux data size], PUSH0 [aux data offset] and + // RETURNCONTRACT first subcontainer + // | + // Header: 1 code section 4 bytes long | + // | | + // version | Header terminator + // | |___________ | |________ + "EF00 01 01 0004 02 0001 0004 03 0001 0014 04 0000 00 00 80 0002 5F5F EE00" + // |‾‾‾‾‾‾ |‾‾‾‾‾‾‾‾‾‾‾ |‾‾‾‾‾‾ |‾‾‾‾‾‾‾‾‾ + // | | Header: data section 0 bytes long + // | | | + // Header: types section 4 bytes long Types section: first code section + // | 0 inputs, non-returning, + // | max stack height 2 + // Header: 1 subcontainer 20 bytes long + // + ////////////////// + // Deployed container (contract doing nothing, see Example 1) + "EF00 01 01 0004 02 0001 0001 04 0000 00 00 80 0000 00"); + + // Put the initcontainer in the `data` field of the transaction, appending some calldata. + tx.data = initcontainer + "ABCDEF"; + // Empty `to` field. + tx.to = std::nullopt; + + // Address of the newly created contract is calculated using the deployer's address and nonce. + expect.post[0x3442a1dec1e72f337007125aa67221498cdd759d_address].exists = true; +} + TEST_F(state_transition, eof_examples_eofcreate) { // # Example 5 diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index d8ed4ded00..8fece4f320 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -1394,7 +1394,7 @@ TEST_F(state_transition, txcreate_auxdata_shorter_than_declared) TEST_F(state_transition, txcreate_stray_data_initcontainer) { - rev = EVMC_PRAGUE; + rev = EVMC_OSAKA; const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); const auto init_code = returncontract(0, 0, 0); From 9e0314e31e651d905b5b4ab71bc12ef9317c0181 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 29 May 2024 12:36:45 +0200 Subject: [PATCH 5/5] Address PR comments --- test/state/host.cpp | 15 ++++----- test/state/host.hpp | 2 +- test/state/state.cpp | 20 ++++++++--- .../state_transition_eof_create_test.cpp | 33 ++++++++++++++++++- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/test/state/host.cpp b/test/state/host.cpp index 8a512bf80c..1074574b47 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -213,7 +213,7 @@ address compute_eofcreate_address( return new_addr; } -std::optional Host::prepare_message(evmc_message msg) +std::optional Host::prepare_message(evmc_message msg) noexcept { if (msg.depth == 0 || msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2 || msg.kind == EVMC_EOFCREATE) @@ -251,19 +251,18 @@ std::optional Host::prepare_message(evmc_message msg) { // Assert this is a legacy EOF creation tx. assert(is_eof_container(input)); - const auto header_or_error = validate_header(m_rev, input); - if (holds_alternative(header_or_error)) - return {}; // Light early exception. - const auto header = std::get(header_or_error); + const auto* header = std::get_if(&header_or_error); + if (header == nullptr) + return {}; // Light early exception. - if (!header.can_init(msg.input_size)) + if (!header->can_init(msg.input_size)) return {}; // Light early exception. const auto container_size = - static_cast(header.data_offset + header.data_size); - // Follows from the header.can_init condition above. + static_cast(header->data_offset + header->data_size); + // Follows from the header->can_init condition above. assert(container_size <= msg.input_size); msg.code = msg.input_data; diff --git a/test/state/host.hpp b/test/state/host.hpp index 1b24aa76aa..45ff038023 100644 --- a/test/state/host.hpp +++ b/test/state/host.hpp @@ -126,7 +126,7 @@ class Host : public evmc::Host /// which may finally be moved to EVM. /// Any state modification is not reverted. /// @return Modified message or std::nullopt in case of EVM exception. - std::optional prepare_message(evmc_message msg); + std::optional prepare_message(evmc_message msg) noexcept; evmc::Result execute_message(const evmc_message& msg) noexcept; }; diff --git a/test/state/state.cpp b/test/state/state.cpp index c76feee679..d6402d4a50 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -71,11 +71,21 @@ evmc_message build_message( const auto is_legacy_eof_create = rev >= EVMC_PRAGUE && !tx.to.has_value() && is_eof_container(tx.data); - return {is_legacy_eof_create ? EVMC_EOFCREATE : - tx.to.has_value() ? EVMC_CALL : - EVMC_CREATE, - 0, 0, execution_gas_limit, recipient, tx.sender, tx.data.data(), tx.data.size(), - intx::be::store(tx.value), {}, recipient, nullptr, 0}; + return {.kind = is_legacy_eof_create ? EVMC_EOFCREATE : + tx.to.has_value() ? EVMC_CALL : + EVMC_CREATE, + .flags = 0, + .depth = 0, + .gas = execution_gas_limit, + .recipient = recipient, + .sender = tx.sender, + .input_data = tx.data.data(), + .input_size = tx.data.size(), + .value = intx::be::store(tx.value), + .create2_salt = {}, + .code_address = recipient, + .code = nullptr, + .code_size = 0}; } } // namespace diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 8fece4f320..9c0b8175bd 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -947,12 +947,43 @@ TEST_F(state_transition, creation_tx_deploy_data) expect.post[create_address].nonce = 1; } -TEST_F(state_transition, creation_tx_auxdata_in_calldata) +TEST_F(state_transition, creation_tx_static_auxdata_in_calldata) { rev = EVMC_PRAGUE; const auto deploy_data = "abcdef"_hex; + // aux_data will be appended as calldata to the creation tx input, and later appended to the + // deployed contract's data section on RETURNCONTRACT. const auto aux_data = "aabbccddeeff"_hex; const auto deploy_data_size = static_cast(deploy_data.size()); + const auto aux_data_size = static_cast(aux_data.size()); + + // aux_data_size included in the declared data section size - static data. + const auto deploy_container = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data, deploy_data_size + aux_data_size); + + const auto init_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + returncontract(0, 0, OP_CALLDATASIZE); + const bytecode init_container = eof_bytecode(init_code, 3).container(deploy_container); + + tx.data = init_container + bytecode(aux_data); + const auto expected_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data); + + expect.post[Sender].nonce = pre.get(Sender).nonce + 1; + const auto create_address = compute_create_address(Sender, pre.get(Sender).nonce); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, creation_tx_dynamic_auxdata_in_calldata) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + // aux_data will be appended as calldata to the creation tx input, and later appended the + // deployed contract's data section on RETURNCONTRACT. + const auto aux_data = "aabbccddeeff"_hex; + const auto deploy_data_size = static_cast(deploy_data.size()); + + // aux_data_size not included in the declared data section size - dynamic data. const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data, deploy_data_size);