Santa's List

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

`Status.NICE` Enum Default Allows Any Unchecked Address to Collect a Present

F6 — Status.NICE Enum Default Allows Any Unchecked Address to Collect a Present

Scope

  • src/SantasList.sol (L69–74, L154–165)

Description

Status.NICE is the first member of the Status enum, giving it the integer value 0. In Solidity, all mapping values default to 0 for uninitialized keys. Therefore, any address that has never been passed to checkList() or checkTwice() already appears as Status.NICE in both state mappings.

enum Status {
@> NICE, // == 0 — the Solidity mapping default for any address
EXTRA_NICE, // == 1
NAUGHTY, // == 2
NOT_CHECKED_TWICE // == 3
}

collectPresent() checks for NICE status but never validates that Santa actually performed the listing:

function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) revert NotChristmasYet();
if (balanceOf(msg.sender) > 0) revert AlreadyCollected();
@> if (s_theListCheckedOnce[msg.sender] == Status.NICE && // always true for new address
@> s_theListCheckedTwice[msg.sender] == Status.NICE) { // always true for new address
_mintAndIncrement();
return;
}
revert SantasList__NotNice();
}

This finding is independent of F1: even if checkList() had the correct onlySanta modifier, any fresh address would still pass the NICE checks without anyone having ever interacted with them.

Risk

Likelihood: High

  • Any Ethereum address can exploit this immediately after Christmas without any prior setup.

  • No capital, tokens, or prior interactions required — a single transaction suffices.

Impact: High

  • The entire purpose of Santa's list — permissioned present distribution — is nullified.

  • When combined with F3 (NFT transfer double-mint bypass), this enables unlimited NFT minting by any single address.

Severity: High

Proof of Concept

unknownUser has never interacted with the protocol. After Christmas, they call collectPresent() and receive an NFT because the mapping returns Status.NICE (0) by default.

function test_H5_UncheckedAddressCanCollectPresent() public {
// unknownUser never passed to checkList or checkTwice
assertEq(uint256(santasList.getNaughtyOrNiceOnce(unknownUser)), 0); // NICE by default
assertEq(uint256(santasList.getNaughtyOrNiceTwice(unknownUser)), 0); // NICE by default
vm.warp(CHRISTMAS_2023 + 1);
vm.prank(unknownUser);
santasList.collectPresent(); // succeeds — no listing required
assertEq(santasList.balanceOf(unknownUser), 1); // NFT minted to unapproved address
}
// [PASS] test_H5_UncheckedAddressCanCollectPresent() (gas: ~153k)
// Combined with F3: infinite mint loop
function test_H5_Combined_With_H3_InfiniteNFTMint() public {
vm.warp(CHRISTMAS_2023 + 1);
address thief = makeAddr("thief");
vm.startPrank(thief);
santasList.collectPresent(); // token 0
santasList.transferFrom(thief, receiver, 0); // reset balance to 0
santasList.collectPresent(); // token 1
santasList.transferFrom(thief, receiver, 1);
santasList.collectPresent(); // token 2 — unbounded
vm.stopPrank();
assertEq(santasList.balanceOf(receiver), 2);
}
// [PASS] test_H5_Combined_With_H3_InfiniteNFTMint() (gas: ~350k)

Recommended Mitigation

Reorder the Status enum so that 0 maps to a non-privileged state that requires explicit Santa action before it is upgraded:

enum Status {
- NICE,
- EXTRA_NICE,
- NAUGHTY,
- NOT_CHECKED_TWICE
+ NOT_CHECKED_TWICE, // 0 — safe default: no action yet
+ NICE, // 1 — requires checkList by Santa
+ EXTRA_NICE, // 2 — requires checkList by Santa (upgraded)
+ NAUGHTY // 3 — requires checkList by Santa
}

With this change, all uninitialized addresses default to NOT_CHECKED_TWICE (0), which causes collectPresent() to revert. Santa must explicitly call checkList() and checkTwice() to elevate an address to NICE or EXTRA_NICE.


Updates

Lead Judging Commences

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