mirror of
https://github.com/supleed2/COMP70017-PoDL-T2.git
synced 2024-11-10 02:15:48 +00:00
277 lines
8.6 KiB
Solidity
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));
|
||
|
}
|
||
|
}
|