Normal behavior: claimSnowman() checks that the receiver has a non-zero Snow balance before proceeding, then uses that balance to determine the NFT mint amount.
The issue: The function checks i_snow.balanceOf(receiver) == 0 at line 76, then re-reads i_snow.balanceOf(receiver) at line 84 for the transfer amount and Merkle leaf computation. Between these two reads the receiver's balance can change. The Merkle tree encodes a committed balance at snapshot time — if the live balance differs, the computed leaf won't match the proof, permanently locking out legitimate users.
Likelihood:
A receiver whose Snow balance changes between Merkle snapshot generation and claim time is permanently locked out with no recovery path.
Front-running bots monitor the mempool and transfer Snow tokens out before the claim transaction executes.
Impact:
Legitimate users are permanently blocked from claiming with no admin override.
An attacker manipulates their own claim amount by transferring tokens between the two balance reads in the same block.
The following test demonstrates that a receiver whose Snow balance changes between Merkle tree generation and claim time is permanently locked out. The Merkle tree encodes the balance at snapshot time, but claimSnowman() recomputes the leaf using the live balance. Any discrepancy causes SA__InvalidProof to revert permanently.
The fix passes amount as an explicit parameter instead of reading the live balance. The Merkle proof verifies the receiver is entitled to exactly that amount, the live balance check is removed since the Merkle proof provides the source of truth. This eliminates both the TOCTOU race and the balance manipulation vector.
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.