EOF Contract Creation
相关视频
正文
Abstract
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 RETURNCODE 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.
Wherever not explicitly listed, the rules of EOF contract creation, as well as the EOFCREATE instruction, should be identical or analogous to those of CREATE2 instruction. This includes but is not limited to:
- behavior on
accessed_addressesand address collision (EIP-684 and EIP-2929) - EVM execution frame created for the
EOFCREATEinitcode - memory, account context etc. - nonce bumping of the account of newly created contract EIP-161
- balance checking and transfer for the creation endowment (
valueargument)
Parameters
| Constant | Value |
|---|---|
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 |
MAX_CODE_SIZE | Defined as 24576 in EIP-170 |
We introduce two new instructions on the same block number EIP-3540 is activated on:
EOFCREATE(0xec)RETURNCODE(0xee)
If the code is legacy bytecode, any of these instructions result in an exceptional halt. (Note: This means no change to behaviour.)
Execution Semantics
- The instructions
CREATE,CREATE2are made obsolete and rejected by validation in EOF contracts. They are only available in legacy contracts. - If instructions
CREATEandCREATE2have EOF code as initcode (starting withEF00magic)- deployment fails (returns 0 on the stack)
- caller's nonce is not updated and gas for initcode execution is not consumed
In the context of legacy bytecode execution any of these instructions (EOFCREATE, RETURNCODE) result in an exceptional halt. (Note: This means no change to behaviour.)
Overview of the new contract creation flow
In EOF EVM, new bytecode is introduced to the state by means of InitcodeTransaction delivering an EOF container (initcontainer). Such a container may include 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 RETURNCODE instruction, which will refer to a subcontainer to be finally deployed to an address.
InitcodeTransactions are defined in detail in EIP-7873.
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 InitcodeTransaction is that the initcontainer is selected to be one of the subcontainers of the EOF container calling EOFCREATE (and not one of transaction.initcodes). 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_COSTgas - halt with exceptional failure if the current frame is in
static-mode. - read immediate operand
initcontainer_index, encoded as 8-bit unsigned value - pop
salt,input_offset,input_size,valuefrom the operand stack - perform (and charge for) memory expansion using
[input_offset, input_size] - load initcode EOF subcontainer at
initcontainer_indexin the container from whichEOFCREATEis executed- let
initcontainerbe that EOF container
- let
- check that current call depth is below
STACK_DEPTH_LIMITand that caller balance is enough to transfervalue- 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
senderaccount's nonce - calculate
new_addressaskeccak256(0xff || sender32 || salt)[12:], wheresender32is the sender address left-padded to 32 bytes with zeros - an unsuccessful execution of initcode results in pushing
0onto the stack- can populate returndata if execution
REVERTed
- can populate returndata if execution
- a successful execution ends with initcode executing
RETURNCODE{deploy_container_index}(aux_data_offset, aux_data_size)instruction (see below). After that:- load deploy EOF subcontainer at
deploy_container_indexin the container from whichRETURNCODEis executed - concatenate data section with
(aux_data_offset, aux_data_offset + aux_data_size)memory segment and update data size in the header if needed. - if updated deploy container size exceeds
MAX_CODE_SIZEinstruction exceptionally aborts - set
state[new_address].codeto the updated deploy container - push
new_addressonto the stack
- load deploy EOF subcontainer at
- deduct
GAS_CODE_DEPOSIT * deployed_code_sizegas
RETURNCODE
- read immediate operand
deploy_container_index, encoded as 8-bit unsigned value - pop two values from the operand stack:
aux_data_offset,aux_data_sizereferring 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 caller frame where
deploy_container_indexandaux_dataare 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_indexmust be less thannum_container_sectionsEOFCREATEthe subcontainer pointed to byinitcontainer_indexmust have itslen(data_section)equaldata_size, i.e. data section content is exactly as the size declared in the header (see Data section lifecycle)EOFCREATEthe subcontainer pointed to byinitcontainer_indexmust not contain either aRETURNorSTOPinstructionRETURNCODEdeploy_container_indexmust be less thannum_container_sectionsRETURNCODEthe subcontainer pointed todeploy_container_indexmust not contain aRETURNCODEinstruction- It is an error for a container to contain both
RETURNCODEand either ofRETURNorSTOP - 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
RETURNCODEandEOFCREATE RJUMP,RJUMPIandRJUMPVimmediate argument value (jump destination relative offset) validation: code section is invalid in case offset points to the byte directly following eitherEOFCREATEorRETURNCODEinstruction.
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.
pre_deploy_data_section
| |
\___________pre_deploy_data_size______/
For a deployed EOF container, the final data_section becomes:
pre_deploy_data_section | static_aux_data | dynamic_aux_data
| | | |
| \___________aux_data___________/
| | |
\___________pre_deploy_data_size______/ |
| |
\________________________data_size_______________________/
where:
aux_datais the data which is appended topre_deploy_data_sectiononRETURNCODEinstruction.static_aux_datais a subrange ofaux_data, which size is known beforeRETURNCODEand equalspre_deploy_data_size - len(pre_deploy_data_section).dynamic_aux_datais the remainder ofaux_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).
keccak256(initcontainer) in the new_address hashing scheme
new_address = keccak256(0xff || sender || salt || keccak256(initcontainer))[12:] was originally proposed as the way to calculate the address of newly created contract, similar, but not exactly equal, to what CREATE2 uses.
This alternative however goes against code non-observability, because it locks in the contents of the initcontainer e.g. preventing re-writing it in some future upgrade. It also seems unnecessarily expensive: EOFCREATE can only pick up one of its subcontainers, yet the hash value would need to be recalculated on every execution of EOFCREATE.
Other ways of removing code observability, yet keeping some form of witness of the code, were considered. However, keeping only sender and salt allows the implementer of the factory contract (i.e. one containing the EOFCREATE instruction) to include the code witness via the salt anyway, if that's necessary for the particular use case. Therefore, keeping the new_address formula minimal is the most flexible approach with better separation of concerns.
Leaving the keccak256(initcontainer) out of the new_address hash has also the benefit of making the new_address independent of the metadata section (proposed for the EOF in a separate EIP), which is a desired property. Unfortunately, if a factory wants to opt into committing to a particular initcontainer, it needs to include it in the salt, and that will include the metadata section.
EOFCREATE stack argument order
EXT*CALL instructions from EIP-7069 have had their stack argument order changed, as compared to that of legacy instructions *CALL. We follow the same change to have EOFCREATE stack arg order match those of EXTCALL.
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:
| Initcode | Expected result |
|---|---|
0xEF | initcode starts execution and fails |
0xEF01 | initcode starts execution and fails |
0xEF5f | initcode starts execution and fails |
0xEF00 | CREATE / CREATE2 fails early, returns 0 and keeps sender nonce intact |
0xEF0001 | as above |
| valid EOFv1 container | as above |
Security Considerations
It is the EOF InitcodeTransaction (specified in EIP-7873) 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
Copyright
Copyright and related rights waived via CC0.