Trick or Treat

First Flight #27
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Invalid

NFT Locking Vulnerability in SpookySwap contract

Summary

The SpookySwap contract contains a vulnerability allowing an attacker to mint an NFT onto the contract’s address by sending a minimal amount of ETH (e.g., 1 wei). If the attacker does not complete the transaction by calling the resolveTrick function, the NFT remains "stuck" on the contract address indefinitely. This prevents the contract owner from either reclaiming the NFT or obtaining its full purchase value.

Vulnerability Details

The vulnerability is located in the trickOrTreat function, specifically in the code block handling the "trick" scenario (i.e., where the treat costs double). When a user sends insufficient ETH for a doubled-cost treat, the function mints an NFT to the contract's address and records it as pending. However, the contract lacks a mechanism to enforce the completion of this purchase. If the resolveTrick function is not called by the original buyer, the NFT remains locked on the contract address.

77-92:

} else {
// User didn't send enough ETH
// Mint NFT to contract and store pending purchase
uint256 tokenId = nextTokenId;
_mint(address(this), tokenId);
_setTokenURI(tokenId, treat.metadataURI);
nextTokenId += 1;
pendingNFTs[tokenId] = msg.sender;
pendingNFTsAmountPaid[tokenId] = msg.value;
tokenIdToTreatName[tokenId] = _treatName;
emit Swapped(msg.sender, _treatName, tokenId);
// User needs to call fellForTrick() to finish the transaction
}

In the case of an attacker attempting to exploit this vulnerability with a minimal amount of ETH (for example, 1 wei):

  • If random = 1 or falls within the standard pricing scenario (random != 2), the check require(msg.value >= requiredCost, "Insufficient ETH sent for treat"); will revert the transaction due to insufficient ETH, causing the attacker to pay only the gas fee for each reverted attempt.

  • Therefore, the transaction will only succeed when random == 2, allowing the attacker to pay only the gas fee on repeated attempts until they reach the desired scenario.

This approach makes the attack possible but reduces its cost, as the attacker will only be paying gas fees for each reverted transaction until he achieves an random == 2 outcome.

Impact

This vulnerability allows a malicious user to mint an NFT for a minimal ETH amount and effectively lock it on the contract, thus:

  1. Preventing the contract owner from receiving the NFT’s full value.

  2. Potentially accumulating "stuck" NFTs on the contract’s balance, which may reduce contract functionality and cause financial losses for the owner.

Tools Used

VS

Recommendations

  1. Implement an Expiration Mechanism: Introduce a timestamp when each pending NFT is minted to the contract address. After a defined period (e.g., 24 hours), the contract owner should be able to reclaim or resell unresolved NFTs.

  2. Allow Owner Reclaim: Add a function allowing the contract owner to transfer unresolved NFTs back to their address or to an escrow address after the expiration period has passed. This function should only be accessible for NFTs pending beyond the set timeframe.

  3. Enforce Minimum Payment: Ensure trickOrTreat requires a minimum ETH amount (such as the NFT’s standard price or half its price in the "treat" scenario) to mitigate the risk of malicious users locking NFTs for negligible amounts.

Updates

Appeal created

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[invalid] Unlimited pending NFTs

The protocol can work correctly with more than 20000 tokens in it. It is informational.

Support

FAQs

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