Summary
It is possible to feed a horse whose token has not been minted yet or is burned.
Vulnerability Details
It is possible to feed a horse with an ID that has not yet been minted and subsequently mint a horse with the same ID. This can lead to incorrect results when verifying the horse's happiness, as the verification process may indicate that the horse is happy even when it shouldn't be.
Impact
Feeding a non-minted horse may cause false happiness verification results post-minting.
Proof of Concept (PoC)
Add the next test in Base_Test.t.sol
.
function test_FeedingNotMintedHorseIsAllowed(uint256 horseId, uint256 feedAt) public {
vm.warp(feedAt);
horseStore.feedHorse(horseId);
assertEq(horseStore.horseIdToFedTimeStamp(horseId), feedAt);
}
Run a test with forge test --mt test_FeedingNotMintedHorseIsAllowed
.
Tools Used
Recommendations
To properly feed a horse, it's important to verify the validity of the token ID.
Recommended changes in HorseStore.sol::feedHorse()
function:
function feedHorse(uint256 horseId) external {
+ _requireOwned(horseId); // reverts if the token with `horseId` has not been minted yet, or if it has been burned
horseIdToFedTimeStamp[horseId] = block.timestamp;
}
Add the next test to HorseStoreSolidity.t.sol
.
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
function test_FeedingHorseBeforeMintingItIsNotAllowed(address randomOwner) public {
vm.assume(randomOwner != address(0));
vm.assume(!_isContract(randomOwner));
uint256 horseId = horseStore.totalSupply();
vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, horseId));
horseStore.feedHorse(horseId);
}
Run a test with forge test --mt test_FeedingHorseBeforeMintingItIsNotAllowed
.
Recommended changes to HorseStore.huff::FEED_HORSE()
function:
#define macro FEED_HORSE() = takes (0) returns (0) {
timestamp // [timestamp]
0x04 calldataload // [horseId, timestamp]
+ dup1 // [horseId, horseId, timestamp]
+ // revert if owner is zero address/not minted
+ [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, horseId, timestamp]
+ dup1 continue jumpi
+ NOT_MINTED(0x00)
+ continue:
+ dup3 dup3 // [horseId, timestamp, owner, horseId, timestamp]
+ STORE_ELEMENT(0x00) // [owner, horseId, timestamp]
// End execution
0x11 timestamp mod
endFeed jumpi
revert
endFeed:
stop
}
Add the next test to HorseStoreHuff.t.sol
.
function test_FeedingHorseBeforeMintingItIsNotAllowed(address randomOwner) public {
vm.assume(randomOwner != address(0));
vm.assume(!_isContract(randomOwner));
uint256 horseId = horseStore.totalSupply();
vm.expectRevert("NOT_MINTED");
horseStore.feedHorse(horseId);
}
Run a test with forge test --mt test_FeedingHorseBeforeMintingItIsNotAllowed
.