增加用于主网的nftlocker

This commit is contained in:
CounterFire2023 2024-07-17 15:52:09 +08:00
parent 92cd4db0d8
commit a832a127d1
11 changed files with 22246 additions and 0 deletions

20819
build/contracts/BEBadgeV2.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
const market = {
feeToAddress: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
mallFeeAddress: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
paymentTokens: [
"0x514609B71340E149Cb81A80A953D07A7Fe41bd4F", // USDT
],
};
const admins = {
admin: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
proposers: [
"0x50A8e60041A206AcaA5F844a1104896224be6F39",
"0x746338765a8FbDD1c5aB61bfb92CD6D960C3C662",
],
confirmers: ["0x50A8e60041A206AcaA5F844a1104896224be6F39"],
executors: [
"0x50A8e60041A206AcaA5F844a1104896224be6F39",
"0x746338765a8FbDD1c5aB61bfb92CD6D960C3C662",
"0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
],
};
const token = {
baseTokenURIHero: "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',
}
const mint = {
// 2期mint支付的代币
mintCurrency: '0xFd42bfb03212dA7e1A4608a44d7658641D99CF34',
// 2期mint, 单个nft价格
mintPrice: '75000000000000000',
// 2期mint接收代币的钱包地址
mintFeeAddress: '0x50A8e60041A206AcaA5F844a1104896224be6F39',
// 2期mint nftid 开始
mintStartNftId: '6240603010010301',
// 2期mint 最大可mint数量
maxSupply: 2000,
// 2期mint airdrop数量
airdropCount: 500,
}
var config = {
market,
admins,
token,
imtbl,
mint
};
module.exports = config;

View File

@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
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;
}
contract NFTLockMain is ERC721Holder, 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 => mapping(uint256 tokenId => address user)) public addressOriginal;
mapping(address nft => mapping(address user => EnumerableSet.UintSet tokenIdSet)) private lockedRecords;
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;
}
/**
* lock NFT
* from eoa only
* @param nft nft address
* @param to passport address for game
* @param tokenIds nft token id list
*/
function lock(address nft, address to, uint256[] calldata tokenIds) external whenNotPaused{
require(tokenIds.length <= maxBatch, "tokenIds too many");
require(to != address(0), "passport can't be zero");
require(supportNftList[nft], "not support nft");
address _sender = _msgSender();
for (uint256 i = 0; i < tokenIds.length; i++) {
addressOriginal[nft][tokenIds[i]] = _sender;
lockedRecords[nft][_sender].add(tokenIds[i]);
INFT(nft).transferFrom(_sender, address(this), tokenIds[i]);
}
emit Lock(nft, _sender, to, tokenIds);
}
/**
* @dev unlock or mint nft
* from passport only
* if tokenId not exists, mint it
* if exists and user is owner, unlock it
*/
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++) {
if (nftList[i].isMint) {
INFT(nft).mint(nftList[i].to, nftList[i].tokenId);
} else {
require(addressOriginal[nft][nftList[i].tokenId] == _sender, "not owner");
delete addressOriginal[nft][nftList[i].tokenId];
lockedRecords[nft][_sender].remove(nftList[i].tokenId);
INFT(nft).transferFrom(address(this), nftList[i].to, nftList[i].tokenId);
}
}
emit UnLock(nft, _sender, saltNonce, nftList);
}
/**
* @dev unlock nft
* from game svr only
*/
function unlockWithSvr(address nft, uint256[] calldata tokenIds) external onlyOwner{
require(tokenIds.length <= maxBatch, "tokenIds too many");
for (uint256 i = 0; i < tokenIds.length; i++) {
address _sender = addressOriginal[nft][tokenIds[i]];
delete addressOriginal[nft][tokenIds[i]];
lockedRecords[nft][_sender].remove(tokenIds[i]);
INFT(nft).transferFrom(address(this), _sender, tokenIds[i]);
}
}
/** ------get------- **/
function lockedNum(address token, address user) public view returns (uint256) {
return lockedRecords[token][user].length();
}
function lockedNft(address token, address user) public view returns (uint256[] memory) {
return lockedRecords[token][user].values();
}
function updateBatch(uint256 _maxBatch) external onlyOwner {
maxBatch = _maxBatch;
}
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), "NFTClaimer: 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("NFTLockMain", {
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 = ["NFTLockMain"];
export default deployNFTClaim;

View File

@ -0,0 +1 @@
11155111

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -56,6 +56,10 @@ const config: HardhatUserConfig = {
url: process.env.IMTBL_TEST_URL || "",
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
sepolia_test: {
url: process.env.SEPOLIA_URL || "",
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
},
};

14
out/sepolia_test_dev.json Normal file
View File

@ -0,0 +1,14 @@
[
{
"name": "NFTLock",
"type": "logic",
"json": "assets/contracts/NFTLock.json",
"address": "0x7F2b4DB626d878778e178B4F0C7bA3a2870C6dd0"
},
{
"name": "BEBadgeV2",
"type": "erc721",
"json": "assets/contracts/BEBadgeV2.json",
"address": "0x1304E6AA241eE3C9ea44Db9e593e85Ae76eC41F1"
}
]

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: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",
"solhint": "solhint --config ./.solhint.json"

126
test/testNFTLockerMain.ts Normal file
View File

@ -0,0 +1,126 @@
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("NFTLockMain");
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(nftLock.target)).to.equal(1);
expect(await nftLock.addressOriginal(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, owner.address, [tokenId]);
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, owner.address, false]);
const signature = await owner.signMessage(getBytes(localMsgHash));
//@ts-ignore
await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, owner.address, false]], now, nonce, signature);
// await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, false]], now, nonce, signature);
expect(await nft.ownerOf(tokenId)).to.equal(owner.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", "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 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", "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(
"ERC721: token already minted"
);
});
it('should unlock NFT with svr', 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]);
//@ts-ignore
await nftLock.unlockWithSvr(nft.target, [tokenId]);
// await nftLock.connect(otherAccount).unlockOrMint(nft.target, [[tokenId, false]], now, nonce, signature);
expect(await nft.ownerOf(tokenId)).to.equal(otherAccount.address);
});
});
})