The SpookySwap contract allows adding treats with the same name but different costs, which causes issues with pending treat purchases. This inconsistency in treat costs can lead to transaction reverts and disrupt user experience.
The SpookySwap contract has a vulnerability where treats with the same name can be re-added with a different cost while there are pending purchases for that treat. As a result, users who have already committed to purchasing a treat with an initial price might face unexpected reverts if the treat’s cost is later modified, even if they provided sufficient ETH for the original cost.
This creates an inconsistency in the treat cost for pending purchases and affects the functionality of resolveTrick, potentially leading to failed transactions.
pragma solidity ^0.8.24;
import {Test, console} from "forge-std/Test.sol";
import {SpookySwap} from "../src/TrickOrTreat.sol";
contract TrickOrTreatTest is Test {
SpookySwap spookyswapInstance;
address owner;
address user;
function setUp() public {
owner = vm.addr(0x1);
user = vm.addr(0x2);
vm.label(owner, "Owner");
vm.deal(owner, 100 ether);
vm.label(user, "User");
vm.deal(user, 100 ether);
SpookySwap.Treat[] memory TreatList;
vm.prank(owner);
spookyswapInstance = new SpookySwap(TreatList);
}
function test_poc_addTreatPendingTreatChaneTreatCost() public {
vm.prank(owner);
spookyswapInstance.addTreat("Example", 1 ether, "test");
uint256 tokenId = spookyswapInstance.nextTokenId();
vm.warp(123456790);
vm.prevrandao(987654324);
vm.prank(user);
spookyswapInstance.trickOrTreat{value: 1 ether}("Example");
uint256 cost;
(, cost, ) = spookyswapInstance.treatList(spookyswapInstance.tokenIdToTreatName(tokenId));
console.log("Before Pending Token Cost: ", cost);
vm.prank(owner);
spookyswapInstance.addTreat("Example", 5 ether, "test");
(, cost, ) = spookyswapInstance.treatList(spookyswapInstance.tokenIdToTreatName(tokenId));
console.log("Before Pending Token Cost: ", cost);
vm.prank(user);
vm.expectRevert("Insufficient ETH sent to complete purchase");
spookyswapInstance.resolveTrick{value: 2 ether}(tokenId);
}
}
$ forge test --mt test_poc_addTreatPendingTreatChaneTreatCost -vvvv
[⠊] Compiling...
[⠢] Compiling 2 files with Solc 0.8.27
[⠆] Solc 0.8.27 finished in 2.19s
Compiler run successful!
Ran 1 test for test/test.t.sol:TrickOrTreatTest
[PASS] test_poc_addTreatPendingTreatChaneTreatCost() (gas: 355496)
Logs:
random 2
Before Pending Token Cost: 1000000000000000000
Before Pending Token Cost: 5000000000000000000
Traces:
[358296] TrickOrTreatTest::test_poc_addTreatPendingTreatChaneTreatCost()
├─ [0] VM::prank(Owner: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf])
│ └─ ← [Return]
├─ [118652] SpookySwap::addTreat("Example", 1000000000000000000 [1e18], "test")
│ ├─ emit TreatAdded(name: "Example", cost: 1000000000000000000 [1e18], metadataURI: "test")
│ └─ ← [Stop]
├─ [2341] SpookySwap::nextTokenId() [staticcall]
│ └─ ← [Return] 1
├─ [0] VM::warp(123456790 [1.234e8])
│ └─ ← [Return]
├─ [0] VM::prevrandao(987654324 [9.876e8])
│ └─ ← [Return]
├─ [0] VM::prank(User: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF])
│ └─ ← [Return]
├─ [155065] SpookySwap::trickOrTreat{value: 1000000000000000000}("Example")
│ ├─ [0] console::log("random", 2) [staticcall]
│ │ └─ ← [Stop]
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: SpookySwap: [0xF2E246BB76DF876Cef8b38ae84130F4F55De395b], tokenId: 1)
│ ├─ emit MetadataUpdate(_tokenId: 1)
│ ├─ emit Swapped(user: User: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF], treatName: "Example", tokenId: 1)
│ └─ ← [Stop]
├─ [1464] SpookySwap::tokenIdToTreatName(1) [staticcall]
│ └─ ← [Return] "Example"
├─ [3042] SpookySwap::treatList("Example") [staticcall]
│ └─ ← [Return] "Example", 1000000000000000000 [1e18], "test"
├─ [0] console::log("Before Pending Token Cost: ", 1000000000000000000 [1e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::prank(Owner: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf])
│ └─ ← [Return]
├─ [28952] SpookySwap::addTreat("Example", 5000000000000000000 [5e18], "test")
│ ├─ emit TreatAdded(name: "Example", cost: 5000000000000000000 [5e18], metadataURI: "test")
│ └─ ← [Stop]
├─ [1464] SpookySwap::tokenIdToTreatName(1) [staticcall]
│ └─ ← [Return] "Example"
├─ [3042] SpookySwap::treatList("Example") [staticcall]
│ └─ ← [Return] "Example", 5000000000000000000 [5e18], "test"
├─ [0] console::log("Before Pending Token Cost: ", 5000000000000000000 [5e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::prank(User: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF])
│ └─ ← [Return]
├─ [0] VM::expectRevert(Insufficient ETH sent to complete purchase)
│ └─ ← [Return]
├─ [6824] SpookySwap::resolveTrick{value: 2000000000000000000}(1)
│ └─ ← [Revert] revert: Insufficient ETH sent to complete purchase
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.34ms (1.56ms CPU time)
Ran 1 test suite in 792.45ms (11.34ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)