-
-
owned this note
-
Published
Linked with GitHub
# EOFv2 Design Space
###### tags: `EOF`
[toc]
## Summary
We introduce completely new semantics for contract creation (in the context of EOF contracts). This results in legacy transaction types not being able to create EOF contracts, and a new transaction type only allowing EOF contract creation. This reduces complexity and costs. Furthermore we replace the `CREATE` and `CREATE2` instructions with `CREATE3`, which calculates addresses similar to `CREATE2`, but handles EOF containers opaquely. As a side effect, the `nonce` field in the account becomes inert in EOF accounts.
This new design has three (beneficial) side-effects:
1. The concept of "data contracts" are not implementable in EOF context. We think this is a good feature. (It could be enabled with introducing `EXTDATACOPY`,.. instructions.)
2. ~~Initcode can use `REVERT`/`RETURN` to communicate information, but do not control what is being deployed.~~
3. Create transactions can be used (in a limited capacity) similarly to ["rich transactions"](https://ethereum-magicians.org/t/rich-transactions-via-evm-bytecode-execution-from-externally-owned-accounts/4025). The difference is they do not impersonate the caller address.
## Container
Add new section kind `kind_container = 0x04` to be able to specify zero or more container sections. Each section contains a valid EOF container.
```
container := header, body
header :=
magic, version,
kind_types, types_size,
kind_code, num_code_sections, code_size+,
kind_data, data_size,
kind_container, num_container_sections, container_size+,
terminator
body := types_section, code_section+, data_section, container_section+
```
> Should data section be the last? [name=alex]
> Data is more related to code sections so it does not make sense to separate theses with containers. Containers are used less often than data. [name=pawel]
> A thought here should be the current conventions for metadata - currently metadata is at the end of data - if containers are at the end, it's easy to confuse nested container's metadata with the outer container's metadata... On the other hand, we may want to adopt the convention to put metadata at the *beginning* of the data section instead for EOF contracts anyways, which would solve this. But this would mean that e.g. for sourcify EOF wouldn't be transparent anymore. [name=Daniel]
> Is `num_container_sections` still 2-bytes for consistency with `num_code_sections`? [name=andrei]
> If there are no embedded containers, does the header still include `(kind_container, 0)` (similar to how empty data section is still in the header)? I think it does not to make CREATE3-rollout backwards compatible. [name=andrei]
### Validation
Container validation is extended:
1. Each included container section must be a full and valid EOF container.
2. The number of container sections must not exceed 256.
> Introduce a "can create" flag in `type[i].outputs`, which marks a code which runs in "initcode" context. Only `code[0]` can have this flag. Then at validation time `RETURN` instruction and other cases can be checked. ~~Conversely `RETURNCONTAINER` should not be allowed in a non-initcode context.~~ [name=alex]
## Execution Semantics
Note: in the notation below `{x}` signals an immediate argument and `(y)` signals a stack argument.
Note that one aim for redesigning creation is to eventually remove code observability entirely, for this reason consider that `(EXT)CODECOPY` is not available.
1. Data section access instructions
1. `DATACOPY(dst_offset, src_offset, size)`
- Copy from data section to memory
2. `DATASIZE`
- Returns data section size
3. `DATALOAD(offset)`
- Pushes on stack 32-byte value read from data section
4. `DATALOADI{offset}` instruction
- Has 16-bit unsigned immediate `offset`
- Pushes on stack 32-byte value from data section starting from `offset` (relative to data section start)
- Used to efficiently access "immutable storage" constants stored in data section
2. `CREATE3` instruction
- A new instruction is introduced: `CREATE3{initcontainer_index}(value, input_offset, input_size, salt)`
- Has 8-bit unsigned immediate `initcontainer_index`
- Existence of `container_sections[initcontainer_index]` is checked at validation time
- Returns 0 on failure
- Returns the address on success
> Is new account created with nonce == 1? [name=andrei]
3. Initcode execution context (with `RETURNCONTAINER` instruction)
- The initcode execution context is reached either via `CREATE3` or the `CreateTransaction`. In this context we already know the address of the newly created account.
- Address calculation is performed as: `keccak256(0xff || address || salt || keccak256(initcontainer) || calldata)[12:]`
- Difference to [EIP-1014](https://eips.ethereum.org/EIPS/eip-1014) is that `initcontainer` and `calldata` is hashed separately.
> Suggestion by lightclient: drop the `keccak256(initcontainer)`.
- A new instruction is introduced: `RETURNCONTRACT{embedded_container_index}(aux_data_offset, aux_data_length)`
- Is a terminating instruction
- New contract's container is constructed as:
1. Take container from the container section `embedded_container_index`
- Note that this is container section inside initcontainer that's being executed.
3. Append the memory chunk `(aux_data_offset, aux_data_offset + aux_data_size)` to the container data section
4. Update data section size in EOF header
- The instruction can only be executed in initcode context.
- Initcode is allowed terminate execution with `STOP`. It keeps the changes done to the state but the new contract is not created. This allows using initcode as ephemeral code.
- Initcode is allowed to use `REVERT`, which only populate the returndata buffer.
- Initcode is not allowed to use `RETURN`, i.e. the execution results in exceptional abort.
- Note: encoding dynamic immediates likely will use "ABI encoding" as a convention
>
5. `CREATE`/`CREATE2` are banned in EIP-3670 validation
## Transactions
### Existing transaction types (recap)
1. Legacy transaction
- As mentioned in EIP-2718: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])`
2. Legacy transaction with chainid
- This is actually the same structure as legacy, only semantics of fields changed.
3. Access lists
- As defined in [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930): `0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS])`
4. EIP-1559:
- As defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559): `0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])`
Remember: Contract creation is signaled with an empty `to`/`destination` field.
Creating EOF contracts are not allowed in these transaction types. Transactions with `data` starting with the `EF` byte are invalid and not includable.
(This is similar to [EIP-3541](https://eips.ethereum.org/EIPS/eip-3541).)
### New transaction type: `BlobTransaction` (Option 1 -- OBSOLETE)
We introduce a new [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction type based on the `BlobTransaction` proposed by [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844). For further clarification on the fields refer to EIP-4844:
```python
class BlobTransaction(Container):
chain_id: uint256
nonce: uint64
max_priority_fee_per_gas: uint256
max_fee_per_gas: uint256
gas: uint64
to: Union[None, Address] # Address = Bytes20
value: uint256
code: ByteList[MAX_INITCODE_SIZE] # This is the difference from EIP-4844
data: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_fee_per_data_gas: uint256
blob_versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE]
```
The `MAX_INITCODE_SIZE` is defined in [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) as `49152`.
We define create transactions where `to` equals `None`.
Options (TODO decide):
(`Initcode = ByteList[MAX_INITCODE_SIZE]`)
1. `to` is `Union[Initcode, Address]`
3. `code` is an `Union[None, Initcode]`
4. `code` is simply `Initcode`, but enforce `size=0` for non-create transaction
#### Transaction validation
1. Regular (non-Create) transactions MUST have a zero-length `code` field.
2. Create transactions MUST have a non-zero length `code`.
2a. `code` starting with `EF00` must pass EOF validation, otherwise the transaction cannot be included
2b. `code` starting with other than `EF00` is invalid, and the transaction cannot be included
3. The cost of `code` is charged the same as `data`, i.e. 16 for non-zero and 0 for zero bytes (EIP-2028)
- (Could consider 3 gas per byte (as per [EIP-4488](https://eips.ethereum.org/EIPS/eip-4488)) given this is bound at `MAX_INITCODE_SIZE`.)
#### Transaction execution
See below.
### New transaction type: `CreateTransaction` (Option 2)
Instead of extending the [`BlobTransaction`](https://eips.ethereum.org/EIPS/eip-4844), we introduce a new transaction type for contract creation, and at the same time ban contract creation in `BlobTransaction` (accomplished with changing `to` from `Union[None, Address]` to `Address`).
```python
TransactionType = 0x06
class CreateTransaction(Container):
chain_id: uint256
nonce: uint64
max_priority_fee_per_gas: uint256
max_fee_per_gas: uint256
gas: uint64
value: uint256
salt: Byte32
code_container: ByteList[MAX_INITCODE_SIZE]
data: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
```
The `MAX_INITCODE_SIZE` is defined in [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) as `49152`.
As it can be noted, this transaction does not support carrying blobs.
Transaction validation:
1. `code` starting with `EF00` must pass EOF validation, otherwise the transaction cannot be included
2. `code` starting with other than `EF00` is invalid, and the transaction cannot be included
3. The cost of `code` is charged the same as `data`, i.e. 16 for non-zero and 0 for zero bytes (EIP-2028)
- (Could consider 3 gas per byte (as per [EIP-4488](https://eips.ethereum.org/EIPS/eip-4488)) given this is bound at `MAX_INITCODE_SIZE`.)
The semantics of this transaction equal to the context of a `CREATE3` instruction, where:
- the code executing is `code_container` (which must be valid EOF),
- the `data` is accessible via `CALLDATA*`,
- the `salt` is the salt used for address calculation.
### Crazy transaction version (Option 3)
1. We change `BlobTransaction` so that it has a `initcode_blob: List[Initcode, 16]`.
- Each `initcode_blob` fulfill the validation requirements explained in *Option 2*.
3. We introduce `CREATE4{call_initcode_blob_index}(value, salt, aux_data_offset, aux_data_size)`.
- The `call_initcode_blob_index` refers to `initcode_blob[index]`, which is used for creation.
- Note that the initcode is not part of the hash, only the salt.
- Q: Should this instruction only be available in the outermost tx?
4. We insert a "deployer code" at a known address (let's say `0xA`):
```solidity
let size = calldatasize()
calldatacopy(32, size)
create4{0}(callvalue(), calldataload(), 32, size)
```
*Side-effect:* Assuming this deployer is present at the same address on all EOF-enabled chains, then the created addresses are consistent across.
This option allows complete compatibility with features like [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337).
## Code observability
After having reworked contract creation, it is possible to consider further restricting code observability (expanding on [banning introspection of EOF accounts](https://ethereum-magicians.org/t/eof-proposal-ban-code-introspection-of-eof-accounts/12113)).
1. Disallow `CODESIZE`, `CODECOPY`, `EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY` in EOF contracts.
2. In other contexts, where they are available, `(EXT)CODE{COPY,SIZE,HASH}` to return a specific value on EOF accounts.
### Option 1
We treat every EOF account as if its code would be `EF00`. This implies:
- `EXTCODESIZE` returns 2,
- `EXTCODEHASH` returns `keccak256('ef00')`,
- `EXTCODCOPY` works as expected.
### Option 2
Mostly we do not change the behaviour:
- `EXTCODESIZE` and `EXTCODEHASH` returns the accurate value for both legacy and EOF targets,
- `EXTCODECOPY` copies nothing if the target is an EOF account.
Additionally we make the commitment that if EOF accounts were to be updated, their resulting size and hash WILL differ.
## Gas observability
- Remember that `CALLCODE` and `SELFDESTRUCT` are already disallowed in EOFv1.
- Disallow `CALL`, `DELEGATECALL`, `STATICCALL`, `GAS`.
- Introduce `EXTCALL`.
- This instruction has an 8-bit immediate for `flags`. The flags defined are:
- `0x01` - View-context (static call).
- `0x02` - Delegate-context.
- (This may be a stretch, and perhaps keeping a number of opcodes for these different call types is better. Such as `EXTCALL`, `EXTCALLDELEGATE`, `EXTCALLSTATIC`.)
- The stack arguments are: `value`, `destination`, `data_memory_offset`, `data_memory_size`.
- The return value is an enum:
- 0 - Success
- 1 - Failure
- 2 - Revert
- Note that the gas field and return offsets are removed.
- The stipend is removed?