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. Between these two reads, the receiver's balance can change. Additionally, the Merkle leaf is computed using the live balance at claim time rather than a committed balance, meaning the Merkle proof (which was generated for a specific amount) may not match the current balance, causing legitimate claims to fail or allowing balance manipulation to affect claim amounts.
Likelihood:
A receiver can transfer Snow tokens to another address between the check and the read in the same block.
Front-running bots can manipulate the balance between the two reads.
Receiver can manipulate claim amount by changing balance between check and use.
Legitimate users whose balance changes between Merkle tree generation and claim time may be permanently blocked.
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. Additionally a receiver can manipulate their
claim amount by transferring tokens before calling claimSnowman(), causing
the live balance to differ from the committed Merkle amount.
The fix passes amount as an explicit parameter to claimSnowman() instead
of reading it from the live balance. The Merkle proof verifies that the
receiver is entitled to exactly that amount — no more, no less. The live
balance check is removed entirely since the Merkle proof provides the
source of truth. This eliminates both the TOCTOU race condition and the
balance manipulation vector in a single change.
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.