After pool expiry, claimExpired() should settle against the agreement state that currently governs the scoped contract's BattleChain lifecycle. Once BattleChain has legitimately re-linked the scoped contract to a newer Binding Agreement in AttackRegistry, expiry settlement should not keep resolving from a stale historical agreement address.
The issue is that ConfidencePool snapshots one agreement address during initialization and later treats that stored address as the only source of truth for expiry settlement. It never checks whether the scoped contract is still bound to that agreement upstream. When the scoped contract has moved from historical Agreement A to current Agreement B, claimExpired() can still finalize from stale Agreement A.
This causes wrong terminal settlement. If stale Agreement A is PRODUCTION, the pool resolves SURVIVED and releases stake plus sponsor-funded bonus even while the scoped contract's current Binding Agreement B is CORRUPTED. If stale Agreement A is only pre-terminal, the same path resolves EXPIRED and returns principal even while the current binding is already corrupted.
The missing check is that expiry settlement does not verify the current AttackRegistry binding before using the stored historical agreement. Upstream BattleChain maintains a live contract -> agreement pointer and allows re-linking once the old agreement is no longer active. After such a legitimate re-link, the pool can keep finalizing from Agreement A even though Agreement B is now authoritative for the scoped contract.
Likelihood:
A legitimate agreement rollover / migration can create the stale condition after the pool was initially created correctly. This does not require a maliciously wrong pool at creation time.
Once the stale condition exists, the exploit trigger is permissionless. Any caller, including a non-staker with zero pool balance, can call claimExpired() after expiry and finalize the stale settlement branch.
Impact:
When stale Agreement A is PRODUCTION, claimExpired() incorrectly resolves SURVIVED; an honest staker can then claim the original stake plus sponsor-funded bonus even though the scoped contract's current Binding Agreement B is already CORRUPTED.
When stale Agreement A is pre-terminal, claimExpired() incorrectly resolves EXPIRED; an honest staker can reclaim principal from the wrong branch even though the current Binding Agreement is already CORRUPTED.
The caller does not directly steal funds. The impact is incorrect terminal settlement: a permissionless caller can permanently choose the wrong payout/refund branch for the pool by forcing it to resolve from stale historical agreement state.
Run with:
Expected result:
Full PoC file:
Before expiry settlement, verify that the scoped contract is still bound to the stored agreement in the upstream AttackRegistry. If the current binding has changed, the pool should not resolve from the stale historical agreement.
Illustrative mitigation:
If immutable-at-birth agreement semantics are intended, then pools should be explicitly invalidated or made non-canonical once their scoped contracts are legitimately re-linked upstream. The current implementation silently continues using the old agreement for terminal settlement, which is the dangerous behavior.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.