For the complete documentation index, see llms.txt. This page is also available as Markdown.

74557 sc low pause blocks verifier nullification but not game resolution

Submitted on Apr 23rd 2026 at 12:30:42 UTC by @silverologist for Audit Comp | Base Azul

  • Report ID: #74557

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/base/contracts/tree/v8.1.0/src/multiproof

  • Impacts:

    • Bypassing the soundness alert mechanism — two conflicting valid proofs of the same type (both TEE or both ZK) fail to trigger automatic game nullification

Description

Summary

When the system is paused, AggregateVerifier can still resolve a game, but the defensive nullify() path is blocked because Verifier::nullify requires the calling game to still be proper. Since AnchorStateRegistry::isGameProper returns false while paused, pause disables the verifier kill switch without actually stopping a game from being resolved.

Description

In multiproof, each AggregateVerifier instance is one dispute game for one proposed root. A proposer creates a game with DisputeGameFactory.createWithInitData(...), supplying an initial proof. The game verifies that proof in AggregateVerifier::initializeWithInitData. Later, another actor may add a second proof with AggregateVerifier::verifyProposalProof. Once enough time passes and the proof threshold is met, AggregateVerifier::resolve decides the winner.

There are two different defensive mechanisms in the game:

  • AggregateVerifier::challenge is the normal path for disputing a TEE-backed proposal. It is only available when a TEE proof already exists and no ZK proof has been submitted yet, so it cannot be used against a ZK-only game.

  • AggregateVerifier::nullify is meant for cases where a verifier has justified conflicting outputs and must be invalidated. After checking the conflicting proof, AggregateVerifier::nullify calls the verifier’s global kill switch through IVerifier::nullify.

That kill switch is implemented in Verifier::nullify. It only succeeds if the caller is both a respected game and a proper game. Whether a game is proper is determined by AnchorStateRegistry::isGameProper, and that function returns false whenever the system is paused:

So once the system is paused, Verifier::nullify is blocked for all games.

The problem is that pause does not block the rest of the game lifecycle consistently. AggregateVerifier::resolve has no pause check at all. It only checks that the game is still in progress, that the parent has resolved, that the resolution time has passed, and that enough proofs exist. AggregateVerifier::closeGame does check ANCHOR_STATE_REGISTRY.paused(), but that only blocks anchoring while the pause is active. Once the pause is lifted, the already-resolved game can still be closed and promoted.

Pause blocks the same-type verifier-nullification path for both TEE and ZK proofs, but the practical risk is highest for ZK-only games because they do not have the separate TEE->ZK challenge path.

This creates the following sequence:

  1. A ZK-only game is created.

  2. The system is paused.

  3. Defenders find a proof that nullifies the ZK verifier but cannot use nullify() because it now reverts due to the pause check in AnchorStateRegistry::isGameProper.

  4. The game is eventually resolved successfully with the defender winning because AggregateVerifier::resolve does not care that the system is paused.

  5. After the pause is lifted, AggregateVerifier::closeGame promotes the already-resolved game into the anchor state.

Impact

A root may still be elevated to the anchor state even if the verifier behind the proof is invalid. This happens because the system's paused state disables the nullification process, while still allowing the game's resolution process to continue.

This is a perfect match for the following high severity impact listed as in-scope: Bypassing the soundness alert mechanism — two conflicting valid proofs of the same type (both TEE or both ZK) fail to trigger automatic game nullification.

Recommendation

Allow verifier nullification even when the system is paused.

Proof of Concept

What it demonstrates:

  1. A ZK-only game is created.

  2. Pause is enabled.

  3. nullify() fails through Verifier.NotProperGame.

  4. The same paused game still resolves.

  5. Pause is lifted.

  6. closeGame() succeeds.

  7. The resolved game becomes the new anchor.

Run as forge test --match-path test/multiproof/AggregateVerifierPausePoC.t.sol on branch v8.1.0 after adding the following to the POC file:

Was this helpful?