Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EOF: TXCREATE instruction #702

Merged
merged 7 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ constexpr std::array<instruction_exec_fn, 256> 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;
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/baseline_instruction_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}();
Expand Down
8 changes: 8 additions & 0 deletions lib/evmone/eof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(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
Expand Down
21 changes: 21 additions & 0 deletions lib/evmone/execution_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class ExecutionState

private:
evmc_tx_context m_tx = {};
std::optional<std::unordered_map<evmc::bytes32, bytes_view>> m_initcodes;

public:
/// Pointer to code analysis.
Expand Down Expand Up @@ -203,5 +204,25 @@ class ExecutionState
m_tx = host.get_tx_context();
return m_tx;
}

/// Get initcode by its hash from transaction initcodes.
///
/// Returns empty bytes_view if no such initcode was found.
[[nodiscard]] bytes_view get_tx_initcode_by_hash(const evmc_bytes32& hash) noexcept
{
if (!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
5 changes: 4 additions & 1 deletion lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1083,8 +1083,11 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex
inline constexpr auto create = create_impl<OP_CREATE>;
inline constexpr auto create2 = create_impl<OP_CREATE2>;

Result eofcreate(
template <Opcode Op>
Result create_eof_impl(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
inline constexpr auto eofcreate = create_eof_impl<OP_EOFCREATE>;
inline constexpr auto txcreate = create_eof_impl<OP_TXCREATE>;

inline code_iterator callf(StackTop stack, ExecutionState& state, code_iterator pos) noexcept
{
Expand Down
56 changes: 48 additions & 8 deletions lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,17 @@
return {EVMC_SUCCESS, gas_left};
}

Result eofcreate(
template <Opcode Op>
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 =
rodiazet marked this conversation as resolved.
Show resolved Hide resolved
(Op == OP_TXCREATE) ? intx::be::store<evmc::bytes32>(stack.pop()) : evmc::bytes32{};
const auto endowment = stack.pop();
const auto salt = stack.pop();
const auto input_offset_u256 = stack.pop();
Expand All @@ -324,6 +322,33 @@
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 = pos[1];
pos += 2;
const auto& container = state.original_code;
const auto& eof_header = state.analysis.baseline->eof_header;
initcontainer = eof_header.get_container(container, initcontainer_index);
}
else
{
pos += 1;

initcontainer = state.get_tx_initcode_by_hash(initcode_hash);
// In case initcode was not found, empty bytes_view was returned.
// Transaction initcodes are not allowed to be empty.
if (initcontainer.empty())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you actually provide an empty initcontainer in the transaction?

Copy link
Member Author

@gumb0 gumb0 Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually you cannot, we forgot to remove this with the introduction of this rule.

You cannot, but state.get_tx_initcode_by_hash() returns empty bytes_view when initcontainer not found.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change get_tx_initcode_by_hash returning std::optional

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};

Check warning on line 349 in lib/evmone/instructions_calls.cpp

View check run for this annotation

Codecov / codecov/patch

lib/evmone/instructions_calls.cpp#L349

Added line #L349 was not covered by tests
chfast marked this conversation as resolved.
Show resolved Hide resolved
}

// Charge for initcode hashing.
constexpr auto initcode_word_cost_hashing = 6;
const auto initcode_cost_hashing = num_words(initcontainer.size()) * initcode_word_cost_hashing;
rodiazet marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -340,6 +365,17 @@
intx::be::load<uint256>(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;
Expand Down Expand Up @@ -373,4 +409,8 @@
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result create_impl<OP_CREATE2>(
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
template Result create_eof_impl<OP_EOFCREATE>(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
template Result create_eof_impl<OP_TXCREATE>(
StackTop stack, int64_t gas_left, ExecutionState& state, code_iterator& pos) noexcept;
} // namespace evmone::instr::core
1 change: 1 addition & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ enum Opcode : uint8_t
OP_EXCHANGE = 0xe8,

OP_EOFCREATE = 0xec,
OP_TXCREATE = 0xed,
OP_RETURNCONTRACT = 0xee,

OP_CREATE = 0xf0,
Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -411,6 +412,7 @@ constexpr inline std::array<Traits, 256> 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};
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/instructions_xmacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
\
Expand Down
9 changes: 9 additions & 0 deletions test/state/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
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,
Expand Down Expand Up @@ -66,6 +69,12 @@
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";

Check warning on line 77 in test/state/errors.hpp

View check run for this annotation

Codecov / codecov/patch

test/state/errors.hpp#L72-L77

Added lines #L72 - L77 were not covered by tests
case CREATE_BLOB_TX:
return "blob transaction must not be a create transaction";
case EMPTY_BLOB_HASHES_LIST:
Expand Down
2 changes: 2 additions & 0 deletions test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ evmc_tx_context Host::get_tx_context() const noexcept
intx::be::store<uint256be>(m_block.blob_base_fee),
m_tx.blob_hashes.data(),
m_tx.blob_hashes.size(),
m_tx_initcodes.data(),
m_tx_initcodes.size(),
};
}

Expand Down
13 changes: 12 additions & 1 deletion test/state/host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -57,12 +58,22 @@ class Host : public evmc::Host
const BlockInfo& m_block;
const Transaction& m_tx;
std::vector<Log> m_logs;
std::vector<evmc_tx_initcode> 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<Log>&& take_logs() noexcept { return std::move(m_logs); }

Expand Down
35 changes: 33 additions & 2 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <evmone/eof.hpp>
#include <evmone/evmone.h>
#include <evmone/execution_state.hpp>
#include <algorithm>

namespace evmone::state
{
Expand Down Expand Up @@ -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<const bytes> 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;
Expand All @@ -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
Expand Down Expand Up @@ -253,8 +262,30 @@ std::variant<int64_t, std::error_code> 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);
Expand Down
4 changes: 4 additions & 0 deletions test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -217,6 +220,7 @@ struct Transaction
intx::uint256 r;
intx::uint256 s;
uint8_t v = 0;
std::vector<bytes> initcodes;
};

struct Log
Expand Down
6 changes: 6 additions & 0 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<bytes32>(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<bytes>(initcode));
}
}

template <>
Expand Down
1 change: 1 addition & 0 deletions test/unittests/instructions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ void state_transition::export_state_test(const TransactionReceipt& receipt, cons
jtx["maxPriorityFeePerGas"] = hex0x(tx.max_priority_gas_price);
}

if (tx.type == Transaction::Type::initcodes)
{
auto& jinitcodes = jtx["initcodes"] = json::json::array();
for (const auto& initcode : tx.initcodes)
jinitcodes.emplace_back(hex0x(initcode));
}

jtx["data"][0] = hex0x(tx.data);
jtx["gasLimit"][0] = hex0x(tx.gas_limit);
jtx["value"][0] = hex0x(tx.value);
Expand Down
Loading