增加用于处理金砖的lock合约

This commit is contained in:
CounterFire2023 2024-07-24 14:02:40 +08:00
parent 1c3ac2ff53
commit 09789251e3
4 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {HasSignature} from "../core/HasSignature.sol";
import {TimeChecker} from "../utils/TimeChecker.sol";
interface INFT {
function mint(address to, uint256 tokenID) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function burn(uint256 tokenId) external;
function ownerOf(uint256 tokenId) external view returns (address);
}
contract NFTLockV2 is HasSignature, TimeChecker, Pausable {
using EnumerableSet for EnumerableSet.UintSet;
uint256 public immutable _CACHED_CHAIN_ID;
address public immutable _CACHED_THIS;
address public verifier;
uint256 public maxBatch = 100;
struct NFTInfo {
uint256 tokenId;
address to;
bool isMint;
}
mapping(address nft => bool status) public supportNftList;
event UnLock(address indexed nft, address indexed user, uint256 nonce, NFTInfo[] nftList);
event Lock(address indexed nft, address indexed sender, address indexed to, uint256[] tokenIds);
event VerifierUpdated(address indexed verifier);
constructor(uint256 _duration, address _verifier) TimeChecker(_duration) {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_THIS = address(this);
verifier = _verifier;
}
/**
* from eoa or passport
*/
function lock(address nft, address to, uint256[] calldata tokenIds) external whenNotPaused{
require(tokenIds.length <= maxBatch, "tokenIds too many");
require(to != address(0), "to can't be zero");
require(supportNftList[nft], "not support nft");
address _sender = _msgSender();
for (uint256 i = 0; i < tokenIds.length; i++) {
address owner = INFT(nft).ownerOf(tokenIds[i]);
require(owner == _sender, "not owner");
INFT(nft).burn(tokenIds[i]);
}
emit Lock(nft, _sender, to, tokenIds);
}
/**
* @dev mint nft
*/
function unlockOrMint(
address nft,
NFTInfo[] calldata nftList,
uint256 signTime,
uint256 saltNonce,
bytes calldata signature
) external signatureValid(signature) timeValid(signTime) {
require(nftList.length <= maxBatch, "tokenIds too many");
address _sender = _msgSender();
bytes32 messageHash = getMessageHash(_sender, nft, nftList, _CACHED_THIS, _CACHED_CHAIN_ID, signTime, saltNonce);
checkSigner(verifier, messageHash, signature);
_useSignature(signature);
for (uint256 i = 0; i < nftList.length; i++) {
require(nftList[i].isMint, "mint only");
INFT(nft).mint(nftList[i].to, nftList[i].tokenId);
}
emit UnLock(nft, _sender, saltNonce, nftList);
}
function addSupportNftList(address[] calldata nftList) external onlyOwner {
for (uint256 i = 0; i < nftList.length; i++) {
supportNftList[nftList[i]] = true;
}
}
function removeSupportNft(address nftAddress) external onlyOwner {
require(supportNftList[nftAddress], "can't remove");
delete supportNftList[nftAddress];
}
/**
* @dev update verifier address
*/
function updateVerifier(address _verifier) external onlyOwner {
require(_verifier != address(0), "address can not be zero");
verifier = _verifier;
emit VerifierUpdated(_verifier);
}
function getMessageHash(
address _to,
address _nft,
NFTInfo[] memory _ids,
address _contract,
uint256 _chainId,
uint256 _signTime,
uint256 _saltNonce
) public pure returns (bytes32) {
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].to));
encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].isMint));
}
return keccak256(encoded);
}
}

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 config = require(`../config/config_${hre.network.name}`);
const verifier = config.admins.admin
const ret = await hre.deployments.deploy("NFTLockV2", {
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 = ["NFTLockV2"];
export default deployNFTClaim;

View File

@ -12,6 +12,7 @@
"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:nftlock:v2": "hardhat deploy --tags NFTLockV2 --network imtbl_test --reset",
"deploy:nftlock:main": "hardhat deploy --tags NFTLockMain --network sepolia_test --reset",
"deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",
"deploy:airdrop": "hardhat deploy --tags AirdropToken --network imtbl_test --reset",

128
test/testNFTLockerV2.ts Normal file
View File

@ -0,0 +1,128 @@
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("NFTLockV2");
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, 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, owner.address, [tokenId]);
expect(await nft.balanceOf(owner.address)).to.equal(0);
});
it('should revert lock NFT for not owner', async function() {
const { nftLock, nft, otherAccount, owner } = await loadFixture(deployOneContract);
const tokenId = "1001"
// @ts-ignore
await nft.connect(otherAccount).approve(nftLock.target, tokenId);
await expect(nftLock.lock(nft.target, owner.address, [tokenId])).to.be.revertedWith("not owner");
});
it('should revert lock NFT for not approved', async function() {
const { nftLock, nft, otherAccount, owner } = await loadFixture(deployOneContract);
const tokenId = "1001"
// @ts-ignore
await expect(nftLock.connect(otherAccount).lock(nft.target, owner.address, [tokenId])).to.be.revertedWith("ERC721: caller is not token owner or approved");
});
it('should revert lock NFT for invalid token id', async function() {
const { nftLock, nft, otherAccount, owner } = await loadFixture(deployOneContract);
const tokenId = "1002"
// @ts-ignore
await expect(nft.connect(otherAccount).approve(nftLock.target, tokenId)).to.be.revertedWith("ERC721: invalid token ID");
});
})
describe("UnLock", function () {
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", "address", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, otherAccount.address, isMint]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, otherAccount.address, isMint]], now, nonce, signature);
expect(await nft.ownerOf(tokenId)).to.equal(otherAccount.address);
});
it('should revert NFT unlock', async function() {
const { nftLock, nft, otherAccount, chainId, owner } = await loadFixture(deployOneContract);
const tokenId = '1001'
const isMint = false
const nonce = (Math.random() * 1000) | 0;
const now = Date.now() / 1000 | 0;
let localMsgHash = solidityPackedKeccak256(["address", "address", "address", "uint256", "uint256", "uint256", "uint256", "address", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, otherAccount.address, isMint]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await expect(nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, otherAccount.address, isMint]], now, nonce, signature)).to.be.revertedWith(
"mint only"
);
});
it('should revert NFT unlock for alreay burned', 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, owner.address, [tokenId]);
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", "address", "bool"],
[otherAccount.address, nft.target, nftLock.target, chainId, now, nonce, tokenId, otherAccount.address, isMint]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await expect(nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, otherAccount.address, isMint]], now, nonce, signature)).to.be.revertedWithCustomError(
nft,
"IImmutableERC721TokenAlreadyBurned"
).withArgs(tokenId);
});
});
})