Santa's List

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

Wrong Default Enum Value Allows Any Address to Collect a Free NFT

Description

  • collectPresent requires that an address has been explicitly approved by Santa with NICE or EXTRA_NICE status in both
    the once-checked and twice-checked mappings before an NFT is minted to them.

  • The Status enum places NICE at index 0, which is also the default value for all uninitialized mapping(address =>
    Status) entries in Solidity. Both s_theListCheckedOnce and s_theListCheckedTwice therefore return Status.NICE for every
    address Santa has never touched, causing collectPresent's NICE branch to pass for any fresh address with no Santa
    interaction whatsoever.

enum Status {
NICE, // @> index 0 — this is the Solidity default for uninitialized mapping values
EXTRA_NICE,
NAUGHTY,
NOT_CHECKED_TWICE // @> index 3 — intended default, but never actually the default in storage
}

function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) {
revert SantasList__NotChristmasYet();
}
if (balanceOf(msg.sender) > 0) {
revert SantasList__AlreadyCollected();
}
// @> Both mappings return Status.NICE (0) for any unchecked address
// @> This condition is TRUE for every address Santa never interacted with
if (s_theListCheckedOnce[msg.sender] == Status.NICE
&& s_theListCheckedTwice[msg.sender] == Status.NICE)
{
_mintAndIncrement();
return;
}
revert SantasList__NotNice();
}

Risk

Likelihood:

  • Every Ethereum address that has never been checked by Santa satisfies the condition — the attack requires no setup, no
    prior transaction, and no Santa interaction

  • The exploit is available to the entire address space after CHRISTMAS_2023_BLOCK_TIME, which is already in the past on
    any live deployment

Impact:

  • The entire scarcity model of the NFT is destroyed — unlimited unique addresses can each claim one free NFT with no
    authorization

  • Protocol NFT value and collector trust are nullified as any wallet can mint on demand

Proof of Concept

function testAnyoneCanCollectByDefault() public {
address randomUser = makeAddr("randomUser");
// Santa never called checkList or checkTwice for randomUser

  vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);                                                                
                                                                                                                      
  // randomUser collects with no Santa approval                                                                       
  vm.prank(randomUser);                                 
  santasList.collectPresent();

  assertEq(santasList.balanceOf(randomUser), 1);                                                                      

}

Recommended Mitigation

Reorder the enum so NOT_CHECKED_TWICE is at index 0, making it the Solidity storage default. Update all comparisons
accordingly.

enum Status {                                           
  • NICE,
    
  • EXTRA_NICE,                                                                                                       
    
  • NAUGHTY,
    
  • NOT_CHECKED_TWICE                                                                                                 
    
  • NOT_CHECKED_TWICE,  // index 0 — safe uninitialized default
    
  • NICE,                                                                                                             
    
  • EXTRA_NICE,
    
  • NAUGHTY                                                                                                           
    

    }

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!