The Optimism team is building the OVM, a fully-featured, EVM-compliant execution environment designed for Layer 2 systems. Starting on March 15th, 2021, we have audited Optimism’s code base during 7 weeks with 3 auditors.
The engagement involved auditing two different versions of the Solidity smart contracts:
The first audited commit was
18e128343731b9bde23812ce932e24d81440b6b7. We worked with this commit for the first 4 weeks.The second audited commit was
a935e276f5620b40802b52721e3474232e458f72. We worked with this commit for the last 3 weeks.
The Solidity files in scope were those in the contracts/optimistic-ethereum/OVM/ and contracts/optimistic-ethereum/libraries/folders, except the OVM_BondManager.sol, OVM_SafetyChecker.sol, ERC1820Registry.sol and OVM_DeployerWhitelist.sol files. It should be noted that while the Lib_RingBuffer.sol file was originally included in the audit’s scope, after feedback during the audit the file has been deprecated and is expected to change in the short term – we have therefore not conducted a full assessment of the security of this particular library. Moreover, the specified commits contain production code that has been temporarily commented out. Our analysis assumes it will be restored.
All other components of the stack (such as compilers, off-chain services, Layer 2 nodes, or bytecode checkers) were left completely out scope from the beginning of the audit. We have assumed any out-of-scope component behaves as intended and documented by the Optimism team.
Furthermore, the Optimism team independently identified a number of issues in the code base that they shared with us during the audit. For completeness, these are included in an informational note titled “[N01] Additional issues”.
During the audit we were able to uncover issues of varied nature. The most important relate to critical security flaws in the fraud proof verification process, which is the fundamental mechanic ensuring security of the Layer 2 system. We also detected particularly interesting issues in cross-domain deposits and withdrawals of tokens, mishandling of transaction fees, mismatches in the treatment of sequenced and queued transactions, as well as potential abuses of the reward dynamics during fraud proof contribution. Additionally, while not strictly pertaining to security-related issues, this report includes a significant number of recommendations aimed at improving the overall quality of the system.
In terms of specification and documentation, even though there have been notable advances, we still find that there is room for improvements. Gas accounting, upgradeability of core modules, genesis process, progressive decentralization roadmap, and interactions between Layer 1 contracts and off-chain services (such as the Sequencer), stand out as significant attention points in this regard. We acknowledge that the system is still under development, which can render documentation efforts pointless. Still, we expect that as the system matures and its fundamental mechanics are settled, the Optimism team will make efforts to produce a comprehensive baseline specification of their layer 2 solution that can serve as the bedrock upon which future versions of the system build.
Regarding overall health and maturity, we found the code to be readable and well-structured, with enough separation of concerns and modularity to favor long-term maintenance and sustainability. Having audited an earlier version of the system in November 2020, this time we found a more robust code base with simpler and more straightforward implementations. The remarkable difference in number and severity of issues identified between audits is a reflection of how much the code base has matured in the last months. We expect the system to continue in this path as it further evolves, incorporating additional feedback from security-minded professionals, development partners and users alike.
As a final remark, we must highlight that given the sandboxed environment allows for arbitrary code execution of Layer 2 transactions, the number of possible interactions cannot be audited to exhaustion. We therefore highly advise:
Following best practices of secure software development, thorough test-driven development, and mandatory peer-reviews.
Further promoting a public bug bounty program to engage independent security researchers from the community in uncovering further misbehaviors in the system as the code base evolves.
Continuing with beta testing phases until several projects have been onboarded to the Layer 2 system, and their dynamics have become battle-tested, in particular with respect to fraud proof verification in Layer 1. In this regard, we value the slow progressive decentralization approach taken by the Optimism team.
Documentation about the system main components can be found in the official documentation and research articles, as well as in our original audit report. On top of the core components audited in the early version of the system, in this audit we also included:
Bridge contracts, which essentially allow for message passing between Layer 1 and 2.
Predeploy contracts, which provide essential utilities in Layer 2 (such as a tokenized versioned of ETH, or decompression of sequenced calldata).
The owner of the
Lib_AddressManagercontract can arbitrarily add, delete and modify the addresses stored. It is therefore entitled to impersonate or change the logic of critical components of the system at will.The Sequencer is a single, semi-trusted, off-chain service that is expect to process, order and append batches of transactions to the Canonical Transaction chain.
There are accounts that can propose state roots in the State Commitment chain. These accounts should have deposited a bond to be granted the role, and are expected to be penalized if they misbehave.
As discussed further in the report (see issue “[M03] Initial state root cannot be challenged”), the entity that provides the first state root to the State Commitment Chain can decide on the OVM’s genesis state.
All serious issues have been fixed or acknowledged by the Optimism team. The code has been migrated to a new repository with a different commit history. Our review disregards all changes introduced by the migration, other pull requests, or unrelated changes within the reviewed pull requests.
Below, our findings in order of importance.
The run function of the OVM_ExecutionManager contract is executed during a fraud proof to move the associated State Transitioner from pre-execution to post-execution phase. Transactions with an invalid gas limit can still be run in a fraud proof, but they are expected to ultimately result in a no-op. In other words, the pre- and post-state of a transaction with an invalid gas limit should be the same.
Right before finishing execution of a regular transaction (that is, one that does not revert nor return early), the run function resets to zerothe reference to the State Manager stored in the ovmStateManager state variable. However, this reference is not reset after finishing execution early when the transaction’s gas limit is not valid. As a result, the Execution Manager will still be able to execute calls to the associated State Manager, and in turn the State Manager will consider the caller Execution Manager as correctly authenticated.
The described behavior allows for anyone to call sensitive functions of the Execution Manager contract right after the execution of run, which could result in arbitrary state modifications in the State Manager contract. As a consequence, it would not be possible to successfully finish the fraud proof in the State Transitioner contract. For example, a malicious actor could call the ovmCREATEEOA function of the OVM_ExecutionManager contract, creating a new account in state, which would in turn increment the total number of uncommitted accounts in the State Manager. This would effectively prevent completing the transition in the OVM_StateTransitioner contract.
Consider clearing the ovmStateManager state variable after execution of transactions with invalid gas limits.
Update: Fixed in pull request #366 of the archived ethereum-optimism repository.
Inside the _walkNodePath function of the Lib_MerkleTrie library, when an EXTENSION node is encountered which shares some, but not all of its key with keyRemainder, the walk will move on to the node which the EXTENSION node points to, but will only increment the key index by the sharedNibbleLength.
More specifically, when the sharedNibbleLength is not 0, it will be assumed that all nibbles are shared with the EXTENSION node. The walk will then move to the node which the node links to, while the key increment will be set to sharedNibbleLength. This will result in an incorrect currentKeyIndex on the next loop iteration. The _walkNodePath function will assume it has reached the node which the EXTENSION node points to, while the currentKeyIndex will correspond to a path in the middle of the EXTENSION node.
This means that the Merkle Trie may incorrectly identify some elements and there may be multiple keys that map to the same element. During fraud proof execution the described flaw could cause the OVM_StateTransitioner contract to incorrectly update storage or account elements, which could lead to a security vulnerability where invalid fraud proofs would succeed due to incorrect updates in trie roots.
Since the _walkNodePath function should identify the nearest sibling to the key which is being “walked” to, consider modifying the _walkNodePath function so that it breaks out of the loop and returns when a non-fully matching EXTENSION key is found.
Update: Fixed in pull request #747.

