Trick or Treat

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

Price Manipulation in trickOrTreat: Risk of Incomplete NFT Purchase and Financial Loss Due to Unsecured Double-Price Mechanism

Summary

When it's a trick, a user is requird to pay double amount for the Treat. However, the NFT is minted to SpookySwapand user can complete the purchase by paying the remaining amount later by calling resolveTrick. But the contract owner can increase the cost of the Treat and make user pay more for it or block from completing the purchase.

Vulnerability Details

The trickOrTreat function in the SpookySwap contract uses a pseudo-random number to adjust the cost of purchasing NFTs.

For a double-price purchase, the NFT is first minted to the SpookySwap contract address, and the user pays an initial amount of ETH. The user must then call the resolveTrick function to complete the purchase by paying the remaining required ETH.

The contract owner has the authority to call setTreatCost function to increase the price of the NFT between the user's initial trickOrTreat transaction and the follow-up resolveTrick call. If the owner raises the NFT price before the user can call resolveTrick, the user would not be able to complete the purchase under the originally determined cost, effectively blocking the purchase or forcing the user to pay an arbitrarily higher price.

Proof Of Concept

  1. User buys the NFT by calling trickOrTreat

  2. If the random number is 2, it's a trick and user has to pay double cost.

  3. The NFT is minted to SpookySwap.

  4. Admin calls the setTreatCost function to increase the cost of the Treat.

  5. When user calls resolveTrick with required amount, the transaction would revert as the cost has been increased.

  6. The user has to pay more ETH to complete the purchase.

<details>
<summary>Proof of Code</summary>
Create a test file by the name `TrickOrTreat.t.sol`.
Add the following test in it.
```javascript
function testTrickIncreaseCostResolveTrickFails() public {
vm.warp(119);
vm.roll(123560);
vm.prank(user);
spookySwap.trickOrTreat{value: 5e18}("Bheem");
// already paid 5 ether, trick Costed 10 ether
spookySwap.setTreatCost("Bheem", 7e18); // increase the cost
// new trick price
(,uint256 _cost, ) = spookySwap.treatList("Bheem");
assert(_cost == 7e18); // new cost 7e18
// double cost = 14e18
// to resolve, required = 9e18
vm.prank(user);
vm.expectRevert();
spookySwap.resolveTrick{value: 5e18}(1);
}
```
</details>

Impact

The user would be blocked from completing the purchase of the NFT. Moreover, if they want it, they are required to pay higher cost.

The user is at the mercy of the owner's pricing adjustments, leading to potential unfair practices.

Tools Used

VS Code, Foundry, Manual review

Recommendations

  1. Record the Treat's initial price at the time of trickOrTreat call in the pendingNFTs data. Ensure this price is locked in for the subsequent resolveTrick call, preventing any modification by the owner.

  2. Consider using immutable variables for base prices or restricting price changes to specified intervals (e.g. every 24 hours) using time-based restrictions.

Updates

Appeal created

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

[invalid] Change cost between the call of trickOrTreat and resolveTrick

Only the owner has the rights to change the cost of the treat. Therefore it is assumed that the owner will not change the cost of the pending NFTs. The owner role is trusted.

Support

FAQs

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