Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7873: EOF - TXCREATE and InitcodeTransaction type

Adds a `TXCREATE` instruction to EOF and an accompanying transaction type allowing to create EOF contracts from transaction data

Authors Piotr Dobaczewski (@pdobacz), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), Alex Beregszaszi (@axic), Danno Ferrin (@shemnon)
Created 2025-01-31
Discussion Link https://ethereum-magicians.org/t/eip-7873-eof-txcreate-instruction-and-initcodetransaction-type/22765
Requires EIP-3540, EIP-3670, EIP-3860, EIP-7620

Abstract

EVM Object Format (EOF) removes the possibility to create contracts using creation transactions (with an empty to field), CREATE or CREATE2 instructions. We introduce a new instruction: TXCREATE, as well as a new transaction type (InitcodeTransaction), to provide a way to create contracts using EOF containers in transaction data.

Motivation

This EIP uses terminology from the EIP-3540 which introduces the EOF format.

Creation transaction and creation instructions CREATE and CREATE2 are means provided by legacy EVM to deploy new code, but per requirement of removing code observability, they are not allowed to deploy EOF code. To allow Externally Owned Accounts (EOAs) to deploy EOF contrats, there must be a way to create EOF contracts using bytecode delivered in transaction data.

Additionally, the new instruction and transaction type introduced in this EIP enable contracts to create other contracts using initcode from the transaction data, which in legacy EVM is achieved via a combination of CREATE or CREATE2 and loading the initcode from calldata.

This mechanism complements EOFCREATE and RETURNCONTRACT instructions from EIP-7620, and thus all use cases of contract creation that are available in legacy EVM are enabled for EOF.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Parameters

Constant Value
INITCODE_TX_TYPE Bytes1(0x05)
MAX_INITCODE_COUNT 256
CREATOR_CONTRACT_ADDRESS tbd
CREATOR_CONTRACT_BYTECODE tbd
TX_CREATE_COST Defined as 32000 in the Ethereum Execution Layer Specs
STACK_DEPTH_LIMIT Defined as 1024 in the Ethereum Execution Layer Specs
GAS_CODE_DEPOSIT Defined as 200 in the Ethereum Execution Layer Specs
TX_DATA_COST_PER_ZERO Defined as 4 in the Ethereum Execution Layer Specs
TX_DATA_COST_PER_NON_ZERO Defined as 16 in the Ethereum Execution Layer Specs
MAX_CODE_SIZE Defined as 24576 in EIP-170
MAX_INITCODE_SIZE Defined as 2 * MAX_CODE_SIZE in EIP-3860

Transaction Types

Introduce new transaction InitcodeTransaction (type INITCODE_TX_TYPE) which extends EIP-1559 (type 2) transaction by adding a new field initcodes: List[ByteList[MAX_INITCODE_SIZE], MAX_INITCODE_COUNT].

The initcodes can only be accessed via the TXCREATE instruction (see below), therefore InitcodeTransactions are intended to be sent to contracts including TXCREATE in their execution.

We introduce a standardized Creator Contract, which eliminates the need to have create transactions with empty to. See Creator Contract for details.

Gas schedule

initcodes items data costs the same as calldata: transaction gas of an InitcodeTransaction is extended to include tokens in initcodes alongside tokens in calldata. Using the conventions from EIP-7623, the transaction gas is calculated as:

STANDARD_TOKEN_COST = 4
TOTAL_COST_FLOOR_PER_TOKEN = 10

tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
tokens_in_initcodes = zero_bytes_in_initcodes + nonzero_bytes_in_initcodes * 4
tx.gasUsed = (
    21000
    +
    max(
        STANDARD_TOKEN_COST * (tokens_in_calldata + tokens_in_initcodes)
        + execution_gas_used,
        TOTAL_COST_FLOOR_PER_TOKEN * (tokens_in_calldata + tokens_in_initcodes)
    )
)

Transaction validation

  • InitcodeTransaction is invalid if there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
  • InitcodeTransaction is invalid if any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
  • InitcodeTransaction is invalid if the to is nil.

Under transaction validation rules initcodes are not validated for conforming to the EOF specification. They are only validated when accessed via TXCREATE. This avoids potential DoS attacks of the mempool. If during the execution of an InitcodeTransaction no TXCREATE instruction is called, such transaction is still valid.

Legacy creation transactions (any transactions with empty to) that start with the two bytes EF00 will be invalid. They will not be parsed into EOF containers and they will not be executed as legacy bytecode.

RLP and signature

Given the definitions from EIP-2718 the TransactionPayload for an InitcodeTransaction is the RLP serialization of:

[chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, initcodes, y_parity, r, s]

TransactionType is INITCODE_TX_TYPE and the signature values y_parity, r, and s are calculated by constructing a secp256k1 signature over the following digest:

keccak256(INITCODE_TX_TYPE || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, initcodes]))

The EIP-2718 ReceiptPayload for this transaction is rlp([status, cumulative_transaction_gas_used, logs_bloom, logs]).

Execution Semantics

Introduce a new instruction on the same block number EIP-3540 is activated on: TXCREATE (0xed).

TXCREATE

  • deduct TX_CREATE_COST gas
  • halt with exceptional failure if the current frame is in static-mode.
  • pop tx_initcode_hash, value, salt, input_offset, input_size from the operand stack
  • perform (and charge for) memory expansion using [input_offset, input_size]
  • load initcode EOF container from the transaction initcodes array which hashes to tx_initcode_hash
    • fails (returns 0 on the stack) if such initcode does not exist in the transaction, or if called from a transaction of TransactionType other than INITCODE_TX_TYPE
      • caller’s nonce is not updated and gas for initcode execution is not consumed.
    • let initcontainer be that EOF container, and initcontainer_size its length in bytes
  • check that current call depth is below STACK_DEPTH_LIMIT and that caller balance is enough to transfer value
    • in case of failure return 0 on the stack, caller’s nonce is not updated and gas for initcode execution is not consumed.
  • validate the initcode container and all its subcontainers recursively
    • unlike in general validation, initcontainer is additionally required to have data_size declared in the header equal to actual data_section size.
    • validation includes checking that the initcontainer does not contain RETURN or STOP
  • fails (returns 0 on the stack) if container was invalid
    • caller’s nonce is not updated and gas for initcode execution is not consumed.
  • caller’s memory slice [input_offset:input_size] is used as calldata
  • execute the container and deduct gas for execution. The 63/64th rule from EIP-150 applies.
  • increment sender account’s nonce
  • calculate new_address as keccak256(0xff || sender || salt)[12:]
  • behavior on accessed_addresses and address collision is same as CREATE2 (rules for CREATE2 from EIP-684 and EIP-2929 apply to TXCREATE)
  • an unsuccessful execution of initcode results in pushing 0 onto the stack
    • can populate returndata if execution REVERTed
    • sender’s nonce stays updated
  • a successful execution ends with initcode executing RETURNCONTRACT{deploy_container_index}(aux_data_offset, aux_data_size) instruction (see EIP-7620). After that:
    • load deploy EOF subcontainer at deploy_container_index in the container from which RETURNCONTRACT is executed
    • concatenate data section with (aux_data_offset, aux_data_offset + aux_data_size) memory segment and update data size in the header
    • if updated deploy container size exceeds MAX_CODE_SIZE, instruction exceptionally aborts
    • set state[new_address].code to the updated deploy container
    • push new_address onto the stack
  • deduct GAS_CODE_DEPOSIT * deployed_code_size gas

Note that the implementations are expected to cache the result of container validation for the time of current transaction execution, and therefore the cost of each container’s validation is sufficiently covered by InitcodeTransaction intrinsic cost (initcodes charge).

Creator Contract

We introduce a standardised Creator Contract (i.e. written in EVM, but existing at a known address), which eliminates the need to have create transactions with empty to.

At the start of the block in which EIP-3540 activates, set the code of CREATOR_CONTRACT_ADDRESS to CREATOR_CONTRACT_BYTECODE. CREATOR_CONTRACT_ADDRESS is not treated like a precompile. CREATOR_CONTRACT_BYTECODE corresponds to the following source code compiled to EOF:

{
    /// Takes [tx_initcode_hash][salt][init_data] as input,
    /// creates contract and returns the address or failure otherwise

    // init_data.length can be 0, but the first 2 words are mandatory
    let size := calldatasize()
    if lt(size, 64) { revert(0, 0) }

    let tx_initcode_hash := calldataload(0)
    let salt := calldataload(32)

    mstore8(0, 0xff)  // a magic value to ensure a specific preimage space
    calldatacopy(1, 0, size)  // copy tx_initcode_hash, salt and init_data to memory to hash
    let commitment_size := add(size, 1)
    let final_salt := keccak256(0, commitment_size)

    let init_data_size := sub(size, 64)
    calldatacopy(0, 64, init_data_size)

    let ret := txcreate(tx_initcode_hash, callvalue(), final_salt, 0, init_data_size)
    if iszero(ret) { revert(0, 0) }

    mstore(0, ret)
    return(0, 32)

    // Helper to compile this with existing Solidity (with --strict-assembly mode)
    function txcreate(a, b, c, d, e) -> f {
        f := verbatim_5i_1o(hex"ed", a, b, c, d, e)
    }
}

Rationale

TXCREATE failure modes

TXCREATE has two “light” failure modes in case the initcontainer is not present and in case the EOF validation is unsuccessful. An alternative design where both cases led to a “hard” failure (consuming the entire gas available) was considered. We decided to have the more granular and forgiving failure modes in order to align the gas costs incurred to the actual work the EVM performs.

Creator Contract

EOF contract creation requires a predeployed Creator Contract to be introduced, because neither legacy contracts nor create transactions can deploy EOF code to bootstrap. The alternative approach was to continue using legacy creation mechanisms, by either still relying on fetching the initcode from memory and not satisfy the overarching requirement of code non-observability, or to abuse the legacy creation transactions mechanism.

Since previous discussions about TXCREATE, which led to its removal from EOF along with the Creator Contract, there is wider consensus to allow predeploy contracts be put into the state, for example as a means to reduce the number of supported precompiles. Meanwhile, opinions about the importance of supporting deployments to deterministic, counterfactual, cross-chain addresses have been voiced by the community, making the case for this proposal stronger.

This also makes EIP-7698 (EOF - Creation transaction) no longer an essential requirement for deploying EOF contracts onto the chain. The EIP could be removed from EOFv1 and withdrawn.

New address hashing scheme

TXCREATE uses the scheme new_address = keccak256(0xff || sender || salt)[12:], same as EOFCREATE instruction. The decision whether to include initcontainer hash into salt is left to the TXCREATE caller. See EIP-7620 for detailed rationale.

EOF creation transactions vs deployment patterns

Relying on the EOF creation transactions as alternative solution makes it impossible for smart contract wallets to deploy arbitrary EOF contracts (only EOAs can). At the same time, it is a use case current legacy creation rules allow, thanks to CREATE and CREATE2 instructions. A workaround where those arbitrary EOF contracts are first “uploaded” to a factory contract, and then deployed using an EXTDELEGATECALL-EOFCREATE sequence, is very expensive, as it requires the deployed contract to be put on-chain twice. Because of this, the approach proposed in this EIP is more compatible with the Account Abstraction (AA) roadmap, where smart contract wallets should have feature parity with EOAs.

On top of this, relying on nonce-based hashing scheme to obtain addresses of newly created contracts, like in the case of the EOF creation transactions, would prevent EOF contracts from being deployed counterfactually to deterministic, cross-chain addresses. Introduction of the TXCREATE instruction, combined with the proposed “toehold” Creator Contract predeployed to an agreed upon address, supports this out of the box. ERCs can be written to provide other toehold contracts with features not supported by this inital contract, such as salt-less deployment and hashing in the sender’s address as part of the salt.

Backwards Compatibility

This change poses no risk to backwards compatibility, as it is introduced at the same time EIP-3540 is. The new instruction are not introduced for legacy bytecode (code which is not EOF formatted), and the contract creation options do not change for legacy bytecode. The transactions of the new type are invalid until this change activates.

Security Considerations

Needs discussion.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Piotr Dobaczewski (@pdobacz), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), Alex Beregszaszi (@axic), Danno Ferrin (@shemnon), "EIP-7873: EOF - TXCREATE and InitcodeTransaction type [DRAFT]," Ethereum Improvement Proposals, no. 7873, January 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7873.