EVM Object Format (EOF) removes the possibility to create contracts using CREATE or CREATE2 instructions. We introduce a new/replacement method in form of pair of instructions : EOFCREATE and RETURNCONTRACT to provide a way to create contracts using EOF containers.
Motivation
This EIP uses terminology from the EIP-3540 which introduces the EOF format.
EOF aims to remove code observability, which is a prerequisite to legacy EVM contract creation logic using legacy-style create transactions, CREATE or CREATE2, because both the initcode and code are available to the EVM and can be manipulated. On the same premise, EOF removes opcodes like CODECOPY and EXTCODECOPY, introducing EOF subcontainers as a replacement to cater for factory contracts creating other contracts.
The new instructions introduced in this EIP operate on EOF containers enabling factory contract use case that legacy EVM has.
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.
We introduce two new instructions on the same block number EIP-3540 is activated on:
EOFCREATE (0xec)
RETURNCONTRACT (0xee)
Execution Semantics
The instructions CREATE, CREATE2 are made obsolete and rejected by validation in EOF contracts. They are only available in legacy contracts.
If instructions CREATE and CREATE2 have EOF code as initcode (starting with EF00 magic)
deployment fails (returns 0 on the stack)
caller’s nonce is not updated and gas for initcode execution is not consumed
Overview of the new contract creation flow
In EOF EVM, new bytecode is delivered by means of creation transactions (with an empty to) in the form of an EOF container (initcontainer). Such a container may contain arbitrarily deeply nesting subcontainers. The initcontainer and its subcontainers are recursively validated according to all the validation rules applicable for the EOF version in question. Next, the 0th code section of the initcontainer is executed and may eventually call a RETURNCONTRACT instruction, which will refer to a subcontainer to be finally deployed to an address.
EOF creation transactions (ones with an empty to and with data starting with EF00 magic) are defined in detail in EIP-7698.
EOFCREATE instruction is in turn a replacement of the CREATE and CREATE2 legacy instructions allowing factory contracts to create other contracts. The main difference to the creation transaction is that the initcontainer is selected to be one of the subcontainers of the EOF container calling EOFCREATE. It is worth noting that no validation is performed at this point, as it has already been done when the factory contract containing EOFCREATE was deployed.
Details on each instruction follow in the next sections.
EOFCREATE
deduct TX_CREATE_COST gas
halt with exceptional failure if the current frame is in static-mode.
read immediate operand initcontainer_index, encoded as 8-bit unsigned value
pop value, salt, input_offset, input_size from the operand stack
perform (and charge for) memory expansion using [input_offset, input_size]
load initcode EOF subcontainer at initcontainer_index in the container from which EOFCREATE is executed
let initcontainer be that EOF container, and initcontainer_size its length in bytes declared in its parent container header
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.
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 || keccak256(initcontainer))[12:]
behavior on accessed_addresses and address collision is same as CREATE2 (rules for CREATE2 from EIP-684 and EIP-2929 apply to EOFCREATE)
an unsuccessful execution of initcode results in pushing 0 onto the stack
can populate returndata if execution REVERTed
a successful execution ends with initcode executing RETURNCONTRACT{deploy_container_index}(aux_data_offset, aux_data_size) instruction (see below). 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
RETURNCONTRACT
read immediate operand deploy_container_index, encoded as 8-bit unsigned value
pop two values from the operand stack: aux_data_offset, aux_data_size referring to memory section that will be appended to deployed container’s data
cost 0 gas + possible memory expansion for aux data
ends initcode frame execution and returns control to EOFCREATE/4 caller frame where deploy_container_index and aux_data are used to construct deployed contract (see above)
instruction exceptionally aborts if after the appending, data section size would overflow the maximum data section size or underflow (i.e. be less than data section size declared in the header)
Code Validation
We extend code section validation rules (as defined in EIP-3670).
EOFCREATEinitcontainer_index must be less than num_container_sections
EOFCREATE the subcontainer pointed to by initcontainer_index must have its len(data_section) equal data_size, i.e. data section content is exactly as the size declared in the header (see Data section lifecycle)
EOFCREATE the subcontainer pointed to by initcontainer_index must not contain either a RETURN or STOP instruction
RETURNCONTRACTdeploy_container_index must be less than num_container_sections
RETURNCONTRACT the subcontainer pointed to deploy_container_index must not contain a RETURNCONTRACT instruction
It is an error for a container to contain both RETURNCONTRACT and either of RETURN or STOP
It is an error for a subcontainer to never be referenced in its parent container
It is an error for a given subcontainer to be referenced by both RETURNCONTRACT and EOFCREATE
RJUMP, RJUMPI and RJUMPV immediate argument value (jump destination relative offset) validation: code section is invalid in case offset points to the byte directly following either EOFCREATE or RETURNCONTRACT instruction.
Data Section Lifecycle
For an EOF container which has not yet been deployed, the data_section is only a portion of the final data_section after deployment.
Let’s define it as pre_deploy_data_section and as pre_deploy_data_size the data_size declared in that container’s header.
pre_deploy_data_size >= len(pre_deploy_data_section), which anticipates more data to be appended to the pre_deploy_data_section during the process of deploying.
aux_data is the data which is appended to pre_deploy_data_section on RETURNCONTRACT instruction.
static_aux_data is a subrange of aux_data, which size is known before RETURNCONTRACT and equals pre_deploy_data_size - len(pre_deploy_data_section).
dynamic_aux_data is the remainder of aux_data.
data_size in the deployed container header is updated to be equal len(data_section).
Summarizing, there are pre_deploy_data_size bytes in the final data section which are guaranteed to exist before the EOF container is deployed and len(dynamic_aux_data) bytes which are known to exist only after.
This impacts the validation and behavior of data-section-accessing instructions: DATALOAD, DATALOADN, and DATACOPY, see EIP-7480.
Rationale
Data section appending
The data section is appended to during contract creation and also its size needs to be updated in the header. Alternative designs were considered, where:
additional section kinds for the data were introduced
additional fields describing a subcontainer were introduced
data section would be written over as opposed to being appended to, requiring it to be filled with 0 bytes prior to deployment
All of these alternatives either complicated the otherwise simple data structures or took away useful features (like the dynamically sized portion of the data section).
Backwards Compatibility
This change poses no risk to backwards compatibility, as it is introduced at the same time EIP-3540 is. The new instructions are not introduced for legacy bytecode (code which is not EOF formatted), and the contract creation options do not change for legacy bytecode.
CREATE and CREATE2 calls with EF00 initcode fail early without executing the initcode. Previously, in both cases the initcode execution would begin and fail on the first undefined instruction EF.
Test Cases
Creation transaction, CREATE and CREATE2 cannot have its code starting with 0xEF, but such cases are covered already in EIP-3541. However, new cases must be added where CREATE or CREATE2 have its initcode being (validly or invalidly) EOF formatted:
It is the EOF creation transaction (specified in EIP-7698) which needs a detailed review and discussion as that is where external unverified code enters the state. Among others:
Is its complexity under control, ruling out any DoS attempts
Is it correctly priced and always charged for
Is the validation comprehensive and not allowing problematic code to be saved into the state