The Status enum lists NICE first, so it occupies index 0:
In Solidity an unset mapping value reads back as the zero value of its type, and for this enum the zero value is Status.NICE. So for any address that Santa has never touched, both s_theListCheckedOnce[addr] and s_theListCheckedTwice[addr] already read NICE.
collectPresent gates the free NFT on exactly that condition:
The first branch — once == NICE && twice == NICE — is satisfied by the default state of every address. The entire "must be checked twice by Santa" requirement is therefore bypassed: any account can call collectPresent (after the Christmas timestamp) and mint itself an NFT, without Santa ever having checked it. The intended sentinel NOT_CHECKED_TWICE was placed at index 3 instead of index 0, so it never guards anything.
The balanceOf(msg.sender) > 0 check caps it at one NFT per address, but an attacker simply uses many addresses; the naughty/nice gate — the whole point of the contract — is defeated for the NFT present.
Impact: High. The core authorization invariant (only Santa-verified NICE/EXTRA_NICE users may collect) is broken by default. Any address, including ones Santa never reviewed or would mark NAUGHTY, can mint the Christmas NFT for free, and can farm arbitrarily many by cycling addresses.
Likelihood: High. No special conditions beyond the (unavoidable) Christmas timestamp; every fresh address qualifies automatically.
Expected: an unchecked address is NOT_CHECKED_TWICE and collectPresent reverts with SantasList__NotNice. Actual: it defaults to NICE and mints.
Make the "unchecked" state the zero value so the default can never satisfy collectPresent. Reorder the enum so the sentinel is index 0:
With this ordering an address that Santa has not processed reads NOT_CHECKED_TWICE on both lists, fails both branches of collectPresent, and correctly reverts until Santa has actually checked it once and twice.
## 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.