fundContest + duplicate funding permanently strandedThe owner creates a contest and calls fundContest(index) once, which moves exactly totalRewards into the Pot. The Pot then pays out totalRewards through claims and the close.
fundContest writes no state and does not compare the Pot's balance with totalRewards, so every call transfers another full totalRewards.
Pot accounts only through remainingRewards. That value is set once from totalRewards in the constructor, and the Pot never reads its own token balance. Neither contract has a sweep or refund.
So the surplus sits outside every payout formula and can never go back to the owner.
Likelihood:
The owner repeats fundContest for an index while holding a standing allowance of at least twice totalRewards, for example a max approval. Typical causes are a retried transaction sent with a new nonce, a funding script that re-runs, or an index mix-up across several pots. With an exact approval, the second call reverts inside the token (ERC20InsufficientAllowance), not because of any guard in the protocol.
Only the trusted owner can trigger it, so it takes an operational mistake by a trusted role.
Impact:
A full duplicate of totalRewards is lost to the owner. In the PoC the owner is down 2000 for a 1000 contest. After every player claims and the pot is closed twice, the Pot still holds 1000 while getContestRemainingRewards reports 0.
When some rewards are unclaimed, repeated closes (the separate non-terminal-close issue) can push the surplus out, but only to claimants and to ContestManager, where it is locked. In the PoC five closes paid p1 500 + 5 x 225 = 1625 and locked 250 in ContestManager. None of the surplus ever returns to the owner.
The test deploys ContestManager and the repo's test/ERC20Mock.sol. All three tests pass:
test_baseline_exactApproval_secondFundRevertsOnAllowanceOnly: with an exact approval, the second fundContest(0) reverts only with ERC20InsufficientAllowance(conMan, 0, 1000).
test_doubleFund_surplusPermanentlyStuck:
With a max approval, two fundContest(0) calls leave 2000 in the Pot, while both views still say 1000.
Both players claim 500. Two closes move nothing.
The Pot keeps 1000, getContestRemainingRewards returns 0, and the owner's balance is START - 2000. The admin cannot call pot.closePot() directly.
test_doubleFund_surplusLeaksOnlyToClaimantsAndManager: p2 never claims, and five closes pay p1 1625 in total and lock 250 in ContestManager. The owner recovers nothing.
Save the file as test/PoC_MC-05.t.sol and run from the repository root:
Record that a pot has been funded and reject a second funding.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.