增加用于处理金砖的lock合约
This commit is contained in:
parent
1c3ac2ff53
commit
09789251e3
113
contracts/game/NFTLockV2.sol
Normal file
113
contracts/game/NFTLockV2.sol
Normal 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);
|
||||
}
|
||||
}
|
29
deploy/4_2_deploy_nftlocker_v2.ts
Normal file
29
deploy/4_2_deploy_nftlocker_v2.ts
Normal 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;
|
@ -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
128
test/testNFTLockerV2.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user