Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

```SantaList::collectPresent()``` is unredeemable if the user has received a bought Present

Summary

If a user classified as "NICE" or "EXTRA_NICE" receives a SantaList::buyPresent() NFT prior to redeeming their gift, they are precluded from redeeming the present by invoking SantaList::collectPresent().

Vulnerability Details

function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) {
revert SantasList__NotChristmasYet();
}
@> if (balanceOf(msg.sender) > 0) {
@> revert SantasList__AlreadyCollected();
}
if (s_theListCheckedOnce[msg.sender] == Status.NICE && s_theListCheckedTwice[msg.sender] == Status.NICE) {
_mintAndIncrement();
return;
} else if (
s_theListCheckedOnce[msg.sender] == Status.EXTRA_NICE
&& s_theListCheckedTwice[msg.sender] == Status.EXTRA_NICE
) {
_mintAndIncrement();
i_santaToken.mint(msg.sender);
return;
}
revert SantasList__NotNice();
}

Impact

function testCollectPresentNiceIsUncollectable() public {
//The user2 is EXTRA_NICE and collects the present and one SantaToken
vm.startPrank(santa);
santasList.checkList(user2, SantasList.Status.EXTRA_NICE);
santasList.checkTwice(user2, SantasList.Status.EXTRA_NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.startPrank(user2);
santasList.collectPresent();
assertEq(santasList.balanceOf(user2), 1);
assertEq(santaToken.balanceOf(user2), 1e18);
vm.stopPrank();
//The user is NICE
vm.startPrank(santa);
santasList.checkList(user, SantasList.Status.NICE);
santasList.checkTwice(user, SantasList.Status.NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
//User receive an NFT present from user2
vm.startPrank(user2);
santasList.safeTransferFrom(user2, user, 0);
assertEq(santasList.balanceOf(user), 1);
vm.stopPrank();
//User tries to collect the present but he/she can't because he has already received a present from another address (user2)
vm.startPrank(user);
vm.expectRevert();
santasList.collectPresent();
vm.stopPrank();
}

Tools Used

Manual review

Recommendations

Considering to store the status of the Present collection (collected/uncollected) using a mapping.

+ mapping(address => bool) private s_presentCollected;
function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) {
revert SantasList__NotChristmasYet();
}
- if (balanceOf(msg.sender) > 0)
+ if (s_presentCollected[msg.sender]) {
revert SantasList__AlreadyCollected();
}
if (s_theListCheckedOnce[msg.sender] == Status.NICE && s_theListCheckedTwice[msg.sender] == Status.NICE) {
+ s_presentCollected[msg.sender] = true;
_mintAndIncrement();
return;
} else if (
s_theListCheckedOnce[msg.sender] == Status.EXTRA_NICE
&& s_theListCheckedTwice[msg.sender] == Status.EXTRA_NICE
) {
+ s_presentCollected[msg.sender] = true;
_mintAndIncrement();
i_santaToken.mint(msg.sender);
return;
}
revert SantasList__NotNice();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak Already Collected Check

Relying on balanceOf > 0 in collectPresent() allows the msg.sender to send their present to another address and then collect again.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.