import { expect } from 'chai' import hre from "hardhat"; import { getBytes, solidityPackedKeccak256, } from 'ethers' import { loadFixture, } from "@nomicfoundation/hardhat-toolbox/network-helpers"; describe('NFTClaimStage2', function() { async function deployOneContract() { // Contracts are deployed using the first signer/account by default const [owner, otherAccount] = await hre.ethers.getSigners(); const verifier = owner.address; const OperatorAllowlist = await hre.ethers.getContractFactory("OperatorAllowlist"); const operatorAllowlist = await OperatorAllowlist.deploy(owner.address); const CFFT = await hre.ethers.getContractFactory("ImmutableERC20MinterBurnerPermit"); const ft = await CFFT.deploy(owner.address, owner.address, owner.address, "test usdc", "usdc", '100000000000000000000000000'); await ft.grantMinterRole(owner.address); await ft.mint(otherAccount.address, '1000'); const CFNFTGame = await hre.ethers.getContractFactory("CFNFTGame"); const nft = await CFNFTGame.deploy(owner.address, 'name', 'symbol', 'baseURI', 'contractURI', operatorAllowlist.target, owner.address, 5); const nftAddress = nft.target; const NFTClaimStage2 = await hre.ethers.getContractFactory("NFTClaimStage2"); const mintConfig = [ 1000, 2000, ft.target, 100, owner.address ] const nftClaimer = await NFTClaimStage2.deploy( nftAddress, verifier, mintConfig); await nft.grantMinterRole(nftClaimer.target) const chainId = hre.network.config.chainId return { nftClaimer, owner, otherAccount, verifier, nftAddress, ft, nft, chainId }; } describe("Deployment", function () { it('should deploy NFTClaimStage2', async function() { const { nftClaimer } = await loadFixture(deployOneContract); expect((await nftClaimer.mintConfig()).mintPrice).to.equal(100); }); it('should deploy NFTClaimStage2 with the correct verifier', async function() { const { nftClaimer, verifier } = await loadFixture(deployOneContract); expect(await nftClaimer.verifier()).to.equal(verifier); }); it('should deploy NFTClaimStage2 with the correct NFT address', async function() { const { nftClaimer, nftAddress } = await loadFixture(deployOneContract); expect(await nftClaimer.nftAddress()).to.equal(nftAddress); }); }) describe("update settings", function () { it('should update mintConfig', async function() { const { nftClaimer } = await loadFixture(deployOneContract); const mintConfig = [ 1000, 2000, '0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D', 200, '0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888' ] await nftClaimer.updateMintConfig(mintConfig); expect((await nftClaimer.mintConfig()).mintPrice).to.equal(200); }); it('should update verifier', async function() { const { nftClaimer, otherAccount } = await loadFixture(deployOneContract); await nftClaimer.updateVerifier(otherAccount.address); expect(await nftClaimer.verifier()).to.equal(otherAccount.address); }); it('should update parse', async function() { const { nftClaimer } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); expect(await nftClaimer.mintParse()).to.equal(1); }); }); describe("claim", function () { it('should claim NFT', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 100 //use ft.connect() to send a transaction from another account // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const contractMsgHash = await nftClaimer.getMessageHash( otherAccount.address, nft.target, ids, price, nftClaimer.target, chainId, nonce ) expect(localMsgHash).equal(contractMsgHash); expect(await nftClaimer.verifier()).equal(owner.address); expect(await nftClaimer.nftAddress()).equal(nft.target); const signature = await owner.signMessage(getBytes(contractMsgHash)); // @ts-ignore await nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature); expect(await nft.balanceOf(otherAccount.address)).to.equal(1); expect(await nftClaimer.totalCount()).to.equal(1); }); it('should claim NFTS', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 100 const ids = ['1002', '1003', '1004'] const tokenAmount = price * ids.length // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, tokenAmount) const nonce = (Math.random() * 1000) | 0; let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, tokenAmount, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await nftClaimer.connect(otherAccount).claim(ids, tokenAmount, nonce, signature); expect(await nft.balanceOf(otherAccount.address)).to.equal(ids.length); expect(await ft.balanceOf(owner.address)).to.equal(tokenAmount) }); it('should revert claim NFT if the signature error', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 100 // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); const nonce2 = (Math.random() * 1000) | 0; // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce2, signature)).to.be.revertedWith( "invalid signature" ); }); it('should revert claim NFT if the balance not enough', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 1001 const mintConfig = [ 1000, 2000, ft.target, price, owner.address ] await nftClaimer.updateMintConfig(mintConfig); // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "ERC20: transfer amount exceeds balance" ); }); it('should revert claim NFT if allowance not enough', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 100 // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price - 1) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "ERC20: insufficient allowance" ); }); it('should revert claim NFT if the price not correct', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 99 // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "NFTClaimer: insufficient token amount" ); }); it('should revert claim NFT if the activity not open', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(0); const price = 100 // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price) const nonce = (Math.random() * 1000) | 0; const ids = ['1002'] let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "NFTClaimer: not begin or ended" ); }); it('should revert claim NFT if the exceed parse1 max num', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(1); const price = 100 const mintConfig = [ 1, 2000, ft.target, price, owner.address ] await nftClaimer.updateMintConfig(mintConfig); const ids = ['1002', '1003'] // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price * ids.length) const nonce = (Math.random() * 1000) | 0; let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price * ids.length, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "NFTClaimer: exceed parse 1 max supply" ); }); it('should revert claim NFT if the exceed total num', async function() { const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract); await nftClaimer.updateMintParse(2); const price = 100 const mintConfig = [ 1000, 1, ft.target, price, owner.address ] await nftClaimer.updateMintConfig(mintConfig); const ids = ['1002', '1003'] // @ts-ignore await ft.connect(otherAccount).approve(nftClaimer.target, price * ids.length) const nonce = (Math.random() * 1000) | 0; let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")], [otherAccount.address, nft.target, price * ids.length, nftClaimer.target, chainId, nonce, ...ids]); const signature = await owner.signMessage(getBytes(localMsgHash)); // @ts-ignore await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith( "NFTClaimer: exceed max supply" ); }); }); })