01. Relevant GitHub Links
02. Summary
The smart contract does not handle treats with a zero-cost properly, which leads to critical functionality issues, including setting and purchasing treats.
03. Vulnerability Details
The SpookySwap contract contains a vulnerability where treats with zero cost are added but are not handled correctly when attempting to modify or purchase them. Specifically, the contract lacks adequate checks to prevent zero-cost treats from being added, and subsequently, the functions that modify or allow for purchase of such treats revert as expected but could create confusion and disrupt the intended operations of the contract.
03. Impact
Inability to Update Treat Cost: The owner cannot update the cost of a treat once it has been set to zero, as it triggers a revert condition, rendering the treat configuration immutable under these circumstances.
Impossible to Purchase Zero-Cost Treats: Users are prevented from purchasing treats that were mistakenly added with a zero cost, which can limit the intended functionality and lead to unintended transaction failures.
04. Proof of Concept
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_NobodyHandle0CostTreat() public {
vm.prank(owner);
spookyswapInstance.addTreat("0CostTreat", 0, "test");
vm.prank(owner);
vm.expectRevert("Treat must cost something.");
spookyswapInstance.setTreatCost("0CostTreat", 1 ether);
vm.prank(user);
vm.expectRevert("Treat cost not set.");
spookyswapInstance.trickOrTreat{value: 1 ether}("0CostTreat");
}
}
$ forge test --mt test_poc_NobodyHandle0CostTreat -vvvv
[⠊] Compiling...
[⠊] Compiling 2 files with Solc 0.8.27
[⠒] Solc 0.8.27 finished in 1.97s
Compiler run successful!
Ran 1 test for test/test.t.sol:TrickOrTreatTest
[PASS] test_poc_NobodyHandle0CostTreat() (gas: 130020)
Traces:
[130020] TrickOrTreatTest::test_poc_NobodyHandle0CostTreat()
├─ [0] VM::prank(Owner: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf])
│ └─ ← [Return]
├─ [98752] SpookySwap::addTreat("0CostTreat", 0, "test")
│ ├─ emit TreatAdded(name: "0CostTreat", cost: 0, metadataURI: "test")
│ └─ ← [Stop]
├─ [0] VM::prank(Owner: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf])
│ └─ ← [Return]
├─ [0] VM::expectRevert(Treat must cost something.)
│ └─ ← [Return]
├─ [1393] SpookySwap::setTreatCost("0CostTreat", 1000000000000000000 [1e18])
│ └─ ← [Revert] revert: Treat must cost something.
├─ [0] VM::prank(User: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF])
│ └─ ← [Return]
├─ [0] VM::expectRevert(Treat cost not set.)
│ └─ ← [Return]
├─ [7841] SpookySwap::trickOrTreat{value: 1000000000000000000}("0CostTreat")
│ └─ ← [Revert] revert: Treat cost not set.
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.26ms (1.10ms CPU time)
Ran 1 test suite in 785.64ms (11.26ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
05. Tools Used
Manual Code Review and Foundry
06. Recommended Mitigation
Prevent Zero-Cost Treats from Being Added: Add a check in the addTreat function to ensure that treats cannot be added with a zero-cost.
require(cost > 0, "Treat must have a non-zero cost.");
Improve Error Handling for Cost-Related Functions: Ensure all functions that interact with treat cost validate the cost to ensure that zero-cost treats are appropriately handled and cannot cause unintended reverts.