From 5ee8548e82efc5f0a2d10a9dfb6f9c2deff0c414 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:46:48 +0100 Subject: [PATCH 01/18] Bring back TXCREATE --- spec/eof.md | 56 ++++++++++++++++++++++++++++++++++- spec/eof_future_upgrades.md | 59 +------------------------------------ 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 0c1e2a2..880bf92 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -103,6 +103,45 @@ On top of the types defined in the table above, the following validity constrain - the total size of not yet deployed container might be up to `data_size` lower than the above values due to how the data section is rewritten and resized during deployment (see [Data Section Lifecycle](#data-section-lifecycle)) - the total size of a container must not exceed `MAX_INITCODE_SIZE` (as defined in EIP-3860) +## Transaction Types + +Introduce new transaction type `InitcodeTransaction` 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. + +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. + +`initcodes` data is similar to calldata for two reasons: +1) It must be fully transmitted in the transaction. +2) It is accessible to the EVM, but it can't be fully loaded into EVM memory. + +For these reasons, define cost of each of the `initcodes` items same as calldata (16 gas for non-zero bytes, 4 for zero bytes -- see EIP-2028). The intrinsic gas of an `InitcodeTransaction` is extended by the sum of all those items' costs. + +EIP-3860 and EIP-170 still apply, i.e. `MAX_CODE_SIZE` as 24576, `MAX_INITCODE_SIZE` as `2 * MAX_CODE_SIZE`. Define `MAX_INITCODE_COUNT` as 256. + +`InitcodeTransaction` is invalid if either: +- there are more than `MAX_INITCODE_COUNT` entries in `initcodes` +- `initcodes` is an empty array +- length of any entry in `initcodes` exceeds `MAX_INITCODE_SIZE` +- any entry in `initcodes` has zero length +- the `to` is `nil` + +#### RLP and signature + +Given the definitions from [EIP-2718](https://eips.ethereum.org/EIPS/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` (`0x04`) 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](https://eips.ethereum.org/EIPS/eip-2718) `ReceiptPayload` for this transaction is `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`. + ## Execution Semantics Code executing within an EOF environment will behave differently than legacy code. We can break these differences down into i) changes to existing behavior and ii) introduction of new behavior. @@ -216,11 +255,26 @@ The following instructions are introduced in EOF code: - set `state[new_address].code` to the updated deploy container - push `new_address` onto the stack - deduct `200 * deployed_code_size` gas +- `TXCREATE (0xed)` instruction + - Works the same as `EOFCREATE` except: + - does not have `initcontainer_index` immediate + - pops one more value from the stack (first argument): `tx_initcode_hash` + - loads the 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. Only `TXCREATE` constant gas was consumed + - let `initcontainer` be that EOF container, and `initcontainer_size` its length in bytes + - in addition to hashing charge as in `EOFCREATE`, deducts `2 * ((initcontainer_size + 31) // 32)` gas (EIP-3860 charge) + - just before executing the initcode container: + - **validates the initcode container and all its subcontainers recursively** + - validation includes checking that the `initcontainer` does not contain `RETURN` or `STOP` + - in addition to this, checks if the initcode container has its `len(data_section)` equal to `data_size`, i.e. data section content is exactly as the size declared in the header (see [Data section lifecycle](#data-section-lifecycle)) + - fails (returns 0 on the stack) if any of those was invalid + - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant, EIP-3860 gas and hashing gas were consumed - `RETURNCODE (0xee)` instruction - loads `uint8` immediate `deploy_container_index` - pops two values from the 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` caller frame (unless called in the topmost frame of a creation transaction). + - ends initcode frame execution and returns control to `EOFCREATE` or `TXCREATE` caller frame. - `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) - `DATALOAD (0xd0)` instruction diff --git a/spec/eof_future_upgrades.md b/spec/eof_future_upgrades.md index 04a458b..0019580 100644 --- a/spec/eof_future_upgrades.md +++ b/spec/eof_future_upgrades.md @@ -2,61 +2,4 @@ **This document gathers the designs which were excluded from the [Mega EOF spec](./eof.md), i.e. will not be a part of the first EOF release. They are planned to be introduced in a future upgrade.** -# `TXCREATE` and `InitcodeTransaction` - -## Transaction Types - -Introduce new transaction type `InitcodeTransaction` 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. - -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. - -`initcodes` data is similar to calldata for two reasons: -1) It must be fully transmitted in the transaction. -2) It is accessible to the EVM, but it can't be fully loaded into EVM memory. - -For these reasons, define cost of each of the `initcodes` items same as calldata (16 gas for non-zero bytes, 4 for zero bytes -- see EIP-2028). The intrinsic gas of an `InitcodeTransaction` is extended by the sum of all those items' costs. - -EIP-3860 and EIP-170 still apply, i.e. `MAX_CODE_SIZE` as 24576, `MAX_INITCODE_SIZE` as `2 * MAX_CODE_SIZE`. Define `MAX_INITCODE_COUNT` as 256. - -`InitcodeTransaction` is invalid if either: -- there are more than `MAX_INITCODE_COUNT` entries in `initcodes` -- `initcodes` is an empty array -- length of any entry in `initcodes` exceeds `MAX_INITCODE_SIZE` -- any entry in `initcodes` has zero length -- the `to` is `nil` - -#### RLP and signature - -Given the definitions from [EIP-2718](https://eips.ethereum.org/EIPS/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` (`0x04`) 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](https://eips.ethereum.org/EIPS/eip-2718) `ReceiptPayload` for this transaction is `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`. - -### New Behavior - -- `TXCREATE (0xed)` instruction - - Works the same as `EOFCREATE` except: - - does not have `initcontainer_index` immediate - - pops one more value from the stack (first argument): `tx_initcode_hash` - - loads the 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. Only `TXCREATE` constant gas was consumed - - let `initcontainer` be that EOF container, and `initcontainer_size` its length in bytes - - in addition to hashing charge as in `EOFCREATE`, deducts `2 * ((initcontainer_size + 31) // 32)` gas (EIP-3860 charge) - - just before executing the initcode container: - - **validates the initcode container and all its subcontainers recursively** - - validation includes checking that the `initcontainer` does not contain `RETURN` or `STOP` - - in addition to this, checks if the initcode container has its `len(data_section)` equal to `data_size`, i.e. data section content is exactly as the size declared in the header (see [Data section lifecycle](#data-section-lifecycle)) - - fails (returns 0 on the stack) if any of those was invalid - - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant, EIP-3860 gas and hashing gas were consumed +(nothing here so far) \ No newline at end of file From 2f427d51dd696fa1cc9529e6a03a3f9d806e0682 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:39:30 +0100 Subject: [PATCH 02/18] Bring back Creator Contract, remove creation tx --- spec/eof.md | 57 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 880bf92..659ec81 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -109,6 +109,8 @@ Introduce new transaction type `InitcodeTransaction` which extends EIP-1559 (typ 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 standardised Creator Contract (i.e. written in EVM, but existing at a known address, such as precompiles), which eliminates the need to have create transactions with empty `to`. Deployment of the Creator Contract will require an irregular state change at EOF activation block. Note that such introduction of the Creator Contract is needed, because only EOF contracts can create EOF contracts. See the appendix below for Creator Contract code. + 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. `initcodes` data is similar to calldata for two reasons: @@ -157,6 +159,7 @@ Code executing within an EOF environment will behave differently than legacy cod - The instruction `JUMPDEST` is renamed to `NOP` and remains charging 1 gas without any effect. - Note: jumpdest-analysis is not performed anymore. - EOF contract may not deploy legacy code (it is naturally rejected on the code validation stage) +- Legacy creation transactions (any tranactions with empty `to`) are invalid in case `data` contains EOF code (starts with `EF00` magic) - When executed from a legacy contract, 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 @@ -165,29 +168,6 @@ Code executing within an EOF environment will behave differently than legacy cod **NOTE** Like for legacy targets, the aforementioned behavior of `EXTCODECOPY`, `EXTCODEHASH` and `EXTCODESIZE` does not apply to EOF contract targets mid-creation, i.e. those report same as accounts without code. -#### Creation transactions - -Creation transactions (tranactions with empty `to`), with `data` containing EOF code (starting with `EF00` magic) are interpreted as having a concatenation of EOF `initcontainer` and `calldata` in the `data` and: - -1. intrinsic gas cost rules and limits defined in EIP-3860 for legacy creation transaction apply. The entire `data` of the transaction is used for these calculations -2. Find the split of `data` into `initcontainer` and `calldata`: - - Parse EOF header - - Find `intcontainer` size by reading all section sizes from the header and adding them up with the header size to get the full container size. -3. Validate the `initcontainer` 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` -4. If EOF header parsing or full container validation fails, transaction is considered valid and failing. Gas for initcode execution is not consumed, only intrinsic creation transaction costs are charged. -5. `calldata` part of transaction `data` that follows `initcontainer` is treated as calldata to pass into the execution frame -6. execute the container and deduct gas for execution - 1. Calculate `new_address` as `keccak256(sender || sender_nonce)[12:]` - 2. A successful execution ends with initcode executing `RETURNCODE{deploy_container_index}(aux_data_offset, aux_data_size)` instruction (see below). After that: - - load deploy-contract from EOF subcontainer at `deploy_container_index` in the container from which `RETURNCODE` is executed - - concatenate data section with `(aux_data_offset, aux_data_offset + aux_data_size)` memory segment and update data size in the header - - let `deployed_code_size` be updated deploy container size - - if `deployed_code_size > MAX_CODE_SIZE` instruction exceptionally aborts - - set `state[new_address].code` to the updated deploy container -7. deduct `200 * deployed_code_size` gas - **NOTE** Legacy contract and legacy creation transactions may not deploy EOF code, that is behavior from [EIP-3541](https://eips.ethereum.org/EIPS/eip-3541) is not modified. ### New Behavior @@ -402,6 +382,36 @@ During scanning, for each instruction: Annotated examples of EOF formatted containers demonstrating several key features of EOF can be found in [this test file within the `evmone` project repository](https://github.com/ethereum/evmone/blob/master/test/unittests/eof_example_test.cpp). +## Appendix: Creator Contract + +```solidity +{ + /// Takes [index][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_index := calldataload(0) + let salt := calldataload(32) + + let init_data_size := sub(size, 64) + calldatacopy(0, 64, init_data_size) + + let ret := txcreate(tx_initcode_index, callvalue(), 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) + } +} +``` + ## Appendix: Original EIPs These are the individual EIPs which evolved into this spec. @@ -416,4 +426,3 @@ These are the individual EIPs which evolved into this spec. - 📃[EIP-663](https://eips.ethereum.org/EIPS/eip-663): Unlimited SWAP and DUP instructions [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-663.md) - 📃[EIP-7069](https://eips.ethereum.org/EIPS/eip-7069): Revamped CALL instructions (*does not require EOF*) [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7069.md) - 📃[EIP-7620](https://eips.ethereum.org/EIPS/eip-7620): EOF - Contract Creation Instructions [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7620.md) -- 📃[EIP-7698](https://eips.ethereum.org/EIPS/eip-7698): EOF - Creation transaction [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7698.md) From c6f9338218e19e245829dbf9e41bb2fb2a8ea521 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:21:18 +0100 Subject: [PATCH 03/18] Minor updates: tx type 5 & "predeployed" contract to follow 7666 --- spec/eof.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 659ec81..eb46ca4 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -109,7 +109,7 @@ Introduce new transaction type `InitcodeTransaction` which extends EIP-1559 (typ 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 standardised Creator Contract (i.e. written in EVM, but existing at a known address, such as precompiles), which eliminates the need to have create transactions with empty `to`. Deployment of the Creator Contract will require an irregular state change at EOF activation block. Note that such introduction of the Creator Contract is needed, because only EOF contracts can create EOF contracts. See the appendix below for Creator Contract code. +We introduce a standardised Creator Contract (i.e. written in EVM, but existing at a known address, such as precompiles), which eliminates the need to have create transactions with empty `to`. The Creator Contract will be predeployed at the EOF activation block. Note that such introduction of the Creator Contract is needed, because only EOF contracts can create EOF contracts. See the appendix below for Creator Contract code. 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. @@ -136,7 +136,7 @@ Given the definitions from [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) t [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` (`0x04`) and the signature values `y_parity`, `r`, and `s` are calculated by constructing a secp256k1 signature over the following digest: +`TransactionType` is `INITCODE_TX_TYPE` (`0x05`) 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])) From 31b4723837658451fa1e7a2d6d884e1f52ff7e38 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:26:01 +0100 Subject: [PATCH 04/18] (tentative) - remove initcode hash from new_address --- spec/eof.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index eb46ca4..2209ea6 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -217,13 +217,12 @@ The following instructions are introduced in EOF code: - peform (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 - - deduct `6 * ((initcontainer_size + 31) // 32)` gas (hashing charge) - check call depth limit and whether caller balance is enough to transfer `value` - in case of failure returns 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:]` + - calculate `new_address` as `keccak256(0xff || sender || salt)[12:]` - behavior on `accessed_addresses` and address colission is same as `CREATE2` (rules for `CREATE2` from [EIP-684](https://eips.ethereum.org/EIPS/eip-684) and [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929) apply to `EOFCREATE`) - an unsuccesful execution of initcode results in pushing `0` onto the stack - can populate returndata if execution `REVERT`ed @@ -243,13 +242,13 @@ The following instructions are introduced in EOF code: - 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. Only `TXCREATE` constant gas was consumed - let `initcontainer` be that EOF container, and `initcontainer_size` its length in bytes - - in addition to hashing charge as in `EOFCREATE`, deducts `2 * ((initcontainer_size + 31) // 32)` gas (EIP-3860 charge) + - deduct `2 * ((initcontainer_size + 31) // 32)` gas (EIP-3860 charge) - just before executing the initcode container: - **validates the initcode container and all its subcontainers recursively** - validation includes checking that the `initcontainer` does not contain `RETURN` or `STOP` - in addition to this, checks if the initcode container has its `len(data_section)` equal to `data_size`, i.e. data section content is exactly as the size declared in the header (see [Data section lifecycle](#data-section-lifecycle)) - fails (returns 0 on the stack) if any of those was invalid - - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant, EIP-3860 gas and hashing gas were consumed + - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant and EIP-3860 gas were consumed - `RETURNCODE (0xee)` instruction - loads `uint8` immediate `deploy_container_index` - pops two values from the stack: `aux_data_offset`, `aux_data_size` referring to memory section that will be appended to deployed container's data From fe7d1d5defc0aae744a5b475c7c033f718990d93 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:13:25 +0100 Subject: [PATCH 05/18] Fixup: TXCREATE uses tx_initcode_hash not index --- spec/eof.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 2209ea6..42f6f5b 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -385,20 +385,20 @@ Annotated examples of EOF formatted containers demonstrating several key feature ```solidity { - /// Takes [index][salt][init_data] as input, + /// 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_index := calldataload(0) + let tx_initcode_hash := calldataload(0) let salt := calldataload(32) let init_data_size := sub(size, 64) calldatacopy(0, 64, init_data_size) - let ret := txcreate(tx_initcode_index, callvalue(), salt, 0, init_data_size) + let ret := txcreate(tx_initcode_hash, callvalue(), salt, 0, init_data_size) if iszero(ret) { revert(0, 0) } mstore(0, ret) From 6a6ef734276e708fa0bc1d8c7bec58576553b20f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:32:25 +0100 Subject: [PATCH 06/18] Fixup: Creator Contract must include tx_initcode_hash in salt --- spec/eof.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index 42f6f5b..a998f21 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -395,10 +395,13 @@ Annotated examples of EOF formatted containers demonstrating several key feature let tx_initcode_hash := calldataload(0) let salt := calldataload(32) + calldatacopy(0, 0, 64) // copy tx_initcode_hash and salt to memory to hash + let final_salt := keccak256(0, 64) + let init_data_size := sub(size, 64) calldatacopy(0, 64, init_data_size) - let ret := txcreate(tx_initcode_hash, callvalue(), salt, 0, 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) From 19a2f7a5ab6e2f440068f3f187983832f878f8ca Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:42:51 +0100 Subject: [PATCH 07/18] Fixup: Specify a magic value to the final_salt's preimage --- spec/eof.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index a998f21..1a41d97 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -395,8 +395,9 @@ Annotated examples of EOF formatted containers demonstrating several key feature let tx_initcode_hash := calldataload(0) let salt := calldataload(32) - calldatacopy(0, 0, 64) // copy tx_initcode_hash and salt to memory to hash - let final_salt := keccak256(0, 64) + mstore8(0, 0xff) // a magic value to ensure a specific preimage space + calldatacopy(1, 0, 64) // copy tx_initcode_hash and salt to memory to hash + let final_salt := keccak256(0, 65) let init_data_size := sub(size, 64) calldatacopy(0, 64, init_data_size) From 8999bea52ca6f4a95f69979f26278b952eb6a8ba Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 4 Feb 2025 21:05:02 +0100 Subject: [PATCH 08/18] Creator Contract: Include init_data in final_salt hashing --- spec/eof.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 1a41d97..06382e0 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -396,8 +396,9 @@ Annotated examples of EOF formatted containers demonstrating several key feature let salt := calldataload(32) mstore8(0, 0xff) // a magic value to ensure a specific preimage space - calldatacopy(1, 0, 64) // copy tx_initcode_hash and salt to memory to hash - let final_salt := keccak256(0, 65) + 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) From 7e73e1433ebf9cb8e2a5d90f15804fc5ab94783d Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 6 Feb 2025 16:25:06 +0100 Subject: [PATCH 09/18] Remove EIP-3869 charge from TXCREATE The cost of validation is covered by transaction's initcode cost (part of intrinsic cost.) --- spec/eof.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 06382e0..cdc6db5 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -242,13 +242,12 @@ The following instructions are introduced in EOF code: - 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. Only `TXCREATE` constant gas was consumed - let `initcontainer` be that EOF container, and `initcontainer_size` its length in bytes - - deduct `2 * ((initcontainer_size + 31) // 32)` gas (EIP-3860 charge) - just before executing the initcode container: - **validates the initcode container and all its subcontainers recursively** - validation includes checking that the `initcontainer` does not contain `RETURN` or `STOP` - in addition to this, checks if the initcode container has its `len(data_section)` equal to `data_size`, i.e. data section content is exactly as the size declared in the header (see [Data section lifecycle](#data-section-lifecycle)) - fails (returns 0 on the stack) if any of those was invalid - - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant and EIP-3860 gas were consumed + - caller’s nonce is not updated and gas for initcode execution is not consumed. Only `TXCREATE` constant gas was consumed - `RETURNCODE (0xee)` instruction - loads `uint8` immediate `deploy_container_index` - pops two values from the stack: `aux_data_offset`, `aux_data_size` referring to memory section that will be appended to deployed container's data From fd11939faa21b93745078d1c57549e7572d5c319 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 10 Feb 2025 13:14:02 +0100 Subject: [PATCH 10/18] Adjust InitcodeTransaction cost of initcodes spec to align with EIP-7623 --- spec/eof.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index cdc6db5..811b5b4 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -117,9 +117,25 @@ Under transaction validation rules `initcodes` are not validated for conforming 1) It must be fully transmitted in the transaction. 2) It is accessible to the EVM, but it can't be fully loaded into EVM memory. -For these reasons, define cost of each of the `initcodes` items same as calldata (16 gas for non-zero bytes, 4 for zero bytes -- see EIP-2028). The intrinsic gas of an `InitcodeTransaction` is extended by the sum of all those items' costs. +For these reasons, define cost of `initcodes` bytes same as calldata: formula for transaction gas from EIP-7623 is extened to include tokens in initcodes, priced the same as `tokens_in_calldata`: + +```python +STANDARD_TOKEN_COST = 4 +TOTAL_COST_FLOOR_PER_TOKEN = 10 + +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) + ) +) +``` -EIP-3860 and EIP-170 still apply, i.e. `MAX_CODE_SIZE` as 24576, `MAX_INITCODE_SIZE` as `2 * MAX_CODE_SIZE`. Define `MAX_INITCODE_COUNT` as 256. +EIP-3860 and EIP-170 limits still apply, i.e. `MAX_CODE_SIZE` as 24576, `MAX_INITCODE_SIZE` as `2 * MAX_CODE_SIZE`. Define `MAX_INITCODE_COUNT` as 256. `InitcodeTransaction` is invalid if either: - there are more than `MAX_INITCODE_COUNT` entries in `initcodes` From 24887be5c88037af1314ab71a294dc0f275293d7 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:02:43 +0100 Subject: [PATCH 11/18] Update spec/eof.md - Creator Contract return revert reason Co-authored-by: Francisco Giordano --- spec/eof.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index 811b5b4..7b4b6e6 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -419,7 +419,11 @@ Annotated examples of EOF formatted containers demonstrating several key feature 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) } + if iszero(ret) { + let ret_data_size := returndatasize() + returndatacopy(0, ret_data_size) + revert(0, ret_data_size) + } mstore(0, ret) return(0, 32) From 94835067f2368c4098fe3f4e6c3b19891f5a71b4 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:05:59 +0100 Subject: [PATCH 12/18] Fix typo --- spec/eof.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index 7b4b6e6..7237838 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -230,7 +230,7 @@ The following instructions are introduced in EOF code: - halt with exceptional failure if the current frame is in `static-mode`. - read uint8 operand `initcontainer_index` - pops `value`, `salt`, `input_offset`, `input_size` from the stack - - peform (and charge for) memory expansion using `[input_offset, input_size]` + - 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 - check call depth limit and whether caller balance is enough to transfer `value` From d9c5e8b9fcd2c12141f66377f36254ae57816249 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:25:29 +0100 Subject: [PATCH 13/18] Remove `0xff` magic value from the `final_salt` calculation --- spec/eof.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 7237838..bf65912 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -407,18 +407,15 @@ Annotated examples of EOF formatted containers demonstrating several key feature 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) + calldatacopy(0, 0, size) // copy tx_initcode_hash, salt and init_data to memory to hash + let final_salt := keccak256(0, size) + let tx_initcode_hash := calldataload(0) 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) + // reuse init_data which has been already copied to memory above + let ret := txcreate(tx_initcode_hash, callvalue(), final_salt, 64, init_data_size) + if iszero(ret) { let ret_data_size := returndatasize() returndatacopy(0, ret_data_size) From 7c293e6334e3c00b47d47500a20f729e6389c39f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:01:33 +0100 Subject: [PATCH 14/18] Fix returndatacopy opcode usage --- spec/eof.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index bf65912..bf6e1bb 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -418,7 +418,7 @@ Annotated examples of EOF formatted containers demonstrating several key feature if iszero(ret) { let ret_data_size := returndatasize() - returndatacopy(0, ret_data_size) + returndatacopy(0, 0, ret_data_size) revert(0, ret_data_size) } From ac2a52a1b355187e97aec3db0f53c8b3500a1b47 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:45:56 +0100 Subject: [PATCH 15/18] Align Contract Creator to changes done in EIP-7873 --- spec/eof.md | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index bf6e1bb..eb18a66 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -400,21 +400,47 @@ Annotated examples of EOF formatted containers demonstrating several key feature ```solidity { - /// Takes [tx_initcode_hash][salt][init_data] as input, - /// creates contract and returns the address or failure otherwise + /// Takes [tx_initcode_hash][salt][unsafe_flag][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 + /// tx_initcode_hash and salt are 32 bytes wide, unsafe_flag is 1 byte wide, init_data can be any width. + /// If unsafe_flag is 0x00, `CALLER` is included in the salt and impacts the target address of the created account. + /// If unsafe_flag is non-zero, `CALLER` is excluded, with 32 zero-bytes used instead. Deployment by any sender will result in same target address. + /// Deployments using non-zero unsafe_flag are susceptible to front-running. + + // init_data.length can be 0, but the first 65 bytes are mandatory let size := calldatasize() - if lt(size, 64) { revert(0, 0) } + if lt(size, 65) { revert(0, 0) } + + // copy tx_initcode_hash and salt to memory to hash. + // note that the unsafe_flag doesn't need to be included. If set to non-zero, 32 zero-bytes + // are used instead of the CALLER address bytes, which prevents hash collisions between + // deployments using unsafe_flag and those not using it. + calldatacopy(0, 0, 64) + + // mask out the 31 bytes that follow the flag in input data + let unsafe_flag := byte(0, calldataload(64)) + + if iszero(unsafe_flag) { + // store caller in memory to hash, just after salt + mstore(64, caller()) + } else { + // exclude sender from salt - susceptible to front-running + // assuming no bytes were written to memory word at 64 and it's all zeros + } + + let init_data_size := sub(size, 65) + + // copy init_data to memory to hash, just after caller (or zeros) + calldatacopy(96, 65, init_data_size) - calldatacopy(0, 0, size) // copy tx_initcode_hash, salt and init_data to memory to hash - let final_salt := keccak256(0, size) + // final_salt = keccak256(tx_initcode_hash | salt | caller_or_zeros | init_data) + let final_salt := keccak256(0, 96 + init_data_size) let tx_initcode_hash := calldataload(0) - let init_data_size := sub(size, 64) // reuse init_data which has been already copied to memory above - let ret := txcreate(tx_initcode_hash, callvalue(), final_salt, 64, init_data_size) + let ret := txcreate(tx_initcode_hash, callvalue(), final_salt, 96, init_data_size) if iszero(ret) { let ret_data_size := returndatasize() From ca2e8c68c82b7df96c09e0dce41c65c5ee2548c6 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:52:42 +0100 Subject: [PATCH 16/18] Make EOFCREATE and TXCREATE ASE-compatible --- spec/eof.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index eb18a66..f2a1b0f 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -238,7 +238,7 @@ The following instructions are introduced in EOF code: - 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:]` + - calculate `new_address` as `keccak256(0xff || sender32 || salt)[12:]`, where `sender32` is the sender address left-padded to 32 bytes with zeros - behavior on `accessed_addresses` and address colission is same as `CREATE2` (rules for `CREATE2` from [EIP-684](https://eips.ethereum.org/EIPS/eip-684) and [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929) apply to `EOFCREATE`) - an unsuccesful execution of initcode results in pushing `0` onto the stack - can populate returndata if execution `REVERT`ed From fb0d6b8c06dcb9202f6a134fded284429ed7878f Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:04:31 +0200 Subject: [PATCH 17/18] Change INITCODE_TX_TYPE to 6 to avoid conflict with 7702 --- spec/eof.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/eof.md b/spec/eof.md index f2a1b0f..0aa1919 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -152,7 +152,7 @@ Given the definitions from [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) t [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` (`0x05`) and the signature values `y_parity`, `r`, and `s` are calculated by constructing a secp256k1 signature over the following digest: +`TransactionType` is `INITCODE_TX_TYPE` (`0x06`) 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])) From c777af0b01433df112c123a88cbce1fb8467f82c Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:10:09 +0200 Subject: [PATCH 18/18] Unban TXCREATE in legacy EVM for bootstrapping --- spec/eof.md | 69 ++++------------------------------------------------- 1 file changed, 5 insertions(+), 64 deletions(-) diff --git a/spec/eof.md b/spec/eof.md index 0aa1919..dfc1a51 100644 --- a/spec/eof.md +++ b/spec/eof.md @@ -109,8 +109,6 @@ Introduce new transaction type `InitcodeTransaction` which extends EIP-1559 (typ 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 standardised Creator Contract (i.e. written in EVM, but existing at a known address, such as precompiles), which eliminates the need to have create transactions with empty `to`. The Creator Contract will be predeployed at the EOF activation block. Note that such introduction of the Creator Contract is needed, because only EOF contracts can create EOF contracts. See the appendix below for Creator Contract code. - 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. `initcodes` data is similar to calldata for two reasons: @@ -322,6 +320,10 @@ The following instructions are introduced in EOF code: **NOTE**: The replacement instructions `EXT*CALL` continue being treated as **undefined** in legacy code. +`TXCREATE` instruction is introduced also in legacy EVM code, with the exact behavior as when it occurs in EOF code. + +**NOTE** `TXCREATE` is only such instruction from the above list of EOF instructions. All the other instructions from this list cause an exceptional halt if they occur in legacy EVM code. + ## Code Validation - no unassigned instructions used @@ -396,68 +398,6 @@ During scanning, for each instruction: Annotated examples of EOF formatted containers demonstrating several key features of EOF can be found in [this test file within the `evmone` project repository](https://github.com/ethereum/evmone/blob/master/test/unittests/eof_example_test.cpp). -## Appendix: Creator Contract - -```solidity -{ - /// Takes [tx_initcode_hash][salt][unsafe_flag][init_data] as input, - /// creates contract and returns the address or failure otherwise. - - /// tx_initcode_hash and salt are 32 bytes wide, unsafe_flag is 1 byte wide, init_data can be any width. - /// If unsafe_flag is 0x00, `CALLER` is included in the salt and impacts the target address of the created account. - /// If unsafe_flag is non-zero, `CALLER` is excluded, with 32 zero-bytes used instead. Deployment by any sender will result in same target address. - /// Deployments using non-zero unsafe_flag are susceptible to front-running. - - // init_data.length can be 0, but the first 65 bytes are mandatory - let size := calldatasize() - if lt(size, 65) { revert(0, 0) } - - // copy tx_initcode_hash and salt to memory to hash. - // note that the unsafe_flag doesn't need to be included. If set to non-zero, 32 zero-bytes - // are used instead of the CALLER address bytes, which prevents hash collisions between - // deployments using unsafe_flag and those not using it. - calldatacopy(0, 0, 64) - - // mask out the 31 bytes that follow the flag in input data - let unsafe_flag := byte(0, calldataload(64)) - - if iszero(unsafe_flag) { - // store caller in memory to hash, just after salt - mstore(64, caller()) - } else { - // exclude sender from salt - susceptible to front-running - // assuming no bytes were written to memory word at 64 and it's all zeros - } - - let init_data_size := sub(size, 65) - - // copy init_data to memory to hash, just after caller (or zeros) - calldatacopy(96, 65, init_data_size) - - // final_salt = keccak256(tx_initcode_hash | salt | caller_or_zeros | init_data) - let final_salt := keccak256(0, 96 + init_data_size) - - let tx_initcode_hash := calldataload(0) - - // reuse init_data which has been already copied to memory above - let ret := txcreate(tx_initcode_hash, callvalue(), final_salt, 96, init_data_size) - - if iszero(ret) { - let ret_data_size := returndatasize() - returndatacopy(0, 0, ret_data_size) - revert(0, ret_data_size) - } - - 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) - } -} -``` - ## Appendix: Original EIPs These are the individual EIPs which evolved into this spec. @@ -472,3 +412,4 @@ These are the individual EIPs which evolved into this spec. - 📃[EIP-663](https://eips.ethereum.org/EIPS/eip-663): Unlimited SWAP and DUP instructions [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-663.md) - 📃[EIP-7069](https://eips.ethereum.org/EIPS/eip-7069): Revamped CALL instructions (*does not require EOF*) [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7069.md) - 📃[EIP-7620](https://eips.ethereum.org/EIPS/eip-7620): EOF - Contract Creation Instructions [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7620.md) +- 📃[EIP-7873](https://eips.ethereum.org/EIPS/eip-7873): EOF - TXCREATE and InitcodeTransaction type [_history_](https://github.com/ethereum/EIPs/commits/master/EIPS/eip-7873.md)