###### tags: `Technical Notes` # Impact of EOF on Solidity, Yul and EVM assembly syntax ### `EOFCREATE` address calculation changed EIPs: - [EIP-7620: EOF Contract Creation](https://eips.ethereum.org/EIPS/eip-7620) - [Add EIP: EOF - TXCREATE and InitcodeTransaction type #9299](https://github.com/ethereum/EIPs/pull/9299) Creation opcodes before EOF: - `CREATE(value, input_offset, input_size)` - `address = keccak256(sender || nonce)[12:]` - `CREATE2(value, input_offset, input_size, salt)` - `address = keccak256(0xff || sender || salt || keccak256(initcode || constructor_args))[12:]` Creation opcodes on EOF: - `EOFCREATE{initcontainer_index}(value, input_offset, input_size, salt)` - `address = keccak256(0xff || sender || salt)[12:]` - `TXCREATE(tx_initcode_hash, value, input_offset, input_size, salt)` - `address = keccak256(0xff || sender || salt)[12:]` - Only available in a special `InitcodeTransaction`. Consequences - No equivalent for unsalted creation performed by `new` without the `salt` call option. - In salted creation the contract being deployed and its arguments no longer affect the address. - Easy address collisions user is unaware. #### No unsalted creation Solutions: - Disallow unsalted `new`. - Assume default, constant salt (e.g. 0). - `new C(1); new C(2);` silently overwrites the contract (same address, same initcode). - `new C(); new D()` reverts (same address, differrent initcode). #### Address calculation unaffected by contract and arguments Solutions: - Ignore it. It's just different now. - Simulate old address calculation. - Hide the actual `salt` from user and automatically mix init container and argument hashes into the raw salt. - Expose the original salt as a new call option: `raw_salt`. ### `EOFCREATE` requires an initcontainer index [EIP-7620: EOF Contract Creation](https://eips.ethereum.org/EIPS/eip-7620) `EOFCREATE` is defined by EOF: ``` EOFCREATE{initcontainer_index}(value, input_offset, input_size, salt) ``` And currently exposed in Yul as (EOF prototype): ```yul eofcreate(yul_object_name, value, salt) ``` Where `yul_object_name` is a literal string argument that must refer to the name of an existing Yul subobject. It is translated to `initcontainer_index` during assembling. Yul objects do not necessarily correspond go subcontainers 1:1, because assembling may involve removing unreferenced containers (EOF does not allow them) and unifying duplicates. In Solidity there is no direct access to Yul objects. It can only be assumed that every contract whose bytecode is accessed (via `new`, `type(C).creationCode` or `type(C).runtimeCode`) will result in one object being generated (with one or more nested subobjects). Consequences: - No way to reliably use `eofcreate()` in inline assembly. Solutions: - Disallow `eofcreate()` in inline assembly. - Make `eofcreate()` accept numerical indices and expose them via a builtin like `type(C).initcontainerIndex`. - Expose Yul object names via a builtins like `type(C).creationObjectName` and `type(C).deployedObjectName`. - Expose Yul object names in inline assembly on the contract type via members like `C.creationObject` and `C.deployedObject` (similar to how state variable `.slot` and `.offset` is exposed now). ### `TXCREATE` and `InitcodeTransaction` [Add EIP: EOF - TXCREATE and InitcodeTransaction type #9299](https://github.com/ethereum/EIPs/pull/9299) The EIP introduces a new transaction type (`InitcodeTransaction`). The transaction has a set of initcode containers attached. The new `TXCREATE` opcode is a variant of `EOFCREATE` that can can only be used by the code executed in such a transaction. It can refer to the containers by hash and the hash is passed on the stack (rather than via immediate argument), which means that it can be passed in to the contract from the outside. There are few limits on such transactions: - Can't be used to deploy contracts in a traditional way (code in calldata + empty `to` address). - There must be at least one initcontainer attached. Which means that they can be thought of as a generalized form of the standard transaction rather than a limited creation transactions. The use of `TXCREATE` is not mandatory and initicontainers do not have to be referenced, so such transactions can also execute any functions that standard trransactions can. In addition to the opcode, the EIP introduces a predeployed contract, which accepts `(tx_initcode_hash, salt, init_data)` in calldatan and deploys the referenced initcontainer. It would be an ordinary contract at a predefined address, identical on all chains. It would not be a precompile, but the address might be one in the precompile range. The predeployed contract is a mechanism to bootstrap EOF creation (`InitcodeTransaction` must call an existing EOF contract and standard creation cannot be used to depoloy EOF contracts) and may initially be the main use case for `TXCREATE` but there are potentially more: - Generic factories, which run extra logic before deployment. - Factories that batch deploy multiple contracts. - Allowing smart contract wallets to deploy contracts. - Unlike EOAs, SC wallets cannot initiate a new transaction (they run inside one) and work by issuing external calls with calldata specified by the user instead. If we had a specialized creation transaction, rather than an opcode, they couldn't use it. Consequences: - Need to expose the new functionality in the language. Solution: - Add `txcreate()` Yul builtin. - Expose it in inline assembly. No issue with container index like with `eofcreate()`. - Add a new variant of `new`: `new{hash: <initcontainer_hash>} C()` - No compile-time restrictions necessary, any code can end up being executed in an `InitcodeTransaction`, even constructor. It will revert in a standard transaction. - Add `type(C).codehash` to make it possible to refer to a known contract. - However, note that deploying contracts known at compilation time is not the main use case of `TXCREATE`. ### No gas introspection EOF removes `GAS` opcode and the call opcodes that allowed specifying a gas limit. Consequences: - No access to the opcode. - No way to implement several Solidity features: - `gasleft()` builtin. - `{gas: ...}` call option. - Stipend in `<address>.send()` and `<address>.transfer()`. Solution: - Remove `gas()` builtin in Yul. - Remove the affected features in Solidity. ### No code introspection EOF removes `EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY`, `CODESIZE` and `CODECOPY`. There are plans to introduce `EXTCODETYPE` as a replacement, but it's not certain if that will be a part of the EOF as implemented in Fusaka. Consequences: - No way to check if the called contract is not an EOA (unless the call is supposed to return something) - Calling an uninitialized external function pointer no longer reverts - No way to implement `<address>.code`, `<address>.codehash`. Solutions: - Do not perform the EOA check on external calls. - Disallow `type(C).creationCode` and `type(C).runtimeCode` for consistency (and due to a lack of a good use case). - Keep `type(C).creationCode` and `type(C).runtimeCode`. ### No `selfdestruct` *TODO* - Disallow `selfdestruct` ### Data section *TODO* - Yul builtins for accessing predeploy data, static auxdata and dynamic auxdata. - At Solidity level: - predeploy data is for values internally moved to code and metadata. - static auxdata is for immutables of static size. - dynamic auxdata is for immutables of dynamic size. - What needs to be exposed in inline assembly? - `auxdataloadn()` - the only way to access data section present in the initcontainer. - Should we restore `loadimmutable()` or keep `auxdataloadn()` for immutables? - Need a way to access `data` objects at Yul level. - Should the compiler explicitly put predeploy data in `data` objects rather than doing it implicitly during assembling? ### Constructor arguments are now in calldata *TODO* - `msg.data` in constructor now has arguments. - Allow `calldata` arguments in constructor. ### IR-only implementation *TODO* - Disallow `pragma abicoder v1`. ### Implicit size limits *TODO* - Number of functions - Number of immutables - Function size / relative jump length - Main code section size - Number of subcontainers For each of these: should the compiler try to work around it or just report an error and give up? ### Portability *TODO* - `isEOF()` builtin - Reserve EOF opcode/builtin names in legacy? - Release non-EOF opcode/builtin names on EOF? ### Stack validation *TODO* - Changes to `verbatim` builtin. ### EVM assembly output *TODO* ### `returndata` as data location *TODO*