Summary
SpookySwap:addTreat
with the same name, overrides previously added treats, affecting treatList[_treatName]
access, which queries the latest.
Vulnerability Details
No overrides protection, users are supposed to TrickOrTreat
over specific items tagged by _name
and eventually being spooked by double/half/same
price. Though ahead of his TX
on-chain might exists a one which overrides
with newer prices affecting dependent functions
of treatList[_treatName]
.
function addTreat(string memory _name, uint256 _rate, string memory _metadataURI) public onlyOwner {
@> treatList[_name] = Treat(_name, _rate, _metadataURI);
treatNames.push(_name);
emit TreatAdded(_name, _rate, _metadataURI);
...impl...
*/
}
function trickOrTreat(string memory _treatName) public payable nonReentrant {
@> Treat memory treat = treatList[_treatName];
require(treat.cost > 0, "Treat cost not set.");
...impl...
*/
}
function resolveTrick(uint256 tokenId) public payable nonReentrant {
require(pendingNFTs[tokenId] == msg.sender, "Not authorized to complete purchase");
string memory treatName = tokenIdToTreatName[tokenId];
@> Treat memory treat = treatList[treatName];
...impl...
*/
}
Impact
Overrides cause invalid queries for treatList[_treatName]
which makes SpookySwap:setTreatCost
to update the cost of the latest treat, besides SpookySwap:trickOrTreat
mints the latest treat and SpookySwap:resolveTrick
even being tied to a tokenId
it still queries a treat by _name
.
Tools Used
Manual Review
Foundry Testing tool
function testTreatOverrides() public {
protocol.addTreat("candy", 0.1 ether, "uri1");
protocol.addTreat("candy", 0.2 ether, "uri2");
uint256 tokenId3 = protocol.nextTokenId();
protocol.addTreat("candy", 0.3 ether, "uri3");
uint256 treats = (protocol.getTreats()).length;
assertEq(treats, 3);
protocol.setTreatCost("candy", 0.5 ether);
(string memory name, uint256 cost,) = protocol.treatList("candy");
assert(cost == 0.5 ether && Strings.equal(name, "candy"));
vm.prank(user);
protocol.trickOrTreat{ value: 1 ether }("candy");
string memory uri = protocol.tokenURI(tokenId3);
assert(Strings.equal(uri, "uri3"));
}
Recommendations
Implement a mapping that indicates which treat name has been already added to treatList[_treatName]
, check name uniqueness before adding
contract SpookySwap is ERC721URIStorage, Ownable(msg.sender), ReentrancyGuard {
+ mapping(string => bool) public exists
/* impl */
function addTreat(string memory _name, uint256 _rate, string memory _metadataURI) public onlyOwner {
+ require(!exists[_name], "Treat already added");
treatList[_name] = Treat(_name, _rate, _metadataURI);
treatNames.push(_name);
emit TreatAdded(_name, _rate, _metadataURI);
}
}