Optimism

Optimism u17

Cantina Security Report

Engagement Type

Cantina Reviews

Period

-

Researchers


Findings

Informational

3 findings

0 fixed

3 acknowledged


Informational3 findings

  1. DEPLOY_V2_DISPUTE_GAMES rollback leaves V2 args on legacy game

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    When DEPLOY_V2_DISPUTE_GAMES is enabled, OPContractsManager.registerPermissionedGameV2 writes the 124‑byte constructor payload into the factory via setImplementation(gameType, impl, gameArgs). Later, switching the feature flag off falls back to the three-argument helper, which only invokes setImplementation(gameType, impl) and never clears gameArgs[gameType]. Because DisputeGameFactory.create always concatenates the stored constructor blob, the stale V2 payload is still appended when cloning the legacy FaultDisputeGame. That legacy initializer rejects any calldata length other than 122 bytes and reverts with BadExtraData(), so every rollback deployment bricked fault-proof creation once V2 had been enabled. Impact: toggling off the feature via the documented governance flow leaves the system unable to produce fault proofs until an operator manually deletes the stored args.

    Recommendation

    When reinstalling the legacy implementation, explicitly clear the cached constructor blob (for example delete gameArgs[_gameType]; before calling the two-argument setter).

  2. Permissioned bond starts at 0 until addGameType

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    _registerPermissionedGameV2 registers the v2 permissioned game without touching DisputeGameFactory.setInitBond, so a fresh factory keeps the default zero bond. The team clarified that new chains intentionally launch with only the permissioned game and no bond; once permissionless proofs are enabled via opcm.addGameType, that upgrade also sets the init bond for both permissioned and permissionless games. This sequencing is expected and should be documented so the zero bond at genesis isn’t mistaken for a misconfiguration.

    Recommendation

    No code change required. Document the deployment sequence to clarify that the permissioned game bond is intentionally zero at genesis and will be aligned with the permissionless bond when opcm.addGameType runs.

  3. Fork-Based Upgrade Tests Summary

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    We invested effort in building manual, fork-based tests on an Optimism fork so we could observe the real contract-level behavior and gas costs of the u17 upgrade and deployment flows. Running them against live chain state ensures what we see mirrors actual production transactions.

    The fork tests can be located in the following gist.

    • The test_upgrade_u16_to_u17_fork scenario starts from a freshly deployed U16 rollup configuration and delegates to OPContractsManagerUpgrader.upgrade. The delegatecall itself costs about 4.99 M gas and proves that the non-interop upgrade path is sound: every critical proxy: SystemConfig, OptimismPortal2, L1 bridges, L1CrossDomainMessenger, DisputeGameFactory, AnchorStateRegistry and DelayedWETH lands on the U17 implementation while ownership remains with the configured proxy-admin owner. Because no dev features are enabled, the legacy ETHLockbox pointer is left untouched, the portal retains its ETH and the permissioned dispute game still swaps via the two-argument setter (so any stale V2 constructor blob could still block a rollback).

    • The test_deploy_u17_chain_end_to_end path invokes u17Deployer.deploy once, at a cost of roughly 12.94 M gas, to stand up a fully interop-enabled U17 stack. With DevFeatures.OPTIMISM_PORTAL_INTEROP baked into the contracts container, the call instantiates SystemConfig, OptimismPortalInterop, ETHLockbox, all three L1 bridges, the L1 messenger, OptimismMintableERC20Factory, DisputeGameFactory, AnchorStateRegistry, DelayedWETH and the legacy permissioned dispute game. The test then inspects every initializer outcome (owners, batcher hash, unsafe signer, PDG constructor params, start anchor, lockbox authorization, ASR/DGF wiring) to demonstrate that a single transaction yields an immediately usable, interop-ready rollup. This effectively serves as the reference deploy script for new chains.

    • Finally, the test_upgrade_u16_to_u17_interop_migrates_lockbox covers the interop-enabled upgrade. After seeding the legacy portal with 5 ETH, it runs OPContractsManagerUpgrader.upgrade (gas ~5.00 M) with the interop dev feature enabled, then, still acting as the proxy-admin owner, calls SystemConfig.setFeature("ETH_LOCKBOX", true) followed by OptimismPortalInterop.migrateLiquidity. This sequence is essential because the upgrader itself neither flips the feature flag nor sweeps funds. The test confirms the portal still points to the existing lockbox proxy (the upgrade reuses it), the manual migration drains the portal balance to zero while crediting the lockbox with exactly 5 ETH, and the portal remains authorized. In effect it documents the post-upgrade runbook: toggle the feature, run the migration and verify that the lockbox holds the ETH.

    Notes & potential follow-ups

    It would be helpful adding a path that clears stale dispute-game constructor blobs to remove the lingering rollback hazard. Also, another thing to remark is that the u17 deployment test consumes ~12.9M gas, which should be acceptable operationally but is noteworthy for planning and cost predictability.