Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

s_claimers Array Declared But Never Populated

Root + Impact

Description

SnowmanAirdrop declares a private storage array s_claimers with a comment indicating it should track the
addresses of all claimers. No code path in the contract ever writes to this array — claimSnowman() sets
s_hasClaimedSnowman[receiver] = true and emits SnowmanClaimedSuccessfully, but never calls
s_claimers.push(receiver). The array is permanently empty.

Any off-chain integrations, dashboards, or future contract logic that depends on s_claimers to enumerate claim
recipients will silently receive an empty list. The slot occupies a storage position, paying deployment gas for
functionality that does not exist.

There is also no public getter for s_claimers, making it entirely unreachable from outside the contract even if
it were populated.

// SnowmanAirdrop.sol:42
@> address[] private s_claimers; // array to store addresses of claimers
// SnowmanAirdrop.sol:92-98 — claimSnowman() never pushes to s_claimers
i_snow.safeTransferFrom(receiver, address(this), amount);
s_hasClaimedSnowman[receiver] = true; // mapping updated
emit SnowmanClaimedSuccessfully(receiver, amount);
i_snowman.mintSnowman(receiver, amount);
@> // s_claimers.push(receiver) is absent

Risk

Likelihood:

  • When any developer or integrator queries the list of claimers expecting a populated array

  • When a future contract upgrade or airdrop phase attempts to iterate s_claimers to check prior recipients

  • When off-chain tooling reads contract storage slots expecting claimer data

Impact:

  • Off-chain systems relying on s_claimers receive an empty list — claimer history is silently incomplete

  • Any future contract logic gating on s_claimers.length > 0 or iterating the array will malfunction

  • Wasted storage slot and deployment gas with no functional benefit

Proof of Concept

The absence is statically verifiable. A grep for any write to s_claimers in the contract returns no results:

rg "s_claimers" src/SnowmanAirdrop.sol
# Output:
# 42: address[] private s_claimers;
# (declaration only — no push, no assignment)

Recommended Mitigation

Either populate the array on each successful claim, or remove it entirely in favor of the event log (which
already provides an enumerable claimer history off-chain via SnowmanClaimedSuccessfully).

Option A — populate the array:
s_hasClaimedSnowman[receiver] = true;
+ s_claimers.push(receiver);
emit SnowmanClaimedSuccessfully(receiver, amount);
Also add a getter if the array is intended to be readable:
+function getClaimers() external view returns (address[] memory) {
+ return s_claimers;
+}
Option B — remove dead storage (preferred):
- address[] private s_claimers;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!