Permanent freezing of funds in the bridge or in dispute game bonds with no available recovery path
Description
Brief/Intro
In AggregateVerifier, games with PROOF_THRESHOLD = 2 must receive two proofs before resolving. The first proof resets the finalization delay (from ∞ to 7 days) but without a second proof, the game can never satisfy resolve(). The bond then becomes irrecoverable, freezing ETH indefinitely.
Vulnerability Details
Upon initialization, expectedResolution is set to type(uint64).max.
// Set expected resolution. expectedResolution = Timestamp.wrap(type(uint64).max);
When a valid proof is submitted (_proofVerifiedUpdate is called), proofCount increments to 1 and _decreaseExpectedResolution sets expectedResolution = block.timestamp + SLOW_FINALIZATION_DELAY (7 days).
Thus after the first proof, expectedResolution is finite (7 days). However, resolve() requires gameOver() and proofCount >= PROOF_THRESHOLD.
If no second proof arrives, proofCount < 2 and resolve() always reverts NotEnoughProofs().
Meanwhile, claimCredit() checks if expectedResolution != max and requires the game to have resolvedAt != 0.
Since resolvedAt remains 0 (game never resolves), claimCredit() also reverts. The 14-day fallback timeout is only reachable when expectedResolution == max, but here it was lowered, so that branch is skipped.
The net effect is a permanent deadlock: the game stays IN_PROGRESS, the bond is locked in DelayedWETH, and no child games can proceed.
Impact Details
An honest proposer who posts one proof will have their entire ETH bond effectively locked forever if the second proof is missing or too costly to obtain. This violates in-scope impacts (“Permanent freezing of funds in dispute game bonds”) and blocks all dependent games. The protocol cannot finalize this proposal nor refund the bond without an explicit fix.
function _getDelay() internal view returns (uint64) {
...
} else if (proofCount == 1) {
return SLOW_FINALIZATION_DELAY;
...
}
function resolve() external returns (GameStatus) {
...
// Game must be completed with a valid proof and enough proofs.
if (!gameOver()) revert GameNotOver();
if (proofCount < PROOF_THRESHOLD) revert NotEnoughProofs();
...
}
function claimCredit() external nonReentrant {
...
if (expectedResolution.raw() != type(uint64).max) {
if (resolvedAt.raw() == 0) revert GameNotResolved();
...