stake(uint256 amount) does not bind the transaction to the expiry that the staker reviewed. Before the first stake, setExpiry lets the sponsor choose any deadline up to type(uint32).max. stake accepts only amount, so an intervening expiry change does not make the pending transaction revert. The successful stake then sets expiryLocked = true, permanently committing the sponsor-selected value.
If the agreement is already UNDER_ATTACK, that same stake seals riskWindowStart. Deposits are allowed in this state, but withdraw is disabled once the risk window starts. claimExpired checks the new locked expiry rather than the deadline the staker accepted. A sponsor can therefore front-run the first stake with setExpiry(type(uint32).max) and the victim's unchanged stake still succeeds and becomes trapped.
This is configuration slippage, not incorrect admin input: both calls are valid, but the value-transfer function cannot reject a sponsor change made after the user reviewed the pool. This defeats the design's stated guarantee that the first stake protects reliance on the advertised deadline.
The sponsor can sustain the condition under the pinned BattleChain dependency. The agreement owner becomes its attackModerator and controls promote, so they can leave the agreement UNDER_ATTACK. A trusted registry moderator can intervene, but the staker has no unilateral recovery path.
The malicious expiry can be 4,294,967,295, approximately February 2106. The proof deposits 100 tokens and shows that withdraw() and claimExpired() revert at the advertised deadline. claimExpired() still reverts 50 years later, with all 100 tokens held by the pool.
Severity is Medium: principal availability is severely affected, but exploitation requires targeting the first stake while the agreement is UNDER_ATTACK. No theft or unconditional permanent lock is claimed because a trusted registry moderator can end the state.
Add test/poc/ExpiryFrontRun.t.sol:
Run:
Result:
Bind a stake to the deadline the user accepted:
As defense in depth, disallow setExpiry whenever the live registry is in an active-risk state. Add a regression test in which the sponsor changes expiry before a pending stake and assert that the stale stake reverts without transferring tokens.
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.