Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: high
Valid

[H-03] Default enum value `NICE` (0) lets any unchecked address collect presents

Description

The Status enum defines NICE = 0 as its first value. Solidity mappings return 0 for uninitialized keys. Both s_theListCheckedOnce and s_theListCheckedTwice default to NICE for any address that was never checked by Santa. The collectPresent() eligibility check passes for these unchecked addresses, letting anyone collect an NFT without ever appearing on Santa's list.

Vulnerability Details

// src/SantasList.sol, lines 69-74
enum Status {
NICE, // @> 0 — this is the default mapping value
EXTRA_NICE, // 1
NAUGHTY, // 2
NOT_CHECKED_TWICE // 3
}
// src/SantasList.sol, lines 79-80
mapping(address person => Status naughtyOrNice) private s_theListCheckedOnce;
mapping(address person => Status naughtyOrNice) private s_theListCheckedTwice;
// @> Both default to Status.NICE (0) for any uninitialized key
// src/SantasList.sol, lines 154-156
if (s_theListCheckedOnce[msg.sender] == Status.NICE
&& s_theListCheckedTwice[msg.sender] == Status.NICE) {
_mintAndIncrement(); // @> passes for any address never checked
return;
}

Any address that Santa never called checkList or checkTwice on has both mappings returning 0, which equals Status.NICE. The condition evaluates to true, and _mintAndIncrement() mints an NFT. The entire two-pass checking system is rendered meaningless.

Risk

Likelihood:

  • Every address that has never interacted with Santa automatically qualifies. No setup or precondition required.

Impact:

  • Complete bypass of the naughty/nice classification. Any address can mint an NFT after Christmas without Santa's approval. The core protocol invariant ("only checked-twice NICE/EXTRA_NICE users get presents") is broken.

Proof of Concept

function testExploit_DefaultEnumNice() public {
// Attacker was NEVER checked by Santa
// Default mapping value = 0 = Status.NICE
// Both s_theListCheckedOnce and s_theListCheckedTwice default to NICE
vm.prank(attacker);
santasList.collectPresent();
// Attacker got an NFT without ever being checked!
assertEq(santasList.balanceOf(attacker), 1);
}

Output:

[PASS] testExploit_DefaultEnumNice() (gas: 99797)

Recommendations

Reorder the enum so the default value (0) represents an unchecked state:

enum Status {
+ NOT_CHECKED_TWICE, // 0 — safe default
NICE,
EXTRA_NICE,
- NAUGHTY,
- NOT_CHECKED_TWICE
+ NAUGHTY
}

And update collectPresent() to explicitly reject NOT_CHECKED_TWICE:

+ if (s_theListCheckedTwice[msg.sender] == Status.NOT_CHECKED_TWICE) {
+ revert SantasList__NotNice();
+ }
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-02] All addresses are considered `NICE` by default and are able to claim a NFT through `collectPresent` function before any Santa check.

## Description `collectPresent` function is supposed to be called by users that are considered `NICE` or `EXTRA_NICE` by Santa. This means Santa is supposed to call `checkList` function to assigned a user to a status, and then call `checkTwice` function to execute a double check of the status. Currently, the enum `Status` assigns its default value (0) to `NICE`. This means that both mappings `s_theListCheckedOnce` and `s_theListCheckedTwice` consider every existent address as `NICE`. In other words, all users are by default double checked as `NICE`, and therefore eligible to call `collectPresent` function. ## Vulnerability Details The vulnerability arises due to the order of elements in the enum. If the first value is `NICE`, this means the enum value for each key in both mappings will be `NICE`, as it corresponds to `0` value. ## Impact The impact of this vulnerability is HIGH as it results in a flawed mechanism of the present distribution. Any unchecked address is currently able to call `collectPresent` function and mint an NFT. This is because this contract considers by default every address with a `NICE` status (or 0 value). ## Proof of Concept The following Foundry test will show that any user is able to call `collectPresent` function after `CHRISTMAS_2023_BLOCK_TIME` : ``` function testCollectPresentIsFlawed() external { // prank an attacker's address vm.startPrank(makeAddr("attacker")); // set block.timestamp to CHRISTMAS_2023_BLOCK_TIME vm.warp(1_703_480_381); // collect present without any check from Santa santasList.collectPresent(); vm.stopPrank(); } ``` ## Recommendations I suggest to modify `Status` enum, and use `UNKNOWN` status as the first one. This way, all users will default to `UNKNOWN` status, preventing the successful call to `collectPresent` before any check form Santa: ``` enum Status { UNKNOWN, NICE, EXTRA_NICE, NAUGHTY } ``` After modifying the enum, you can run the following test and see that `collectPresent` call will revert if Santa didn't check the address and assigned its status to `NICE` or `EXTRA_NICE` : ``` function testCollectPresentIsFlawed() external { // prank an attacker's address vm.startPrank(makeAddr("attacker")); // set block.timestamp to CHRISTMAS_2023_BLOCK_TIME vm.warp(1_703_480_381); // collect present without any check from Santa vm.expectRevert(SantasList.SantasList__NotNice.selector); santasList.collectPresent(); vm.stopPrank(); } ```

Support

FAQs

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

Give us feedback!