From 1f8c91b9ab398583a639553785fc0e35fbfecf4b Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 27 Mar 2024 15:31:42 +0100 Subject: [PATCH 01/19] EOFCREATE and RETURNCONTRACT implementations Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> --- evmc | 2 +- lib/evmone/advanced_instructions.cpp | 26 +++ lib/evmone/baseline.cpp | 34 +++- lib/evmone/baseline_instruction_table.cpp | 2 + lib/evmone/eof.cpp | 200 ++++++++++++++++++---- lib/evmone/eof.hpp | 33 +++- lib/evmone/execution_state.hpp | 3 + lib/evmone/instructions.hpp | 30 ++++ lib/evmone/instructions_calls.cpp | 69 ++++++++ lib/evmone/instructions_opcodes.hpp | 3 + lib/evmone/instructions_traits.hpp | 4 + lib/evmone/instructions_xmacro.hpp | 4 +- test/state/host.cpp | 65 +++++-- test/state/host.hpp | 11 ++ test/state/state.cpp | 16 +- test/unittests/eof_validation.cpp | 6 + test/unittests/instructions_test.cpp | 6 +- 17 files changed, 441 insertions(+), 73 deletions(-) diff --git a/evmc b/evmc index d2de33f10f..fc86231960 160000 --- a/evmc +++ b/evmc @@ -1 +1 @@ -Subproject commit d2de33f10fcef45a4ac18dc8c271a6d163a62f9c +Subproject commit fc86231960348790bbee8254a809bf1f9d7c8517 diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 7afcbedd29..2a6fe86183 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -62,6 +62,22 @@ inline TermResult impl(AdvancedExecutionState& state) noexcept return CoreFn(state.stack.top_item, state.gas_left, state); } +template > +inline Result impl(AdvancedExecutionState& state, code_iterator pos) noexcept +{ + // Stack height adjustment may be omitted. + return CoreFn(state.stack.top_item, state.gas_left, state, pos); +} + +template > +inline TermResult impl(AdvancedExecutionState& state, code_iterator pos) noexcept +{ + // Stack height adjustment may be omitted. + return CoreFn(state.stack.top_item, state.gas_left, state, pos); +} + template > inline code_iterator impl(AdvancedExecutionState& state, code_iterator pos) noexcept @@ -87,6 +103,14 @@ inline code_iterator impl(AdvancedExecutionState& state, code_iterator pos) noex template const Instruction* op(const Instruction* /*instr*/, AdvancedExecutionState& state) noexcept; +/// Wraps the generic instruction implementation to advanced instruction function signature. +template +const Instruction* op(const Instruction* instr, AdvancedExecutionState& state) noexcept; + +/// Wraps the generic instruction implementation to advanced instruction function signature. +template +const Instruction* op(const Instruction* instr, AdvancedExecutionState& state) noexcept; + namespace { using advanced::op; @@ -286,6 +310,8 @@ constexpr std::array instruction_implementations = []( table[OP_DUPN] = op_undefined; table[OP_SWAPN] = op_undefined; table[OP_EXCHANGE] = op_undefined; + table[OP_EOFCREATE] = op_undefined; + table[OP_RETURNCONTRACT] = op_undefined; return table; }(); diff --git a/lib/evmone/baseline.cpp b/lib/evmone/baseline.cpp index 4cba518697..94a79bdc66 100644 --- a/lib/evmone/baseline.cpp +++ b/lib/evmone/baseline.cpp @@ -209,7 +209,30 @@ struct Position state.status = result.status; return nullptr; } -/// @} + +[[release_inline]] inline code_iterator invoke( + Result (*instr_fn)(StackTop, int64_t, ExecutionState&, code_iterator&) noexcept, Position pos, + int64_t& gas, ExecutionState& state) noexcept +{ + const auto result = instr_fn(pos.stack_top, gas, state, pos.code_it); + gas = result.gas_left; + if (result.status != EVMC_SUCCESS) + { + state.status = result.status; + return nullptr; + } + return pos.code_it; +} + +[[release_inline]] inline code_iterator invoke( + TermResult (*instr_fn)(StackTop, int64_t, ExecutionState&, code_iterator) noexcept, + Position pos, int64_t& gas, ExecutionState& state) noexcept +{ + const auto result = instr_fn(pos.stack_top, gas, state, pos.code_it); + gas = result.gas_left; + state.status = result.status; + return nullptr; +} /// A helper to invoke the instruction implementation of the given opcode Op. template @@ -358,8 +381,13 @@ evmc_result execute( const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0; assert(state.output_size != 0 || state.output_offset == 0); - const auto result = evmc::make_result(state.status, gas_left, gas_refund, - state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size); + const auto result = + (state.deploy_container.has_value() ? + evmc::make_result(state.status, gas_left, gas_refund, + state.deploy_container->data(), state.deploy_container->size()) : + evmc::make_result(state.status, gas_left, gas_refund, + state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, + state.output_size)); if (INTX_UNLIKELY(tracer != nullptr)) tracer->notify_execution_end(result); diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index 1709974011..ee1f57f53e 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -41,6 +41,8 @@ constexpr auto legacy_cost_tables = []() noexcept { tables[EVMC_PRAGUE][OP_EXTCALL] = instr::undefined; tables[EVMC_PRAGUE][OP_EXTSTATICCALL] = instr::undefined; tables[EVMC_PRAGUE][OP_EXTDELEGATECALL] = instr::undefined; + tables[EVMC_PRAGUE][OP_EOFCREATE] = instr::undefined; + tables[EVMC_PRAGUE][OP_RETURNCONTRACT] = instr::undefined; return tables; }(); diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index 8d0e39bdd6..d6e4dcf17b 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -4,6 +4,7 @@ #include "eof.hpp" #include "baseline_instruction_table.hpp" +#include "execution_state.hpp" #include "instructions_traits.hpp" #include @@ -27,9 +28,11 @@ 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; constexpr auto OUTPUTS_INPUTS_NUMBER_LIMIT = 0x7F; constexpr auto REL_OFFSET_SIZE = sizeof(int16_t); @@ -42,13 +45,21 @@ size_t eof_header_size(const EOFSectionHeaders& headers) noexcept { const auto non_code_section_count = 2; // type section and data section const auto code_section_count = headers[CODE_SECTION].size(); + const auto container_section_count = headers[CONTAINER_SECTION].size(); constexpr auto non_code_section_header_size = 3; // (SECTION_ID + SIZE) per each section - constexpr auto code_section_size_size = 2; + constexpr auto section_size_size = 2; - return sizeof(MAGIC) + 1 + // 1 version byte - non_code_section_count * non_code_section_header_size + sizeof(CODE_SECTION) + 2 + - code_section_count * code_section_size_size + sizeof(TERMINATOR); + auto header_size = sizeof(MAGIC) + 1 + // 1 version byte + non_code_section_count * non_code_section_header_size + + sizeof(CODE_SECTION) + 2 + code_section_count * section_size_size + + sizeof(TERMINATOR); + + if (container_section_count != 0) + { + header_size += sizeof(CONTAINER_SECTION) + 2 + container_section_count * section_size_size; + } + return header_size; } EOFValidationError get_section_missing_error(uint8_t section_id) noexcept @@ -92,7 +103,10 @@ std::variant validate_eof_headers(bytes_v { section_id = *it++; - if (section_id != expected_section_id) + // 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) @@ -122,6 +136,20 @@ std::variant validate_eof_headers(bytes_v 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); } @@ -129,7 +157,7 @@ std::variant validate_eof_headers(bytes_v } case State::section_size: { - if (section_id == CODE_SECTION) + 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) @@ -167,12 +195,19 @@ std::variant validate_eof_headers(bytes_v if (state != State::terminated) return EOFValidationError::section_headers_not_terminated; - const auto section_bodies_size = section_headers[TYPE_SECTION].front() + - std::accumulate(section_headers[CODE_SECTION].begin(), - section_headers[CODE_SECTION].end(), 0) + - section_headers[DATA_SECTION].front(); + 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; - if (section_bodies_size != remaining_container_size) + // 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) @@ -215,7 +250,7 @@ std::variant, EOFValidationError> validate_types( } EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& header, - size_t code_idx, bytes_view container, + std::span subcontainer_headers, size_t code_idx, bytes_view container, std::unordered_set& accessed_code_sections) noexcept { const bytes_view code{header.get_code(container, code_idx)}; @@ -276,6 +311,19 @@ EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& he return EOFValidationError::invalid_dataloadn_index; i += 2; } + else if (op == OP_EOFCREATE || op == OP_RETURNCONTRACT) + { + const auto container_idx = code[i + 1]; + if (container_idx >= header.container_sizes.size()) + return EOFValidationError::invalid_container_section_index; + + if (op == OP_EOFCREATE && subcontainer_headers[container_idx].data_offset + + subcontainer_headers[container_idx].data_size != + header.container_sizes[container_idx]) + return EOFValidationError::eofcreate_with_truncated_container; + + ++i; + } else i += instr::traits[op].immediate_size; } @@ -522,7 +570,10 @@ std::variant validate_max_stack_height( return max_stack_height_it->max; } -std::variant validate_eof1( +std::variant validate_eof_container( + evmc_revision rev, bytes_view container) noexcept; + +std::variant validate_eof1( // NOLINT(misc-no-recursion) evmc_revision rev, bytes_view container) noexcept { const auto section_headers_or_error = validate_eof_headers(container); @@ -551,13 +602,36 @@ std::variant validate_eof1( 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; + } + const auto data_offset = static_cast(offset); + std::unordered_set accessed_code_sections = {0}; - EOF1Header header{container[2], code_sizes, code_offsets, data_size, types}; + EOF1Header header{container[2], code_sizes, code_offsets, data_size, data_offset, + container_sizes, container_offsets, types}; + + std::vector subcontainer_headers; + for (size_t subcont_idx = 0; subcont_idx < header.container_sizes.size(); ++subcont_idx) + { + const bytes_view subcontainer{header.get_container(container, subcont_idx)}; + + auto error_subcont_or_header = validate_eof_container(rev, subcontainer); + if (const auto* error_subcont = std::get_if(&error_subcont_or_header)) + return *error_subcont; + + auto& subcont_header = std::get(error_subcont_or_header); + subcontainer_headers.emplace_back(std::move(subcont_header)); + } for (size_t code_idx = 0; code_idx < header.code_sizes.size(); ++code_idx) { - const auto error_instr = - validate_instructions(rev, header, code_idx, container, accessed_code_sections); + const auto error_instr = validate_instructions( + rev, header, subcontainer_headers, code_idx, container, accessed_code_sections); if (error_instr != EOFValidationError::success) return error_instr; @@ -578,8 +652,40 @@ std::variant validate_eof1( return header; } +// NOLINTNEXTLINE(misc-no-recursion) +std::variant validate_eof_container( + 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) + { + if (rev < EVMC_PRAGUE) + return EOFValidationError::eof_version_unknown; + + return validate_eof1(rev, container); + } + else + return EOFValidationError::eof_version_unknown; +} } // namespace + +size_t EOF1Header::data_size_position() const noexcept +{ + const auto num_code_sections = code_sizes.size(); + const auto num_container_sections = container_sizes.size(); + return std::size(MAGIC) + 1 + // magic + version + 3 + // type section kind + size + 3 + 2 * num_code_sections + // code sections kind + count + sizes + // container sections kind + count + sizes + (num_container_sections != 0 ? 3 + 2 * num_container_sections : 0) + + 1; // data section kind +} + bool is_eof_container(bytes_view container) noexcept { return container.size() > 1 && container[0] == MAGIC[0] && container[1] == MAGIC[1]; @@ -593,7 +699,7 @@ EOF1Header read_valid_eof1_header(bytes_view container) while (*it != TERMINATOR) { const auto section_id = *it++; - if (section_id == CODE_SECTION) + if (section_id == CODE_SECTION || section_id == CONTAINER_SECTION) { const auto code_section_num = read_uint16_be(it); it += 2; @@ -635,9 +741,42 @@ EOF1Header read_valid_eof1_header(bytes_view container) header.data_size = section_headers[DATA_SECTION][0]; + header.container_sizes = section_headers[CONTAINER_SECTION]; + auto container_offset = code_offset; + for (const auto container_size : header.container_sizes) + { + header.container_offsets.emplace_back(static_cast(container_offset)); + container_offset += container_size; + } + + header.data_offset = static_cast(container_offset); + return header; } +bool append_data_section(bytes& container, bytes_view aux_data) +{ + const auto header = read_valid_eof1_header(container); + + const auto new_data_size = container.size() - header.data_offset + aux_data.size(); + if (new_data_size > std::numeric_limits::max()) + return false; + + // Check that appended data size is greater or equal of what header declaration expects. + if (new_data_size < header.data_size) + return false; + + // Appending aux_data to the end, assuming data section is always the last one. + container.append(aux_data); + + // Update data size + const auto data_size_pos = header.data_size_position(); + container[data_size_pos] = static_cast(new_data_size >> 8); + container[data_size_pos + 1] = static_cast(new_data_size); + + return true; +} + uint8_t get_eof_version(bytes_view container) noexcept { return (container.size() >= 3 && container[0] == MAGIC[0] && container[1] == MAGIC[1]) ? @@ -647,24 +786,11 @@ uint8_t get_eof_version(bytes_view container) noexcept EOFValidationError validate_eof(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) - { - if (rev < EVMC_PRAGUE) - return EOFValidationError::eof_version_unknown; - - const auto header_or_error = validate_eof1(rev, container); - if (const auto* error = std::get_if(&header_or_error)) - return *error; - else - return EOFValidationError::success; - } + const auto header_or_error = validate_eof_container(rev, container); + if (const auto* error = std::get_if(&header_or_error)) + return *error; else - return EOFValidationError::eof_version_unknown; + return EOFValidationError::success; } std::string_view get_error_message(EOFValidationError err) noexcept @@ -737,6 +863,12 @@ std::string_view get_error_message(EOFValidationError err) noexcept return "invalid_non_returning_flag"; case EOFValidationError::callf_to_non_returning_function: return "callf_to_non_returning_function"; + case EOFValidationError::too_many_container_sections: + return "too_many_container_sections"; + case EOFValidationError::invalid_container_section_index: + return "invalid_container_section_index"; + case EOFValidationError::eofcreate_with_truncated_container: + return "eofcreate_with_truncated_container"; case EOFValidationError::impossible: return "impossible"; } diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index cccc463bb8..c450391cb2 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -13,6 +13,7 @@ namespace evmone { +using bytes = std::basic_string; using bytes_view = std::basic_string_view; struct EOFCodeType @@ -37,7 +38,18 @@ struct EOF1Header /// Offset of every code section from the beginning of the EOF container. std::vector code_offsets; + /// Size of the data section. + /// In case of deploy container it is the minimal data size of the container that will be + /// deployed, taking into account part of data appended at deploy-time (static_aux_data). + /// In this case the size of data section present in current container can be less than + /// @data_size. uint16_t data_size = 0; + /// Offset of data container section start. + uint16_t data_offset = 0; + /// Size of every container section. + std::vector container_sizes; + /// Offset of every container section start; + std::vector container_offsets; std::vector types; @@ -51,11 +63,19 @@ struct EOF1Header /// A helper to extract reference to the data section. [[nodiscard]] bytes_view get_data(bytes_view container) const noexcept { - if (data_size == 0) - return {}; + return container.substr(data_offset); + } - return container.substr(code_offsets.back() + code_sizes.back(), data_size); + /// A helper to extract reference to a specific container section. + [[nodiscard]] bytes_view get_container( + bytes_view container, size_t container_idx) const noexcept + { + assert(container_idx < container_offsets.size()); + return container.substr(container_offsets[container_idx], container_sizes[container_idx]); } + + /// Offset of the data section size value in the header. + [[nodiscard]] size_t data_size_position() const noexcept; }; /// Checks if code starts with EOF FORMAT + MAGIC, doesn't validate the format. @@ -65,6 +85,10 @@ struct EOF1Header /// (must be true for all EOF contracts on-chain) [[nodiscard]] EVMC_EXPORT EOF1Header read_valid_eof1_header(bytes_view container); +/// Modifies container by appending aux_data to data section and updating data section size +/// in the header. +bool append_data_section(bytes& container, bytes_view aux_data); + enum class EOFValidationError { success, @@ -101,6 +125,9 @@ enum class EOFValidationError jumpf_destination_incompatible_outputs, invalid_non_returning_flag, callf_to_non_returning_function, + too_many_container_sections, + invalid_container_section_index, + eofcreate_with_truncated_container, impossible, }; diff --git a/lib/evmone/execution_state.hpp b/lib/evmone/execution_state.hpp index 2527f3856b..5b676d4487 100644 --- a/lib/evmone/execution_state.hpp +++ b/lib/evmone/execution_state.hpp @@ -142,6 +142,9 @@ class ExecutionState size_t output_offset = 0; size_t output_size = 0; + /// Container to be deployed returned from RETURNCONTRACT, used only inside EOFCREATE execution. + std::optional deploy_container; + private: evmc_tx_context m_tx = {}; diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 315525fe14..8f80ea3181 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1077,6 +1077,9 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex inline constexpr auto create = create_impl; inline constexpr auto create2 = create_impl; +Result eofcreate( + StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept; + inline code_iterator callf(StackTop stack, ExecutionState& state, code_iterator pos) noexcept { const auto index = read_uint16_be(&pos[1]); @@ -1147,6 +1150,33 @@ inline TermResult return_impl(StackTop stack, int64_t gas_left, ExecutionState& inline constexpr auto return_ = return_impl; inline constexpr auto revert = return_impl; +inline TermResult returncontract( + StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator pos) noexcept +{ + 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}; + + const auto deploy_container_index = size_t{pos[1]}; + + const auto header = read_valid_eof1_header(state.original_code); + bytes deploy_container{header.get_container(state.original_code, deploy_container_index)}; + + // Append (offset, size) to data section + if (!append_data_section(deploy_container, + {&state.memory[static_cast(offset)], static_cast(size)})) + return {EVMC_OUT_OF_GAS, gas_left}; + + state.deploy_container = std::move(deploy_container); + + return {EVMC_SUCCESS, gas_left}; +} + inline TermResult selfdestruct(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept { if (state.in_static_mode()) diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index f517a7647d..14b8b5f526 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -300,6 +300,75 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex return {EVMC_SUCCESS, gas_left}; } +Result eofcreate( + StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept +{ + if (state.in_static_mode()) + return {EVMC_STATIC_MODE_VIOLATION, gas_left}; + + const auto initcontainer_index = uint8_t{pos[1]}; + pos += 2; + + const auto& container = state.original_code; + const auto eof_header = read_valid_eof1_header(state.original_code); + const auto initcontainer = eof_header.get_container(container, initcontainer_index); + + const auto endowment = stack.pop(); + const auto salt = stack.pop(); + const auto input_offset_u256 = stack.pop(); + const auto input_size_u256 = stack.pop(); + + stack.push(0); // Assume failure. + state.return_data.clear(); + + if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256)) + return {EVMC_OUT_OF_GAS, gas_left}; + + // Charge for initcode hashing. + constexpr auto initcode_word_cost_hashing = 6; + const auto initcode_cost_hashing = num_words(initcontainer.size()) * initcode_word_cost_hashing; + if ((gas_left -= initcode_cost_hashing) < 0) + return {EVMC_OUT_OF_GAS, gas_left}; + + const auto input_offset = static_cast(input_offset_u256); + const auto input_size = static_cast(input_size_u256); + + if (state.msg->depth >= 1024) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + + if (endowment != 0 && + intx::be::load(state.host.get_balance(state.msg->recipient)) < endowment) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + + auto msg = evmc_message{}; + msg.gas = gas_left - gas_left / 64; + msg.kind = EVMC_EOFCREATE; + if (input_size > 0) + { + // input_data may be garbage if init_code_size == 0. + msg.input_data = &state.memory[input_offset]; + msg.input_size = input_size; + } + + msg.sender = state.msg->recipient; + msg.depth = state.msg->depth + 1; + msg.create2_salt = intx::be::store(salt); + msg.value = intx::be::store(endowment); + // init_code is guaranteed to be non-empty by validation of container sections + msg.code = initcontainer.data(); + msg.code_size = initcontainer.size(); + + const auto result = state.host.call(msg); + gas_left -= msg.gas - result.gas_left; + state.gas_refund += result.gas_refund; + + state.return_data.assign(result.output_data, result.output_size); + if (result.status_code == EVMC_SUCCESS) + stack.top() = intx::be::load(result.create_address); + + return {EVMC_SUCCESS, gas_left}; +} + template Result create_impl( StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; template Result create_impl( diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 98f63be1cb..6629732698 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -174,6 +174,9 @@ enum Opcode : uint8_t OP_SWAPN = 0xe7, OP_EXCHANGE = 0xe8, + OP_EOFCREATE = 0xec, + OP_RETURNCONTRACT = 0xee, + OP_CREATE = 0xf0, OP_CALL = 0xf1, OP_CALLCODE = 0xf2, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 3d37cc7418..eb0e52b948 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -188,6 +188,8 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_PRAGUE][OP_EXTCALL] = warm_storage_read_cost; table[EVMC_PRAGUE][OP_EXTDELEGATECALL] = warm_storage_read_cost; table[EVMC_PRAGUE][OP_EXTSTATICCALL] = warm_storage_read_cost; + table[EVMC_PRAGUE][OP_EOFCREATE] = 32000; + table[EVMC_PRAGUE][OP_RETURNCONTRACT] = 0; return table; }(); @@ -408,6 +410,8 @@ constexpr inline std::array traits = []() noexcept { table[OP_DELEGATECALL] = {"DELEGATECALL", 0, false, 6, -5, EVMC_HOMESTEAD}; table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE}; table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, EVMC_PRAGUE}; + table[OP_EOFCREATE] = {"EOFCREATE", 1, false, 4, -3, EVMC_PRAGUE}; + table[OP_RETURNCONTRACT] = {"RETURNCONTRACT", 1, true, 2, -2, EVMC_PRAGUE}; table[OP_EXTCALL] = {"EXTCALL", 0, false, 4, -3, EVMC_PRAGUE}; table[OP_EXTDELEGATECALL] = {"EXTDELEGATECALL", 0, false, 3, -2, EVMC_PRAGUE}; table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index b1f4cbf17e..2897952db4 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -282,9 +282,9 @@ ON_OPCODE_UNDEFINED(0xe9) \ ON_OPCODE_UNDEFINED(0xea) \ ON_OPCODE_UNDEFINED(0xeb) \ - ON_OPCODE_UNDEFINED(0xec) \ + ON_OPCODE_IDENTIFIER(OP_EOFCREATE, eofcreate) \ ON_OPCODE_UNDEFINED(0xed) \ - ON_OPCODE_UNDEFINED(0xee) \ + ON_OPCODE_IDENTIFIER(OP_RETURNCONTRACT, returncontract) \ ON_OPCODE_UNDEFINED(0xef) \ \ ON_OPCODE_IDENTIFIER(OP_CREATE, create) \ diff --git a/test/state/host.cpp b/test/state/host.cpp index 989c05a398..c557c1aa3d 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -178,9 +178,29 @@ address compute_create2_address( return addr; } +address compute_eofcreate_address( + const address& sender, const bytes32& salt, bytes_view initcontainer) noexcept +{ + const auto initcontainer_hash = keccak256(initcontainer); + const auto buffer_size = 1 + sizeof(sender) + sizeof(salt) + sizeof(initcontainer_hash); + bytes buffer; + buffer.reserve(buffer_size); + buffer += uint8_t{0xff}; + buffer += bytes_view{sender.bytes, std::size(sender.bytes)}; + buffer += bytes_view{salt.bytes, std::size(salt.bytes)}; + buffer += bytes_view{initcontainer_hash.bytes, std::size(initcontainer_hash.bytes)}; + + const auto addr_base_hash = keccak256(buffer); + + evmc_address new_addr{}; + std::copy_n(&addr_base_hash.bytes[12], sizeof(new_addr), new_addr.bytes); + return new_addr; +} + std::optional Host::prepare_message(evmc_message msg) { - if (msg.depth == 0 || msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) + if (msg.depth == 0 || msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2 || + msg.kind == EVMC_EOFCREATE) { auto& sender_acc = m_state.get(msg.sender); const auto sender_nonce = sender_acc.nonce; @@ -193,15 +213,25 @@ std::optional Host::prepare_message(evmc_message msg) m_state.journal_bump_nonce(msg.sender); ++sender_acc.nonce; // Bump sender nonce. - if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) + if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2 || msg.kind == EVMC_EOFCREATE) { // Compute and set the address of the account being created. assert(msg.recipient == address{}); assert(msg.code_address == address{}); - msg.recipient = (msg.kind == EVMC_CREATE) ? - compute_create_address(msg.sender, sender_nonce) : - compute_create2_address( - msg.sender, msg.create2_salt, {msg.input_data, msg.input_size}); + if (msg.kind == EVMC_CREATE) + msg.recipient = compute_create_address(msg.sender, sender_nonce); + else if (msg.kind == EVMC_CREATE2) + { + msg.recipient = compute_create2_address( + msg.sender, msg.create2_salt, {msg.input_data, msg.input_size}); + } + 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); + } // By EIP-2929, the access to new created address is never reverted. access_account(msg.recipient); @@ -213,7 +243,7 @@ std::optional Host::prepare_message(evmc_message msg) evmc::Result Host::create(const evmc_message& msg) noexcept { - assert(msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2); + assert(msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2 || msg.kind == EVMC_EOFCREATE); // Check collision as defined in pseudo-EIP https://github.com/ethereum/EIPs/issues/684. // All combinations of conditions (nonce, code, storage) are tested. @@ -249,17 +279,15 @@ evmc::Result Host::create(const evmc_message& msg) noexcept new_acc.balance += value; // The new account may be prefunded. auto create_msg = msg; - const bytes_view initcode{msg.input_data, msg.input_size}; - create_msg.input_data = nullptr; - create_msg.input_size = 0; - - if (m_rev >= EVMC_PRAGUE && (is_eof_container(initcode) || is_eof_container(sender_acc.code))) + const auto initcode = (msg.kind == EVMC_EOFCREATE ? bytes_view{msg.code, msg.code_size} : + bytes_view{msg.input_data, msg.input_size}); + if (msg.kind != EVMC_EOFCREATE) { - if (validate_eof(m_rev, initcode) != EOFValidationError::success) - return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; + create_msg.input_data = nullptr; + create_msg.input_size = 0; } - auto result = m_vm.execute(*this, m_rev, create_msg, msg.input_data, msg.input_size); + auto result = m_vm.execute(*this, m_rev, create_msg, initcode.data(), initcode.size()); if (result.status_code != EVMC_SUCCESS) { result.create_address = msg.recipient; @@ -270,6 +298,11 @@ evmc::Result Host::create(const evmc_message& msg) noexcept assert(gas_left >= 0); const bytes_view code{result.output_data, result.output_size}; + + // for EOFCREATE successful result is guaranteed to be non-empty + // because container section is not allowed to be empty + assert(msg.kind != EVMC_EOFCREATE || result.status_code != EVMC_SUCCESS || !code.empty()); + if (m_rev >= EVMC_SPURIOUS_DRAGON && code.size() > max_code_size) return evmc::Result{EVMC_FAILURE}; @@ -305,7 +338,7 @@ evmc::Result Host::create(const evmc_message& msg) noexcept evmc::Result Host::execute_message(const evmc_message& msg) noexcept { - if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) + if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2 || msg.kind == EVMC_EOFCREATE) return create(msg); if (msg.kind == EVMC_CALL) diff --git a/test/state/host.hpp b/test/state/host.hpp index aaeb22edf1..cbdc94d2d5 100644 --- a/test/state/host.hpp +++ b/test/state/host.hpp @@ -38,6 +38,17 @@ inline constexpr size_t max_initcode_size = 2 * max_code_size; [[nodiscard]] address compute_create2_address( const address& sender, const bytes32& salt, bytes_view init_code) noexcept; +/// Computes the address of to-be-created contract with the EOFCREATE scheme. +/// +/// Computes the new account address for the contract creation context of the EOFCREATE instruction. +/// +/// @param sender The address of the message sender. +/// @param salt The salt. +/// @param initcontainer The contract creation init container. +/// @return The address computed with the EOFCREATE scheme. +[[nodiscard]] address compute_eofcreate_address( + const address& sender, const bytes32& salt, bytes_view initcontainer) noexcept; + class Host : public evmc::Host { evmc_revision m_rev; diff --git a/test/state/state.cpp b/test/state/state.cpp index 7150311276..204c0c2b9c 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -57,19 +57,9 @@ int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noex evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) 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, - }; + 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}; } } // namespace diff --git a/test/unittests/eof_validation.cpp b/test/unittests/eof_validation.cpp index 935a01c76e..b8f9595787 100644 --- a/test/unittests/eof_validation.cpp +++ b/test/unittests/eof_validation.cpp @@ -80,6 +80,12 @@ std::string_view get_tests_error_message(EOFValidationError err) noexcept return "EOF_InvalidNonReturningFlag"; case EOFValidationError::callf_to_non_returning_function: return "EOF_CallfToNonReturningFunction"; + case EOFValidationError::too_many_container_sections: + return "EOF_TooManyContainerSections"; + case EOFValidationError::invalid_container_section_index: + return "EOF_InvalidContainerSectionIndex"; + case EOFValidationError::eofcreate_with_truncated_container: + return "EOF_EofCreateWithTruncatedContainer"; case EOFValidationError::impossible: return "impossible"; } diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index eca1725dac..437f4c6c96 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -40,6 +40,7 @@ constexpr bool is_terminating(uint8_t op) noexcept case OP_RETURN: case OP_RETF: case OP_JUMPF: + case OP_RETURNCONTRACT: case OP_REVERT: case OP_INVALID: case OP_SELFDESTRUCT: @@ -61,7 +62,8 @@ constexpr void validate_traits_of() noexcept static_assert(tr.immediate_size == 2); else if constexpr (Op == OP_RJUMPV) static_assert(tr.immediate_size == 1); - else if constexpr (Op == OP_DUPN || Op == OP_SWAPN || Op == OP_EXCHANGE) + else if constexpr (Op == OP_DUPN || Op == OP_SWAPN || Op == OP_EXCHANGE || Op == OP_EOFCREATE || + Op == OP_RETURNCONTRACT) static_assert(tr.immediate_size == 1); else if constexpr (Op == OP_DATALOADN) static_assert(tr.immediate_size == 2); @@ -130,6 +132,8 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept case OP_EXTCALL: case OP_EXTDELEGATECALL: case OP_EXTSTATICCALL: + case OP_EOFCREATE: + case OP_RETURNCONTRACT: return true; default: return false; From d0997926ef67980a058e6ba3b99f37b6faa28b52 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 4 Mar 2024 14:48:58 +0100 Subject: [PATCH 02/19] Forbid RETURN and STOP in EOFCREATE context --- lib/evmone/instructions.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 8f80ea3181..5ec6399798 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -144,9 +144,15 @@ 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; @@ -1136,6 +1142,13 @@ 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]; From 661af01623c899d868204e38244e5ade6aa0f7e5 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 13 Mar 2024 15:53:09 +0100 Subject: [PATCH 03/19] Allow deploying EOF code from EOFCREATE --- test/state/host.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/state/host.cpp b/test/state/host.cpp index c557c1aa3d..98d6ddaaf3 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -321,8 +321,10 @@ 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. - assert(msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2); - return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; + // 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); } else if (m_rev >= EVMC_LONDON) { From 9b9e223a434124602d6ce29b645381bf8a6e767b Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 4 Mar 2024 15:18:00 +0100 Subject: [PATCH 04/19] Add tests for embedded container validation and for EOFCREATE execution Also support EOFCREATE and EOF with containers in bytecode helper. Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> --- test/unittests/eof_validation_test.cpp | 109 +++++++++++++++++++++++-- test/unittests/evm_eof_test.cpp | 96 ++++++++++++++++++++++ test/utils/bytecode.hpp | 55 ++++++++++++- 3 files changed, 249 insertions(+), 11 deletions(-) diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index d7bd6f654d..0d7c97d6d1 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -178,10 +178,9 @@ TEST_F(eof_validation, EOF1_truncated_section) EOFValidationError::invalid_section_bodies_size); add_test_case("EF0001 010004 0200010002 040000 00 00800000 FE", EOFValidationError::invalid_section_bodies_size); - add_test_case("EF0001 010004 0200010001 040002 00 00800000 FE", - EOFValidationError::invalid_section_bodies_size); - add_test_case("EF0001 010004 0200010001 040002 00 00800000 FE AA", - EOFValidationError::invalid_section_bodies_size); + // Data section may be truncated + add_test_case("EF0001 010004 0200010001 040002 00 00800000 FE", EOFValidationError::success); + add_test_case("EF0001 010004 0200010001 040002 00 00800000 FE AA", EOFValidationError::success); } TEST_F(eof_validation, EOF1_code_section_offset) @@ -290,12 +289,13 @@ TEST_F(eof_validation, EOF1_undefined_opcodes) for (uint16_t opcode = 0; opcode <= 0xff; ++opcode) { - // PUSH*, DUPN, SWAPN, RJUMP*, CALLF, JUMPF require immediate argument to be valid, - // checked in a separate test. + // PUSH*, DUPN, SWAPN, RJUMP*, CALLF, JUMPF, EOFCREATE, RETRUNCONTRACT require immediate + // argument to be valid, checked in a separate test. if ((opcode >= OP_PUSH1 && opcode <= OP_PUSH32) || opcode == OP_DUPN || opcode == OP_SWAPN || opcode == OP_EXCHANGE || opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_CALLF || opcode == OP_RJUMPV || - opcode == OP_DATALOADN || opcode == OP_JUMPF) + opcode == OP_DATALOADN || opcode == OP_JUMPF || opcode == OP_EOFCREATE || + opcode == OP_RETURNCONTRACT) continue; // These opcodes are deprecated since Prague. // gas_cost table current implementation does not allow to undef instructions. @@ -574,6 +574,30 @@ TEST_F(eof_validation, EOF1_section_order) // 03 02 01 add_test_case("EF0001 040002 0200010006 010004 00 AABB 6000E0000000 00800000", EOFValidationError::type_section_missing); + + // 01 02 03 04 + add_test_case( + "EF0001 010004 0200010006 0300010014 040002 00 00800001 6000E0000000 " + "EF000101000402000100010400000000800000FE AABB", + EOFValidationError::success); + + // 03 01 02 04 + add_test_case( + "EF0001 0300010014 010004 0200010006 040002 00 EF000101000402000100010400000000800000FE " + "00800001 6000E0000000 AABB", + EOFValidationError::type_section_missing); + + // 01 03 02 04 + add_test_case( + "EF0001 010004 0300010014 0200010006 040002 00 00800001 " + "EF000101000402000100010400000000800000FE 6000E0000000 AABB", + EOFValidationError::code_section_missing); + + // 01 02 04 03 + add_test_case( + "EF0001 010004 0200010006 040002 0300010014 00 00800001 6000E0000000 AABB " + "EF000101000402000100010400000000800000FE", + EOFValidationError::header_terminator_missing); } TEST_F(eof_validation, deprecated_instructions) @@ -911,3 +935,74 @@ TEST_F(eof_validation, unreachable_code_sections) add_test_case(code_sections_256_err_254, EOFValidationError::unreachable_code_sections); } } + +TEST_F(eof_validation, EOF1_embedded_container) +{ + // no data section + add_test_case( + "EF0001 010004 0200010006 0300010014 040000 00 00800001 6000E0000000 " + "EF000101000402000100010400000000800000FE", + EOFValidationError::success); + + // no data section in container, but anticipated aux_data + add_test_case( + "EF0001 010004 0200010006 0300010014 040002 00 00800001 6000E0000000 " + "EF000101000402000100010400000000800000FE", + EOFValidationError::success); + + // with data section + add_test_case( + "EF0001 010004 0200010006 0300010014 040002 00 00800001 6000E0000000 " + "EF000101000402000100010400000000800000FE AABB", + EOFValidationError::success); + + // garbage in container section - not allowed + add_test_case( + "EF0001 010004 0200010006 0300010006 040000 00 00800001 6000E0000000 aabbccddeeff", + EOFValidationError::invalid_prefix); + + // multiple container sections + add_test_case( + "EF0001 010004 0200010006 03000200140016 040000 00 00800001 6000E0000000 " + "EF000101000402000100010400000000800000FE " + "EF0001010004020001000304000000008000025F5FF3", + EOFValidationError::success); + + // Max number (256) of container sections + const auto containers_header = bytecode{"030100"} + 256 * bytecode{"0014"}; + const auto containers_body = 256 * bytecode{"EF000101000402000100010400000000800000FE"}; + add_test_case("EF0001 010004 0200010006" + containers_header + + "040000 00 00800001 6000E0000000" + containers_body, + EOFValidationError::success); +} + +TEST_F(eof_validation, EOF1_embedded_container_invalid) +{ + // Truncated container header + add_test_case("EF0001 010004 0200010006 03", EOFValidationError::incomplete_section_number); + add_test_case("EF0001 010004 0200010006 0300", EOFValidationError::incomplete_section_number); + add_test_case( + "EF0001 010004 0200010006 030001", EOFValidationError::section_headers_not_terminated); + add_test_case("EF0001 010004 0200010006 03000100", EOFValidationError::incomplete_section_size); + add_test_case( + "EF0001 010004 0200010006 0300010014", EOFValidationError::section_headers_not_terminated); + + // Zero container sections + add_test_case("EF0001 010004 0200010006 030000 040000 00 00800001 60005D000000", + EOFValidationError::zero_section_size); + + // Container section with 0 size + add_test_case("EF0001 010004 0200010006 0300010000 040000 00 00800001 60005D000000", + EOFValidationError::zero_section_size); + + // Container body missing + add_test_case("EF0001 010004 0200010006 0300010014 040000 00 00800001 60005D000000", + EOFValidationError::invalid_section_bodies_size); + + // Too many container sections + const auto containers_header = bytecode{"030101"} + 257 * bytecode{"0001"}; + const auto containers_body = 257 * bytecode{"00"}; + add_test_case("EF0001 010004 0200010006" + containers_header + + "040000 00 00800001 60005D000000" + containers_body, + EOFValidationError::too_many_container_sections); +} diff --git a/test/unittests/evm_eof_test.cpp b/test/unittests/evm_eof_test.cpp index 209d5dab5d..f383be15a0 100644 --- a/test/unittests/evm_eof_test.cpp +++ b/test/unittests/evm_eof_test.cpp @@ -5,6 +5,7 @@ #include "evm_fixture.hpp" #include "evmone/eof.hpp" +using namespace evmc::literals; using evmone::test::evm; TEST_P(evm, eof1_execution) @@ -251,3 +252,98 @@ TEST_P(evm, datacopy_memory_cost) execute(17, code); EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); } + +TEST_P(evm, eof_eofcreate) +{ + if (is_advanced()) + return; + + 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() + aux_data.size()); + const bytecode deploy_container = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data, deploy_data_size); + + const auto init_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + OP_CALLDATASIZE + 0 + OP_RETURNCONTRACT + Opcode{0}; + const auto init_container = eof_bytecode(init_code, 3).container(deploy_container); + + const auto create_code = calldatacopy(0, 0, OP_CALLDATASIZE) + + eofcreate().input(0, OP_CALLDATASIZE).salt(0xff) + ret_top(); + const auto container = eof_bytecode(create_code, 4).container(init_container); + + // test executing create code mocking EOFCREATE call + host.call_result.output_data = deploy_container.data(); + host.call_result.output_size = deploy_container.size(); + host.call_result.create_address = 0xcc010203040506070809010203040506070809ce_address; + + execute(container, aux_data); + EXPECT_STATUS(EVMC_SUCCESS); + + ASSERT_EQ(host.recorded_calls.size(), 1); + const auto& call_msg = host.recorded_calls.back(); + + EXPECT_EQ(call_msg.input_size, aux_data.size()); + + 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) +{ + rev = EVMC_CANCUN; + const auto code = calldatacopy(0, 0, OP_CALLDATASIZE) + + eofcreate().input(0, OP_CALLDATASIZE).salt(0xff) + ret_top(); + + execute(code); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} + +TEST_P(evm, returncontract_undefined_in_legacy) +{ + rev = EVMC_CANCUN; + const auto code = + calldatacopy(0, 0, OP_CALLDATASIZE) + OP_CALLDATASIZE + 0 + OP_RETURNCONTRACT + Opcode{0}; + + execute(code); + 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()) + return; + + rev = EVMC_PRAGUE; + msg.flags |= EVMC_STATIC; + const auto code = eof_bytecode(4 * push0() + OP_EOFCREATE + "00" + OP_STOP, 4) + .container(eof_bytecode(push0() + push0() + OP_REVERT, 2)); + execute(code); + EXPECT_EQ(result.status_code, EVMC_STATIC_MODE_VIOLATION); + EXPECT_EQ(result.gas_left, 0); +} diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index ccfe2333bc..735d345a97 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -97,6 +97,8 @@ struct eof_bytecode std::vector m_codes; std::vector m_types; bytecode m_data; + uint16_t m_data_size = 0; + std::vector m_containers; /// Constructs EOF header bytes bytecode header() const @@ -105,6 +107,7 @@ struct eof_bytecode assert(m_codes[0].size() <= std::numeric_limits::max()); assert(m_data.size() <= std::numeric_limits::max()); assert(m_codes.size() == m_types.size()); + assert(m_containers.size() <= std::numeric_limits::max()); constexpr uint8_t version = 0x01; @@ -123,8 +126,22 @@ struct eof_bytecode out += big_endian(code_size); } + // containers header + if (!m_containers.empty()) + { + const auto container_count = static_cast(m_containers.size()); + out += "03"_hex + big_endian(container_count); + for (const auto& container : m_containers) + { + assert(container.size() <= std::numeric_limits::max()); + const auto container_size = static_cast(container.size()); + out += big_endian(container_size); + } + } + // data header - const auto data_size = static_cast(m_data.size()); + const auto data_size = + (m_data_size == 0 ? static_cast(m_data.size()) : m_data_size); out += "04" + big_endian(data_size); out += "00"; // terminator @@ -146,9 +163,16 @@ struct eof_bytecode return *this; } - auto& data(bytecode d) + auto& data(bytecode d, uint16_t size = 0) { m_data = std::move(d); + m_data_size = size; + return *this; + } + + auto& container(bytecode c) + { + m_containers.emplace_back(std::move(c)); return *this; } @@ -157,6 +181,8 @@ struct eof_bytecode bytecode out{header()}; for (const auto& code : m_codes) out += code; + for (const auto& container : m_containers) + out += container; out += m_data; return out; } @@ -565,6 +591,7 @@ struct create_instruction bytecode m_input = 0; bytecode m_input_size = 0; bytecode m_salt = 0; + uint8_t m_container_index = 0; public: auto& value(bytecode v) @@ -581,18 +608,34 @@ struct create_instruction } template - typename std::enable_if::type salt(bytecode salt) + typename std::enable_if::type salt( + bytecode salt) { m_salt = std::move(salt); return *this; } + template + typename std::enable_if::type container(uint8_t index) + { + m_container_index = index; + return *this; + } + operator bytecode() const { bytecode code; if constexpr (kind == OP_CREATE2) code += m_salt; - code += m_input_size + m_input + m_value + kind; + else if constexpr (kind == OP_EOFCREATE) + code += m_input_size + m_input + m_salt; + + if constexpr (kind != OP_EOFCREATE) + code += m_input_size + m_input; + + code += m_value + kind; + if constexpr (kind == OP_EOFCREATE) + code += bytecode{bytes{m_container_index}}; // immediate argument return code; } }; @@ -607,6 +650,10 @@ inline auto create2() return create_instruction{}; } +inline auto eofcreate() +{ + return create_instruction{}; +} inline std::string hex(Opcode opcode) noexcept { From 7d195134c738f3fdbbddbd2bfdf2cf2275fcceab Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 26 Mar 2024 21:14:30 +0100 Subject: [PATCH 05/19] Add EOFCREATE state transition tests --- .../state_transition_eof_create_test.cpp | 665 +++++++++++++++++- test/utils/bytecode.hpp | 6 + 2 files changed, 669 insertions(+), 2 deletions(-) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 270d94a36c..a193e1f1a3 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -8,6 +8,11 @@ using namespace evmc::literals; using namespace evmone::test; +namespace +{ +constexpr bytes32 Salt{0xff}; +} + TEST_F(state_transition, create_tx_with_eof_initcode) { rev = EVMC_PRAGUE; @@ -52,7 +57,7 @@ TEST_F(state_transition, create2_with_eof_initcode) const auto factory_code = mstore(0, push(init_container)) + // init_container will be left-padded in memory to 32 bytes - sstore(0, create2().input(32 - init_container.size(), init_container.size()).salt(0xff)) + + sstore(0, create2().input(32 - init_container.size(), init_container.size()).salt(Salt)) + sstore(1, 1); tx.to = To; @@ -122,7 +127,7 @@ TEST_F(state_transition, create2_deploying_eof) const auto factory_code = mstore(0, push(init_code)) + // init_code will be left-padded in memory to 32 bytes - sstore(0, create2().input(32 - init_code.size(), init_code.size()).salt((0xff))) + + sstore(0, create2().input(32 - init_code.size(), init_code.size()).salt((Salt))) + sstore(1, 1); tx.to = To; @@ -133,3 +138,659 @@ TEST_F(state_transition, create2_deploying_eof) expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; } + +TEST_F(state_transition, eofcreate_empty_auxdata) +{ + 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); + + const auto factory_code = eofcreate().container(0).input(0, 0).salt(Salt) + ret_top(); + 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; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_auxdata_equal_to_declared) +{ + 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() + aux_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); + + const auto factory_code = calldatacopy(0, 0, OP_CALLDATASIZE) + + eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt) + + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + tx.to = To; + + tx.data = aux_data; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_auxdata_longer_than_declared) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto aux_data1 = "aabbccdd"_hex; + const auto aux_data2 = "eeff"_hex; + const auto deploy_data_size = static_cast(deploy_data.size() + aux_data1.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); + + const auto factory_code = calldatacopy(0, 0, OP_CALLDATASIZE) + + eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt) + + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + tx.to = To; + + tx.data = aux_data1 + aux_data2; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data1 + aux_data2); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_auxdata_shorter_than_declared) +{ + 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() + aux_data.size() + 1); + 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 auto init_container = eof_bytecode(init_code, 3).container(deploy_container); + + 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; + + tx.data = aux_data; + + 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_dataloadn_referring_to_auxdata) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = bytes(64, 0); + const auto aux_data = bytes(32, 0); + 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 = returncontract(0, 0, 32); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + const auto factory_code = + sstore(0, eofcreate().container(0).input(0, 0).salt(Salt)) + sstore(1, 1) + 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}); + + const auto expected_container = eof_bytecode(deploy_code, 2).data(deploy_data + aux_data); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_with_auxdata_and_subcontainer) +{ + 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() + aux_data.size()); + const auto deploy_container = eof_bytecode(OP_INVALID) + .container(eof_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); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt)) + sstore(1, 1) + + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + tx.to = To; + + tx.data = aux_data; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = eof_bytecode(bytecode(OP_INVALID)) + .container(eof_bytecode(OP_INVALID)) + .data(deploy_data + aux_data); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_revert_empty_returndata) +{ + rev = EVMC_PRAGUE; + const auto init_code = revert(0, 0); + 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)) + + sstore(1, OP_RETURNDATASIZE) + 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; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, eofcreate_revert_non_empty_returndata) +{ + rev = EVMC_PRAGUE; + const auto init_code = mstore8(0, 0xaa) + revert(0, 1); + 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)) + + sstore(1, OP_RETURNDATASIZE) + 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; + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, eofcreate_initcontainer_aborts) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{Opcode{OP_INVALID}}; + 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_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; + 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); + + 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; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, eofcreate_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 auto init_container = eof_bytecode(init_code, 2).container(deploy_container); + + 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_appended_data_size_larger_than_64K) +{ + 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 aux_data = bytes(std::numeric_limits::max(), 0); + const auto deploy_data = "aa"_hex; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data); + + 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); + + static constexpr bytes32 salt2{0xfe}; + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + // with aux data, final data size = 2**16 + sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt)) + + // no aux_data - final data size = 1 + sstore(1, eofcreate().container(0).salt(salt2)) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + tx.to = To; + + tx.data = aux_data; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 2; // 1 successful creation + 1 hard fail + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + const auto create_address = compute_eofcreate_address(*tx.to, salt2, init_container); + expect.post[*tx.to].storage[0x01_bytes32] = to_bytes32(create_address); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_deploy_container_with_aux_data_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 = (0x5fff - eof_header_size) * bytecode{Opcode{OP_JUMPDEST}} + OP_STOP; + const bytecode deploy_container = eof_bytecode(deploy_code); + EXPECT_EQ(deploy_container.size(), 0x6000); + + // 1 byte aux data + const auto init_code = returncontract(0, 0, 1); + const auto init_container = eof_bytecode(init_code, 2).container(deploy_container); + + 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_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); + + 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; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + 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, eofcreate_nested_eofcreate_revert) +{ + rev = EVMC_PRAGUE; + + 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 auto init_container_nested = + eof_bytecode(init_code_nested, 2).container(deploy_container_nested); + + const auto init_code = sstore(0, eofcreate().container(0).salt(Salt)) + revert(0, 0); + const auto init_container = eof_bytecode(init_code, 4).container(init_container_nested); + + 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; + + 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_caller_balance_too_low) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto deploy_container = eof_bytecode(bytecode{Opcode{OP_INVALID}}).data(deploy_data); + + const auto init_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + returncontract(0, 0, OP_CALLDATASIZE); + const auto init_container = eof_bytecode(init_code, 3).container(deploy_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt).value(10)) + + sstore(1, 1) + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, eofcreate_not_enough_gas_for_initcode_charge) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + + 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 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; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.status = EVMC_OUT_OF_GAS; + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, eofcreate_not_enough_gas_for_mem_expansion) +{ + rev = EVMC_PRAGUE; + auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + // max size aux data + const auto aux_data_size = static_cast( + std::numeric_limits::max() - bytecode(deploy_container).size()); + deploy_container.data({}, aux_data_size); + EXPECT_EQ( + bytecode(deploy_container).size() + aux_data_size, std::numeric_limits::max()); + + 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); + + const auto factory_code = + sstore(0, eofcreate().container(0).input(0, aux_data_size).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 + mem expansion size = not enough for + // pushes before EOFCREATE + const auto initcode_size_words = (init_container.size() + 31) / 32; + const auto aux_data_size_words = (aux_data_size + 31) / 32; + tx.gas_limit = 21'000 + 32'000 + static_cast(6 * initcode_size_words) + + 3 * aux_data_size_words + aux_data_size_words * aux_data_size_words / 512; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.status = EVMC_OUT_OF_GAS; + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, returncontract_not_enough_gas_for_mem_expansion) +{ + 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; + + auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); + // max size aux data + const auto aux_data_size = static_cast( + std::numeric_limits::max() - bytecode(deploy_container).size()); + deploy_container.data({}, aux_data_size); + EXPECT_EQ( + bytecode(deploy_container).size() + aux_data_size, std::numeric_limits::max()); + + const auto init_code = returncontract(0, 0, aux_data_size); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + const auto factory_code = eofcreate().container(0).salt(Salt) + OP_POP + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + tx.to = To; + // tx intrinsic cost + EOFCREATE cost + initcode charge + mem expansion size = not enough for + // pushes before RETURNDATALOAD + const auto initcode_size_words = (init_container.size() + 31) / 32; + const auto aux_data_size_words = (aux_data_size + 31) / 32; + tx.gas_limit = 21'000 + 32'000 + static_cast(6 * initcode_size_words) + + 3 * aux_data_size_words + aux_data_size_words * aux_data_size_words / 512; + + 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_clears_returndata) +{ + static constexpr auto returning_address = 0x3000_address; + + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(OP_STOP); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + const auto factory_code = sstore(0, extcall(returning_address)) + sstore(1, returndatasize()) + + sstore(2, eofcreate().container(0).salt(Salt)) + + sstore(3, returndatasize()) + sstore(4, 1) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 4).container(init_container); + + const auto returning_code = ret(0, 10); + + tx.to = To; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + pre.insert(returning_address, {.nonce = 1, .code = returning_code}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x0a_bytes32; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x02_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x03_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; + expect.post[returning_address].nonce = 1; +} + +TEST_F(state_transition, eofcreate_failure_after_eofcreate_success) +{ + 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(OP_STOP); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + const auto factory_code = sstore(0, eofcreate().container(0).salt(Salt)) + + sstore(1, eofcreate().container(0).salt(Salt)) + // address collision + sstore(2, returndatasize()) + sstore(3, 1) + 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 + 2; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x02_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x03_bytes32] = 0x01_bytes32; + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index 735d345a97..ce7da63775 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -375,6 +375,12 @@ inline bytecode ret(bytecode c) return c + ret_top(); } +inline bytecode returncontract( + uint8_t container_index, bytecode aux_data_offset, bytecode aux_data_size) +{ + return aux_data_size + aux_data_offset + OP_RETURNCONTRACT + bytecode{bytes{container_index}}; +} + inline bytecode revert(bytecode index, bytecode size) { return size + index + OP_REVERT; From ccffe61d862cca5db4a09c661c03f2520987c9a1 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 4 Mar 2024 15:05:20 +0100 Subject: [PATCH 06/19] Add EOFCREATE and RETURNCONTRACT instruction validation tests --- test/unittests/eof_validation_test.cpp | 136 +++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 0d7c97d6d1..fa2989107e 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -476,6 +476,18 @@ TEST_F(eof_validation, EOF1_rjump_invalid_destination) // To PUSH immediate (offset = -4) add_test_case("EF0001 010004 0200010006 040000 00 00800000 6000E0FFFC00", EOFValidationError::invalid_rjump_destination); + + // To EOFCREATE immediate + const auto embedded = eof_bytecode(bytecode{OP_INVALID}); + add_test_case( + eof_bytecode(rjump(9) + 0 + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0} + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::invalid_rjump_destination); + + // To RETURNCONTRACT immediate + add_test_case( + eof_bytecode(rjump(5) + 0 + 0 + OP_RETURNCONTRACT + Opcode{0}, 2).container(embedded), + EOFValidationError::invalid_rjump_destination); } TEST_F(eof_validation, EOF1_rjumpi_invalid_destination) @@ -503,6 +515,19 @@ TEST_F(eof_validation, EOF1_rjumpi_invalid_destination) // To PUSH immediate (offset = -4) add_test_case("EF0001 010004 0200010006 040000 00 00800000 6000E1FFFC00", EOFValidationError::invalid_rjump_destination); + + // To EOFCREATE immediate + const auto embedded = eof_bytecode(bytecode{OP_INVALID}); + add_test_case( + eof_bytecode( + rjumpi(9, 0) + 0 + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0} + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::invalid_rjump_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); } TEST_F(eof_validation, EOF1_rjumpv_invalid_destination) @@ -547,6 +572,19 @@ TEST_F(eof_validation, EOF1_rjumpv_invalid_destination) // table = [0,3,6] case = 2 add_test_case("EF0001 010004 020001000F 040000 00 00800000 6002E2020000000300066001006002", EOFValidationError::invalid_rjump_destination); + + // To EOFCREATE immediate + const auto embedded = eof_bytecode(bytecode{OP_INVALID}); + add_test_case( + eof_bytecode( + rjumpv({9}, 0) + 0 + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0} + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::invalid_rjump_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); } TEST_F(eof_validation, EOF1_section_order) @@ -1006,3 +1044,101 @@ TEST_F(eof_validation, EOF1_embedded_container_invalid) "040000 00 00800001 60005D000000" + containers_body, EOFValidationError::too_many_container_sections); } + +TEST_F(eof_validation, EOF1_eofcreate_valid) +{ + // initcontainer_index = 0 + const auto embedded = eof_bytecode(bytecode{OP_INVALID}); + add_test_case( + eof_bytecode( + eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(0xff) + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::success); + + // initcontainer_index = 1 + add_test_case( + eof_bytecode( + eofcreate().container(1).input(0, OP_CALLDATASIZE).salt(0xff) + OP_POP + OP_STOP, 4) + .container(embedded) + .container(embedded), + EOFValidationError::success); + + // initcontainer_index = 255 + auto cont = eof_bytecode( + eofcreate().container(255).input(0, OP_CALLDATASIZE).salt(0xff) + OP_POP + OP_STOP, 4); + for (auto i = 0; i < 256; ++i) + cont.container(embedded); + add_test_case(cont, EOFValidationError::success); +} + +TEST_F(eof_validation, EOF1_eofcreate_invalid) +{ + // truncated immediate + const auto embedded = eof_bytecode(bytecode{OP_INVALID}); + add_test_case(eof_bytecode(bytecode(0) + 0xff + 0 + 0 + OP_EOFCREATE, 4).container(embedded), + EOFValidationError::truncated_instruction); + + // last instruction + add_test_case( + eof_bytecode(bytecode(0) + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0}, 4).container(embedded), + EOFValidationError::no_terminating_instruction); + + // referring to non-existent container section + add_test_case( + eof_bytecode(bytecode(0) + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{1} + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::invalid_container_section_index); + add_test_case( + eof_bytecode(bytecode(0) + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0xff} + OP_POP + OP_STOP, 4) + .container(embedded), + EOFValidationError::invalid_container_section_index); + + // referring to container with truncated data + const auto embedded_truncated_data = eof_bytecode(OP_INVALID).data("aabb"_hex, 3); + add_test_case( + eof_bytecode(bytecode(0) + 0xff + 0 + 0 + OP_EOFCREATE + Opcode{0} + OP_POP + OP_STOP, 4) + .container(embedded_truncated_data), + EOFValidationError::eofcreate_with_truncated_container); +} + +TEST_F(eof_validation, EOF1_returncontract_valid) +{ + // initcontainer_index = 0 + 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); + + // initcontainer_index = 1 + add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{1}, 2) + .container(embedded) + .container(embedded), + 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); +} + +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); + + // 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); + add_test_case( + eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0xff}, 4).container(embedded), + EOFValidationError::invalid_container_section_index); + + // Unreachable code after RETURNCONTRACT + add_test_case(eof_bytecode(bytecode(0) + 0 + OP_RETURNCONTRACT + Opcode{0} + OP_STOP, 2) + .container(embedded), + EOFValidationError::unreachable_instructions); +} From 39fb1937d413b99770f006c38bbe622946cb6d85 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 23 Jan 2024 15:04:56 +0100 Subject: [PATCH 07/19] Add embedded container tests for read_valid_eof1_header() --- test/unittests/eof_test.cpp | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/test/unittests/eof_test.cpp b/test/unittests/eof_test.cpp index e2e9d9f190..9a40486430 100644 --- a/test/unittests/eof_test.cpp +++ b/test/unittests/eof_test.cpp @@ -33,6 +33,7 @@ TEST(eof, read_valid_eof1_header) uint16_t types_size; uint16_t data_size; std::vector code_sizes; + std::vector container_sizes; }; std::string code_sections_256; @@ -41,22 +42,42 @@ TEST(eof, read_valid_eof1_header) code_sections_256 += "5B5B00"; const TestCase test_cases[] = { - {"EF00 01 010004 0200010001 040000 00 00800000 00", 4, 0, {1}}, - {"EF00 01 010004 0200010006 040000 00 00800002 600160005500", 4, 0, {6}}, - {"EF00 01 010004 0200010001 040001 00 00800000 00 AA", 4, 1, {1}}, - {"EF00 01 010004 0200010006 040004 00 00800002 600160005500 AABBCCDD", 4, 4, {6}}, - {"EF00 01 01000C 020003000300030003 040000 00 008000000080000000800000" - "E50001 E50002 5B5B00", - 12, 0, {3, 3, 3}}, - {"EF00 01 01000C 020003000300030003 040004 00 008000000080000000800000 E50001 E50002 5B5B00" - "FFFFFFFF", - 12, 4, {3, 3, 3}}, + {"EF00 01 010004 0200010001 040000 00 00800000 00", 4, 0, {1}, {}}, + {"EF00 01 010004 0200010006 040000 00 00800002 600160005500", 4, 0, {6}, {}}, + {"EF00 01 010004 0200010001 040001 00 00800000 00 AA", 4, 1, {1}, {}}, + {"EF00 01 010004 0200010006 040004 00 00800002 600160005500 AABBCCDD", 4, 4, {6}, {}}, + {"EF00 01 01000C 020003000300030003 040000 00 008000000080000000800000 E50001 E50002 " + "5B5B00", + 12, 0, {3, 3, 3}, {}}, + {"EF00 01 01000C 020003000300030003 040004 00 008000000080000000800000 E50001 E50002 " + "5B5B00 FFFFFFFF", + 12, 4, {3, 3, 3}, {}}, {"EF00 01 010004 0200010100 041000 00 00800000" + hex(255 * bytecode("5B")) + "00" + std::string(8192, 'F'), - 4, 4096, {256}}, + 4, 4096, {256}, {}}, {"EF00 01 010400 020100" + hex(256 * bytecode("0003")) + " 041000 00 " + hex(256 * bytecode("00800000")) + code_sections_256 + std::string(8192, 'F'), - 4 * 256, 4096, std::vector(256, 3)}, + 4 * 256, 4096, std::vector(256, 3), {}}, + {"EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 " + "EF000101000402000100010400000000800000FE", + 4, 0, {1}, {20}}, + {"EF00 01 010004 0200010001 030003001400160018 040000 00 00800000 00 " + "EF000101000402000100010400000000800000FE EF0001010004020001000304000000008000025F5FFD " + "EF00010100040200010005040000000080000260015F5500", + 4, 0, {1}, {20, 22, 24}}, + {"EF00 01 010004 0200010001 030003001400160018 040003 00 00800000 00 " + "EF000101000402000100010400000000800000FE EF0001010004020001000304000000008000025F5FFD " + "EF00010100040200010005040000000080000260015F5500 ddeeff", + 4, 3, {1}, {20, 22, 24}}, + {"EF00 01 01000C 020003000300030001 030003001400160018 040003 00 008000000080000000800000 " + "E50001 E50002 00 EF000101000402000100010400000000800000FE " + "EF0001010004020001000304000000008000025F5FFD " + "EF00010100040200010005040000000080000260015F5500 ddeeff", + 12, 3, {3, 3, 1}, {20, 22, 24}}, + {"EF00 01 010004 0200010001 030100" + hex(256 * bytecode("0014")) + + "040000 00 00800000 00" + + hex(256 * bytecode("EF000101000402000100010400000000800000FE")), + 4, 0, {1}, std::vector(256, 20)}, }; for (const auto& test_case : test_cases) @@ -68,6 +89,7 @@ TEST(eof, read_valid_eof1_header) EXPECT_EQ(header.code_sizes, test_case.code_sizes) << test_case.code; EXPECT_EQ(header.data_size, test_case.data_size) << test_case.code; EXPECT_EQ(header.types.size() * 4, test_case.types_size) << test_case.code; + EXPECT_EQ(header.container_sizes, test_case.container_sizes) << test_case.code; } } From 324d75504e69d2eb9a50c72c5efb799f8ab55ca2 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 26 Mar 2024 18:23:28 +0100 Subject: [PATCH 08/19] Add test with calling a EOFCREATE-deployed contract and DATA* instructions --- .../state_transition_eof_create_test.cpp | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index a193e1f1a3..36232b5762 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -794,3 +794,57 @@ TEST_F(state_transition, eofcreate_failure_after_eofcreate_success) expect.post[create_address].code = deploy_container; expect.post[create_address].nonce = 1; } + +TEST_F(state_transition, eofcreate_call_created_contract) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; // 3 bytes + const auto static_aux_data = + "aabbccdd00000000000000000000000000000000000000000000000000000000"_hex; // 32 bytes + const auto dynamic_aux_data = "eeff"_hex; // 2 bytes + const auto deploy_data_size = + static_cast(deploy_data.size() + static_aux_data.size()); + const auto deploy_code = rjumpv({6, 12}, calldataload(0)) + // jump to one of 3 cases + 35 + OP_DATALOAD + rjump(9) + // read dynamic aux data + OP_DATALOADN + "0000" + rjump(3) + // read pre_deploy_data_section + OP_DATALOADN + "0003" + // read static aux data + 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); + + const auto create_address = compute_eofcreate_address(To, Salt, init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, eofcreate().container(0).input(0, OP_CALLDATASIZE).salt(Salt)) + + mcopy(0, OP_CALLDATASIZE, 32) + // zero out first 32-byte word of memory + extcall(create_address).input(0, 1) + OP_POP + // calldata 0 + sstore(1, returndataload(0)) + mstore8(31, 1) + + extcall(create_address).input(0, 32) + // calldata 1 + 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, 4).container(init_container); + + tx.to = To; + + tx.data = static_aux_data + dynamic_aux_data; + + 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] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = + 0xabcdefaabbccdd00000000000000000000000000000000000000000000000000_bytes32; + evmc::bytes32 static_aux_data_32; + std::copy_n(static_aux_data.data(), static_aux_data.size(), &static_aux_data_32.bytes[0]); + expect.post[*tx.to].storage[0x02_bytes32] = static_aux_data_32; + evmc::bytes32 dynamic_aux_data_32; + std::copy_n(dynamic_aux_data.data(), dynamic_aux_data.size(), &dynamic_aux_data_32.bytes[0]); + expect.post[*tx.to].storage[0x03_bytes32] = dynamic_aux_data_32; + expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; + expect.post[create_address].nonce = 1; +} From 635aad41b9be18730571bdaae934d7052105ed37 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 3 Apr 2024 12:02:05 +0200 Subject: [PATCH 09/19] Update state tests --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 07a1adf541..9ef2c0b122 100644 --- a/circle.yml +++ b/circle.yml @@ -457,7 +457,7 @@ jobs: ~/tests/EIPTests/BlockchainTests/ - download_execution_tests: repo: ipsilon/tests - rev: eof-calls-20240321 + rev: fix-error-names legacy: false - run: name: "State tests (EOF)" From e86fc8509868cd4da6b07730dc699e4fead7f93c Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 27 Mar 2024 15:41:25 +0100 Subject: [PATCH 10/19] Increase stack allowance --- lib/evmone/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index aef7b89d73..dd8bffa315 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -45,7 +45,7 @@ if(CABLE_COMPILER_GNULIKE) target_compile_options( evmone PRIVATE -fno-exceptions - $<$:-Wstack-usage=2800> + $<$:-Wstack-usage=2900> ) if(NOT SANITIZE MATCHES undefined) # RTTI can be disabled except for UBSan which checks vptr integrity. From 67cbffe5ce19f2d9f25bc23279deebcd6df212f8 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:26:00 +0200 Subject: [PATCH 11/19] Add test for initcode-mode non-transitiveness --- .../state_transition_eof_create_test.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 36232b5762..4084933dcd 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -161,6 +161,37 @@ 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; From 2dc5ed3e54d3e107576f5c236a46cad482cb3b2b Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 25 Mar 2024 11:43:22 +0100 Subject: [PATCH 12/19] Implement TXCREATE instruction and initcode transactions support in Host Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> --- lib/evmone/advanced_instructions.cpp | 1 + lib/evmone/eof.hpp | 8 +++++ lib/evmone/execution_state.hpp | 18 ++++++++++ lib/evmone/instructions.hpp | 5 ++- lib/evmone/instructions_calls.cpp | 54 +++++++++++++++++++++++----- lib/evmone/instructions_opcodes.hpp | 1 + lib/evmone/instructions_traits.hpp | 2 ++ lib/evmone/instructions_xmacro.hpp | 2 +- test/state/errors.hpp | 9 +++++ test/state/host.cpp | 17 +++------ test/state/host.hpp | 13 ++++++- test/state/state.cpp | 35 ++++++++++++++++-- test/state/state.hpp | 4 +++ test/unittests/instructions_test.cpp | 1 + test/unittests/state_tx_test.cpp | 3 ++ 15 files changed, 148 insertions(+), 25 deletions(-) diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 2a6fe86183..3fe0b5db97 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -311,6 +311,7 @@ constexpr std::array instruction_implementations = []( table[OP_SWAPN] = op_undefined; table[OP_EXCHANGE] = op_undefined; table[OP_EOFCREATE] = op_undefined; + table[OP_TXCREATE] = op_undefined; table[OP_RETURNCONTRACT] = op_undefined; return table; diff --git a/lib/evmone/eof.hpp b/lib/evmone/eof.hpp index c450391cb2..ddcec16480 100644 --- a/lib/evmone/eof.hpp +++ b/lib/evmone/eof.hpp @@ -66,6 +66,14 @@ struct EOF1Header return container.substr(data_offset); } + /// A helper to check whether the container can be an initcontainer. + [[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; + return !truncated_data; + } + /// A helper to extract reference to a specific container section. [[nodiscard]] bytes_view get_container( bytes_view container, size_t container_idx) const noexcept diff --git a/lib/evmone/execution_state.hpp b/lib/evmone/execution_state.hpp index 5b676d4487..0c310806f3 100644 --- a/lib/evmone/execution_state.hpp +++ b/lib/evmone/execution_state.hpp @@ -147,6 +147,7 @@ class ExecutionState private: evmc_tx_context m_tx = {}; + std::optional> m_initcodes; public: /// Pointer to code analysis. @@ -203,5 +204,22 @@ class ExecutionState m_tx = host.get_tx_context(); return m_tx; } + + [[nodiscard]] bytes_view get_tx_initcode_by_hash(const evmc_bytes32& hash) noexcept + { + if (INTX_UNLIKELY(!m_initcodes.has_value())) + { + m_initcodes.emplace(); + const auto& tx_context = get_tx_context(); + for (size_t i = 0; i < tx_context.initcodes_count; ++i) + { + const auto& initcode = tx_context.initcodes[i]; + m_initcodes->insert({initcode.hash, {initcode.code, initcode.code_size}}); + } + } + + const auto it = m_initcodes->find(hash); + return it != m_initcodes->end() ? it->second : bytes_view{}; + } }; } // namespace evmone diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 5ec6399798..12d57b397a 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1083,8 +1083,11 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex inline constexpr auto create = create_impl; inline constexpr auto create2 = create_impl; -Result eofcreate( +template +Result create_eof_impl( StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept; +inline constexpr auto eofcreate = create_eof_impl; +inline constexpr auto txcreate = create_eof_impl; inline code_iterator callf(StackTop stack, ExecutionState& state, code_iterator pos) noexcept { diff --git a/lib/evmone/instructions_calls.cpp b/lib/evmone/instructions_calls.cpp index 14b8b5f526..1430cefed5 100644 --- a/lib/evmone/instructions_calls.cpp +++ b/lib/evmone/instructions_calls.cpp @@ -300,19 +300,16 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex return {EVMC_SUCCESS, gas_left}; } -Result eofcreate( +template +Result create_eof_impl( StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept { + static_assert(Op == OP_EOFCREATE || Op == OP_TXCREATE); + if (state.in_static_mode()) return {EVMC_STATIC_MODE_VIOLATION, gas_left}; - const auto initcontainer_index = uint8_t{pos[1]}; - pos += 2; - - const auto& container = state.original_code; - const auto eof_header = read_valid_eof1_header(state.original_code); - const auto initcontainer = eof_header.get_container(container, initcontainer_index); - + const auto initcode_hash = (Op == OP_TXCREATE) ? stack.pop() : uint256{}; const auto endowment = stack.pop(); const auto salt = stack.pop(); const auto input_offset_u256 = stack.pop(); @@ -324,6 +321,32 @@ Result eofcreate( if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256)) return {EVMC_OUT_OF_GAS, gas_left}; + bytes_view initcontainer; + if constexpr (Op == OP_EOFCREATE) + { + const auto initcontainer_index = uint8_t{pos[1]}; + pos += 2; + const auto& container = state.original_code; + const auto eof_header = read_valid_eof1_header(state.original_code); + initcontainer = eof_header.get_container(container, initcontainer_index); + } + else + { + pos += 1; + + initcontainer = + state.get_tx_initcode_by_hash(intx::be::store(initcode_hash)); + if (initcontainer.empty()) + return {EVMC_SUCCESS, gas_left}; // "Light" failure + + // Charge for initcode validation. + constexpr auto initcode_word_cost_validation = 2; + const auto initcode_cost_validation = + num_words(initcontainer.size()) * initcode_word_cost_validation; + if ((gas_left -= initcode_cost_validation) < 0) + return {EVMC_OUT_OF_GAS, gas_left}; + } + // Charge for initcode hashing. constexpr auto initcode_word_cost_hashing = 6; const auto initcode_cost_hashing = num_words(initcontainer.size()) * initcode_word_cost_hashing; @@ -340,6 +363,17 @@ Result eofcreate( intx::be::load(state.host.get_balance(state.msg->recipient)) < endowment) return {EVMC_SUCCESS, gas_left}; // "Light" failure. + if constexpr (Op == OP_TXCREATE) + { + const auto error_subcont = validate_eof(state.rev, initcontainer); + if (error_subcont != EOFValidationError::success) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + + const auto initcontainer_header = read_valid_eof1_header(initcontainer); + if (!initcontainer_header.can_init(initcontainer.size())) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + } + auto msg = evmc_message{}; msg.gas = gas_left - gas_left / 64; msg.kind = EVMC_EOFCREATE; @@ -373,4 +407,8 @@ template Result create_impl( StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; template Result create_impl( StackTop stack, int64_t gas_left, ExecutionState& state) noexcept; +template Result create_eof_impl( + StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept; +template Result create_eof_impl( + StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept; } // namespace evmone::instr::core diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 6629732698..9cf083238c 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -175,6 +175,7 @@ enum Opcode : uint8_t OP_EXCHANGE = 0xe8, OP_EOFCREATE = 0xec, + OP_TXCREATE = 0xed, OP_RETURNCONTRACT = 0xee, OP_CREATE = 0xf0, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index eb0e52b948..080664b3c2 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -189,6 +189,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_PRAGUE][OP_EXTDELEGATECALL] = warm_storage_read_cost; table[EVMC_PRAGUE][OP_EXTSTATICCALL] = warm_storage_read_cost; table[EVMC_PRAGUE][OP_EOFCREATE] = 32000; + table[EVMC_PRAGUE][OP_TXCREATE] = 32000; table[EVMC_PRAGUE][OP_RETURNCONTRACT] = 0; return table; @@ -411,6 +412,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE}; table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, EVMC_PRAGUE}; table[OP_EOFCREATE] = {"EOFCREATE", 1, false, 4, -3, EVMC_PRAGUE}; + table[OP_TXCREATE] = {"TXCREATE", 0, false, 5, -4, EVMC_PRAGUE}; table[OP_RETURNCONTRACT] = {"RETURNCONTRACT", 1, true, 2, -2, EVMC_PRAGUE}; table[OP_EXTCALL] = {"EXTCALL", 0, false, 4, -3, EVMC_PRAGUE}; table[OP_EXTDELEGATECALL] = {"EXTDELEGATECALL", 0, false, 3, -2, EVMC_PRAGUE}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 2897952db4..94103769ea 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -283,7 +283,7 @@ ON_OPCODE_UNDEFINED(0xea) \ ON_OPCODE_UNDEFINED(0xeb) \ ON_OPCODE_IDENTIFIER(OP_EOFCREATE, eofcreate) \ - ON_OPCODE_UNDEFINED(0xed) \ + ON_OPCODE_IDENTIFIER(OP_TXCREATE, txcreate) \ ON_OPCODE_IDENTIFIER(OP_RETURNCONTRACT, returncontract) \ ON_OPCODE_UNDEFINED(0xef) \ \ diff --git a/test/state/errors.hpp b/test/state/errors.hpp index 516b918635..b93e5ba493 100644 --- a/test/state/errors.hpp +++ b/test/state/errors.hpp @@ -23,6 +23,9 @@ enum ErrorCode : int GAS_LIMIT_REACHED, SENDER_NOT_EOA, INIT_CODE_SIZE_LIMIT_EXCEEDED, + INIT_CODE_EMPTY, + INIT_CODE_COUNT_LIMIT_EXCEEDED, + INIT_CODE_COUNT_ZERO, CREATE_BLOB_TX, EMPTY_BLOB_HASHES_LIST, INVALID_BLOB_HASH_VERSION, @@ -66,6 +69,12 @@ inline const std::error_category& evmone_category() noexcept return "sender not an eoa:"; case INIT_CODE_SIZE_LIMIT_EXCEEDED: return "max initcode size exceeded"; + case INIT_CODE_EMPTY: + return "initcode empty"; + case INIT_CODE_COUNT_LIMIT_EXCEEDED: + return "max initcode count exceeded"; + case INIT_CODE_COUNT_ZERO: + return "initcode list empty"; case CREATE_BLOB_TX: return "blob transaction must not be a create transaction"; case EMPTY_BLOB_HASHES_LIST: diff --git a/test/state/host.cpp b/test/state/host.cpp index 98d6ddaaf3..c2b17646fe 100644 --- a/test/state/host.cpp +++ b/test/state/host.cpp @@ -410,20 +410,13 @@ evmc_tx_context Host::get_tx_context() const noexcept std::min(m_tx.max_priority_gas_price, m_tx.max_gas_price - m_block.base_fee); const auto effective_gas_price = m_block.base_fee + priority_gas_price; - return evmc_tx_context{ - intx::be::store(effective_gas_price), // By EIP-1559. - m_tx.sender, - m_block.coinbase, - m_block.number, - m_block.timestamp, - m_block.gas_limit, + return evmc_tx_context{intx::be::store(effective_gas_price), // By EIP-1559. + m_tx.sender, m_block.coinbase, m_block.number, m_block.timestamp, m_block.gas_limit, m_block.prev_randao, 0x01_bytes32, // Chain ID is expected to be 1. - uint256be{m_block.base_fee}, - intx::be::store(m_block.blob_base_fee), - m_tx.blob_hashes.data(), - m_tx.blob_hashes.size(), - }; + uint256be{m_block.base_fee}, intx::be::store(m_block.blob_base_fee), + m_tx.blob_hashes.data(), m_tx.blob_hashes.size(), m_tx_initcodes.data(), + m_tx_initcodes.size()}; } bytes32 Host::get_block_hash(int64_t block_number) const noexcept diff --git a/test/state/host.hpp b/test/state/host.hpp index cbdc94d2d5..1b24aa76aa 100644 --- a/test/state/host.hpp +++ b/test/state/host.hpp @@ -14,6 +14,7 @@ using evmc::uint256be; inline constexpr size_t max_code_size = 0x6000; inline constexpr size_t max_initcode_size = 2 * max_code_size; +inline constexpr size_t max_initcode_count = 256; /// Computes the address of to-be-created contract with the CREATE scheme. /// @@ -57,12 +58,22 @@ class Host : public evmc::Host const BlockInfo& m_block; const Transaction& m_tx; std::vector m_logs; + std::vector m_tx_initcodes; public: Host(evmc_revision rev, evmc::VM& vm, State& state, const BlockInfo& block, const Transaction& tx) noexcept : m_rev{rev}, m_vm{vm}, m_state{state}, m_block{block}, m_tx{tx} - {} + { + if (tx.type == Transaction::Type::initcodes) + { + for (const auto& initcode : tx.initcodes) + { + const auto hash = keccak256({initcode.data(), initcode.size()}); + m_tx_initcodes.push_back({hash, initcode.data(), initcode.size()}); + } + } + } [[nodiscard]] std::vector&& take_logs() noexcept { return std::move(m_logs); } diff --git a/test/state/state.cpp b/test/state/state.cpp index 204c0c2b9c..a839602f20 100644 --- a/test/state/state.cpp +++ b/test/state/state.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace evmone::state { @@ -41,6 +42,14 @@ int64_t compute_access_list_cost(const AccessList& access_list) noexcept return cost; } +int64_t compute_initcode_list_cost(evmc_revision rev, std::span initcodes) noexcept +{ + int64_t cost = 0; + for (const auto& initcode : initcodes) + cost += compute_tx_data_cost(rev, initcode); + return cost; +} + int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noexcept { static constexpr auto call_tx_cost = 21000; @@ -51,7 +60,7 @@ int64_t compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noex 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; return tx_cost + compute_tx_data_cost(rev, tx.data) + compute_access_list_cost(tx.access_list) + - initcode_cost; + compute_initcode_list_cost(rev, tx.initcodes) + initcode_cost; } evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) noexcept @@ -253,8 +262,30 @@ std::variant validate_transaction(const Account& sende return make_error_code(INVALID_BLOB_HASH_VERSION); if (std::cmp_greater(tx.blob_gas_used(), blob_gas_left)) return make_error_code(BLOB_GAS_LIMIT_EXCEEDED); - [[fallthrough]]; + break; + case Transaction::Type::initcodes: + if (rev < EVMC_PRAGUE) + return make_error_code(TX_TYPE_NOT_SUPPORTED); + if (tx.initcodes.size() > max_initcode_count) + return make_error_code(INIT_CODE_COUNT_LIMIT_EXCEEDED); + if (tx.initcodes.empty()) + return make_error_code(INIT_CODE_COUNT_ZERO); + if (std::any_of(tx.initcodes.begin(), tx.initcodes.end(), + [](const bytes& v) { return v.size() > max_initcode_size; })) + return make_error_code(INIT_CODE_SIZE_LIMIT_EXCEEDED); + if (std::any_of( + tx.initcodes.begin(), tx.initcodes.end(), [](const bytes& v) { return v.empty(); })) + return make_error_code(INIT_CODE_EMPTY); + break; + + default:; + } + + switch (tx.type) + { + case Transaction::Type::blob: + case Transaction::Type::initcodes: case Transaction::Type::eip1559: if (rev < EVMC_LONDON) return make_error_code(TX_TYPE_NOT_SUPPORTED); diff --git a/test/state/state.hpp b/test/state/state.hpp index 321c560e91..61de8c43a3 100644 --- a/test/state/state.hpp +++ b/test/state/state.hpp @@ -192,6 +192,9 @@ struct Transaction /// The typed blob transaction (with array of blob hashes). /// Introduced by EIP-4844 https://eips.ethereum.org/EIPS/eip-4844. blob = 3, + + /// The typed transaction with initcode list. + initcodes = 4, }; /// Returns amount of blob gas used by this transaction @@ -217,6 +220,7 @@ struct Transaction intx::uint256 r; intx::uint256 s; uint8_t v = 0; + std::vector initcodes; }; struct Log diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index 437f4c6c96..c0c00e72bb 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -133,6 +133,7 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept case OP_EXTDELEGATECALL: case OP_EXTSTATICCALL: case OP_EOFCREATE: + case OP_TXCREATE: case OP_RETURNCONTRACT: return true; default: diff --git a/test/unittests/state_tx_test.cpp b/test/unittests/state_tx_test.cpp index dfb4be2eef..f156653d09 100644 --- a/test/unittests/state_tx_test.cpp +++ b/test/unittests/state_tx_test.cpp @@ -30,6 +30,7 @@ TEST(state_tx, validate_nonce) .nonce = 1, .r = 0, .s = 0, + .initcodes = {}, }; ASSERT_FALSE(holds_alternative( @@ -66,6 +67,7 @@ TEST(state_tx, validate_sender) .nonce = 0, .r = 0, .s = 0, + .initcodes = {}, }; ASSERT_FALSE(holds_alternative( @@ -175,6 +177,7 @@ TEST(state_tx, validate_data) .nonce = 1, .r = 0, .s = 0, + .initcodes = {}, }; ASSERT_FALSE(holds_alternative( From ff729d48f5e8a03b8571ad29c8c26675f9c4a7f8 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 28 Mar 2024 11:23:59 +0100 Subject: [PATCH 13/19] TXCREATE state transition tests Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com> --- .../state_transition_eof_create_test.cpp | 1022 +++++++++++++++++ test/utils/bytecode.hpp | 29 +- 2 files changed, 1046 insertions(+), 5 deletions(-) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 4084933dcd..d839abd3d4 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -879,3 +879,1025 @@ TEST_F(state_transition, eofcreate_call_created_contract) expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; expect.post[create_address].nonce = 1; } + +TEST_F(state_transition, txcreate_empty_auxdata) +{ + 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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt) + 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.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_auxdata_equal_to_declared) +{ + 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() + aux_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.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) + + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + tx.data = aux_data; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_auxdata_longer_than_declared) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; + const auto aux_data1 = "aabbccdd"_hex; + const auto aux_data2 = "eeff"_hex; + const auto deploy_data_size = static_cast(deploy_data.size() + aux_data1.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.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) + + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + tx.data = aux_data1 + aux_data2; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = + eof_bytecode(bytecode(OP_INVALID)).data(deploy_data + aux_data1 + aux_data2); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_auxdata_shorter_than_declared) +{ + 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() + aux_data.size() + 1); + 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.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) + + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + tx.data = aux_data; + 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_dataloadn_referring_to_auxdata) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = bytes(64, 0); + const auto aux_data = bytes(32, 0); + 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 = returncontract(0, 0, 32); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + sstore(0, txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt)) + + sstore(1, 1) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + const auto expected_container = eof_bytecode(deploy_code, 2).data(deploy_data + aux_data); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; + expect.post[create_address].code = expected_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_revert_empty_returndata) +{ + rev = EVMC_PRAGUE; + const auto init_code = revert(0, 0); + const bytecode init_container = eof_bytecode(init_code, 2); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + sstore(1, OP_RETURNDATASIZE) + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_revert_non_empty_returndata) +{ + rev = EVMC_PRAGUE; + const auto init_code = mstore8(0, 0xaa) + revert(0, 1); + const bytecode init_container = eof_bytecode(init_code, 2); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + sstore(1, OP_RETURNDATASIZE) + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, txcreate_initcontainer_aborts) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{Opcode{OP_INVALID}}; + const bytecode init_container = eof_bytecode(init_code, 0); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_initcontainer_return) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{0xaa + ret_top()}; + const bytecode init_container = eof_bytecode(init_code, 2); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_initcontainer_stop) +{ + rev = EVMC_PRAGUE; + const auto init_code = bytecode{Opcode{OP_STOP}}; + const bytecode init_container = eof_bytecode(init_code, 0); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt) + 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.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_initcontainer_empty) +{ + rev = EVMC_PRAGUE; + + const bytecode empty_init_container{}; + + 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).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + tx.initcodes.push_back(empty_init_container); + + const auto factory_code = txcreate().initcode(keccak256(init_container)) + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.tx_error = INIT_CODE_EMPTY; +} + +TEST_F(state_transition, txcreate_no_initcontainer) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + + const auto factory_code = txcreate().initcode(keccak256(bytecode())) + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.tx_error = INIT_CODE_COUNT_ZERO; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt) + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.tx_error = INIT_CODE_SIZE_LIMIT_EXCEEDED; +} + +TEST_F(state_transition, txcreate_too_many_initcontainers) +{ + 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 = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.assign(257, init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt) + ret_top(); + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.tx_error = INIT_CODE_COUNT_LIMIT_EXCEEDED; +} + +TEST_F(state_transition, initcode_transaction_before_prague) +{ + rev = EVMC_CANCUN; + + 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.type = Transaction::Type::initcodes; + tx.initcodes.assign(257, init_container); + + tx.to = To; + + expect.tx_error = TX_TYPE_NOT_SUPPORTED; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_appended_data_size_larger_than_64K) +{ + 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 aux_data = bytes(std::numeric_limits::max(), 0); + const auto deploy_data = "aa"_hex; + const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)).data(deploy_data); + + 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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + static constexpr bytes32 salt2{0xfe}; + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + // with aux data, final data size = 2**16 + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + // no aux_data - final data size = 1 + sstore(1, txcreate().initcode(keccak256(init_container)).salt(salt2)) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 5); + + tx.to = To; + tx.data = aux_data; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 2; // 1 successful creation + 1 hard fail + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + const auto create_address = compute_eofcreate_address(*tx.to, salt2, init_container); + expect.post[*tx.to].storage[0x01_bytes32] = to_bytes32(create_address); + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_deploy_container_with_aux_data_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 = (0x5fff - eof_header_size) * bytecode{Opcode{OP_JUMPDEST}} + OP_STOP; + const bytecode deploy_container = eof_bytecode(deploy_code); + EXPECT_EQ(deploy_container.size(), 0x6000); + + // 1 byte aux data + const auto init_code = returncontract(0, 0, 1); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_nested_txcreate) +{ + 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, txcreate().initcode(keccak256(init_container_nested)).salt(Salt)) + + returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 5).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + tx.initcodes.push_back(init_container_nested); + + const auto factory_code = + sstore(0, txcreate().initcode(keccak256(init_container)).salt(Salt)) + 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; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + 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, txcreate_nested_txcreate_revert) +{ + rev = EVMC_PRAGUE; + 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, txcreate().initcode(keccak256(init_container_nested)).salt(Salt)) + revert(0, 0); + const bytecode init_container = eof_bytecode(init_code, 5); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + tx.initcodes.push_back(init_container_nested); + + const auto factory_code = + sstore(0, txcreate().initcode(keccak256(init_container)).salt(Salt)) + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + sstore(0, txcreate().initcode(keccak256(init_container)).salt(Salt)) + 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; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + 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, txcreate_called_balance_too_low) +{ + 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 = + calldatacopy(0, 0, OP_CALLDATASIZE) + returncontract(0, 0, OP_CALLDATASIZE); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, txcreate() + .initcode(keccak256(init_container)) + .input(0, OP_CALLDATASIZE) + .salt(Salt) + .value(10)) + + sstore(1, 1) + 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; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, txcreate_clears_returndata) +{ + static constexpr auto returning_address = 0x3000_address; + + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(OP_STOP); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = sstore(0, extcall(returning_address)) + sstore(1, returndatasize()) + + sstore(2, txcreate().initcode(keccak256(init_container)).salt(Salt)) + + sstore(3, returndatasize()) + sstore(4, 1) + OP_STOP; + const auto factory_container = eof_bytecode(factory_code, 5); + + const auto returning_code = ret(0, 10); + + tx.to = To; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + pre.insert(returning_address, {.nonce = 1, .code = returning_code}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x01_bytes32] = 0x0a_bytes32; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x02_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x03_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; + expect.post[returning_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_failure_after_txcreate_success) +{ + 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(OP_STOP); + + const auto init_code = returncontract(0, 0, 0); + const bytecode init_container = eof_bytecode(init_code, 2).container(deploy_container); + + 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, 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); + + tx.to = To; + + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 2; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, init_container); + expect.post[*tx.to].storage[0x00_bytes32] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x02_bytes32] = 0x00_bytes32; + expect.post[*tx.to].storage[0x03_bytes32] = 0x01_bytes32; + expect.post[create_address].code = deploy_container; + expect.post[create_address].nonce = 1; +} + +TEST_F(state_transition, txcreate_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + // TODO: extract this common code for a testing deployer contract + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).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.gas_used = 55764; + + 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_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + // TODO: extract this common code for a testing deployer contract + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).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.gas_used = 55776; + + 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_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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).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.gas_used = 55776; + + 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_missing_initcontainer) +{ + 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).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = txcreate().initcode(keccak256(bytecode())).input(0, 0).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.gas_used = 55748; + + 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_light_failure_stack) +{ + 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).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + push(0x123) + txcreate().value(1).initcode(keccak256(bytecode())).input(2, 3).salt(Salt) + + push(1) + OP_SSTORE + // store result from TXCREATE + push(2) + + OP_SSTORE + // store the preceding push value, nothing else should remain on stack + ret(0); + const auto factory_container = eof_bytecode(factory_code, 6); + + tx.to = To; + pre.insert(*tx.to, {.nonce = 1, .code = factory_container}); + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; // TXCREATE has pushed 0x0 on stack + expect.post[*tx.to].storage[0x02_bytes32] = + 0x0123_bytes32; // TXCREATE fails but has cleared its args first +} + +TEST_F(state_transition, txcreate_missing_deploycontainer) +{ + rev = EVMC_PRAGUE; + const auto init_code = returncontract(0, 0, 0); + const bytes init_container = eof_bytecode(init_code, 2); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).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.gas_used = 55500; + + 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_deploy_code_with_dataloadn_invalid) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = bytes(32, 0); + // DATALOADN{64} - referring to offset out of bounds even after appending aux_data later + const auto deploy_code = bytecode(OP_DATALOADN) + "0040" + ret_top(); + const auto aux_data = bytes(32, 0); + const auto deploy_data_size = static_cast(deploy_data.size() + aux_data.size()); + const auto deploy_container = eof_bytecode(deploy_code, 2).data(deploy_data, deploy_data_size); + + const auto init_code = returncontract(0, 0, 0); + const bytes init_container = eof_bytecode(init_code, 2).container(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).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.gas_used = 56048; + + 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_call_created_contract) +{ + rev = EVMC_PRAGUE; + const auto deploy_data = "abcdef"_hex; // 3 bytes + const auto static_aux_data = + "aabbccdd00000000000000000000000000000000000000000000000000000000"_hex; // 32 bytes + const auto dynamic_aux_data = "eeff"_hex; // 2 bytes + const auto deploy_data_size = + static_cast(deploy_data.size() + static_aux_data.size()); + const auto deploy_code = rjumpv({6, 12}, calldataload(0)) + // jump to one of 3 cases + 35 + OP_DATALOAD + rjump(9) + // read dynamic aux data + OP_DATALOADN + "0000" + rjump(3) + // read pre_deploy_data_section + OP_DATALOADN + "0003" + // read static aux data + 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.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto create_address = compute_eofcreate_address(To, Salt, init_container); + + const auto factory_code = + calldatacopy(0, 0, OP_CALLDATASIZE) + + sstore(0, + txcreate().initcode(keccak256(init_container)).input(0, OP_CALLDATASIZE).salt(Salt)) + + mcopy(0, OP_CALLDATASIZE, 32) + // zero out first 32-byte word of memory + extcall(create_address).input(0, 1) + // calldata 0 + OP_POP + sstore(1, returndataload(0)) + mstore8(31, 1) + + extcall(create_address).input(0, 32) + // calldata 1 + 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); + + tx.to = To; + + tx.data = static_aux_data + dynamic_aux_data; + + 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] = to_bytes32(create_address); + expect.post[*tx.to].storage[0x01_bytes32] = + 0xabcdefaabbccdd00000000000000000000000000000000000000000000000000_bytes32; + evmc::bytes32 static_aux_data_32; + std::copy_n(static_aux_data.data(), static_aux_data.size(), &static_aux_data_32.bytes[0]); + expect.post[*tx.to].storage[0x02_bytes32] = static_aux_data_32; + evmc::bytes32 dynamic_aux_data_32; + std::copy_n(dynamic_aux_data.data(), dynamic_aux_data.size(), &dynamic_aux_data_32.bytes[0]); + expect.post[*tx.to].storage[0x03_bytes32] = dynamic_aux_data_32; + expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; + expect.post[create_address].nonce = 1; +} diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index ce7da63775..ab48973512 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -598,6 +598,7 @@ struct create_instruction bytecode m_input_size = 0; bytecode m_salt = 0; uint8_t m_container_index = 0; + bytecode m_initcode_hash = 0; public: auto& value(bytecode v) @@ -614,8 +615,9 @@ struct create_instruction } template - typename std::enable_if::type salt( - bytecode salt) + typename std::enable_if::type + salt(bytecode salt) { m_salt = std::move(salt); return *this; @@ -628,18 +630,30 @@ struct create_instruction return *this; } + template + typename std::enable_if::type initcode(bytecode hash) + { + m_initcode_hash = std::move(hash); + return *this; + } + operator bytecode() const { bytecode code; if constexpr (kind == OP_CREATE2) code += m_salt; - else if constexpr (kind == OP_EOFCREATE) + else if constexpr (kind == OP_EOFCREATE || kind == OP_TXCREATE) code += m_input_size + m_input + m_salt; - if constexpr (kind != OP_EOFCREATE) + if constexpr (kind == OP_CREATE || kind == OP_CREATE2) code += m_input_size + m_input; - code += m_value + kind; + code += m_value; + + if constexpr (kind == OP_TXCREATE) + code += m_initcode_hash; + + code += bytecode{kind}; if constexpr (kind == OP_EOFCREATE) code += bytecode{bytes{m_container_index}}; // immediate argument return code; @@ -661,6 +675,11 @@ inline auto eofcreate() return create_instruction{}; } +inline auto txcreate() +{ + return create_instruction{}; +} + inline std::string hex(Opcode opcode) noexcept { return hex(static_cast(opcode)); From fc9c9f7d16c19c6c0b194328496d98b9cadb7514 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 4 Mar 2024 18:50:48 +0100 Subject: [PATCH 14/19] Mark OP_TXCREATE as undefined in legacy --- lib/evmone/baseline_instruction_table.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/evmone/baseline_instruction_table.cpp b/lib/evmone/baseline_instruction_table.cpp index ee1f57f53e..7fc5cf2e07 100644 --- a/lib/evmone/baseline_instruction_table.cpp +++ b/lib/evmone/baseline_instruction_table.cpp @@ -42,6 +42,7 @@ constexpr auto legacy_cost_tables = []() noexcept { tables[EVMC_PRAGUE][OP_EXTSTATICCALL] = instr::undefined; tables[EVMC_PRAGUE][OP_EXTDELEGATECALL] = instr::undefined; tables[EVMC_PRAGUE][OP_EOFCREATE] = instr::undefined; + tables[EVMC_PRAGUE][OP_TXCREATE] = instr::undefined; tables[EVMC_PRAGUE][OP_RETURNCONTRACT] = instr::undefined; return tables; }(); From a0e4f004ba8016f7af7235477db8b19803048a0e Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 12 Jan 2024 17:27:12 +0100 Subject: [PATCH 15/19] Support TXCREATE transactions in state tests Including support in state test runner and in export from state transition tests. --- test/statetest/statetest_loader.cpp | 6 ++++++ test/unittests/state_transition.cpp | 3 +++ 2 files changed, 9 insertions(+) diff --git a/test/statetest/statetest_loader.cpp b/test/statetest/statetest_loader.cpp index cb1040434a..113e603cd1 100644 --- a/test/statetest/statetest_loader.cpp +++ b/test/statetest/statetest_loader.cpp @@ -329,6 +329,12 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o) for (const auto& hash : *it) o.blob_hashes.push_back(from_json(hash)); } + else if (const auto it_initcodes = j.find("initcodes"); it_initcodes != j.end()) + { + o.type = state::Transaction::Type::initcodes; + for (const auto& initcode : *it_initcodes) + o.initcodes.push_back(from_json(initcode)); + } } template <> diff --git a/test/unittests/state_transition.cpp b/test/unittests/state_transition.cpp index 8d289b7d92..7a82aa9b5b 100644 --- a/test/unittests/state_transition.cpp +++ b/test/unittests/state_transition.cpp @@ -197,6 +197,9 @@ void state_transition::export_state_test(const TransactionReceipt& receipt, cons jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price); } + for (size_t i = 0; i < tx.initcodes.size(); ++i) + jtx["initcodes"][i] = hex0x(tx.initcodes[i]); + jtx["data"][0] = hex0x(tx.data); jtx["gasLimit"][0] = hex0x(tx.gas_limit); jtx["value"][0] = hex0x(tx.value); From ccc8ea7e71192088bf54ddfd17651295bdadd145 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 11 Mar 2024 16:02:05 +0100 Subject: [PATCH 16/19] Add transition tests for CREATE/CREATE2 in TXCREATE's initcode --- .../state_transition_eof_create_test.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index d839abd3d4..343a73e651 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -1901,3 +1901,55 @@ TEST_F(state_transition, txcreate_call_created_contract) expect.post[*tx.to].storage[0x04_bytes32] = 0x01_bytes32; expect.post[create_address].nonce = 1; } + + +TEST_F(state_transition, create_nested_in_txcreate) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(OP_STOP); + + const auto init_code = bytecode{OP_DATASIZE} + OP_PUSH0 + OP_PUSH0 + OP_DATACOPY + + create().input(0, OP_DATASIZE) + returncontract(0, 0, 0); + const bytes init_container = + eof_bytecode(init_code, 3).container(deploy_container).data(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).salt(Salt) + push(1) + OP_SSTORE + 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.sender].nonce = pre.get(tx.sender).nonce + 1; + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} + +TEST_F(state_transition, create2_nested_in_txcreate) +{ + rev = EVMC_PRAGUE; + const auto deploy_container = eof_bytecode(OP_INVALID); + + const auto init_code = bytecode{OP_DATASIZE} + OP_PUSH0 + OP_PUSH0 + OP_DATACOPY + + create2().input(0, OP_DATASIZE).salt(Salt) + returncontract(0, 0, 0); + const bytes init_container = + eof_bytecode(init_code, 4).container(deploy_container).data(deploy_container); + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(init_container); + + const auto factory_code = + txcreate().initcode(keccak256(init_container)).input(0, 0).salt(Salt) + push(1) + + OP_SSTORE + 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.sender].nonce = pre.get(tx.sender).nonce + 1; + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; + expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; +} From a65dbd719f022609b0356e0c268a436d18a4ff9e Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 11 Mar 2024 15:59:45 +0100 Subject: [PATCH 17/19] Add transition tests for TXCREATE called from wrong type of transaction --- .../state_transition_eof_create_test.cpp | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 343a73e651..57b3d62881 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -1953,3 +1953,59 @@ TEST_F(state_transition, create2_nested_in_txcreate) expect.post[*tx.to].nonce = pre.get(*tx.to).nonce; expect.post[*tx.to].storage[0x01_bytes32] = 0x00_bytes32; } + +TEST_F(state_transition, txcreate_from_legacy_tx) +{ + rev = EVMC_PRAGUE; + tx.type = Transaction::Type::legacy; + + const auto factory_code = sstore(0, txcreate().initcode(keccak256({})).input(0, 0).salt(Salt)) + + sstore(1, 1) + 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.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[0x00_bytes32] = 0x00_bytes32; // CREATE must fail + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, txcreate_from_1559_tx) +{ + rev = EVMC_PRAGUE; + tx.type = Transaction::Type::eip1559; + + const auto factory_code = sstore(0, txcreate().initcode(keccak256({})).input(0, 0).salt(Salt)) + + sstore(1, 1) + 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.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[0x00_bytes32] = 0x00_bytes32; // CREATE must fail + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, txcreate_from_blob_tx) +{ + rev = EVMC_PRAGUE; + tx.type = Transaction::Type::blob; + tx.blob_hashes.push_back( + 0x0100000000000000000000000000000000000000000000000000000000000007_bytes32); + + const auto factory_code = sstore(0, txcreate().initcode(keccak256({})).input(0, 0).salt(Salt)) + + sstore(1, 1) + 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.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[0x00_bytes32] = 0x00_bytes32; // CREATE must fail + expect.post[*tx.to].storage[0x01_bytes32] = 0x01_bytes32; +} From 17c09c76b07db642adeb8cf2d188dfd1b3900f3f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:56:26 +0200 Subject: [PATCH 18/19] Add test for initcode-mode non-transitiveness (TXCREATE) --- .../state_transition_eof_create_test.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/unittests/state_transition_eof_create_test.cpp b/test/unittests/state_transition_eof_create_test.cpp index 57b3d62881..a809b7b509 100644 --- a/test/unittests/state_transition_eof_create_test.cpp +++ b/test/unittests/state_transition_eof_create_test.cpp @@ -905,6 +905,39 @@ TEST_F(state_transition, txcreate_empty_auxdata) expect.post[create_address].nonce = 1; } +TEST_F(state_transition, txcreate_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); + + 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_PRAGUE; From 0cf6c5b482b23b523381546cfa1031bd45a0214f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:12:58 +0200 Subject: [PATCH 19/19] Add a test for the Creator Contract --- test/unittests/CMakeLists.txt | 1 + .../state_transition_eof_creator_test.cpp | 235 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 test/unittests/state_transition_eof_creator_test.cpp diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 1d65974527..60e6bd03a1 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -62,6 +62,7 @@ target_sources( state_transition_eip663_test.cpp state_transition_eof_calls_test.cpp state_transition_eof_create_test.cpp + state_transition_eof_creator_test.cpp state_transition_extcode_test.cpp state_transition_selfdestruct_test.cpp state_transition_touch_test.cpp diff --git a/test/unittests/state_transition_eof_creator_test.cpp b/test/unittests/state_transition_eof_creator_test.cpp new file mode 100644 index 0000000000..d89e43d7fe --- /dev/null +++ b/test/unittests/state_transition_eof_creator_test.cpp @@ -0,0 +1,235 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "../utils/bytecode.hpp" +#include "state_transition.hpp" + +using namespace evmc::literals; +using namespace evmone::test; + +namespace +{ +constexpr bytes32 Salt{0xff}; +const auto deploy_container = eof_bytecode(bytecode(OP_INVALID)); +const bytecode simple_init_container = + eof_bytecode(returncontract(0, 0, 0), 2).container(deploy_container); + +const auto creator = bytecode( + // EOF header + "EF0001 010004 020001002c 040000 00" + // Types section + "00 80 0005" + // Code section + // Enough calldata or revert? + // rjumpi(5, iszero(64 + calldatasize() + OP_LT)) + revert(0, 0) + "6040 36 10 15 E10003 5F 5F FD" + // Copy input data to memory + // calldatacopy(0, 64, 64 + calldatasize() + OP_SUB) + "6040 36 03 6040 5F 37" + // TXCREATE with arguments + // txcreate() + // .initcode(calldataload(0)) + // .input(0, 64 + calldatasize() + OP_SUB) + // .salt(calldataload(32)) + // .value(OP_CALLVALUE) + "6040 36 03 5F 6020 35 34 5F 35 ED" + // Creation successful or revert? + // rjumpi(5, OP_DUP1) + revert(0, 0) + "80 E10003 5F 5F FD" + // RETURN new address + // ret_top() + "5F 52 6020 5F F3"); + +bytes creator_calldata( + const bytecode& init_container, const bytes32 salt, const bytes& input = bytes{}) +{ + return bytes(keccak256(init_container)) + bytes(salt) + input; +} +} // namespace + +TEST_F(state_transition, create_first) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, create_second) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(bytes{0xff}); + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, create_255th) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + for (int i = 0; i < 255; ++i) + tx.initcodes.push_back(bytes{0xff}); + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, different_salt) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, bytes32{0xfe}); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = + compute_eofcreate_address(*tx.to, bytes32{0xfe}, simple_init_container); + const auto other_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + // Sanity check + ASSERT_NE(create_address, other_address); + + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, duplicate_container) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; +} + +TEST_F(state_transition, undefined_container_reverts) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(keccak256(bytecode()), Salt); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.status = EVMC_REVERT; + expect.post[*tx.to].exists = true; +} + +TEST_F(state_transition, not_enough_calldata_reverts) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(keccak256(bytecode()), Salt).substr(0, 63); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.status = EVMC_REVERT; + expect.post[*tx.to].exists = true; +} + +TEST_F(state_transition, calldata_to_inputdata) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + const bytecode input_init_container = + eof_bytecode(sstore(0, calldataload(0)) + returncontract(0, 0, 0), 2) + .container(deploy_container); + tx.initcodes.push_back(input_init_container); + + tx.to = To; + tx.data = creator_calldata(input_init_container, Salt, bytes(bytes32{0xcc})); + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, input_init_container); + expect.post[create_address].code = deploy_container; + expect.post[create_address].storage[0x00_bytes32] = 0xcc_bytes32; +} + +TEST_F(state_transition, creator_returns_new_address) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + constexpr auto creator_address = 0x0c12ea1012_address; + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + pre.insert(*tx.to, + {.nonce = 1, + .storage = {{0x00_bytes32, {.current = 0xdd_bytes32, .original = 0xdd_bytes32}}}, + .code = eof_bytecode(calldatacopy(0, 0, calldatasize()) + + sstore(0, extdelegatecall(creator_address).input(0, 64)) + + sstore(1, returndataload(0)) + OP_STOP, + 3)}); + pre.insert(creator_address, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; + expect.post[creator_address].exists = true; + + expect.post[*tx.to].storage[0x00_bytes32] = 0x00_bytes32; // Success status code. + expect.post[*tx.to].storage[0x01_bytes32] = to_bytes32(create_address); +} + +TEST_F(state_transition, endowment) +{ + rev = EVMC_PRAGUE; + + tx.type = Transaction::Type::initcodes; + tx.initcodes.push_back(simple_init_container); + + tx.to = To; + tx.data = creator_calldata(simple_init_container, Salt); + tx.value = 1; + pre.insert(*tx.to, {.nonce = 1, .code = creator}); + + expect.post[*tx.to].nonce = pre.get(*tx.to).nonce + 1; + const auto create_address = compute_eofcreate_address(*tx.to, Salt, simple_init_container); + expect.post[create_address].code = deploy_container; + expect.post[create_address].balance = 1; +}