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
}
## 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(); } ```
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.