EOF is extremely complex. It introduces two new contract creation semantics (EOFCREATE and TXCREATE), removes and adds over a dozen opcodes. Furthermore, the purported benefits don’t require EOF, they could be implemented in the current EVM in much less invasive ways. Let’s explore these:
Reducing compiler complexity:
EIP-663 opcodes allow access deeper in the data stack. This is useful, but not a requirement for modern compilers – all modern compilers know how to implement stack/register spilling. This is a shortcoming of solc, not of the underlying VM. (As an example, Vyper does not have stack-too-deep as a result of compiler and language design.)
Improving bytecode size due to JUMPDEST removal:
We could just remove JUMPDESTs.
Makes it easier to upgrade the EVM, e.g. adding or removing opcodes:
We can add validation rules to the existing EVM. Bytecodes of contracts can be analysed at creation, and we can disallow deploying contracts with specific opcodes - or change semantics thereof - if they are deployed after a given fork_blocknum.
Allows for opcodes with immediates:
Again, we don’t need to change semantics of existing contracts. Rather, validate and apply updated semantics to newly created contracts.
Remove gas introspection:
You can just add the EIP-7069 (CALL2, etc) opcodes, which don’t access gas.
Validate that code does not include the GAS opcode.
Because of the 63/64ths rule, contract behaviour depends on gasleft anyways (subcalls can OOG without the outer contract OOG).
Remove code introspection:
You can add validation rules to legacy EVM to remove the relevant opcodes. New creation opcodes would need to be introduced, but it wouldn’t need to be shipped at the same time as all these other changes.
Remove dynamic jumps:
This can also be addressed simply by updating pricing and providing a carveout for PUSH2JUMP(I) in the gas schedule.
Subroutines:
There are existing, less invasive proposals to the EVM which include subroutines and give preference to static jumps.
Address space expansion:
Can change semantics of existing opcodes (like BALANCE) to not zero the top 12 bytes of the address, again via contract versioning.
Remove codesize limits:
We can just do that, since initcode is metered via EIP-3860.
ZK-friendliness
EOF is purportedly more ZK-friendly. However, we have not seen arguments for why this is a hard requirement.
In other words, all the benefits of EOF can be introduced in more piecemeal, less invasive updates to the EVM.
Meanwhile, the complexities of EOF cannot be ignored:
Legacy EVM needs to be maintained, probably indefinitely.
Tooling needs to support EOF. This requires coordination and effort across many different teams.
Risk of vulnerabilities due to complexity. For instance, send() and transfer() now allow reentrancy. This was not noticed until a month before Osaka was scheduled to be finalised, even though EOF has been in development for nearly 4 years.
Related to that, EOF has been a moving target: EOF is looking to get shipped even though parts of the spec keep changing, making it difficult for compiler and app devs to review the entire package.
Work to target the new format for compilers and app devs.
EVM contracts get much more complicated due to headers. An empty contract is now at least 15 bytes.
Bytecode size tradeoffs. For instance, subroutines cost several bytes just to declare, which penalises contracts with many, small subroutines.
I’ll spend the time to give a more detailed response later this week, but I would like to point out that all of these questions were already asked and answered as a part of ACD 192 9 months ago. I do not see any new concerns raised here.
Perhaps as a meta point there seems to be disagreement about whether major EVM changes are desirable in general (including in the notes from the call you mentioned). I would argue that a stable VM, on which people can invest in building up excellent tooling and apps with confidence, is much more valuable than the ability to evolve the VM to compete with other VMs, since this has the cost of scaring away investment in something that’s gonna potentially change in the future. (I’m speaking in part here as someone who becomes much more hesitant about my investments in the EVM, both as user and dev, when I see that stability of the VM isn’t being prioritised.)
I just want to echo this sentiment - As someone who has been building on the EVM for a long time now, all of these changes adding in unnecessary complexity and changing assumptions is a huge turn off for developers building on the supposed “world computer”.
IMHO tracking deployment height and applying different semantics to a contract on that basis has drawbacks. It means that if you have deployment transactions, they might produce different results if you cross the hardfork boundary. Although this is technically true already with gas schedule changes, this seems much more likely to cause issues. This is also true if you work in an Osaka environment and then switch networks. And we haven’t addressed contracts creating contracts which could also be messy. By contrast, versioning seems more elegant and allows explicit opt-in.
That does not mean that all of EOFv1 belongs as a bundle together. In fact it would be possible to introduce versioning on its own and nothing else, but there would be no incentive to upgrade. If we want a carrot, a good choice would be stack validation + relative jumps, which enable optimizations that could enable a gas discount. This pulls in roughly half of the EOF EIPs, which constitute a more minimal set and exclude some of the more problematic changes like gas inobservability. This may be inaccurate because I haven’t been following EOF closely, but the message is: versioning per se uniquely unlocks a more efficient EVM afaict.
If something has to be included, I think I could get behind adding relative jumps (although not yet convinced they are really worth it).
I don’t believe stack validation needs changed semantics. It can be done already via analysis of status-quo EVM code. Is there reason to think otherwise?
I take this opportunity to call out another pattern I have observed in discussion of EOF so far: that proponents seem keen to equivocate between “EOF enables X” and “X requires EOF”, when these are not in fact logically equivalent statements. I hope readers of these discussions take heed that demonstrating “Y enables X” is much easier than proving that “X requires Y”, but it’s the latter that we need proven before biting the bullet on adopting Y (assuming X is highly desired).
Thanks for your point about versioning. On that point my previous comment about stability still feels relevant: I would rather not make plans (like versioning) that make major EVM upgrades easier, because I would rather not have major EVM upgrades and for the benefits of this to be appreciated.
As I stated over here EOF creates new problems for application-layer developers like me. Seeing as it also doesn’t really solve any of the existing problems that I have, I would not adopt EOF. Also seeing as legacy EVM will need support for the foreseeable future, I’m not enthusiastic about EOF.
My 3 major gripes are:
TXCREATE’s functionality (or lack thereof) not supporting CREATE3-like patterns.
The insistance around lack of inspectability of bytecode (and how you can’t completely hide the bytecode if you have anything resembling CREATE2)
Gas rules (not allowing capped-gas *CALL, the 2300 gas rule, 63/64ths rule)
After thinking about it, I personally believe that it would be possible to do stack validation without pulling in EOF. It would involve analyzing “legacy” bytecode using a similar algorithm as EIP-5450. Specific sequences involving the PC opcode would be recognized as static relative jumps, (a bit like aiupc in RISC-V) and PUSH-JUMP as absolute. Then if validation fails, the contract would be marked as non-compliant, but still remain usable with backwards compatible semantics as before. If it is compliant, it would be marked as such and potentially benefit from gas discounts and a higher size limit. This would still require a hardfork, but would be much more transparent to the application layer.
There would need to be an EIP to flesh this out and give it a chance. I am willing to help write it if there is interest.
The question I always ask is who would use EOF if L2s dominate the market share?
With such a huge EVM update it’s not enough for Ethereum to just be a “North Star”. Existing L2 stacks have to upgrade as well or an official stack need to be provided and maintained by EF.
Honestly, there are three things that scare me the most:
Instantly added complexity to EVM.
Even wider divergence of L2s from L1.
Short term concentration on the app layer “fixes”, while not addressing scalability issues and not bringing new ideas to L1.
Also with the current rollup-centric roadmap Ethereum may well become a pure DA layer. So all these EVM shenanigans may not really worth the effort.
Every L1 hard fork needs to be adopted by L2s, which they consistently do. What makes you think L2s wouldn’t adopt this given the exceptionally strong demand for full Ethereum equivalence?
At least for OP Stack EOF support would be inherited from the L1 client implementations quite easily and I don’t see any reason it wouldn’t be supported.