// SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; import {MockERC721, IERC721TokenReceiver} from "../../src/mocks/MockERC721.sol"; import {StdCheats} from "../../src/StdCheats.sol"; import {Test} from "../../src/Test.sol"; contract ERC721Recipient is IERC721TokenReceiver { address public operator; address public from; uint256 public id; bytes public data; function onERC721Received(address _operator, address _from, uint256 _id, bytes calldata _data) public virtual override returns (bytes4) { operator = _operator; from = _from; id = _id; data = _data; return IERC721TokenReceiver.onERC721Received.selector; } } contract RevertingERC721Recipient is IERC721TokenReceiver { function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { revert(string(abi.encodePacked(IERC721TokenReceiver.onERC721Received.selector))); } } contract WrongReturnDataERC721Recipient is IERC721TokenReceiver { function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { return 0xCAFEBEEF; } } contract NonERC721Recipient {} contract Token_ERC721 is MockERC721 { constructor(string memory _name, string memory _symbol) { initialize(_name, _symbol); } function tokenURI(uint256) public pure virtual override returns (string memory) {} function mint(address to, uint256 tokenId) public virtual { _mint(to, tokenId); } function burn(uint256 tokenId) public virtual { _burn(tokenId); } function safeMint(address to, uint256 tokenId) public virtual { _safeMint(to, tokenId); } function safeMint(address to, uint256 tokenId, bytes memory data) public virtual { _safeMint(to, tokenId, data); } } contract MockERC721Test is StdCheats, Test { Token_ERC721 token; function setUp() public { token = new Token_ERC721("Token", "TKN"); } function invariantMetadata() public view { assertEq(token.name(), "Token"); assertEq(token.symbol(), "TKN"); } function testMint() public { token.mint(address(0xBEEF), 1337); assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.ownerOf(1337), address(0xBEEF)); } function testBurn() public { token.mint(address(0xBEEF), 1337); token.burn(1337); assertEq(token.balanceOf(address(0xBEEF)), 0); vm.expectRevert("NOT_MINTED"); token.ownerOf(1337); } function testApprove() public { token.mint(address(this), 1337); token.approve(address(0xBEEF), 1337); assertEq(token.getApproved(1337), address(0xBEEF)); } function testApproveBurn() public { token.mint(address(this), 1337); token.approve(address(0xBEEF), 1337); token.burn(1337); assertEq(token.balanceOf(address(this)), 0); assertEq(token.getApproved(1337), address(0)); vm.expectRevert("NOT_MINTED"); token.ownerOf(1337); } function testApproveAll() public { token.setApprovalForAll(address(0xBEEF), true); assertTrue(token.isApprovedForAll(address(this), address(0xBEEF))); } function testTransferFrom() public { address from = address(0xABCD); token.mint(from, 1337); vm.prank(from); token.approve(address(this), 1337); token.transferFrom(from, address(0xBEEF), 1337); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(0xBEEF)); assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.balanceOf(from), 0); } function testTransferFromSelf() public { token.mint(address(this), 1337); token.transferFrom(address(this), address(0xBEEF), 1337); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(0xBEEF)); assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.balanceOf(address(this)), 0); } function testTransferFromApproveAll() public { address from = address(0xABCD); token.mint(from, 1337); vm.prank(from); token.setApprovalForAll(address(this), true); token.transferFrom(from, address(0xBEEF), 1337); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(0xBEEF)); assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.balanceOf(from), 0); } function testSafeTransferFromToEOA() public { address from = address(0xABCD); token.mint(from, 1337); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, address(0xBEEF), 1337); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(0xBEEF)); assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.balanceOf(from), 0); } function testSafeTransferFromToERC721Recipient() public { address from = address(0xABCD); ERC721Recipient recipient = new ERC721Recipient(); token.mint(from, 1337); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, address(recipient), 1337); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); assertEq(recipient.operator(), address(this)); assertEq(recipient.from(), from); assertEq(recipient.id(), 1337); assertEq(recipient.data(), ""); } function testSafeTransferFromToERC721RecipientWithData() public { address from = address(0xABCD); ERC721Recipient recipient = new ERC721Recipient(); token.mint(from, 1337); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, address(recipient), 1337, "testing 123"); assertEq(token.getApproved(1337), address(0)); assertEq(token.ownerOf(1337), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); assertEq(recipient.operator(), address(this)); assertEq(recipient.from(), from); assertEq(recipient.id(), 1337); assertEq(recipient.data(), "testing 123"); } function testSafeMintToEOA() public { token.safeMint(address(0xBEEF), 1337); assertEq(token.ownerOf(1337), address(address(0xBEEF))); assertEq(token.balanceOf(address(address(0xBEEF))), 1); } function testSafeMintToERC721Recipient() public { ERC721Recipient to = new ERC721Recipient(); token.safeMint(address(to), 1337); assertEq(token.ownerOf(1337), address(to)); assertEq(token.balanceOf(address(to)), 1); assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), 1337); assertEq(to.data(), ""); } function testSafeMintToERC721RecipientWithData() public { ERC721Recipient to = new ERC721Recipient(); token.safeMint(address(to), 1337, "testing 123"); assertEq(token.ownerOf(1337), address(to)); assertEq(token.balanceOf(address(to)), 1); assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), 1337); assertEq(to.data(), "testing 123"); } function testFailMintToZero() public { token.mint(address(0), 1337); } function testFailDoubleMint() public { token.mint(address(0xBEEF), 1337); token.mint(address(0xBEEF), 1337); } function testFailBurnUnMinted() public { token.burn(1337); } function testFailDoubleBurn() public { token.mint(address(0xBEEF), 1337); token.burn(1337); token.burn(1337); } function testFailApproveUnMinted() public { token.approve(address(0xBEEF), 1337); } function testFailApproveUnAuthorized() public { token.mint(address(0xCAFE), 1337); token.approve(address(0xBEEF), 1337); } function testFailTransferFromUnOwned() public { token.transferFrom(address(0xFEED), address(0xBEEF), 1337); } function testFailTransferFromWrongFrom() public { token.mint(address(0xCAFE), 1337); token.transferFrom(address(0xFEED), address(0xBEEF), 1337); } function testFailTransferFromToZero() public { token.mint(address(this), 1337); token.transferFrom(address(this), address(0), 1337); } function testFailTransferFromNotOwner() public { token.mint(address(0xFEED), 1337); token.transferFrom(address(0xFEED), address(0xBEEF), 1337); } function testFailSafeTransferFromToNonERC721Recipient() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337); } function testFailSafeTransferFromToNonERC721RecipientWithData() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337, "testing 123"); } function testFailSafeTransferFromToRevertingERC721Recipient() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337); } function testFailSafeTransferFromToRevertingERC721RecipientWithData() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337, "testing 123"); } function testFailSafeTransferFromToERC721RecipientWithWrongReturnData() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337); } function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData() public { token.mint(address(this), 1337); token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); } function testFailSafeMintToNonERC721Recipient() public { token.safeMint(address(new NonERC721Recipient()), 1337); } function testFailSafeMintToNonERC721RecipientWithData() public { token.safeMint(address(new NonERC721Recipient()), 1337, "testing 123"); } function testFailSafeMintToRevertingERC721Recipient() public { token.safeMint(address(new RevertingERC721Recipient()), 1337); } function testFailSafeMintToRevertingERC721RecipientWithData() public { token.safeMint(address(new RevertingERC721Recipient()), 1337, "testing 123"); } function testFailSafeMintToERC721RecipientWithWrongReturnData() public { token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337); } function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData() public { token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); } function testFailBalanceOfZeroAddress() public view { token.balanceOf(address(0)); } function testFailOwnerOfUnminted() public view { token.ownerOf(1337); } function testMetadata(string memory name, string memory symbol) public { MockERC721 tkn = new Token_ERC721(name, symbol); assertEq(tkn.name(), name); assertEq(tkn.symbol(), symbol); } function testMint(address to, uint256 id) public { if (to == address(0)) to = address(0xBEEF); token.mint(to, id); assertEq(token.balanceOf(to), 1); assertEq(token.ownerOf(id), to); } function testBurn(address to, uint256 id) public { if (to == address(0)) to = address(0xBEEF); token.mint(to, id); token.burn(id); assertEq(token.balanceOf(to), 0); vm.expectRevert("NOT_MINTED"); token.ownerOf(id); } function testApprove(address to, uint256 id) public { if (to == address(0)) to = address(0xBEEF); token.mint(address(this), id); token.approve(to, id); assertEq(token.getApproved(id), to); } function testApproveBurn(address to, uint256 id) public { token.mint(address(this), id); token.approve(address(to), id); token.burn(id); assertEq(token.balanceOf(address(this)), 0); assertEq(token.getApproved(id), address(0)); vm.expectRevert("NOT_MINTED"); token.ownerOf(id); } function testApproveAll(address to, bool approved) public { token.setApprovalForAll(to, approved); assertEq(token.isApprovedForAll(address(this), to), approved); } function testTransferFrom(uint256 id, address to) public { address from = address(0xABCD); if (to == address(0) || to == from) to = address(0xBEEF); token.mint(from, id); vm.prank(from); token.approve(address(this), id); token.transferFrom(from, to, id); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); } function testTransferFromSelf(uint256 id, address to) public { if (to == address(0) || to == address(this)) to = address(0xBEEF); token.mint(address(this), id); token.transferFrom(address(this), to, id); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(address(this)), 0); } function testTransferFromApproveAll(uint256 id, address to) public { address from = address(0xABCD); if (to == address(0) || to == from) to = address(0xBEEF); token.mint(from, id); vm.prank(from); token.setApprovalForAll(address(this), true); token.transferFrom(from, to, id); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); } function testSafeTransferFromToEOA(uint256 id, address to) public { address from = address(0xABCD); if (to == address(0) || to == from) to = address(0xBEEF); if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; token.mint(from, id); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, to, id); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); } function testSafeTransferFromToERC721Recipient(uint256 id) public { address from = address(0xABCD); ERC721Recipient recipient = new ERC721Recipient(); token.mint(from, id); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, address(recipient), id); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); assertEq(recipient.operator(), address(this)); assertEq(recipient.from(), from); assertEq(recipient.id(), id); assertEq(recipient.data(), ""); } function testSafeTransferFromToERC721RecipientWithData(uint256 id, bytes calldata data) public { address from = address(0xABCD); ERC721Recipient recipient = new ERC721Recipient(); token.mint(from, id); vm.prank(from); token.setApprovalForAll(address(this), true); token.safeTransferFrom(from, address(recipient), id, data); assertEq(token.getApproved(id), address(0)); assertEq(token.ownerOf(id), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); assertEq(recipient.operator(), address(this)); assertEq(recipient.from(), from); assertEq(recipient.id(), id); assertEq(recipient.data(), data); } function testSafeMintToEOA(uint256 id, address to) public { if (to == address(0)) to = address(0xBEEF); if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; token.safeMint(to, id); assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); } function testSafeMintToERC721Recipient(uint256 id) public { ERC721Recipient to = new ERC721Recipient(); token.safeMint(address(to), id); assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), ""); } function testSafeMintToERC721RecipientWithData(uint256 id, bytes calldata data) public { ERC721Recipient to = new ERC721Recipient(); token.safeMint(address(to), id, data); assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), data); } function testFailMintToZero(uint256 id) public { token.mint(address(0), id); } function testFailDoubleMint(uint256 id, address to) public { if (to == address(0)) to = address(0xBEEF); token.mint(to, id); token.mint(to, id); } function testFailBurnUnMinted(uint256 id) public { token.burn(id); } function testFailDoubleBurn(uint256 id, address to) public { if (to == address(0)) to = address(0xBEEF); token.mint(to, id); token.burn(id); token.burn(id); } function testFailApproveUnMinted(uint256 id, address to) public { token.approve(to, id); } function testFailApproveUnAuthorized(address owner, uint256 id, address to) public { if (owner == address(0) || owner == address(this)) owner = address(0xBEEF); token.mint(owner, id); token.approve(to, id); } function testFailTransferFromUnOwned(address from, address to, uint256 id) public { token.transferFrom(from, to, id); } function testFailTransferFromWrongFrom(address owner, address from, address to, uint256 id) public { if (owner == address(0)) to = address(0xBEEF); if (from == owner) revert(); token.mint(owner, id); token.transferFrom(from, to, id); } function testFailTransferFromToZero(uint256 id) public { token.mint(address(this), id); token.transferFrom(address(this), address(0), id); } function testFailTransferFromNotOwner(address from, address to, uint256 id) public { if (from == address(this)) from = address(0xBEEF); token.mint(from, id); token.transferFrom(from, to, id); } function testFailSafeTransferFromToNonERC721Recipient(uint256 id) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id); } function testFailSafeTransferFromToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id, data); } function testFailSafeTransferFromToRevertingERC721Recipient(uint256 id) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id); } function testFailSafeTransferFromToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id, data); } function testFailSafeTransferFromToERC721RecipientWithWrongReturnData(uint256 id) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id); } function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) public { token.mint(address(this), id); token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id, data); } function testFailSafeMintToNonERC721Recipient(uint256 id) public { token.safeMint(address(new NonERC721Recipient()), id); } function testFailSafeMintToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { token.safeMint(address(new NonERC721Recipient()), id, data); } function testFailSafeMintToRevertingERC721Recipient(uint256 id) public { token.safeMint(address(new RevertingERC721Recipient()), id); } function testFailSafeMintToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { token.safeMint(address(new RevertingERC721Recipient()), id, data); } function testFailSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { token.safeMint(address(new WrongReturnDataERC721Recipient()), id); } function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) public { token.safeMint(address(new WrongReturnDataERC721Recipient()), id, data); } function testFailOwnerOfUnminted(uint256 id) public view { token.ownerOf(id); } }