SnowmanAirdrop.sol::claimSnowman()
The Merkle leaf in the SnowmanAirdrop.sol::claimSnowman()
function is computed using the dynamic on-chain balance of i_snow
tokens for the receiver
. Specifically, the amount
variable, derived from i_snow.balanceOf(receiver)
, is directly used in the Merkle leaf generation:
javascript uint256 amount = i_snow.balanceOf(receiver); bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(receiver, amount))));
This means the Merkle proof is being verified against a mutable, real-time balance rather than the static amount
from the snapshot used to build the Merkle tree off-chain.
This design introduces critical issues and makes the Merkle proof mechanism unreliable and vulnerable:
1. Denial of Claims for Legitimate Users: If a user's i_snow
balance changes (e.g., they transfer tokens out, or receive more tokens) after the Merkle tree snapshot was taken, the amount
derived from balanceOf
at the time of claiming will no longer match the amount
used to generate their original leaf. This mismatch will cause the MerkleProof.verify
function to fail, preventing legitimate users from claiming their tokens.
2. Potential for Malicious Exploitation (in conjunction with H-6): While the current checks might limit direct double-spending based on proof alone, combined with the unsafe receiver
as signer (H-6), an attacker could potentially find an eligible (receiver, amount)
pair from the Merkle tree and attempt to exploit timing or balance changes to create favorable conditions for their claims or to block others.
The explicit use of uint256 amount = i_snow.balanceOf(receiver);
immediately before computing bytes32 leaf = keccak256(abi.encode(receiver, amount));
highlights the dependency on mutable on-chain state for Merkle proof verification.
The Merkle proof must be computed from immutable, off-chain data that was snapshotted at a specific time.
Require amount
as an Explicit Argument: The amount
that was included in the off-chain snapshot (and used to generate the Merkle proof) should be passed as an explicit argument to the claimSnowman
function. This amount
should also be included in the EIP-712 signed message (refer to H-6).
Merkle Leaf from Immutable Data: The Merkle leaf computation within the contract must use this explicitly passed expectedAmount
argument, which represents the immutable snapshot data. The i_snow.balanceOf(receiver)
should not be used for Merkle leaf generation. If a minimum current balance is still a project requirement, it should be a separate require
check after the Merkle proof validation (e.g., require(i_snow.balanceOf(receiver) >= expectedAmount, "SA__InsufficientCurrentBalance");
), but understand this can still lead to legitimate users being unable to claim if their balance fluctuates.
Claims use snow balance of receiver to compute the merkle leaf, making proofs invalid if the user’s balance changes (e.g., via transfers). Attackers can manipulate balances or frontrun claims to match eligible amounts, disrupting the airdrop.
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.