Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

NICE at index 0 causes all uninitialized addresses to pass second verification by default

Root + Impact-

Status enum defines NICE at position 0. In Solidity, mapping default value is 0. So s_theListCheckedTwice[anyAddress] defaults to NICE even before Santa calls checkTwice. This breaks two-pass verification system.

The SantasList uses two-pass: checkList() first pass, checkTwice() second pass. Only addresses passing both should collect.

Root Cause

File: SantasList.sol - enum definition

enum Status {
NICE, // 0 - default for all addresses
EXTRA_NICE,
NAUGHTY,
NOT_CHECKED_TWICE
}

Both mappings:
mapping(address => Status) s_theListCheckedOnce
mapping(address => Status) s_theListCheckedTwice

Default to 0 = NICE for any address that was never checked. In collectPresent():

if (s_theListCheckedOnce[msg.sender] == Status.NICE
&& s_theListCheckedTwice[msg.sender] == Status.NICE) {
_mintAndIncrement();
}

Second condition is always true by default. No second pass needed.

Impact

Likelihood: High - No attacker action required for vulnerable state
Impact: Medium - Breaks core invariant of dual confirmation

Proof of Concept

function test_DefaultBypass() public {
SantasList list = new SantasList(santa);
// Only first check done by Santa
vm.prank(santa);
list.checkList(alice, SantasList.Status.NICE);
// Alice collects without second pass - PASSES!
vm.prank(alice);
list.collectPresent();
assertEq(list.balanceOf(alice), 1);
}

Description-Description
Status enum defines NICE at position 0. In Solidity, mapping default value is 0. So s_theListCheckedTwice[anyAddress] defaults to NICE even before Santa calls checkTwice. This breaks two-pass verification system.

## Summary
Status enum defines NICE at position 0. In Solidity, mapping default value is 0. So `s_theListCheckedTwice[anyAddress]` defaults to NICE even before Santa calls checkTwice. This breaks two-pass verification system.
​
## Vulnerability Details
File: SantasList.sol - enum definition
​
enum Status {
NICE, // @> 0
EXTRA_NICE,
NAUGHTY,
NOT_CHECKED_TWICE
}
​
- s_theListCheckedOnce defaults to 0 = NICE
- s_theListCheckedTwice defaults to 0 = NICE
​
In collectPresent():
```solidity
if (s_theListCheckedOnce[msg.sender] == Status.NICE
&& s_theListCheckedTwice[msg.sender] == Status.NICE) {
_mintAndIncrement();
}

Risk

Likelihood:Affects every address from deployment

Impact:Two-pass design is broken. Intended security requirement of double confirmation from Santa does not hold. Any address that passes first check can collect without second check.

Proof of Concept-Description

Status enum defines NICE at position 0. In Solidity, mapping default value is 0. So s_theListCheckedTwice[anyAddress] defaults to NICE even before Santa calls checkTwice. This breaks two-pass verification system.

The SantasList uses two-pass: checkList() first pass, checkTwice() second pass. Only addresses passing both should collect.

Root Cause

File: SantasList.sol - enum definition

function test_DefaultBypass() public {
SantasList list = new SantasList(santa);
// No checkTwice called ever
vm.prank(santa);
list.checkList(alice, SantasList.Status.NICE);
// Alice collects without second pass
vm.prank(alice);
list.collectPresent(); // PASSES! because s_theListCheckedTwice[alice] == 0 == NICE by default
assertEq(list.balanceOf(alice), 1);
}

Recommended Mitigation-Put NOT_CHECKED or UNKNOWN at index 0:

## Description
SantasList uses a two-pass check: checkList (first) and checkTwice (second). The Status enum defines NICE at index 0. In Solidity, all mappings default to 0. Therefore s_theListCheckedTwice for any address that was never checked returns NICE by default, making the second verification pass without Santa ever calling checkTwice.
​
## Vulnerability Details
File: SantasList.sol - enum Status
​
enum Status {
NICE, // 0 - default value
EXTRA_NICE,
NAUGHTY,
NOT_CHECKED_TWICE
}
​
When a user never checked twice:
s_theListCheckedTwice[user] == 0 == NICE (true by default)
​
This breaks the invariant in collectPresent():
`if (s_theListCheckedOnce[msg.sender] == NICE && s_theListCheckedTwice[msg.sender] == NICE)`
​
The second condition is true from deployment for all addresses.
​
## Impact
High Likelihood, Medium Impact: Any address that gets only first check can collect present without second Santa approval. With unguarded checkList, attacker can self-mark first pass and collect.
​
## Proof of Concept
// Only first check done
vm.prank(santa); list.checkList(alice, Status.NICE);
// Alice collects without ever calling checkTwice - succeeds due to default
vm.prank(alice); list.collectPresent();
assertEq(list.balanceOf(alice), 1);
​
## Mitigation
Root cause is enum order. NICE should not be default. The fix requires reordering enum so default (0) is a non-privileged state.
​
Recommended Mitigation:
Reorder enum to put NOT_CHECKED_TWICE at index 0. This ensures uninitialized mappings are considered NOT_CHECKED, not NICE.
​
```diff
enum Status {
+ NOT_CHECKED_TWICE,
NICE,
EXTRA_NICE,
- NAUGHTY,
- NOT_CHECKED_TWICE
+ NAUGHTY
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour 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!