We have since discovered new use cases for this in EIP-8141. In particular, it would be very good to have EIP-7997 available as a canonical deployment mechanism for smart accounts.
Pointing to a precompile as the deployer is very nice because it is an obvious choice for allow-listing in mempool.
When you say precompile I think we are overloading three terms, (a) what I would call predeploys, where raw EVM code is loaded into an account and the chain otherwise occurs normally (b) precompiles where a pure function is implemented outside the chain that has no side effects, which is all current ethereum contracts and (c) system contracts, where client code exits the EVM sandboxs and interacts with the blockchain, and returns to the EVM. Ethereum mainnet doesnāt do this right now.
Are you proposing the code is deployed at fork as EVM code or that the effect of this contract become a system contract?
I donāt think this is the common understanding of system contracts. All of the system contracts in ethereum/sys-asm are pure EVM code and donāt exit the EVM.
This EIP is a system contract with an irregular deployment. Unlike previous ones that could be deployed via regular (keyless) transactions, the motivation for this EIP is precisely that such a transaction cannot deploy a multi-chain factory reliably, so it needs a different mechanism.
I agree that āprecompileā is definitely not the right term for this kind of contract though.
I suggest using a opcode like TCREATE, where T stands for transient. The behavior of this opcode is equivalent to calling CREATE2 and then registering for cancellation using SELFDESTRUCT. Additionally, the target address calculation formula would have no SENDER, eliminating dependency on the implementer while still ensuring security as the target address code cannot be spoofed like CREATE3. Itās also useful to build a CREATE3 by combining TCREATE/CREATE at a lower cost because TCREATE doesnāt create state in the network, which would increase the cost via EIP-8037. What do you think?
This contract is a normal EVM contract but it lives in the precompile range. Is this address intended to be pre-warmed (EIP-2929) or not?
As a minor nit, if a contract gets deployed it starts at nonce 1. This contract has a nonce 0. I think we should insert it with nonce 1 This has no implications except that the case where nonce is zero introduces new behavior where we now create contracts by CREATE2 on a contract which has nonce 0 (nonce gets bumped after the CREATE2 succeeds) which AFAIK is not possible now.
It should be clarified when the contract is inserted (at the start of the block, or at the end ā likely start). What happens if 0x12 has balance? Do we insert the code (+nonce?) and overwrite the balance or do we keep it?
What about BALs? I think this needs to be added in the pre-transactions section where we now insert this code. This also means that the fork block has one extra ābonusā item. If we would produce a maximum sized BAL block at the fork block, it means that the maximum size of the fork block has one more entry than the non-fork blocks.
Iām not sure if a random address would be better, and also not sure how precompiles are laid out over other networks. As far as I know L2s reserve a different precompile range than āmainnet precompile rangeā allowing them to fork in mainnet features. But of course other L1s could distribute precompiles different and then a hardcoded precompile address will not work. The random address should be created such that it is clear that the address cannot be a target of normal contract creations (tx create / CREATE / CREATE2). The behavior when inserting a contract at such address (when code already exists) is well-defined, however this creates the situation where it is now possible to overwrite existing code with new code (something not possible anymore since EIP-6780), in case code already exists at such address.
Regarding the recommendations: I would treat it as cold since this is a normal EVM contract which has been inserted in a special way to the chain. There are no reasons to treat this contract different than other contracts. I would insert it with nonce 1 to keep aligned with EIP-161 where contracts since that EIP have at least nonce 1. It should also keep the balance at that address. So the state update would be to set the nonce to 1 and the code to the runtime bytecode, keeping the balance intact.
It should be inserted at the start of the fork block. This should also add an item to BAL, reflecting the insertion of the account at the start of the block (so BAL index 0). This includes the NonceChange and the CodeChange. Note that BalanceChange is not included as the balance does not change. The EIP should not require BALs but it should include a section where this is described. The reason why adding the insertion to BALs is mandatory is because using the BAL to calculate the state root should include all state changed. However chains which do not use BALs should also be able to use this EIP (so BAL is not required here).
Based on this section in the BAL EIP, it sounds like this should be a non-issue given that there is no fixed maximum size but an approximation based on the block gas limit with a buffer for system contracts. It doesnāt sound like this buffer is exact, so I think it should accommodate the factory insertion.
Iām not sure either and I donāt think a comprehensive analysis is feasible. A random address seems safer to me, though a pseudorandom address taken as a hash is not necessarily safer because of what you mentioned. On the other hand, Ethereum mainnet precompile range does seem like a good heuristic for availability on any chain that intends to follow its upgrades and would include this EIP, so I think Iāll just keep it as is for now.
Many addressing issues would be omitted if the predeploy was about etching of the runtime bytecode onto the derived address from that provided runtime bytecode (i.e. no initCode, nonce, sender neither salt impact).
That would enable to deploy any further kind of the factory (like CreateX) by anyone effectively creating the permissionless bootstrapping mechanism.
There are so many EVM factory proposals that I keep losing track where to discuss this.
If the goal of this proposal is only mainnet then it is unecessary, If the goal is solve interoperability it is already doomed, because most EVM chains only implement a subset of well established EIPs, some like Ethereum Classic and Moonbean doesnāt even support EIP-1153 Transient Storage and MCOPY opcode yet, probably will take a few years for them to comply with this EIP (if they do so). However almost all of them support Legacy Transactions thanks to the ERC-820, which some core contracts still rely on.
There are many incompatibilities like:
Astar EVM has the concept of existential deposit, which implies that SELFBALANCE can be less than CALLVALUE if you create a contracts without ED.
Back in 2024 I did a research on how much would cost to deploy a factiry contract using Keyless Deployment to most relevant EVMās networks, which Iāll link here:
Btw thereās a big limitation of a simple CREATE2Factory, the Constructor Arguments and Creation Code are mixed together in the same blob, as result is not possible to initialize immutables because altering the constructors parameters also changes the final contract address, this limitation also lead to CREATE3 proposal. Whoever the same Creation Code can generate a completely different Runtime Code when the contract constructor uses the current chain-state like ORIGIN, BLOCK_NUMBER or calls another contract.
In 2024 I faced this issue, I had to deploy the same contract at the same address in different networks, while initializing different IDs as solidity immutable. None of the existing factories I tested solved this issue so I had to solve it myself implementing the Universal Factory. This factory temporarly store the arguments locally and make it available to the contract constructor, it also handles Atomic Proxy Contract initialization. Was very tricky to implement because the same bytecode must work cross-chain, the difference in gas cost between TSTORE and SSTORE was significant but EIP-1153 isnāt universaly supported, to get the same bytecode working in all EVM chains I had to implement some forbidden magic to detect EIP-1153 support.
I think is important for a factory provide a way to decouple the arguments from the bytecode, because storing arguments locally is the only way I know around this issue, and it adds a lot of unecessary gas overhead.