add mint stage2 with wl

This commit is contained in:
CounterFire2023 2024-06-06 13:03:10 +08:00
parent 5e6e4f30eb
commit 54682fbfc7
13 changed files with 1999 additions and 2342 deletions

File diff suppressed because one or more lines are too long

View File

@ -22,14 +22,27 @@ const admins = {
const token = {
baseTokenURIHero: "https://nft-test.kingsome.cn/hero/meta/13473/",
contractURI: 'https://nft-test.kingsome.cn/hero/meta/13473/',
contractURIHero: 'https://nft-test.kingsome.cn/hero/home_meta/13473',
royaltyReceiver: '0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888',
baseTokenURIGold: "https://nft-test.kingsome.cn/gold_bullion/meta/13473/",
contractURIGold: 'https://nft-test.kingsome.cn/gold_bullion/home_meta/13473',
royaltyFee: 5,
};
const imtbl = {
operatorAllowlist: '0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE'
operatorAllowlist: '0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE',
}
const mint = {
// 2期mint支付的代币
mintCurrency: '0xFd42bfb03212dA7e1A4608a44d7658641D99CF34',
// 2期mint, 单个nft价格
mintPrice: '100',
// 2期mint接收代币的钱包地址
mintFeeAddress: '0x50A8e60041A206AcaA5F844a1104896224be6F39',
// 2期mint nftid 开始
mintStartNftId: '6240603010000001',
// 2期mint 最大可mint数量
maxSupply: 2000,
}
var config = {
@ -37,6 +50,7 @@ var config = {
admins,
token,
imtbl,
mint
};
module.exports = config;

View File

@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* Contract for the activity of NFT claim stage 2.
*/
interface IClaimAbleNFT {
function safeMint(address to, uint256 tokenID) external;
}
contract NFTClaimStage2WL is ReentrancyGuard, AccessControl {
using EnumerableSet for EnumerableSet.UintSet;
/// @notice Only UPDATE_WL_ROLE can add white listing
bytes32 public constant UPDATE_WL_ROLE = bytes32("UPDATE_WL_ROLE");
/// @notice Only MANAGE_ROLE can change mint config
bytes32 public constant MANAGE_ROLE = keccak256("MANAGE_ROLE");
struct MintConfig {
uint256 maxSupply; // max supply for phase2
address currency; // token address which user must pay to mint
uint256 mintPrice; // in wei
address feeToAddress; // wallet address to receive mint fee
}
// parse: 0: not open or end, 1: phase1, 2: phase2
uint256 public mintParse = 0;
address public immutable nftAddress;
uint256 public immutable nftIdStart;
MintConfig public mintConfig;
uint256 public totalCount;
mapping(address user => uint256 num) private _whitelist1;
mapping(address user => uint256 num) private _whitelist2;
mapping(address user => EnumerableSet.UintSet tokenIdSet) private _mintedRecords;
event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids);
event ParseUpdated(uint256 _parse);
event MintConfigUpdated(MintConfig config);
constructor(address _nftAddress, uint256 _nftIdStart, MintConfig memory _mintConfig) {
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(UPDATE_WL_ROLE, _msgSender());
_grantRole(MANAGE_ROLE, _msgSender());
nftAddress = _nftAddress;
mintConfig = _mintConfig;
nftIdStart = _nftIdStart;
}
modifier whenNotPaused() {
require(mintParse > 0, "NFTClaimer: not begin or ended");
_;
}
function updateMintParse(uint256 _mintParse) external onlyRole(MANAGE_ROLE) {
require(_mintParse == 0 || _mintParse == 1 || _mintParse == 2, "NFTClaimer: invalid mintParse");
mintParse = _mintParse;
emit ParseUpdated(_mintParse);
}
function updateMintConfig(MintConfig calldata config) external onlyRole(MANAGE_ROLE) {
mintConfig = config;
emit MintConfigUpdated(config);
}
function addParse1WL(address[] calldata _addressList, uint256[] calldata _nums) external onlyRole(UPDATE_WL_ROLE) {
require(_addressList.length == _nums.length, "NFTClaimer: invalid whitelist");
for (uint256 i = 0; i < _addressList.length; i++) {
_whitelist1[_addressList[i]] = _nums[i];
}
}
function revokeParse1WL(address[] calldata _addressList) external onlyRole(MANAGE_ROLE) {
for (uint256 i = 0; i < _addressList.length; i++) {
delete _whitelist1[_addressList[i]];
}
}
function addParse2WL(address[] calldata _addressList) external onlyRole(UPDATE_WL_ROLE){
for (uint256 i = 0; i < _addressList.length; i++) {
_whitelist2[_addressList[i]] = 1;
}
}
function revokeParse2WL(address[] calldata _addressList) external onlyRole(MANAGE_ROLE) {
for (uint256 i = 0; i < _addressList.length; i++) {
delete _whitelist2[_addressList[i]];
}
}
/**
* @dev claim NFT
* Get whitelist signature from a third-party service, then call this method to claim NFT
*/
function claim(
uint256 nftCount
) external nonReentrant whenNotPaused {
require(nftCount > 0, "NFTClaimer: nft count must be greater than 0");
require(nftCount <= mintConfig.maxSupply - totalCount, "NFTClaimer: exceed max supply");
address to = _msgSender();
uint256 _mintedCount = _mintedRecords[to].length();
if (mintParse == 1) {
require(_whitelist1[to] >= _mintedCount + nftCount, "NFTClaimer: not in whitelist or exceed limit");
} else if (mintParse == 2) {
require(_whitelist1[to] + _whitelist2[to] >= _mintedCount + nftCount, "NFTClaimer: not in whitelist or exceed limit");
}
uint256 _tokenAmount = mintConfig.mintPrice * nftCount;
IERC20(mintConfig.currency).transferFrom(to, mintConfig.feeToAddress, _tokenAmount);
uint256[] memory ids = new uint256[](nftCount);
for (uint256 i = 0; i < nftCount; ++i) {
uint256 _nftId = nftIdStart + totalCount + i;
ids[i] = _nftId;
IClaimAbleNFT(nftAddress).safeMint(to, _nftId);
_mintedRecords[to].add(_nftId);
}
totalCount += nftCount;
// add list
emit NFTClaimed(nftAddress, to, ids);
}
function whiteCount() external view returns (uint256){
uint256 _count1 = _whitelist1[_msgSender()];
uint256 _count2 = _whitelist2[_msgSender()];
if (mintParse == 2) {
return _count1 + _count2 - _mintedRecords[_msgSender()].length();
} else {
return _count1 - _mintedRecords[_msgSender()].length();
}
}
function mintedNum() external view returns (uint256){
return _mintedRecords[_msgSender()].length();
}
function mintedNft() external view returns (uint256[] memory){
return _mintedRecords[_msgSender()].values();
}
}

View File

@ -26,8 +26,8 @@ const deployNFTForGame: DeployFunction =
// testnet: 0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE
// mainnet: 0x5F5EBa8133f68ea22D712b0926e2803E78D89221
// testnet_CF: 0x3c5991E9A0e6c713163cD0a91f246dc61f18d918
const { royaltyReceiver, royaltyFee, baseTokenURIHero, baseTokenURIGold, contractURI } = config.token;
console.log(owner, name, symbol, baseTokenURIHero, contractURI, operatorAllowlist, royaltyReceiver, royaltyFee)
const { royaltyReceiver, royaltyFee, baseTokenURIHero, baseTokenURIGold, contractURIHero, contractURIGold } = config.token;
console.log(owner, name, symbol, baseTokenURIHero, contractURIHero, operatorAllowlist, royaltyReceiver, royaltyFee)
// const ret = await hre.deployments.deploy("CFNFTGame", {
// from,
// args: [owner, name, symbol, baseTokenURIHero, contractURI, operatorAllowlist, royaltyReceiver, royaltyFee],
@ -45,7 +45,7 @@ const deployNFTForGame: DeployFunction =
const symbolGold = "CFG";
const ret = await hre.deployments.deploy("CFNFTGame", {
from,
args: [owner, nameGold, symbolGold, baseTokenURIGold, contractURI, operatorAllowlist, royaltyReceiver, royaltyFee],
args: [owner, nameGold, symbolGold, baseTokenURIGold, contractURIGold, operatorAllowlist, royaltyReceiver, royaltyFee],
log: true,
});
console.log("==CFNFTGame addr=", ret.address);

View File

@ -0,0 +1,36 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { loadData, updateArray } from "../scripts/utils"
const deployNFTClaim: DeployFunction =
async function (hre: HardhatRuntimeEnvironment) {
const provider = hre.ethers.provider;
const from = await (await provider.getSigner()).getAddress();
const config = require(`../config/config_${hre.network.name}`);
const cfgs = loadData({network: hre.network.name})
const nftAddress = cfgs.find((c: any) => c.name === "CFHero").address
const mintConfig = [
config.mint.maxSupply,
config.mint.mintCurrency,
config.mint.mintPrice,
config.mint.mintFeeAddress
]
const startNftId = config.mint.mintStartNftId
const ret = await hre.deployments.deploy("NFTClaimStage2WL", {
from,
args: [nftAddress, startNftId, mintConfig],
log: true,
});
updateArray({
name: "NFTClaimStage2WL",
type: "logic",
json: "assets/contracts/NFTClaimStage2WL.json",
address: ret.address,
network: hre.network.name,
});
};
deployNFTClaim.tags = ["NFTClaimStage2WL"];
export default deployNFTClaim;

View File

@ -7,8 +7,8 @@ const deployNFTClaim: DeployFunction =
async function (hre: HardhatRuntimeEnvironment) {
const provider = hre.ethers.provider;
const from = await (await provider.getSigner()).getAddress();
const verifier = '0x50A8e60041A206AcaA5F844a1104896224be6F39'
const config = require(`../config/config_${hre.network.name}`);
const verifier = config.admins.admin
const ret = await hre.deployments.deploy("NFTLock", {
from,
args: [3600, verifier],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,5 +28,11 @@
"type": "erc721",
"json": "assets/contracts/CFNFTGame.json",
"address": "0x2036A0708AC2F17F67b08357d8b4A7d47cF49c29"
},
{
"name": "NFTClaimStage2WL",
"type": "logic",
"json": "assets/contracts/NFTClaimStage2WL.json",
"address": "0x31F29C9A3D0c1c13C825475aebF0d964b5B47c45"
}
]

View File

@ -9,6 +9,7 @@
"clean": "hardhat clean",
"deploy": "hardhat deploy --network imtbl_test",
"deploy:nftclaim": "hardhat deploy --tags NFTClaimStage2 --network imtbl_test --reset",
"deploy:nftclaimwl": "hardhat deploy --tags NFTClaimStage2WL --network imtbl_test --reset",
"deploy:nft": "hardhat deploy --tags CFNFTGame --network imtbl_test --reset",
"deploy:nftlock": "hardhat deploy --tags NFTLock --network imtbl_test --reset",
"deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",

182
test/testNFTClaimWL.ts Normal file
View File

@ -0,0 +1,182 @@
import { expect } from 'chai'
import hre from "hardhat";
import {
getBytes,
solidityPackedKeccak256,
} from 'ethers'
import {
loadFixture,
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
describe('NFTClaimStage2WL', 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("NFTClaimStage2WL");
const mintConfig = [
2000,
ft.target,
100,
owner.address
]
const nftClaimer = await NFTClaimStage2.deploy( nftAddress, '6240603010000001', mintConfig);
await nft.grantMinterRole(nftClaimer.target)
await nftClaimer.addParse1WL([otherAccount.address], [3])
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, otherAccount } = await loadFixture(deployOneContract);
expect((await nftClaimer.mintConfig()).mintPrice).to.equal(100);
// @ts-ignore
expect((await nftClaimer.connect(otherAccount).whiteCount())).to.equal(3);
});
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 = [
2000,
'0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D',
200,
'0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888'
]
await nftClaimer.updateMintConfig(mintConfig);
expect((await nftClaimer.mintConfig()).mintPrice).to.equal(200);
});
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);
//use ft.connect() to send a transaction from another account
// @ts-ignore
await ft.connect(otherAccount).approve(nftClaimer.target, 200)
expect(await nftClaimer.nftAddress()).equal(nft.target);
// @ts-ignore
await nftClaimer.connect(otherAccount).claim(2);
expect(await nft.balanceOf(otherAccount.address)).to.equal(2);
expect(await nftClaimer.totalCount()).to.equal(2);
// @ts-ignore
expect((await nftClaimer.connect(otherAccount).whiteCount())).to.equal(1);
});
it('should claim NFTS in parse2', async function() {
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
await nftClaimer.updateMintParse(2);
const count = 4
const price = 100
await nftClaimer.addParse2WL([otherAccount.address])
// @ts-ignore
expect((await nftClaimer.connect(otherAccount).whiteCount())).to.equal(count);
const tokenAmount = price * count
// @ts-ignore
await ft.connect(otherAccount).approve(nftClaimer.target, tokenAmount)
// @ts-ignore
await nftClaimer.connect(otherAccount).claim(count);
expect(await nft.balanceOf(otherAccount.address)).to.equal(count);
expect(await ft.balanceOf(owner.address)).to.equal(tokenAmount)
});
it('should revert claim NFT not in whitelist or exceed limit', async function() {
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
await nftClaimer.updateMintParse(1);
//use ft.connect() to send a transaction from another account
// @ts-ignore
await ft.connect(otherAccount).approve(nftClaimer.target, 400)
expect(await nftClaimer.nftAddress()).equal(nft.target);
// @ts-ignore
await expect(nftClaimer.connect(otherAccount).claim(4)).to.be.revertedWith(
"NFTClaimer: not in whitelist or exceed limit"
);
});
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 = [
2000,
ft.target,
price,
owner.address
]
await nftClaimer.updateMintConfig(mintConfig);
// @ts-ignore
await ft.connect(otherAccount).approve(nftClaimer.target, price)
// @ts-ignore
await expect(nftClaimer.connect(otherAccount).claim(1)).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)
// @ts-ignore
await expect(nftClaimer.connect(otherAccount).claim(1)).to.be.revertedWith(
"ERC20: insufficient allowance"
);
});
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)
// @ts-ignore
await expect(nftClaimer.connect(otherAccount).claim(1)).to.be.revertedWith(
"NFTClaimer: not begin or ended"
);
});
it('should revert claim NFT if the exceed max num', async function() {
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
await nftClaimer.updateMintParse(1);
const price = 100
const mintConfig = [
1,
ft.target,
price,
owner.address
]
await nftClaimer.updateMintConfig(mintConfig);
// @ts-ignore
await ft.connect(otherAccount).approve(nftClaimer.target, price * 2)
const nonce = (Math.random() * 1000) | 0;
// @ts-ignore
await expect(nftClaimer.connect(otherAccount).claim(2)).to.be.revertedWith(
"NFTClaimer: exceed max supply"
);
});
});
})