# Fault game factory can be manipulated to DOS game type using malicious `l2BlockNumber`

FaultDisputeGame accepts an unvalidated l2BlockNumber in extraData; a resolved game can set AnchorStateRegistry to type(uint256).max, blocking all future game creation for that game type.

- Payout: Contest reward
- Severity: medium
- Program: Optimism 2024
- Credited: 0xdeadbeef
- Disclosed: 2024-02-08
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2024-02-optimism-2024-judging/issues/90
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-02-optimism-2024%3AM-2
- Tags: domain:web3 impact:dos kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic vuln:dos

**Target:** DisputeGameFactory / AnchorStateRegistry / FaultDisputeGame contracts
**Root cause:** The l2BlockNumber passed via extraData is never cross-checked against the block number actually proven by output bisection (which derives its own l2Number from startingOutputRoot + traceIndex). AnchorStateRegistry.tryUpdateAnchorState then stores game.l2BlockNumber() as the new anchor if it exceeds the current one, with no upper bound.
**Steps:**
1. Create a game via `disputeGameFactory.create(GameType.wrap(0), Claim.wrap(bytes32(uint(1))), abi.encode(type(uint256).max))`.
2. Game initializes because max > current anchor block number; game.l2BlockNumber() == type(uint256).max.
3. Warp past resolution window, call `game.resolveClaim(0)` and `game.resolve()` so status is DEFENDER_WINS.
4. Call `tryUpdateAnchorState()`; anchors[gameType].l2BlockNumber becomes type(uint256).max.
5. Any subsequent `disputeGameFactory.create(...)` for that game type reverts with UnexpectedRootClaim since no l2BlockNumber can exceed max.
**How found:** Code reading: noticed initialize only checks l2BlockNumber > rootBlockNumber, while FaultDisputeGame's oracle load derives l2Number from startingOutputRoot + traceIndex, decoupling the extraData block number from the proof; then traced tryUpdateAnchorState writing game.l2BlockNumber() into anchors unbounded.
**Exploitation:** Attacker submits a game with a valid root claim but extraData l2BlockNumber = type(uint256).max, lets it resolve in defender's favor (or relies on an incorrect resolution), and calls tryUpdateAnchorState to poison the anchor registry, permanently blocking new game creation for that game type and thus L2->L1 withdrawals until admin intervention.
**Escalation:** Single poisoned anchor blocks all future games of the game type; repeatable after game-type switch unless registry/portal are also fixed.
**Insight:** When a value is passed as immutable clone args and later written into a shared registry, check whether the proof/verification path actually consumes that same value; a decoupled metadata field can be set to an extreme to poison monotonic 'latest' state.
