// SPDX-License-Identifier: MPL-2.0 pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; import {InvestorNFTV1} from "../src/nft/InvestorNFTV1.sol"; import {InvestorNFTMinterV1} from "../src/minter/InvestorNFTMinterV1.sol"; import {Merkle} from "murky/Merkle.sol"; contract InvestorNFTTest is Test { address internal _admin = address(1); address internal _user1 = address(2); address internal _user2 = address(3); address internal _user = address(4); bytes32[] internal _merkleTreeLeafs; bytes32 internal _merkleRoot = bytes32(0); InvestorNFTV1 _investorNft; InvestorNFTMinterV1 _investorNftMinter; Merkle _merkle = new Merkle(); function setUp() public { vm.label(_admin, "Admin"); vm.label(_user1, "User 1 mint"); vm.label(_user2, "User 2 mints"); // Setup smart contracts vm.startPrank(_admin); _investorNftMinter = new InvestorNFTMinterV1(_admin, payable(_admin)); _investorNft = new InvestorNFTV1(address(_investorNftMinter)); _investorNftMinter.ownerSetNFTContract(address(_investorNft), _merkleRoot); _defaultWhitelist(); vm.stopPrank(); } function testValidInterface() public { assertTrue(_investorNftMinter.supportsInterface(0x01ffc9a7)); assertTrue(_investorNftMinter.supportsInterface(0x9e340701) == false); } function testTokenURIValidness() public { vm.startPrank(_user); assertTrue(keccak256(bytes(_investorNft.tokenURI(0))) == keccak256("https://l7in.le7el.com/v1/metadata/0")); assertTrue(keccak256(bytes(_investorNft.tokenURI(1))) == keccak256("https://l7in.le7el.com/v1/metadata/1")); assertTrue(keccak256(bytes(_investorNft.tokenURI(106))) == keccak256("https://l7in.le7el.com/v1/metadata/106")); assertTrue(keccak256(bytes(_investorNft.tokenURI(2222))) == keccak256("https://l7in.le7el.com/v1/metadata/2222")); assertTrue(keccak256(bytes(_investorNft.tokenURI(2223))) == keccak256("https://l7in.le7el.com/v1/metadata/2223")); vm.stopPrank(); } function testValidMint() public { _mintNFT(_user); vm.startPrank(_user); assertTrue(_investorNft.balanceOf(_user) == 1); assertTrue(_investorNft.ownerOf(1990) == _user); assertTrue(_user.balance == 0.001 ether); assertTrue(_investorNftMinter.TRUSTED_BENEFICIARY().balance == 0.05 ether); vm.stopPrank(); } function testValidOrderOfMints() public { _mintNFT(_user); vm.startPrank(_user); assertTrue(_investorNft.balanceOf(_user) == 1); assertTrue(_investorNft.ownerOf(1990) == _user); vm.stopPrank(); address _user5 = address(5); _mintNFT(_user5); vm.startPrank(_user5); assertTrue(_investorNft.balanceOf(_user5) == 1); assertTrue(_investorNft.ownerOf(159) == _user5); vm.stopPrank(); address _user6 = address(6); _mintNFT(_user6); vm.startPrank(_user6); assertTrue(_investorNft.balanceOf(_user6) == 1); assertTrue(_investorNft.ownerOf(2217) == _user6); vm.stopPrank(); assertTrue(_investorNftMinter.TRUSTED_BENEFICIARY().balance == 0.05 ether + 0.05 ether + 0.05 ether); } function testMintOverflow() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(3, bytes32(0)); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 1000 ether); for (uint256 _i = 0; _i < 2221; _i++) { _investorNftMinter.mintTo{value: 0.065 ether}(_user); } uint256 _lastId = _investorNftMinter.mintTo{value: 0.065 ether}(_user); assertTrue(_investorNft.balanceOf(_user) == 2222); assertTrue(_lastId == 1437); vm.expectRevert("Sorry, sold out."); _investorNftMinter.mintTo{value: 0.065 ether}(_user); vm.stopPrank(); } function testDirectMint() public { vm.startPrank(_user); vm.expectRevert("Not a minter."); _investorNft.mintTo(_user, 1); vm.stopPrank(); } function testDirectMintOfOverflow() public { vm.startPrank(address(_investorNftMinter)); vm.expectRevert("Sorry, sold out."); _investorNft.mintTo(_user, 0); vm.expectRevert("Sorry, sold out."); _investorNft.mintTo(_user, 2223); vm.stopPrank(); } function testSingleMintPerWhitelist() public { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_user); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.1 ether); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _proof); vm.expectRevert("Whitelist already used."); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _proof); vm.stopPrank(); } function testFreePremint() public { vm.startPrank(_admin); vm.deal(_admin, 1 ether); _investorNftMinter.ownerPremintTo(_user); vm.stopPrank(); assertTrue(_investorNft.balanceOf(_user) == 1); assertTrue(_investorNft.ownerOf(1990) == _user); assertTrue(_admin.balance == 1 ether); } function testMaxFreePremint100NFTs() public { vm.startPrank(_admin); vm.deal(_admin, 1 ether); for (uint256 _i = 0; _i < 100; _i++) _investorNftMinter.ownerPremintTo(_user); vm.expectRevert("Max premint 100 NFTs!"); _investorNftMinter.ownerPremintTo(_user); vm.stopPrank(); assertTrue(_admin.balance == 1 ether); } function testNoPremintInNonPreparationPhase() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, bytes32(0)); vm.expectRevert("Premint finished!"); _investorNftMinter.ownerPremintTo(_user); vm.stopPrank(); } function testOnlyAdminCanPremint() public { vm.startPrank(_user); vm.expectRevert("UNAUTHORIZED"); _investorNftMinter.ownerPremintTo(_user); vm.stopPrank(); } function testFreeMints() public { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_user); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); _investorNftMinter.ownerSetPrice(0, 0); vm.stopPrank(); vm.deal(_user, 0.001 ether); vm.prank(_user); _investorNftMinter.mintTo(_user, _index, _proof); assertTrue(_investorNft.balanceOf(_user) == 1); assertTrue(_investorNft.ownerOf(1990) == _user); assertTrue(_user.balance == 0.001 ether); } function testInvalidProofs() public { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_user); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.1 ether); vm.expectRevert("Invalid proof."); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index + 1, _proof); vm.expectRevert("Invalid proof."); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _merkleTreeLeafs); vm.stopPrank(); vm.startPrank(_user2); vm.deal(_user2, 0.1 ether); vm.expectRevert("Invalid proof."); _investorNftMinter.mintTo{value: 0.05 ether}(_user2, _index, _proof); vm.stopPrank(); } function testDoubleMintForTheSameWallet() public { uint256 _index1 = _merkleTreeLeafs.length; _merkleTreeLeafs.push(keccak256(abi.encodePacked(_index1, _user, uint256(1)))); uint256 _index2 = _merkleTreeLeafs.length; _merkleTreeLeafs.push(keccak256(abi.encodePacked(_index2, _user, uint256(1)))); bytes32 _root = _merkle.getRoot(_merkleTreeLeafs); bytes32[] memory _proof1 = _merkle.getProof(_merkleTreeLeafs, _index1); bytes32[] memory _proof2 = _merkle.getProof(_merkleTreeLeafs, _index2); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.1 ether); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index1, _proof1); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index2, _proof2); assertTrue(_investorNft.balanceOf(_user) == 2); vm.stopPrank(); } function testPrematurePublicMintFCFS() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, bytes32(0)); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.1 ether); vm.expectRevert("Not public, yet"); _investorNftMinter.mintTo{value: 0.065 ether}(_user); vm.stopPrank(); } function testPrematurePublicMintGDT() public { vm.startPrank(_user); vm.deal(_user, 0.1 ether); vm.expectRevert("Not public, yet"); _investorNftMinter.mintTo{value: 0.065 ether}(_user); vm.stopPrank(); } function testPrematureGTD() public { vm.startPrank(_user); vm.deal(_user, 0.1 ether); (uint256 _index,, bytes32[] memory _proof) = _whitelist(_user); vm.expectRevert("Mint not started!"); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _proof); vm.stopPrank(); } function testValidPublicMint() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(3, bytes32(0)); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.1 ether); _investorNftMinter.mintTo{value: 0.065 ether}(_user); assertTrue(_investorNft.balanceOf(_user) == 1); assertTrue(_investorNft.ownerOf(1990) == _user); assertTrue(_user.balance == 0.035 ether); assertTrue(_investorNftMinter.TRUSTED_BENEFICIARY().balance == 0.065 ether); vm.stopPrank(); } function testNoMoneyForGDTMint() public { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_user); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.04 ether); vm.expectRevert(); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _proof); vm.stopPrank(); } function testNoMoneyForFCFSMint() public { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_user); vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.stopPrank(); vm.startPrank(_user); vm.deal(_user, 0.04 ether); vm.expectRevert(); _investorNftMinter.mintTo{value: 0.05 ether}(_user, _index, _proof); vm.stopPrank(); } function testNoMoneyForPublicMint() public { vm.prank(_admin); _investorNftMinter.ownerSetPhase(3, bytes32(0)); vm.startPrank(_user); vm.deal(_user, 0.05 ether); vm.expectRevert(); _investorNftMinter.mintTo{value: 0.065 ether}(_user); vm.stopPrank(); } function testAdminChangePhasesAndRoot() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPhase(1, bytes32(hex"10")); assertTrue(_investorNftMinter.phase() == 1); assertTrue(_investorNftMinter.merkleRoot() == bytes32(hex"10")); _investorNftMinter.ownerSetPhase(2, bytes32(hex"20")); assertTrue(_investorNftMinter.phase() == 2); assertTrue(_investorNftMinter.merkleRoot() == bytes32(hex"20")); _investorNftMinter.ownerSetPhase(3, bytes32(hex"30")); assertTrue(_investorNftMinter.phase() == 3); assertTrue(_investorNftMinter.merkleRoot() == bytes32(hex"30")); vm.expectRevert("Invalid phase."); _investorNftMinter.ownerSetPhase(0, bytes32(hex"40")); assertTrue(_investorNftMinter.phase() == 3); vm.stopPrank(); } function testOnlyAdminCanChangePhasesAndRoot() public { vm.startPrank(_user); vm.expectRevert("UNAUTHORIZED"); _investorNftMinter.ownerSetPhase(1, bytes32(hex"10")); vm.stopPrank(); } function testAdminCantChangeNFTContractAfterItsSet() public { vm.startPrank(_admin); vm.expectRevert("NFT contract is set once"); _investorNftMinter.ownerSetNFTContract(address(10), _merkleRoot); vm.stopPrank(); } function testOnlyAdminCanChangeNFTContract() public { vm.startPrank(_user); vm.expectRevert("UNAUTHORIZED"); _investorNftMinter.ownerSetNFTContract(address(10), _merkleRoot); vm.stopPrank(); } function testAdminCanChangePrice() public { vm.startPrank(_admin); _investorNftMinter.ownerSetPrice(1 ether, 2 ether); assertTrue(_investorNftMinter.price() == 1 ether); assertTrue(_investorNftMinter.pricePublic() == 2 ether); vm.stopPrank(); } function testOnlyAdminCanChangePrice() public { vm.startPrank(_user); vm.expectRevert("UNAUTHORIZED"); _investorNftMinter.ownerSetPrice(1 ether, 2 ether); vm.stopPrank(); } function _mintNFT(address _address) private returns (uint256) { (uint256 _index, bytes32 _root, bytes32[] memory _proof) = _whitelist(_address); vm.prank(_admin); _investorNftMinter.ownerSetPhase(1, _root); vm.deal(_address, 0.051 ether); vm.prank(_address); return _investorNftMinter.mintTo{value: 0.05 ether}(_address, _index, _proof); } function _defaultWhitelist() private { delete _merkleTreeLeafs; _merkleTreeLeafs.push(keccak256(abi.encodePacked(uint256(0), _user1, uint256(1)))); _merkleTreeLeafs.push(keccak256(abi.encodePacked(uint256(1), _user2, uint256(1)))); _merkleTreeLeafs.push(keccak256(abi.encodePacked(uint256(2), _user2, uint256(1)))); } function _whitelist(address _newUser) private returns (uint256, bytes32, bytes32[] memory) { uint256 _index = _merkleTreeLeafs.length; bytes32 _newNode = keccak256(abi.encodePacked(_index, _newUser, uint256(1))); _merkleTreeLeafs.push(_newNode); bytes32 _root = _merkle.getRoot(_merkleTreeLeafs); bytes32[] memory _proof = _merkle.getProof(_merkleTreeLeafs, _index); return (_index, _root, _proof); } }