Trick or Treat

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

## [H-1] Vulnerable Refund Logic Enables Price Manipulation via Conditional receive Function

Summary

IMPACT : HIGH

Likelihood : HIGH/MEDIUM

An attacker could create a smart contract with a receivefunction that only accepts specific values. For instance, if the treat costs 1 ether , the attacker might call trickOrTreat with 1.5 ether , configuring their contract to only allow a refund of 1 ether. By doing this, they could repeatedly revert any refund that doesn’t match this condition, allowing them to revert the trickOrTreat game until they win.

Vulnerability Details

an attacker could have a receive function that looks like this (if we assume the price of the NFT would be 1 ether and the attacker sends 1.5 ether).

receive() external payable {
require(msg.value == 1 ether);
}

Impact

If an attacker can consistently acquire NFTs at 50% of the intended price, they could repeatedly sell them on the market at, let's say for 90% of the original price. This undercuts the market value, creating downward pressure on the NFT's price as the attacker continues the exploit. As a result, the market value would gradually drop below the protocol’s original MintPrice, ultimately forcing the protocol to reduce its minting price to compete with the secondary market. In the worst case, this cycle could drive the price toward zero, undermining the protocol’s revenue and destabilizing the NFT’s perceived value and desirability.

Tools Used

Manual Review

Recommendations

you could spilt the TrickOrTreat function into 2 separate functions, atrickOrTreatand aclaimRefund function. And saving the amount that has to be refunded in a new mapping refundsToBePaid for example

implement this in the trickOrTreat function:

if (msg.value > requiredCost) {
uint256 refund = msg.value - requiredCost;
refundsToBePaid[msg.sender] += refund;
- (bool refundSuccess,) = msg.sender.call{value: refund}("");
- require(refundSuccess, "Refund failed");
}

and adding a separate claimRefund function

function claimRefund() public {
uint256 refund = refundsToBePaid[msg.sender];
require(refund > 0, "No refund available");
delete refundsToBePaid[msg.sender];
(bool success,) = msg.sender.call{value: refund}("");
require(success, "Refund failed");
}

Making the users pull out their funds rather than pushing it to to them.

Updates

Appeal created

bube Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
jumpupjoran Submitter
10 months ago
bube Lead Judge
10 months ago
jumpupjoran Submitter
10 months ago
bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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