Santa's List

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

Status enum default value is NICE, allowing any unchecked address to collect a present by exploiting the default mapping value

Root + Impact

Description

  • SantasList is an NFT contract where Santa marks addresses NICE or NAUGHTY and only approved addresses may call collectPresent to mint a holiday NFT.

  • Both s_theListCheckedOnce and s_theListCheckedTwice are mapping(address => Status) where Status is an enum whose zero-value (Status(0)) is NICE; every address Santa never explicitly checked returns NICE from both mappings, satisfying the guard in collectPresent and minting a free NFT to any caller.

// @> Status(0) == NICE — this is the default for every mapping entry
enum Status { NICE, EXTRA_NICE, NAUGHTY, NOT_CHECKED_TWICE }
// @> both mappings return NICE for any address never written by Santa
mapping(address person => Status) private s_theListCheckedOnce;
mapping(address person => Status) private s_theListCheckedTwice;
function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) { revert SantasList__NotChristmasYet(); }
if (balanceOf(msg.sender) > 0) { revert SantasList__AlreadyCollected(); }
// @> passes for EVERY address Santa never reviewed — both mappings default to NICE
if (s_theListCheckedOnce[msg.sender] == Status.NICE && s_theListCheckedTwice[msg.sender] == Status.NICE) {
_mintAndIncrement();
return;
}
...
}

Risk

Likelihood:

  • Any address can call collectPresent after Christmas without ever interacting with Santa, because the default mapping value satisfies the NICE check unconditionally.

Impact:

  • Every address on-chain can freely mint a SantasList NFT, completely bypassing the access control mechanism and devaluing all legitimate presents.

Proof of Concept

Any account that has never been added to the list calls collectPresent after CHRISTMAS_2023_BLOCK_TIME and receives a free NFT because both mappings silently return Status.NICE.

function test_anyoneCanCollectPresent() public {
address attacker = makeAddr("attacker");
// Santa never called checkList for attacker — mappings default to NICE
vm.warp(CHRISTMAS_2023_BLOCK_TIME);
vm.prank(attacker);
santasList.collectPresent(); // succeeds — attacker now holds an NFT
assertEq(santasList.balanceOf(attacker), 1);
}

The test confirms an address with no list entry successfully mints an NFT.

Recommended Mitigation

Change the default status to NOT_CHECKED_TWICE (or any non-NICE value) by placing it first in the enum, or add an explicit check that the address was reviewed by Santa before treating its status as valid.

- enum Status { NICE, EXTRA_NICE, NAUGHTY, NOT_CHECKED_TWICE }
+ enum Status { NOT_CHECKED_TWICE, NICE, EXTRA_NICE, NAUGHTY }
Updates

Lead Judging Commences

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