When the pool outcome is flagged as CORRUPTED (bad faith), the entire pool balance (staker principal + bonus) is swept to recoveryAddress via claimCorrupted(). Stakers deposit with the understanding that, on corruption, their funds flow to the published recoveryAddress. Other sponsor-controlled parameters like expiry and pool scope have one way lock mechanisms to protect staker reliance expiry locks after the first stake and scope locks when the registry leaves pre attack staging.
However, setRecoveryAddress() has no lock mechanism it can be called by the owner at any time including after the outcome has already been flagged as CORRUPTED. This allows the sponsor to redirect the full pool sweep to an arbitrary address after the outcome is determined but before claimCorrupted() is called.
This creates an inconsistency: expiry and scope are locked to protect staker expectations but recoveryAddress which controls where all staker funds go on corruption is not.
recoveryAddress has no equivalent protection despite controlling the destination of the entire pool on CORRUPTED.
Likelihood: Medium
The sponsor must be malicious (not just negligent) but has a clear financial incentive on bad faith CORRUPTED outcome where they receive the full pool
The attack is trivially executable a single transaction to setRecoveryAddress()before claimCorrupted() is called
No time pressure the window lasts from flagOutcome until claimCorrupted() is called which can be indefinite
Impact: High
On bad faith CORRUPTED: 100% of staker principal + bonus is redirected to the sponsor's chosen address
On good faith CORRUPTED: the residual after the attacker's bounty claim is redirected
On SURVIVED/EXPIRED: sweepUnclaimedBonus and sweepUnclaimedCorrupted send to recoveryAddress, so bonus dust and donations can be redirected
Stakers who verified recoveryAddress before depositing have their assumption violated after the outcome is determined
Attack flow: A malicious sponsor calls setRecoveryAddress() after the pool outcome has been flagged CORRUPTED but before claimCorrupted() is called. Since setRecoveryAddress() has no outcome state check the call succeeds and overwrites the recovery address. When claimCorrupted() is then called it sweeps 100% of the pool balance (staker principal + bonus) to the attacker's new address instead of the original recoveryAddress that stakers trusted at deposit time.
Add an outcome guard to setRecoveryAddress() so it reverts once the pool outcome has been set. This follows the same pattern already used for expiryLocked and scopeLocked sponsor controlled parameters that affect staker funds must be locked once stakers have relied on them. After the outcome is set there is no legitimate reason for the owner to change the recovery address.
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.