# EOF: When Complexity Outweighs Necessity
> This blog post was co-authored by [Matt](https://x.com/lightclients), [Moody](https://x.com/sendmoodz), and [Ramana](https://github.com/xrchz), with additional feedback from [Charles](https://github.com/charles-cooper). Together, these contributors represent the entire EVM stack, from VM, formal specification and compiler maintainers to application and library developers.
> You can share your feedback on this blog post in the discussion thread [here](https://ethereum-magicians.org/t/ethereum-is-turning-into-a-labyrinth-of-unnecessary-complexity-with-eof-lets-reconsider-eof/23136).
This blog post examines the Ethereum Object Format (EOF) proposal — its purported benefits, underlying complexities, and whether its costs truly justify its implementation. We begin with a high-level overview of EOF's design architecture, explore its claimed benefits, and ultimately show that these benefits represent "nice-to-haves" rather than essential improvements to the protocol. Moreover, these improvements could be introduced individually with smaller and simpler changes. Instead, the specific way in which the changes have been bundled together comes with the real cost of **significantly increased protocol complexity**, which raises the risk of consensus bugs.
It also introduces "unknown unknowns", as evidenced by reentrancy considerations that remained undiscovered until four years into its development cycle.
## Understanding EOF's Design
EOF introduces a structured format for EVM code, with its latest specification available [here](https://github.com/ipsilon/eof/blob/main/spec/eof.md). The design revolves around "containers" that separate code and data whilst allowing flexibility for future container types. EOF introduces subroutine functionality through new opcodes — `CALLF`, `RETF`, and (tentatively) `JUMPF` — which are modeled within the format using containers.
Broadly, EOF was designed with several objectives:
- Improving static analysis by replacing dynamic jumps with statically defined control flow,
- Adding subroutines to the EVM, and
- Facilitating EVM upgrades through adding or removing opcodes.
Over the course of development, additional design goals were added: prohibiting code and gas introspection in order to [facilitate upgradeability](https://ethereum-magicians.org/t/eof-proposal-ban-code-introspection-of-eof-accounts/12113) between EOF versions. Due to this, EOF introduces new `*CALL` and creation modes whilst removing existing ones. It also eliminates `*CODECOPY` and `*CODESIZE` instructions. `EOFCREATE` references an existing "subcontainer" within the current container for code deployment.
The purported benefits of EOF are outlined in pcaversaccio's Ethereum Magicians post: [Ethereum is turning into a labyrinth of unnecessary complexity with EOF — Let's reconsider EOF](https://ethereum-magicians.org/t/ethereum-is-turning-into-a-labyrinth-of-unnecessary-complexity-with-eof-lets-reconsider-eof/23136).
Let's classify these into several subgroups and expand on them.
### Compiler Complexity Reduction
[EIP-663](https://eips.ethereum.org/EIPS/eip-663) opcodes enable deeper access to the data stack, potentially allowing Solidity to mitigate or eliminate the famous "stack-too-deep" issue. However, this represents **a compiler design challenge** — specifically with Solidity's language and compiler architecture — rather than a virtual machine design problem. Vyper, for instance, doesn't encounter these issues.
Regarding this, Danno Ferrin [writes](https://ethereum-magicians.org/t/ethereum-is-turning-into-a-labyrinth-of-unnecessary-complexity-with-eof-lets-reconsider-eof/23136/16):
> Compilers indeed can solve the famous "stack too deep" problem with register allocation but they cannot guarantee optimal [sic] solution. But even if they could, stack/register spilling is very cost ineffective without reliable access to cheap memory. The EVM's non-linear gas cost for memory makes access to larger variable pools more expensive and fragile than a paged memory model like one in typical silicon processors.
This is a great point that EVM memory pricing is expensive and complex! Therefore, a less intrusive and arguably more effective solution would be to **reform the EVM memory pricing model directly**, requiring only gas schedule changes without opcode or format modifications (see [here](https://ethereum-magicians.org/t/proposals-to-adjust-memory-gas-costs/10036) and [here](https://ethereum-magicians.org/t/eip-7686-linear-evm-memory-limits/19448)).
Moreover, another drawback of EIP-663 is that it may adversely affect performance in interpreted EVM environments by requiring hot access to additional memory and potentially impact compiled EVM negatively.
Since register allocation is NP-complete (with polynomial algorithms in practice), an increased number of hot stack items could push EVM-to-native compiler performance into superlinear territory, which is at odds with [one of the goals of EOF](#Static-Analysis-Improvement).
### Bytecode Size Improvement
EOF proponents [claim](https://notes.ethereum.org/@ipsilon/solidity_eof_poc#Deployed-code-size-comparison-for-clean-DNSRegistrar-contract) it improves bytecode size and performance. Our compilation tests with Solidity [`0.8.29`](https://github.com/ethereum/solidity/releases/tag/v0.8.29) (the latest version with experimental EOF support) showed that EOF-enabled output was merely `424` bytes smaller, with `350` bytes of improvement coming from `JUMPDEST` removal. This improvement seems questionable, as nothing prevents us from simply removing `JUMPDEST`s from the EVM — there is no fundamental requirement for jump targets to be `JUMPDEST` instructions.
`JUMPDEST`s currently provide convenience for off-chain symbolic tools by constraining the search space. However, symbolic tools can implement heuristics for analysing potential jump targets (and need to do this regardless). Unlike client implementations with soft real-time requirements that cannot rely on heuristics due to DoS vulnerability concerns, heuristics in symbolic executors would, at worst, result in timeouts in local user applications rather than client DoS or chain splits.
Note that removing `JUMPDEST`s would [additionally benefit](https://github.com/charles-cooper/eip-3860-benchmarks/blob/remove-jumpdests/benchmark_results/summary_report.md) "`JUMPDEST` analysis" performance by eliminating special-case handling in the analysis loop. This could improve analysis speed by up to 2x through a completely backwards-compatible change.
Furthermore, EOF does in fact introduce bytecode size trade-offs rather than strictly improving size — section headers require several bytes per subroutine declaration, penalising contracts with numerous small subroutines. This effect is compounded since cross-function jumping isn't available in EOF, requiring compilers to emit additional functions to comply with validation restrictions, which further increases code size.
### EVM Performance Enhancement[^benchmark-footnote]
In EOF, a major claimed benefit is that stack and code validation occur only once, during deployment, and EVM implementations can eliminate many runtime checks and execute code more efficiently. However, the execution of VM instructions is not the bottleneck in Ethereum. The [bottleneck is I/O](https://www.youtube.com/watch?v=Cmuz_Xn_YJw).
This suggests that we are optimising for the wrong thing — a much less invasive optimisation for the EVM would be improving state root efficiency. A few current proposals for this are [delaying the computation](https://eips.ethereum.org/EIPS/eip-7862) of the state root and prefetching state via [block-level access lists](https://eips.ethereum.org/EIPS/eip-7863).
This is just another example of computational resource misallocation. As previously mentioned, removing `JUMPDEST`s would likewise enhance EVM performance, as would improving the gas pricing model for EVM memory and arithmetic operations.
[^benchmark-footnote]: The benchmark [published by Ipsilon](https://notes.ethereum.org/@ipsilon/solidity_eof_poc#TLDR) claims 10-15% performance improvement over non-EOF EVM. However, it benchmarks time spent in a state test, which is not representative of a real-world workload. A state test has the state pre-loaded into RAM, therefore it ignores the real-world cost of loading from disk.
### Facilitation of EVM Upgrades
EOF proponents argue that it simplifies EVM upgrades, such as adding or removing opcodes. One example is [Address Space Expansion (ASE)](https://ethereum-magicians.org/t/increasing-address-size-from-20-to-32-bytes/5485) — existing opcodes currently zero the top bytes of addresses; future EVM iterations may want to expand addresses to use more of the available 32 bytes. A claimed benefit of EOF's versioning and code validation is that it would facilitate such changes. However, **any** EVM versioning scheme would simplify upgrades. (It's worth noting that [EIP-6800](https://eips.ethereum.org/EIPS/eip-6800) (Verkle state tree) also introduces account versioning). Indeed, we could implement versioning and/or validation rules within the existing EVM. Contract bytecode could be analysed at creation, disallowing deployment of contracts with specific opcodes — or changing their semantics — if deployed after a specific `fork_blocknum`.
One wrinkle is that existing contracts may contain "data sections" with arbitrary content. This is a well-known problem with previously proposed solutions in terms of a `BEGINDATA` opcode (proposed in both [EIP-615](https://eips.ethereum.org/EIPS/eip-615) and [EIP-2327](https://eips.ethereum.org/EIPS/eip-2327)) marking itself and subsequent bytes as non-executable would neatly address this without requiring format changes. After this change, determining whether a contract contains only valid opcodes as defined by fork rules would require a single-pass analysis.
Meanwhile, it's debatable whether EVM versioning is appropriate at all — this is a topic which frankly warrants its own (and perhaps separate) discussion. If implemented, however, creation `blocknum`-based versioning offers advantages, namely that all contracts deployed between `fork_blocknum1` and `fork_blocknum2` share identical semantics. This eliminates the need to simultaneously maintain multiple EVM semantic interpretations. In particular, the only differences in validation rules might be which opcodes are considered valid.
In addition, it is unclear that allowing ASE in EOF truly enables ASE EVM-wide, since EOF and non-EOF still need to interact with each other for the foreseeable future.
### Enabling Opcodes With Immediates
This is a follow-on to the EVM upgrade question in general. The current concern with immediate-value opcodes is that if they already exist in contracts (currently as invalid opcodes), by enabling them as opcodes with immediates, contract semantics could be altered (e.g., by overwriting the initial bytes of a `PUSH` instruction). But we only need the aforementioned `BEGINDATA` opcode to enable validation against undefined opcodes, which creates a path to safely introduce immediate-value opcodes later.
Moreover, the only proposed immediate-value opcodes are [EIP-663](https://eips.ethereum.org/EIPS/eip-663) instructions (discussed previously) and EOF-specific instructions. There isn't **a compelling need** for immediate-value opcodes in practice.
### Gas Introspection Elimination
One EOF goal is removing gas introspection. However, it accomplishes this only partially. From [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069):
> One major change from the original `CALL` series of instructions is that the caller has no control over the amount of gas passed in as part of the call. The number of cases where such a feature is essential are probably better served by direct protocol integration.
>
> Removing gas selectability also introduces a valuable property that future revisions to the gas schedule will benefit from: you can always overcome Out of Gas (OOG) errors by sending more gas as part of the transaction (subject to the block gas limit). Previously when raising storage costs ([EIP-1884](https://eips.ethereum.org/EIPS/eip-1884)) some contracts that sent only a limited amount of gas to their calls were broken by the new costing.
>
> Hence some contracts had a gas ceiling they were sending to their next call, permanently limiting the amount of gas they could spend. No amount of extra gas could fix the issue as the call would limit the amount sent. The notion of a stipend floor is retained in this spec. This floor can be changed independent of the smart contracts and still preserve the feature that OOG halts can be fixed by sending more gas as part of the transaction.
Due to the [63/64ths rule](https://eips.ethereum.org/EIPS/eip-150), simply "sending more gas as part of the transaction" doesn't fully resolve the issue, as **execution remains gas-dependent**. The 63/64ths rule means a subcall's success or failure depends on the gas provided to the current call context, affecting the current context's outcome. In other words, increasing available gas can modify program semantics beyond the binary "will OOG or not" determination. (As a simple example, consider a program that `SSTORE`s a subcall's return status — it will store different values depending on whether the subcall has been provided sufficient gas, which is the very definition of gas introspection.)
To actually eliminate gas introspection, one must go beyond removing the `GAS` opcode. The 63/64ths rule must be eliminated, as well as the `1024`-depth call stack it protects against.
Note that, in practice, the `PAY` opcode ([EIP-5920](https://eips.ethereum.org/EIPS/eip-5920), itself a successor to [EIP-5065](https://eips.ethereum.org/EIPS/eip-5065)) addresses the issue that gas introspection removal is supposed to solve. The most common reason for calling contracts with limited stipends is ensuring nonreentrancy during value transfers. `PAY` offers value transfer without reentrancy risk. (And with the versioning scheme described earlier, gas introspection could still be reduced by introducing [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069) opcodes and phasing out legacy `*CALL` opcodes.)
### Elimination of Code Introspection
Vitalik Buterin proposed this concept in an [Ethereum Magician's forum post](https://ethereum-magicians.org/t/eof-proposal-ban-code-introspection-of-eof-accounts/12113), suggesting that EVM upgradeability could be implemented through transpilation. However, it's unclear whether this approach is advisable or whether EOF actually enables code transpilation. [According to Martin Swende](https://ethereum-magicians.org/t/eof-proposal-ban-code-introspection-of-eof-accounts/12113/20), transpiling EOF contracts would represent a change comparable in complexity to Verkle.
Danno Ferrin [states](https://ethereum-magicians.org/t/ethereum-is-turning-into-a-labyrinth-of-unnecessary-complexity-with-eof-lets-reconsider-eof/23136/16):
> It separates the representation of the contract from the consensus about its execution and outcome at the protocol level, allowing code to be transpiled to other formats, such as RISC-V. Allowing the code to enter or leave system memory locks in one particular representation of the contract as part of consensus.
This is only useful if one assumes non-EOF EVM will eventually be deprecated at a certain point — an unlikely scenario. Furthermore, transpiling to RISC-V would sacrifice EOF's benefits, which makes it unclear what its purpose is.
And again — opcode validation and removal can be implemented without a breaking format change. Code introspection could be eliminated without EOF by adding validation rules to non-EOF EVM to remove relevant opcodes and introducing new creation opcodes later. These changes do not need to be deployed simultaneously with other modifications.
### Static Analysis Improvement
The core of EOF's design centres on making jumps static by introducing new control flow opcodes:
- `RJUMP`, `RJUMPI`, `RJUMPV`
- `CALLF`, `JUMPF`, `RETF`
It also bans the `JUMP` and `JUMPI` opcodes.
This claims to offer the following benefits:
1. Potential for an EVM-to-native compiler that traverses contract control flow in linear time, and
2. Ability for off-chain tooling to resolve control flow without heuristics or "path space explosion".
The benefit for existing smart contract compilers appears dubious, given they are already generating EVM code just fine. In fact, this was the primary pushback from the Solidity team against [EIP-2315](https://eips.ethereum.org/EIPS/eip-2315) in 2021 — that it seemed unnecessary compared to the current way of doing things (see [here](https://ethereum-magicians.org/t/eip-2315-simple-subroutines-for-the-evm/3941/125) and [here](https://ethereum-magicians.org/t/eip-2315-simple-subroutines-for-the-evm/3941/109)).
What we find remarkable is, the examples the Solidity team used to to shoot down EIP-2315 by claiming insufficient gas benefits _have identical costs in EOF_(!!!).
Why would EOF — a more restrictive format with identical opcode costs[^jumpsub-footnote] — benefit EVM compilers more than [EIP-2315](https://eips.ethereum.org/EIPS/eip-2315)? The Solidity team hasn't clarified why they opposed EIP-2315 but support EOF, beyond the fact that the current EOF iteration includes [EIP-663](https://eips.ethereum.org/EIPS/eip-663). Would they remain supportive if EIP-663 were removed from EOF?
[^jumpsub-footnote]: Comparing `RJUMPSUB`/`RETURNSUB` vs `CALLF`/`RETF`.
We want to stress that less invasive EVM proposals that include subroutines and static jumps already exist. For instance, combining [EIP-2315](https://eips.ethereum.org/EIPS/eip-2315) with [EIP-2327](https://eips.ethereum.org/EIPS/eip-2327) (`BEGINDATA`) and [EIP-4200](https://eips.ethereum.org/EIPS/eip-4200) (`RJUMP*`) achieves the goal of static analysis without introducing new code and stack validation rules (and their associated potential for consensus bugs).
Charles Cooper [notes](https://ethereum-magicians.org/t/ethereum-is-turning-into-a-labyrinth-of-unnecessary-complexity-with-eof-lets-reconsider-eof/23136/35) that EIP-2315 is actually 1-2 orders of magnitude simpler (10-20 lines versus 1k loc) for compilers to implement, as it affects only the opcodes for calling conventions without requiring compliance with new format or validation rules.
Furthermore, the value of statically analysable code to the EVM is questionable in and of itself. While it might simplify some off-chain tooling implementation, it doesn't enable new use cases. As mentioned previously, the impact of lacking statically resolvable jumps on off-chain tools is potential timeouts off-chain — infinitely preferable to deploying consensus bugs.
The idea that static jumps would enable linear-time EVM-to-native compilation is nice, but in practice there are numerous other factors which affect compilation time (e.g., the aforementioned register allocation problem, which [EIP-663](https://eips.ethereum.org/EIPS/eip-663) actually worsens!). Therefore, it's unclear whether statically resolvable jumps would substantially improve the situation in practice for EVM-to-native compilers.
### Elimination of Codesize Limits
The motivation section of [EIP-7830](https://eips.ethereum.org/EIPS/eip-7830) argues:
> The contract size limit was introduced as a measure against DoS attacks. `JUMPDEST`-analysis is required for legacy contracts, and many of the algorithms performing it are not linear and/or have unknown unknowns. This is one of the reasons for the hesitance of a limit increase.
This simply untrue fear-mongering. `JUMPDEST` analysis is patently linear. Even if one disputed this ([contradicting benchmarks](https://ethereum-magicians.org/t/eip-7830-contract-size-limit-increase-for-eof/21927/17)), the code and stack validation algorithms in EOF are _more_ complex than `JUMPDEST` analysis, not less, yet advertised as linear-time. This represents a logical contradiction.
Moreover, the issues with lifting the [EIP-170](https://eips.ethereum.org/EIPS/eip-170) limit (which were identified as early as 2016, in the EIP text itself!) include, _besides_ performing `JUMPDEST` analysis: [witness size bloat and linear costs for loading from disk](https://github.com/ethereum/EIPs/issues/170). The motivation in [EIP-7830](https://eips.ethereum.org/EIPS/eip-7830) is therefore a slender reed which addresses `JUMPDEST` analysis, but ignores these other issues!
Alternative proposals to increase codesize limits that do address these issues through gas metering include:
- [EIP-659](https://github.com/ethereum/EIPs/issues/659),
- [EIP-5027](https://eips.ethereum.org/EIPS/eip-5027), and
- [EIP-7907](https://github.com/ethereum/EIPs/pull/9483).
Notably, these proposals don't depend on EOF or deploy-time code validation in any way whatsoever.
### ZK-Friendliness
EOF is allegedly more ZK-friendly. According to [Succinct](https://blog.succinct.xyz/eofbenefits), EOF provided performance improvements in proving of EVM contracts. The benchmark is a loop-heavy arithmetic benchmark benchmarking the Fibonacci function, which does not necessarily represent the typical chain workload, so it's unclear that this is actually an improvement in practice.
Additionally, we have seen no compelling arguments for why this represents a hard requirement, or why the same benefits cannot be achieved with more minimal changes.
## EOF's Drawbacks
So far, we have argued that all EOF's benefits could be introduced through more incremental, less disruptive EVM updates.
Meanwhile, even if one acknowledges its benefits, **EOF's complexities cannot be dismissed** (for another resource outlining these complexities, see Marius van der Wijden's blog post from 2024 [here](https://mariusvanderwijden.github.io/blog/2024/07/12/EOF)). The previous section examined EOF's claimed advantages individually; this section outlines its drawbacks, which are direct costs without corresponding benefits (beyond those already addressed).
### Not a True Upgrade
The fundamental issue is that non-EOF EVM must remain supported, most likely indefinitely! This requires EVM teams to maintain both EOF and non-EOF formats **in perpetuity**. Client teams will be required to support (at minimum) two semantics for the EVM, simultaneously!
Additionally, tooling must support EOF, which requires coordination across numerous teams. Compilers, application developers, specification developers, debuggers, symbolic tools, framework maintainers and test writers now need to duplicate efforts across two formats.
This contrasts starkly with the aforementioned [EIP-2315](https://eips.ethereum.org/EIPS/eip-2315), [EIP-615](https://eips.ethereum.org/EIPS/eip-615), and [EIP-2327](https://eips.ethereum.org/EIPS/eip-2327), which preserve the EVM bytecode format and design. Support is a matter of implementing new opcodes, rather than delivering an entirely separate code format.
### A Gulf in the Ecosystem
EOF creates a gulf within the EVM ecosystem, as EOF and non-EOF contracts can interact. This is the reality of the situation, since EOF coexists alongside non-EOF contracts rather than prohibiting them.
In other words **any benefits of EOF are anulled by the existence non-EOF contracts**, which don't benefit from EOF! For example, removing gas introspection (which as we already showed, EOF does not actually accomplish, but consider it here for the sake of argument) would, in theory, allow gas schedule modifications without breaking production contracts. However, since non-EOF contracts still exist, breaking production contracts via the gas schedule remains possible! While reducing gas and code introspection or improving EVM contract analysability represent worthwhile goals, EOF is presented as definitively resolving these issues — when it in fact does not.
### Excessive Feature Coupling
EOF couples multiple diverse changes together. Its proponents claim these must be delivered simultaneously for logistical reasons, creating an awkward `N`-to-`M` coupling where obtaining any single benefit supposedly requires shipping all the changes together.
**It is a kitchen sink of upgrades to the EVM**. If we didn't have to be concerned backwards compatibility, the EOF package _could_ be a good idea (though that in itself is questionable — would a fresh design of the EVM from scratch really look like EOF?).
We contend that the aggregate benefit does not justify breaking backwards compatibility with an entirely new format, particularly since each improvement could be introduced **without** a format-breaking change.
This is why we advocate for changes which can be shipped independently, without coupling. We also advocate for changes which are minimally invasive, e.g. preferring gas schedule adjustments that enable new use cases over new opcodes, and new opcodes over new formats.
### Vulnerability Risk Due to Complexity
Finally, and perhaps most importantly, EOF represents an extraordinarily complex change. Beyond the new format and validation routines, it introduces new creation modes, a new transaction type ([`TXCREATE`](https://eips.ethereum.org/EIPS/eip-7873)), and entirely new contract interaction mechanisms ([EIP-7069](https://eips.ethereum.org/EIPS/eip-7069)).
As an example of unforeseen interactions, Solidity's `send()` and `transfer()` functions now permit [reentrancy](https://x.com/pcaversaccio/status/1900162004003835922)(!) — an issue unrecognised until one month before Osaka's scheduled finalisation, despite EOF's development spanning nearly four years. EOF proponents propose including the `PAY` opcode in the Osaka upgrade as a solution. But as previously argued, `PAY` resolves the very problem EOF aimed to address by prohibiting gas introspection in the first place! We could simply implement the `PAY` opcode and achieve 80% of the benefit without creating the EOF/non-EOF gulf.
Another concern [raised](https://mariusvanderwijden.github.io/blog/2024/07/12/EOF) by Marius van der Wijden is that code and stack validation results aren't stored anywhere. Consequently, validation specification bugs could allow deployment of contracts with undefined runtime behaviour.
## Concluding Thoughts
We must distinguish between "EOF enables X" and "X requires EOF". Ethereum secures over [$300bn](https://defillama.com) in value. Changes to the EVM must be weighed carefully against the risks that they represent.
EOF's benefits, individually, represent admirable goals. However, **each goal can be achieved through less invasive means**. In our assessment, EOF over-emphasises format changes while insufficiently addressing functional EVM improvements. Why are we shipping ivory tower improvements over improvements which directly impact user experience and safety, like contract size limit increases and `PAY`? We should focus on building upon the existing ecosystem rather than repeatedly starting from scratch.
Furthermore, the way in which these changes have been bundled together **substantially increases complexity** through the risk of design interaction faults. It also introduces the novel possibility of **undefined runtime behaviour** if there is any bug in code/stack validation in the clients, which represents a novel source of bugs for Ethereum clients to worry about.
Perhaps we will conclude with the following reflection: _is it more valuable to have a stable EVM, or an innovative one?_ Stability might seem unexciting, or even hazardous ("stagnation"), especially to virtual machine researchers and developers. However, the value of stability, combined with network effects, shouldn't be underestimated. Ethereum derives value from providing stability, robustness, and certainty. Meanwhile, uncertainty for application developers, tooling developers, client developers, and other EVM users carries significant, often invisible costs: the prospect of substantial future changes (even as indicated by promoting versioning) makes these stakeholders more hesitant to invest in developing enduring libraries, tools, and applications that would strengthen Ethereum's network effect.