Try   HackMD

EOF: When Complexity Outweighs Necessity

This blog post was co-authored by Matt, Moody, and Ramana, with additional feedback from Charles. 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.

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. 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 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.

Let's classify these into several subgroups and expand on them.

Compiler Complexity Reduction

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:

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 and here).

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.

Bytecode Size Improvement

EOF proponents claim it improves bytecode size and performance. Our compilation tests with Solidity 0.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 JUMPDESTs from the EVM — there is no fundamental requirement for jump targets to be JUMPDEST instructions.

JUMPDESTs 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 JUMPDESTs would additionally benefit "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[1]

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.

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 of the state root and prefetching state via block-level access lists.

This is just another example of computational resource misallocation. As previously mentioned, removing JUMPDESTs would likewise enhance EVM performance, as would improving the gas pricing model for EVM memory and arithmetic operations.

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) — 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 (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 and 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 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:

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) 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, 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 SSTOREs 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, itself a successor to 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 opcodes and phasing out legacy *CALL opcodes.)

Elimination of Code Introspection

Vitalik Buterin proposed this concept in an Ethereum Magician's forum post, 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, transpiling EOF contracts would represent a change comparable in complexity to Verkle.

Danno Ferrin states:

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 in 2021 — that it seemed unnecessary compared to the current way of doing things (see here and here).

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[2] — benefit EVM compilers more than 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. Would they remain supportive if EIP-663 were removed from EOF?

We want to stress that less invasive EVM proposals that include subroutines and static jumps already exist. For instance, combining EIP-2315 with EIP-2327 (BEGINDATA) and 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 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 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 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), 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 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. The motivation in 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:

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, 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). 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, EIP-615, and 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), and entirely new contract interaction mechanisms (EIP-7069).

As an example of unforeseen interactions, Solidity's send() and transfer() functions now permit reentrancy(!) — 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 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 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.


  1. The benchmark published by Ipsilon 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. ↩︎

  2. Comparing RJUMPSUB/RETURNSUB vs CALLF/RETF. ↩︎