add contract for nft lock

This commit is contained in:
CounterFire2023 2024-06-03 14:06:45 +08:00
parent 89c02d5e47
commit 0cb0cadc67
13 changed files with 1886 additions and 940 deletions

View File

@ -1,3 +1,8 @@
{ {
"printWidth": 120 "printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
} }

View File

@ -38,7 +38,7 @@
"avoid-throw": "error", "avoid-throw": "error",
"avoid-tx-origin": "error", "avoid-tx-origin": "error",
"check-send-result": "error", "check-send-result": "error",
"compiler-version": ["error", "0.8.23"], "compiler-version": ["error", "0.8.19"],
"func-visibility": ["error", {"ignoreConstructors": true}], "func-visibility": ["error", {"ignoreConstructors": true}],
"multiple-sends": "warn", "multiple-sends": "warn",
"no-complex-fallback": "warn", "no-complex-fallback": "warn",

File diff suppressed because one or more lines are too long

View File

@ -9,117 +9,125 @@ import {HasSignature} from "../core/HasSignature.sol";
* Contract for the activity of NFT claim stage 2. * Contract for the activity of NFT claim stage 2.
*/ */
interface IClaimAbleNFT { interface IClaimAbleNFT {
function safeMint(address to, uint256 tokenID) external; function safeMint(address to, uint256 tokenID) external;
} }
contract NFTClaimStage2 is HasSignature, ReentrancyGuard { contract NFTClaimStage2 is HasSignature, ReentrancyGuard {
struct MintConfig { struct MintConfig {
uint256 parse1MaxSupply; // max supply for phase1 uint256 parse1MaxSupply; // max supply for phase1
uint256 maxSupply; // max supply for phase2 uint256 maxSupply; // max supply for phase2
address currency; // token address which user must pay to mint address currency; // token address which user must pay to mint
uint256 mintPrice; // in wei uint256 mintPrice; // in wei
address feeToAddress; // wallet address to receive mint fee address feeToAddress; // wallet address to receive mint fee
}
// parse: 0: not open or end, 1: phase1, 2: phase2
uint256 public mintParse = 0;
uint256 public immutable _CACHED_CHAIN_ID;
address public immutable _CACHED_THIS;
address public immutable nftAddress;
address public verifier;
MintConfig public mintConfig;
uint256 public parse1Count;
uint256 public totalCount;
event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids);
event ParseUpdated(uint256 _parse);
event MintConfigUpdated(MintConfig config);
event VerifierUpdated(address indexed verifier);
constructor(address _nftAddress, address _verifier, MintConfig memory _mintConfig) {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_THIS = address(this);
nftAddress = _nftAddress;
verifier = _verifier;
mintConfig = _mintConfig;
}
modifier whenNotPaused() {
require(mintParse > 0, "NFTClaimer: not begin or ended");
_;
}
function updateMintParse(uint256 _mintParse) external onlyOwner {
mintParse = _mintParse;
emit ParseUpdated(_mintParse);
}
function updateMintConfig(MintConfig calldata config) external onlyOwner {
mintConfig = config;
emit MintConfigUpdated(config);
}
/**
* @dev update verifier address
*/
function updateVerifier(address _verifier) external onlyOwner {
require(_verifier != address(0), "NFTClaimer: address can not be zero");
verifier = _verifier;
emit VerifierUpdated(_verifier);
}
/**
* @dev claim NFT
* Get whitelist signature from a third-party service, then call this method to claim NFT
* @param saltNonce nonce
* @param signature signature
*/
function claim(
uint256[] memory ids,
uint256 tokenAmount,
uint256 saltNonce,
bytes calldata signature
) external nonReentrant whenNotPaused {
// get current parse;
uint256 count = ids.length;
require(count > 0, "NFTClaimer: ids length must be greater than 0");
if (mintParse == 1) {
require(count <= mintConfig.parse1MaxSupply - parse1Count, "NFTClaimer: exceed parse 1 max supply");
} else {
require(count <= mintConfig.maxSupply - totalCount, "NFTClaimer: exceed max supply");
} }
// parse: 0: not open or end, 1: phase1, 2: phase2 require(tokenAmount >= mintConfig.mintPrice * count, "NFTClaimer: insufficient token amount");
uint256 public mintParse = 0; address to = _msgSender();
bytes32 criteriaMessageHash = getMessageHash(
uint256 public immutable _CACHED_CHAIN_ID; to,
address public immutable _CACHED_THIS; nftAddress,
address public immutable nftAddress; ids,
tokenAmount,
address public verifier; _CACHED_THIS,
MintConfig public mintConfig; _CACHED_CHAIN_ID,
uint256 public parse1Count; saltNonce
uint256 public totalCount; );
checkSigner(verifier, criteriaMessageHash, signature);
event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids); IERC20(mintConfig.currency).transferFrom(to, mintConfig.feeToAddress, tokenAmount);
for (uint256 i = 0; i < count; ++i) {
event ParseUpdated(uint256 _parse); IClaimAbleNFT(nftAddress).safeMint(to, ids[i]);
event MintConfigUpdated(MintConfig config);
event VerifierUpdated(address indexed verifier);
constructor(address _nftAddress, address _verifier, MintConfig memory _mintConfig) {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_THIS = address(this);
nftAddress = _nftAddress;
verifier = _verifier;
mintConfig = _mintConfig;
} }
// require(count > 2, "run to here");
modifier whenNotPaused() { totalCount += count;
require(mintParse > 0, "NFTClaimer: not begin or ended"); if (mintParse == 1) {
_; parse1Count += count;
} }
_useSignature(signature);
emit NFTClaimed(nftAddress, to, ids);
}
function updateMintParse(uint256 _mintParse) external onlyOwner { function getMessageHash(
mintParse = _mintParse; address _to,
emit ParseUpdated(_mintParse); address _address,
} uint256[] memory _ids,
uint256 _tokenAmount,
function updateMintConfig(MintConfig calldata config) external onlyOwner { address _contract,
mintConfig = config; uint256 _chainId,
emit MintConfigUpdated(config); uint256 _saltNonce
} ) public pure returns (bytes32) {
bytes memory encoded = abi.encodePacked(_to, _address, _tokenAmount, _contract, _chainId, _saltNonce);
/** for (uint256 i = 0; i < _ids.length; ++i) {
* @dev update verifier address encoded = bytes.concat(encoded, abi.encodePacked(_ids[i]));
*/
function updateVerifier(address _verifier) external onlyOwner {
require(_verifier != address(0), "NFTClaimer: address can not be zero");
verifier = _verifier;
emit VerifierUpdated(_verifier);
}
/**
* @dev claim NFT
* Get whitelist signature from a third-party service, then call this method to claim NFT
* @param saltNonce nonce
* @param signature signature
*/
function claim(
uint256[] memory ids,
uint256 tokenAmount,
uint256 saltNonce,
bytes calldata signature
) external nonReentrant whenNotPaused {
// get current parse;
uint256 count = ids.length;
require(count > 0, "NFTClaimer: ids length must be greater than 0");
if (mintParse == 1) {
require(count <= mintConfig.parse1MaxSupply - parse1Count, "NFTClaimer: exceed parse 1 max supply");
} else {
require(count <= mintConfig.maxSupply - totalCount, "NFTClaimer: exceed max supply");
}
require(tokenAmount >= mintConfig.mintPrice * count, "NFTClaimer: insufficient token amount");
address to = _msgSender();
bytes32 criteriaMessageHash = getMessageHash(to, nftAddress, ids, tokenAmount, _CACHED_THIS, _CACHED_CHAIN_ID, saltNonce);
checkSigner(verifier, criteriaMessageHash, signature);
IERC20(mintConfig.currency).transferFrom(to, mintConfig.feeToAddress, tokenAmount);
for (uint256 i = 0; i < count; ++i) {
IClaimAbleNFT(nftAddress).safeMint(to, ids[i]);
}
// require(count > 2, "run to here");
totalCount += count;
if (mintParse == 1) {
parse1Count += count;
}
_useSignature(signature);
emit NFTClaimed(nftAddress, to, ids);
}
function getMessageHash(
address _to,
address _address,
uint256[] memory _ids,
uint256 _tokenAmount,
address _contract,
uint256 _chainId,
uint256 _saltNonce
) public pure returns (bytes32) {
bytes memory encoded = abi.encodePacked(_to, _address, _tokenAmount, _contract, _chainId, _saltNonce);
for (uint256 i = 0; i < _ids.length; ++i) {
encoded = bytes.concat(encoded, abi.encodePacked(_ids[i]));
}
return keccak256(encoded);
} }
return keccak256(encoded);
}
} }

View File

@ -3,123 +3,122 @@ pragma solidity 0.8.19;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
contract NFTLock is AccessControl, ERC721Holder { import {HasSignature} from "../core/HasSignature.sol";
using EnumerableSet for EnumerableSet.UintSet; import {TimeChecker} from "../utils/TimeChecker.sol";
mapping(address => mapping(uint256 => address)) lockedOriginal; interface INFT {
mapping(address => mapping(address => EnumerableSet.UintSet)) lockedRecords; function mint(address to, uint256 tokenID) external;
mapping(address => bool) supportNftList; function transferFrom(address from, address to, uint256 tokenId) external;
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); }
bytes32 public constant UNLOCK_ROLE = keccak256("UNLOCK_ROLE");
bytes32 public constant RELEASE_ROLE = keccak256("RELEASE_ROLE"); contract NFTLock is ERC721Holder, HasSignature, TimeChecker, Pausable {
using EnumerableSet for EnumerableSet.UintSet;
event Lock(address indexed nft, address indexed user, uint256 indexed tokenId);
event UnLock(address indexed nft, address indexed user, uint256 indexed tokenId); uint256 public immutable _CACHED_CHAIN_ID;
event BatchLock(address indexed nft, address indexed user, uint256[] tokenId); address public immutable _CACHED_THIS;
event Release(address indexed nft, uint256[] tokenIds, string serverId); address public verifier;
constructor() { struct NFTInfo {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); uint256 tokenId;
_setupRole(OPERATOR_ROLE, msg.sender); bool isMint;
_setupRole(UNLOCK_ROLE, msg.sender); }
_setupRole(RELEASE_ROLE, msg.sender); mapping(address nft => mapping(uint256 tokenId => address user)) public lockedOriginal;
} mapping(address nft => mapping(address user => EnumerableSet.UintSet tokenIdSet)) private lockedRecords;
mapping(address nft => bool status) public supportNftList;
function lock(address nft, uint256 tokenId) external {
require(supportNftList[nft], "can't lock this nft"); event UnLock(address indexed nft, address indexed user, uint256 nonce, NFTInfo[] nftList);
IERC721(nft).transferFrom(msg.sender, address(this), tokenId); event Lock(address indexed nft, address indexed user, uint256[] tokenIds);
lockedOriginal[nft][tokenId] = msg.sender; event VerifierUpdated(address indexed verifier);
lockedRecords[nft][msg.sender].add(tokenId);
emit Lock(nft, msg.sender, tokenId); constructor(uint256 _duration, address _verifier) TimeChecker(_duration) {
} _CACHED_CHAIN_ID = block.chainid;
_CACHED_THIS = address(this);
function unlock(address nft, address to, uint256 tokenId) external onlyUnlocker { verifier = _verifier;
IERC721(nft).transferFrom(address(this), to, tokenId); }
lockedRecords[nft][lockedOriginal[nft][tokenId]].remove(tokenId);
delete lockedOriginal[nft][tokenId]; function lock(address nft, uint256[] calldata tokenIds) external whenNotPaused{
emit UnLock(nft, to, tokenId); require(tokenIds.length <= 100, "tokenIds too many");
} address to = _msgSender();
for (uint256 i = 0; i < tokenIds.length; i++) {
function batchLock(address nft, uint256[] calldata tokenIds) external { INFT(nft).transferFrom(to, address(this), tokenIds[i]);
require(tokenIds.length <= 100, "tokenIds too many"); lockedOriginal[nft][tokenIds[i]] = to;
for (uint256 i = 0; i < tokenIds.length; i++) { lockedRecords[nft][to].add(tokenIds[i]);
IERC721(nft).transferFrom(msg.sender, address(this), tokenIds[i]); }
lockedOriginal[nft][tokenIds[i]] = msg.sender; emit Lock(nft, to, tokenIds);
lockedRecords[nft][msg.sender].add(tokenIds[i]); }
} /**
emit BatchLock(nft, msg.sender, tokenIds); * @dev unlock or mint nft
} * if tokenId not exists, mint it
* if exists and user is owner, unlock it
function release(address nft, uint256[] calldata tokenIds, string calldata serverId) external onlyReleaser { */
require(tokenIds.length <= 100, "tokenIds too many"); function unlockOrMint(
for (uint256 i = 0; i < tokenIds.length; i++) { address nft,
IERC721(nft).transferFrom(msg.sender, address(this), tokenIds[i]); NFTInfo[] calldata nftList,
lockedRecords[nft][msg.sender].add(tokenIds[i]); uint256 signTime,
} uint256 saltNonce,
emit Release(nft, tokenIds, serverId); bytes calldata signature
} ) external signatureValid(signature) timeValid(signTime) whenNotPaused {
require(nftList.length <= 100, "tokenIds too many");
function originalOwner(address token, uint256 tokenId) public view returns (address) { address to = _msgSender();
return lockedOriginal[token][tokenId]; bytes32 messageHash = getMessageHash(to, nft, nftList, _CACHED_THIS, _CACHED_CHAIN_ID, signTime, saltNonce);
} checkSigner(verifier, messageHash, signature);
for (uint256 i = 0; i < nftList.length; i++) {
function lockedNum(address token, address user) public view returns (uint256) { if (nftList[i].isMint) {
return lockedRecords[token][user].length(); INFT(nft).mint(to, nftList[i].tokenId);
} } else {
require(lockedOriginal[nft][nftList[i].tokenId] == to, "not owner");
function lockedNft(address token, address user) public view returns (uint256[] memory) { INFT(nft).transferFrom(address(this), to, nftList[i].tokenId);
return lockedRecords[token][user].values(); lockedRecords[nft][to].remove(nftList[i].tokenId);
} delete lockedOriginal[nft][nftList[i].tokenId];
}
/** ------set------- **/ }
function setOperatorRole(address to) external { _useSignature(signature);
grantRole(OPERATOR_ROLE, to); emit UnLock(nft, to, saltNonce, nftList);
} }
function removeOperatorRole(address to) external { /** ------get------- **/
revokeRole(OPERATOR_ROLE, to); function lockedNum(address token, address user) public view returns (uint256) {
} return lockedRecords[token][user].length();
}
function setReleaseRole(address to) external {
grantRole(RELEASE_ROLE, to); function lockedNft(address token, address user) public view returns (uint256[] memory) {
} return lockedRecords[token][user].values();
}
function removeReleaseRole(address to) external {
revokeRole(RELEASE_ROLE, to); function addSupportNftList(address[] calldata nftList) external onlyOwner {
} for (uint256 i = 0; i < nftList.length; i++) {
supportNftList[nftList[i]] = true;
function setUnlockRole(address to) external { }
grantRole(UNLOCK_ROLE, to); }
} function removeSupportNft(address nftAddress) external onlyOwner {
require(supportNftList[nftAddress], "can't remove");
function removeUnlockRole(address to) external { delete supportNftList[nftAddress];
revokeRole(UNLOCK_ROLE, to); }
}
/**
function addSupportNftList(address[] calldata nftList) external onlyOperator { * @dev update verifier address
for (uint256 i = 0; i < nftList.length; i++) { */
supportNftList[nftList[i]] = true; function updateVerifier(address _verifier) external onlyOwner {
} require(_verifier != address(0), "NFTClaimer: address can not be zero");
} verifier = _verifier;
function removeSupportNft(address nftAddress) external onlyOperator { emit VerifierUpdated(_verifier);
require(supportNftList[nftAddress], "can't remove"); }
delete supportNftList[nftAddress];
} function getMessageHash(
address _to,
/** ------modifier------- **/ address _nft,
modifier onlyOperator() { NFTInfo[] memory _ids,
require(hasRole(OPERATOR_ROLE, msg.sender), "not operator role"); address _contract,
_; uint256 _chainId,
} uint256 _signTime,
uint256 _saltNonce
modifier onlyUnlocker() { ) public pure returns (bytes32) {
require(hasRole(UNLOCK_ROLE, msg.sender), "not unlocker role"); bytes memory encoded = abi.encodePacked(_to, _nft, _contract, _chainId, _signTime, _saltNonce);
_; for (uint256 i = 0; i < _ids.length; ++i) {
} encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].tokenId));
encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].isMint));
modifier onlyReleaser() { }
require(hasRole(RELEASE_ROLE, msg.sender), "not releaser role"); return keccak256(encoded);
_; }
}
} }

View File

@ -8,8 +8,8 @@ contract TimeChecker is Ownable {
event DurationUpdated(uint256 indexed duration); event DurationUpdated(uint256 indexed duration);
constructor() { constructor(uint256 _duration) {
duration = 1 days; duration = _duration;
minDuration = 30 minutes; minDuration = 30 minutes;
} }

View File

@ -0,0 +1,29 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { updateArray } from "../scripts/utils"
const deployNFTClaim: DeployFunction =
async function (hre: HardhatRuntimeEnvironment) {
const provider = hre.ethers.provider;
const from = await (await provider.getSigner()).getAddress();
const verifier = '0x50A8e60041A206AcaA5F844a1104896224be6F39'
const ret = await hre.deployments.deploy("NFTLock", {
from,
args: [3600, verifier],
log: true,
});
console.log("==NFTLock addr=", ret.address);
updateArray({
name: "NFTLock",
type: "logic",
json: "assets/contracts/NFTLock.json",
address: ret.address,
network: hre.network.name,
});
};
deployNFTClaim.tags = ["NFTLock"];
export default deployNFTClaim;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,5 +16,11 @@
"type": "logic", "type": "logic",
"json": "assets/contracts/NFTClaimStage2.json", "json": "assets/contracts/NFTClaimStage2.json",
"address": "0xf45702180314187a3549FEDac3B78349b47ca6A0" "address": "0xf45702180314187a3549FEDac3B78349b47ca6A0"
},
{
"name": "NFTLock",
"type": "logic",
"json": "assets/contracts/NFTLock.json",
"address": "0x59e751c2037B710090035B6ea928e0cce80aC03f"
} }
] ]

View File

@ -10,6 +10,7 @@
"deploy": "hardhat deploy --network imtbl_test", "deploy": "hardhat deploy --network imtbl_test",
"deploy:nftclaim": "hardhat deploy --tags NFTClaimStage2 --network imtbl_test --reset", "deploy:nftclaim": "hardhat deploy --tags NFTClaimStage2 --network imtbl_test --reset",
"deploy:nft": "hardhat deploy --tags CFNFTGame --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", "deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",
"solhint": "solhint --config ./.solhint.json" "solhint": "solhint --config ./.solhint.json"
}, },

111
test/testNFTLocker.ts Normal file
View File

@ -0,0 +1,111 @@
import { expect } from 'chai'
import hre from "hardhat";
import {
getBytes,
solidityPackedKeccak256,
} from 'ethers'
import {
loadFixture,
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
describe('NFTLock', function() {
async function deployOneContract() {
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 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 NFTLock = await hre.ethers.getContractFactory("NFTLock");
const nftLock = await NFTLock.deploy( 3600, verifier );
await nft.grantMinterRole(nftLock.target)
await nft.grantMinterRole(owner.address)
await nftLock.addSupportNftList([nftAddress]);
await nft.mint(otherAccount.address, "1001");
const chainId = hre.network.config.chainId
await operatorAllowlist.grantRegistrarRole(owner.address)
await operatorAllowlist.addAddressToAllowlist([nftLock.target])
return { nftLock, owner, otherAccount, verifier, nftAddress, nft, chainId };
}
describe("Deployment", function () {
it('should deploy NFTLock with the correct verifier', async function() {
const { nftLock, verifier } = await loadFixture(deployOneContract);
expect(await nftLock.verifier()).to.equal(verifier);
});
it('should deploy NFTLock with the correct NFT address', async function() {
const { nftLock, nftAddress } = await loadFixture(deployOneContract);
expect(await nftLock.supportNftList(nftAddress)).to.equal(true);
});
})
describe("Lock", function () {
it('should lock NFT', async function() {
const { nftLock, nft, otherAccount } = await loadFixture(deployOneContract);
const tokenId = "1001"
// @ts-ignore
await nft.connect(otherAccount).approve(nftLock.target, tokenId);
//@ts-ignore
await nftLock.connect(otherAccount).lock(nft.target, [tokenId]);
expect(await nft.balanceOf(nftLock.target)).to.equal(1);
expect(await nftLock.lockedOriginal(nft.target, tokenId)).to.equal(otherAccount.address);
expect(await nft.ownerOf(tokenId)).to.equal(nftLock.target);
});
})
describe("UnLock", function () {
it('should unlock NFT from lock', async function() {
const { nftLock, nft, otherAccount, chainId, owner } = await loadFixture(deployOneContract);
const tokenId = '1001'
// @ts-ignore
await nft.connect(otherAccount).approve(nftLock.target, tokenId);
//@ts-ignore
await nftLock.connect(otherAccount).lock(nft.target, [tokenId]);
const nonce = (Math.random() * 1000) | 0;
const now = Date.now() / 1000 | 0;
let localMsgHash = solidityPackedKeccak256(["address", "address", "address", "uint256", "uint256", "uint256", "uint256", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, false]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, false]], now, nonce, signature);
expect(await nft.ownerOf(tokenId)).to.equal(otherAccount.address);
});
it('should mint NFT from lock', async function() {
const { nftLock, nft, otherAccount, chainId, owner } = await loadFixture(deployOneContract);
const tokenId = '1002'
const isMint = true
const nonce = (Math.random() * 1000) | 0;
const now = Date.now() / 1000 | 0;
let localMsgHash = solidityPackedKeccak256(["address", "address", "address", "uint256", "uint256", "uint256", "uint256", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, isMint]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, isMint]], now, nonce, signature);
expect(await nft.ownerOf(tokenId)).to.equal(otherAccount.address);
});
it('should revert NFT mint for nft already minted', async function() {
const { nftLock, nft, otherAccount, chainId, owner } = await loadFixture(deployOneContract);
const tokenId = '1001'
const isMint = true
const nonce = (Math.random() * 1000) | 0;
const now = Date.now() / 1000 | 0;
let localMsgHash = solidityPackedKeccak256(["address", "address", "address", "uint256", "uint256", "uint256", "uint256", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, isMint]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await expect(nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, isMint]], now, nonce, signature)).to.be.revertedWith(
"ERC721: token already minted"
);
});
});
})