COMP70017-PoDL-T2/test/ERC721.t.sol
2023-02-02 20:14:18 +00:00

277 lines
8.6 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import "../src/ERC20.sol";
import "../src/ERC721.sol";
import "../src/interfaces/IERC721TokenReceiver.sol";
contract CompliantReceiver is IERC721TokenReceiver {
bytes4 internal constant _MAGIC_VALUE =
bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
function onERC721Received(
address,
address,
uint256,
bytes memory
) public pure returns (bytes4) {
return _MAGIC_VALUE;
}
}
contract ReceiverWithoutFunction {}
contract ReceiverWithWrongReturnValue is IERC721TokenReceiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public pure returns (bytes4) {
return bytes4(uint32(1));
}
}
contract BaseERC721Test is Test {
event Transfer(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
event Approval(
address indexed _owner,
address indexed _approved,
uint256 indexed _tokenId
);
event ApprovalForAll(
address indexed _owner,
address indexed _operator,
bool _approved
);
ERC20 public paymentToken;
ERC721 public nft;
uint256 public nftPrice = 1e19;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
address public charlie = makeAddr("charlie");
function setUp() public {
paymentToken = new ERC20("Test token", "TST");
nft = new ERC721(
"Test NFT",
"TSN",
"http://example.com/nft/",
paymentToken,
nftPrice
);
}
function _mintNFT(address owner) internal {
vm.prank(address(this));
paymentToken.mint(owner, nftPrice);
vm.prank(owner);
paymentToken.approve(address(nft), nftPrice);
vm.prank(owner);
nft.mint(owner);
}
}
contract ERC721Test is BaseERC721Test {
function testName() public {
assertEq(nft.name(), "Test NFT");
}
function testSymbol() public {
assertEq(nft.symbol(), "TSN");
}
function testTokenURI() public {
assertEq(nft.tokenURI(0), "http://example.com/nft/0");
assertEq(nft.tokenURI(1), "http://example.com/nft/1");
assertEq(nft.tokenURI(9), "http://example.com/nft/9");
assertEq(nft.tokenURI(10), "http://example.com/nft/10");
assertEq(nft.tokenURI(11), "http://example.com/nft/11");
assertEq(nft.tokenURI(23), "http://example.com/nft/23");
assertEq(nft.tokenURI(99), "http://example.com/nft/99");
assertEq(nft.tokenURI(100), "http://example.com/nft/100");
assertEq(nft.tokenURI(101), "http://example.com/nft/101");
assertEq(nft.tokenURI(158), "http://example.com/nft/158");
assertEq(nft.tokenURI(3874), "http://example.com/nft/3874");
assertEq(nft.tokenURI(9999), "http://example.com/nft/9999");
assertEq(nft.tokenURI(10000), "http://example.com/nft/10000");
}
function testSuccessfulMint() public {
paymentToken.mint(alice, nftPrice);
vm.startPrank(alice);
paymentToken.approve(address(nft), nftPrice);
vm.expectEmit(true, true, true, false);
emit Transfer(address(0), alice, 1);
nft.mint(alice);
assertEq(nft.ownerOf(1), alice);
assertEq(nft.balanceOf(alice), 1);
assertEq(paymentToken.balanceOf(alice), 0);
}
function testMintWithInsufficientBalance() public {
vm.startPrank(alice);
paymentToken.approve(address(nft), nftPrice);
vm.expectRevert("insufficient balance");
nft.mint(alice);
}
function testMintWithInsufficientAllowance() public {
paymentToken.mint(alice, nftPrice);
vm.startPrank(alice);
vm.expectRevert("insufficient allowance");
nft.mint(alice);
}
function testSuccessfulMints() public {
paymentToken.mint(alice, 1e20);
vm.startPrank(alice);
paymentToken.approve(address(nft), nftPrice * 10);
nft.mint(alice);
vm.expectEmit(true, true, true, false);
emit Transfer(address(0), alice, 2);
nft.mint(alice);
assertEq(nft.ownerOf(2), alice);
assertEq(nft.balanceOf(alice), 2);
assertEq(
paymentToken.balanceOf(alice),
1e20 - nftPrice - (nftPrice * 11) / 10
);
}
function testApprove() public {
_mintNFT(alice);
vm.startPrank(alice);
vm.expectEmit(true, true, true, false);
emit Approval(alice, charlie, 1);
nft.approve(charlie, 1);
assertEq(nft.getApproved(1), charlie);
nft.approve(address(0), 1);
assertEq(nft.getApproved(1), address(0));
}
function testApproveUnauthorized() public {
vm.startPrank(bob);
vm.expectRevert("not authorized");
nft.approve(charlie, 1);
}
function testTransferFromOwner() public {
_mintNFT(alice);
vm.prank(alice);
vm.expectEmit(true, true, true, false);
emit Transfer(alice, bob, 1);
nft.transferFrom(alice, bob, 1);
assertEq(nft.ownerOf(1), bob);
}
function testTransferFromApproved() public {
_mintNFT(alice);
vm.prank(alice);
nft.approve(charlie, 1);
vm.prank(charlie);
vm.expectEmit(true, true, true, false);
emit Transfer(alice, bob, 1);
nft.transferFrom(alice, bob, 1);
assertEq(nft.ownerOf(1), bob);
}
function testTransferFromManager() public {
_mintNFT(alice);
vm.prank(alice);
nft.setApprovalForAll(charlie, true);
vm.prank(charlie);
vm.expectEmit(true, true, true, false);
emit Transfer(alice, bob, 1);
nft.transferFrom(alice, bob, 1);
assertEq(nft.ownerOf(1), bob);
}
}
contract BonusERC721Test is BaseERC721Test {
function testSafeTransferToEOA() public {
_mintNFT(alice);
vm.prank(alice);
nft.setApprovalForAll(charlie, true);
vm.prank(charlie);
vm.expectEmit(true, true, true, false);
emit Transfer(alice, bob, 1);
nft.safeTransferFrom(alice, bob, 1);
assertEq(nft.ownerOf(1), bob);
}
function testSafeTransferToCompliantContract() public {
_mintNFT(alice);
address receiver = address(new CompliantReceiver());
vm.prank(alice);
nft.safeTransferFrom(alice, receiver, 1);
assertEq(nft.ownerOf(1), receiver);
}
function testSafeTransferToContractMissingFunction() public {
_mintNFT(alice);
address receiver = address(new ReceiverWithoutFunction());
vm.prank(alice);
vm.expectRevert();
nft.safeTransferFrom(alice, receiver, 1);
}
function testSafeTransferToContractWrongReturnValue() public {
_mintNFT(alice);
address receiver = address(new ReceiverWithWrongReturnValue());
vm.prank(alice);
vm.expectRevert("magic value not returned");
nft.safeTransferFrom(alice, receiver, 1);
}
function testSetApproveAll() public {
vm.prank(alice);
vm.expectEmit(true, true, false, true);
emit ApprovalForAll(alice, charlie, true);
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
}
function testSetApproveAllTwice() public {
vm.startPrank(alice);
vm.expectEmit(true, true, false, true);
emit ApprovalForAll(alice, charlie, true);
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
}
function testSetApproveAllFalseAfterSingleAdd() public {
vm.startPrank(alice);
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
nft.setApprovalForAll(charlie, false);
assertFalse(nft.isApprovedForAll(alice, charlie));
}
function testSetApproveAllFalseAfterTwoAdd() public {
vm.startPrank(alice);
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
nft.setApprovalForAll(charlie, true);
assertTrue(nft.isApprovedForAll(alice, charlie));
nft.setApprovalForAll(charlie, false);
assertFalse(nft.isApprovedForAll(alice, charlie));
}
function testSupportsInterface() public {
// ERC721 should have interface id 0x80ac58cd
assertTrue(nft.supportsInterface(0x80ac58cd));
assertFalse(nft.supportsInterface(0x60ac58cd));
}
}