add contracts for nft and nft claimer; add test for nft claimer
This commit is contained in:
parent
59b6fc8f34
commit
89c02d5e47
3
.gitignore
vendored
3
.gitignore
vendored
@ -29,4 +29,5 @@ ignition/deployments/chain-31337
|
||||
.DS_Store
|
||||
/contracts/dist/
|
||||
/contracts/types/
|
||||
openzeppelin
|
||||
openzeppelin
|
||||
imtbl
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,3 +1,3 @@
|
||||
{
|
||||
"solidity.compileUsingRemoteVersion": "v0.8.23+commit.f704f362"
|
||||
"solidity.compileUsingRemoteVersion": "v0.8.19+commit.7dd6d404"
|
||||
}
|
1339
build/contracts/CFNFTGame.json
Normal file
1339
build/contracts/CFNFTGame.json
Normal file
File diff suppressed because one or more lines are too long
836
build/contracts/ImmutableERC20MinterBurnerPermit.json
Normal file
836
build/contracts/ImmutableERC20MinterBurnerPermit.json
Normal file
File diff suppressed because one or more lines are too long
492
build/contracts/NFTClaimStage2.json
Normal file
492
build/contracts/NFTClaimStage2.json
Normal file
File diff suppressed because one or more lines are too long
41
config/config_imtbl_test.js
Normal file
41
config/config_imtbl_test.js
Normal file
@ -0,0 +1,41 @@
|
||||
const market = {
|
||||
feeToAddress: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
|
||||
mallFeeAddress: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
|
||||
paymentTokens: [
|
||||
"0x514609B71340E149Cb81A80A953D07A7Fe41bd4F", // USDT
|
||||
],
|
||||
};
|
||||
|
||||
const admins = {
|
||||
admin: "0x50A8e60041A206AcaA5F844a1104896224be6F39",
|
||||
proposers: [
|
||||
"0x50A8e60041A206AcaA5F844a1104896224be6F39",
|
||||
"0x746338765a8FbDD1c5aB61bfb92CD6D960C3C662",
|
||||
],
|
||||
confirmers: ["0x50A8e60041A206AcaA5F844a1104896224be6F39"],
|
||||
executors: [
|
||||
"0x50A8e60041A206AcaA5F844a1104896224be6F39",
|
||||
"0x746338765a8FbDD1c5aB61bfb92CD6D960C3C662",
|
||||
"0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
|
||||
],
|
||||
};
|
||||
|
||||
const token = {
|
||||
baseTokenURI: "https://market.cebg.games/api/nft/info/",
|
||||
contractURI: 'https://market.cebg.games/api/nft/info/',
|
||||
royaltyReceiver: '0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888',
|
||||
royaltyFee: 5,
|
||||
};
|
||||
|
||||
const imtbl = {
|
||||
operatorAllowlist: '0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE'
|
||||
}
|
||||
|
||||
var config = {
|
||||
market,
|
||||
admins,
|
||||
token,
|
||||
imtbl,
|
||||
};
|
||||
|
||||
module.exports = config;
|
@ -1,99 +1,125 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
pragma solidity 0.8.19;
|
||||
|
||||
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {HasSignature} from "../core/HasSignature.sol";
|
||||
|
||||
/**
|
||||
* Contract for the activity of NFT claim stage 2.
|
||||
*/
|
||||
interface IClaimAbleNFT {
|
||||
function safeMint(
|
||||
address to
|
||||
) external returns (uint256);
|
||||
function safeMint(address to, uint256 tokenID) external;
|
||||
}
|
||||
|
||||
contract NFTClaimStage2 is HasSignature, ReentrancyGuard{
|
||||
uint256 private immutable _CACHED_CHAIN_ID;
|
||||
address private immutable _CACHED_THIS;
|
||||
address public immutable nftAddress;
|
||||
address public verifier;
|
||||
contract NFTClaimStage2 is HasSignature, ReentrancyGuard {
|
||||
struct MintConfig {
|
||||
uint256 parse1MaxSupply; // max supply for phase1
|
||||
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;
|
||||
|
||||
bool public isPaused = false;
|
||||
|
||||
mapping(address user => uint256 status) public claimHistory;
|
||||
uint256 public immutable _CACHED_CHAIN_ID;
|
||||
address public immutable _CACHED_THIS;
|
||||
address public immutable nftAddress;
|
||||
|
||||
event NFTClaimed(
|
||||
address indexed nftAddress,
|
||||
address indexed to,
|
||||
uint256 tokenId,
|
||||
uint256 nonce
|
||||
);
|
||||
address public verifier;
|
||||
MintConfig public mintConfig;
|
||||
uint256 public parse1Count;
|
||||
uint256 public totalCount;
|
||||
|
||||
event StateUpdated(bool isPaused);
|
||||
event VerifierUpdated(address indexed verifier);
|
||||
event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids);
|
||||
|
||||
constructor(address _nftAddress) {
|
||||
_CACHED_CHAIN_ID = block.chainid;
|
||||
_CACHED_THIS = address(this);
|
||||
nftAddress = _nftAddress;
|
||||
}
|
||||
event ParseUpdated(uint256 _parse);
|
||||
event MintConfigUpdated(MintConfig config);
|
||||
event VerifierUpdated(address indexed verifier);
|
||||
|
||||
modifier whenNotPaused() {
|
||||
require(!isPaused, "NFTClaimer: paused");
|
||||
_;
|
||||
}
|
||||
constructor(address _nftAddress, address _verifier, MintConfig memory _mintConfig) {
|
||||
_CACHED_CHAIN_ID = block.chainid;
|
||||
_CACHED_THIS = address(this);
|
||||
nftAddress = _nftAddress;
|
||||
verifier = _verifier;
|
||||
mintConfig = _mintConfig;
|
||||
}
|
||||
|
||||
function updatePaused(bool _isPaused) external onlyOwner {
|
||||
isPaused = _isPaused;
|
||||
emit StateUpdated(_isPaused);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
modifier whenNotPaused() {
|
||||
require(mintParse > 0, "NFTClaimer: not begin or ended");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 saltNonce,
|
||||
bytes calldata signature
|
||||
) external nonReentrant whenNotPaused {
|
||||
address to = _msgSender();
|
||||
require(claimHistory[to] == 0, "NFTClaimer: already claimed");
|
||||
bytes32 criteriaMessageHash = getMessageHash(
|
||||
to,
|
||||
nftAddress,
|
||||
saltNonce
|
||||
);
|
||||
checkSigner(verifier, criteriaMessageHash, signature);
|
||||
uint256 tokenId = IClaimAbleNFT(nftAddress).safeMint(to);
|
||||
claimHistory[to] = tokenId;
|
||||
_useSignature(signature);
|
||||
emit NFTClaimed(nftAddress, to, tokenId, saltNonce);
|
||||
}
|
||||
function updateMintParse(uint256 _mintParse) external onlyOwner {
|
||||
mintParse = _mintParse;
|
||||
emit ParseUpdated(_mintParse);
|
||||
}
|
||||
|
||||
function getMessageHash(
|
||||
address _to,
|
||||
address _address,
|
||||
uint256 _saltNonce
|
||||
) public view returns (bytes32) {
|
||||
bytes memory encoded = abi.encodePacked(
|
||||
_to,
|
||||
_address,
|
||||
_CACHED_CHAIN_ID,
|
||||
_CACHED_THIS,
|
||||
_saltNonce
|
||||
);
|
||||
return keccak256(encoded);
|
||||
}
|
||||
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");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
pragma solidity 0.8.19;
|
||||
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
@ -14,13 +14,13 @@ contract HasSignature is Ownable {
|
||||
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);
|
||||
|
||||
address recovered = ECDSA.recover(ethSignedMessageHash, signature);
|
||||
require(recovered == signer, "[BE] invalid signature");
|
||||
require(recovered == signer, "invalid signature");
|
||||
}
|
||||
|
||||
modifier signatureValid(bytes calldata signature) {
|
||||
require(
|
||||
!_usedSignatures[signature],
|
||||
"[BE] signature used. please send another transaction with new signature"
|
||||
"signature used. please send another transaction with new signature"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
125
contracts/game/NFTLock.sol
Normal file
125
contracts/game/NFTLock.sol
Normal file
@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.19;
|
||||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
|
||||
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
contract NFTLock is AccessControl, ERC721Holder {
|
||||
using EnumerableSet for EnumerableSet.UintSet;
|
||||
|
||||
mapping(address => mapping(uint256 => address)) lockedOriginal;
|
||||
mapping(address => mapping(address => EnumerableSet.UintSet)) lockedRecords;
|
||||
mapping(address => bool) supportNftList;
|
||||
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
|
||||
bytes32 public constant UNLOCK_ROLE = keccak256("UNLOCK_ROLE");
|
||||
bytes32 public constant RELEASE_ROLE = keccak256("RELEASE_ROLE");
|
||||
|
||||
event Lock(address indexed nft, address indexed user, uint256 indexed tokenId);
|
||||
event UnLock(address indexed nft, address indexed user, uint256 indexed tokenId);
|
||||
event BatchLock(address indexed nft, address indexed user, uint256[] tokenId);
|
||||
event Release(address indexed nft, uint256[] tokenIds, string serverId);
|
||||
|
||||
constructor() {
|
||||
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||
_setupRole(OPERATOR_ROLE, msg.sender);
|
||||
_setupRole(UNLOCK_ROLE, msg.sender);
|
||||
_setupRole(RELEASE_ROLE, msg.sender);
|
||||
}
|
||||
|
||||
function lock(address nft, uint256 tokenId) external {
|
||||
require(supportNftList[nft], "can't lock this nft");
|
||||
IERC721(nft).transferFrom(msg.sender, address(this), tokenId);
|
||||
lockedOriginal[nft][tokenId] = msg.sender;
|
||||
lockedRecords[nft][msg.sender].add(tokenId);
|
||||
emit Lock(nft, msg.sender, tokenId);
|
||||
}
|
||||
|
||||
function unlock(address nft, address to, uint256 tokenId) external onlyUnlocker {
|
||||
IERC721(nft).transferFrom(address(this), to, tokenId);
|
||||
lockedRecords[nft][lockedOriginal[nft][tokenId]].remove(tokenId);
|
||||
delete lockedOriginal[nft][tokenId];
|
||||
emit UnLock(nft, to, tokenId);
|
||||
}
|
||||
|
||||
function batchLock(address nft, uint256[] calldata tokenIds) external {
|
||||
require(tokenIds.length <= 100, "tokenIds too many");
|
||||
for (uint256 i = 0; i < tokenIds.length; i++) {
|
||||
IERC721(nft).transferFrom(msg.sender, address(this), tokenIds[i]);
|
||||
lockedOriginal[nft][tokenIds[i]] = msg.sender;
|
||||
lockedRecords[nft][msg.sender].add(tokenIds[i]);
|
||||
}
|
||||
emit BatchLock(nft, msg.sender, tokenIds);
|
||||
}
|
||||
|
||||
function release(address nft, uint256[] calldata tokenIds, string calldata serverId) external onlyReleaser {
|
||||
require(tokenIds.length <= 100, "tokenIds too many");
|
||||
for (uint256 i = 0; i < tokenIds.length; i++) {
|
||||
IERC721(nft).transferFrom(msg.sender, address(this), tokenIds[i]);
|
||||
lockedRecords[nft][msg.sender].add(tokenIds[i]);
|
||||
}
|
||||
emit Release(nft, tokenIds, serverId);
|
||||
}
|
||||
|
||||
function originalOwner(address token, uint256 tokenId) public view returns (address) {
|
||||
return lockedOriginal[token][tokenId];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/** ------set------- **/
|
||||
function setOperatorRole(address to) external {
|
||||
grantRole(OPERATOR_ROLE, to);
|
||||
}
|
||||
|
||||
function removeOperatorRole(address to) external {
|
||||
revokeRole(OPERATOR_ROLE, to);
|
||||
}
|
||||
|
||||
function setReleaseRole(address to) external {
|
||||
grantRole(RELEASE_ROLE, to);
|
||||
}
|
||||
|
||||
function removeReleaseRole(address to) external {
|
||||
revokeRole(RELEASE_ROLE, to);
|
||||
}
|
||||
|
||||
function setUnlockRole(address to) external {
|
||||
grantRole(UNLOCK_ROLE, to);
|
||||
}
|
||||
|
||||
function removeUnlockRole(address to) external {
|
||||
revokeRole(UNLOCK_ROLE, to);
|
||||
}
|
||||
|
||||
function addSupportNftList(address[] calldata nftList) external onlyOperator {
|
||||
for (uint256 i = 0; i < nftList.length; i++) {
|
||||
supportNftList[nftList[i]] = true;
|
||||
}
|
||||
}
|
||||
function removeSupportNft(address nftAddress) external onlyOperator {
|
||||
require(supportNftList[nftAddress], "can't remove");
|
||||
delete supportNftList[nftAddress];
|
||||
}
|
||||
|
||||
/** ------modifier------- **/
|
||||
modifier onlyOperator() {
|
||||
require(hasRole(OPERATOR_ROLE, msg.sender), "not operator role");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyUnlocker() {
|
||||
require(hasRole(UNLOCK_ROLE, msg.sender), "not unlocker role");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyReleaser() {
|
||||
require(hasRole(RELEASE_ROLE, msg.sender), "not releaser role");
|
||||
_;
|
||||
}
|
||||
}
|
178
contracts/test/OperatorAllowlist.sol
Normal file
178
contracts/test/OperatorAllowlist.sol
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright Immutable Pty Ltd 2018 - 2023
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
pragma solidity 0.8.19;
|
||||
|
||||
// Access Control
|
||||
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
// Introspection
|
||||
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
||||
|
||||
// Interfaces
|
||||
import {IOperatorAllowlist} from "@imtbl/contracts/contracts/allowlist/IOperatorAllowlist.sol";
|
||||
|
||||
// Interface to retrieve the implemention stored inside the Proxy contract
|
||||
interface IProxy {
|
||||
// Returns the current implementation address used by the proxy contract
|
||||
// solhint-disable-next-line func-name-mixedcase
|
||||
function PROXY_getImplementation() external view returns (address);
|
||||
}
|
||||
|
||||
/*
|
||||
OperatorAllowlist is an implementation of a Allowlist registry, storing addresses and bytecode
|
||||
which are allowed to be approved operators and execute transfers of interfacing token contracts (e.g. ERC721/ERC1155).
|
||||
The registry will be a deployed contract that tokens may interface with and point to.
|
||||
OperatorAllowlist is not designed to be upgradeable or extended.
|
||||
*/
|
||||
|
||||
contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist {
|
||||
/// ===== State Variables =====
|
||||
|
||||
/// @notice Only REGISTRAR_ROLE can invoke white listing registration and removal
|
||||
bytes32 public constant REGISTRAR_ROLE = bytes32("REGISTRAR_ROLE");
|
||||
|
||||
/// @notice Mapping of Allowlisted addresses
|
||||
mapping(address aContract => bool allowed) private addressAllowlist;
|
||||
|
||||
/// @notice Mapping of Allowlisted implementation addresses
|
||||
mapping(address impl => bool allowed) private addressImplementationAllowlist;
|
||||
|
||||
/// @notice Mapping of Allowlisted bytecodes
|
||||
mapping(bytes32 bytecodeHash => bool allowed) private bytecodeAllowlist;
|
||||
|
||||
/// ===== Events =====
|
||||
|
||||
/// @notice Emitted when a target address is added or removed from the Allowlist
|
||||
event AddressAllowlistChanged(address indexed target, bool added);
|
||||
|
||||
/// @notice Emitted when a target smart contract wallet is added or removed from the Allowlist
|
||||
event WalletAllowlistChanged(bytes32 indexed targetBytes, address indexed targetAddress, bool added);
|
||||
|
||||
/// ===== Constructor =====
|
||||
|
||||
/**
|
||||
* @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `admin` address
|
||||
* @param admin the address to grant `DEFAULT_ADMIN_ROLE` to
|
||||
*/
|
||||
constructor(address admin) {
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
||||
}
|
||||
|
||||
/// ===== External functions =====
|
||||
|
||||
/**
|
||||
* @notice Add a target address to Allowlist
|
||||
* @param addressTargets the addresses to be added to the allowlist
|
||||
*/
|
||||
function addAddressToAllowlist(address[] calldata addressTargets) external onlyRole(REGISTRAR_ROLE) {
|
||||
for (uint256 i; i < addressTargets.length; i++) {
|
||||
addressAllowlist[addressTargets[i]] = true;
|
||||
emit AddressAllowlistChanged(addressTargets[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Remove a target address from Allowlist
|
||||
* @param addressTargets the addresses to be removed from the allowlist
|
||||
*/
|
||||
function removeAddressFromAllowlist(address[] calldata addressTargets) external onlyRole(REGISTRAR_ROLE) {
|
||||
for (uint256 i; i < addressTargets.length; i++) {
|
||||
delete addressAllowlist[addressTargets[i]];
|
||||
emit AddressAllowlistChanged(addressTargets[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Add a smart contract wallet to the Allowlist.
|
||||
* This will allowlist the proxy and implementation contract pair.
|
||||
* First, the bytecode of the proxy is added to the bytecode allowlist.
|
||||
* Second, the implementation address stored in the proxy is stored in the
|
||||
* implementation address allowlist.
|
||||
* @param walletAddr the wallet address to be added to the allowlist
|
||||
*/
|
||||
function addWalletToAllowlist(address walletAddr) external onlyRole(REGISTRAR_ROLE) {
|
||||
// get bytecode of wallet
|
||||
bytes32 codeHash;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
codeHash := extcodehash(walletAddr)
|
||||
}
|
||||
bytecodeAllowlist[codeHash] = true;
|
||||
// get address of wallet module
|
||||
address impl = IProxy(walletAddr).PROXY_getImplementation();
|
||||
addressImplementationAllowlist[impl] = true;
|
||||
|
||||
emit WalletAllowlistChanged(codeHash, walletAddr, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Remove a smart contract wallet from the Allowlist
|
||||
* This will remove the proxy bytecode hash and implementation contract address pair from the allowlist
|
||||
* @param walletAddr the wallet address to be removed from the allowlist
|
||||
*/
|
||||
function removeWalletFromAllowlist(address walletAddr) external onlyRole(REGISTRAR_ROLE) {
|
||||
// get bytecode of wallet
|
||||
bytes32 codeHash;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
codeHash := extcodehash(walletAddr)
|
||||
}
|
||||
delete bytecodeAllowlist[codeHash];
|
||||
// get address of wallet module
|
||||
address impl = IProxy(walletAddr).PROXY_getImplementation();
|
||||
delete addressImplementationAllowlist[impl];
|
||||
|
||||
emit WalletAllowlistChanged(codeHash, walletAddr, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Allows admin to grant `user` `REGISTRAR_ROLE` role
|
||||
* @param user the address that `REGISTRAR_ROLE` will be granted to
|
||||
*/
|
||||
function grantRegistrarRole(address user) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
grantRole(REGISTRAR_ROLE, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Allows admin to revoke `REGISTRAR_ROLE` role from `user`
|
||||
* @param user the address that `REGISTRAR_ROLE` will be revoked from
|
||||
*/
|
||||
function revokeRegistrarRole(address user) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
revokeRole(REGISTRAR_ROLE, user);
|
||||
}
|
||||
|
||||
/// ===== View functions =====
|
||||
|
||||
/**
|
||||
* @notice Returns true if an address is Allowlisted, false otherwise
|
||||
* @param target the address that will be checked for presence in the allowlist
|
||||
*/
|
||||
function isAllowlisted(address target) external view override returns (bool) {
|
||||
if (addressAllowlist[target]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if caller is a Allowlisted smart contract wallet
|
||||
bytes32 codeHash;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
codeHash := extcodehash(target)
|
||||
}
|
||||
if (bytecodeAllowlist[codeHash]) {
|
||||
// If wallet proxy bytecode is approved, check addr of implementation contract
|
||||
address impl = IProxy(target).PROXY_getImplementation();
|
||||
|
||||
return addressImplementationAllowlist[impl];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice ERC-165 interface support
|
||||
* @param interfaceId The interface identifier, which is a 4-byte selector.
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, AccessControl) returns (bool) {
|
||||
return interfaceId == type(IOperatorAllowlist).interfaceId || super.supportsInterface(interfaceId);
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
pragma solidity 0.8.19;
|
||||
|
||||
// import {ImmutableERC721Base} from "@imtbl/contracts/token/erc721/abstract/ImmutableERC721Base.sol";
|
||||
import {ImmutableERC721MintByID} from "@imtbl/contracts/token/erc721/preset/ImmutableERC721MintByID.sol";
|
||||
import {ImmutableERC721MintByID} from "@imtbl/contracts/contracts/token/erc721/preset/ImmutableERC721MintByID.sol";
|
||||
|
||||
contract CFNFTGame is ImmutableERC721MintByID{
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
pragma solidity 0.8.19;
|
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
contract TimeChecker is Ownable {
|
||||
|
35
deploy/1_deploy_nft.ts
Normal file
35
deploy/1_deploy_nft.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { DeployFunction } from "hardhat-deploy/types";
|
||||
import { updateArray } from "../scripts/utils"
|
||||
|
||||
const deployNFTForGame: 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}`);
|
||||
console.log(config);
|
||||
const owner = from;
|
||||
const name = "CFHero";
|
||||
const symbol = "CFH";
|
||||
// testnet: 0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE
|
||||
// mainnet: 0x5F5EBa8133f68ea22D712b0926e2803E78D89221
|
||||
const { operatorAllowlist } = config.imtbl;
|
||||
const { royaltyReceiver, feeNumerator, baseURI, contractURI } = config.token;
|
||||
const ret = await hre.deployments.deploy("CFNFTGame", {
|
||||
from,
|
||||
args: [owner, name, symbol, baseURI, contractURI, operatorAllowlist, royaltyReceiver, feeNumerator],
|
||||
log: true,
|
||||
});
|
||||
console.log("==CFNFTGame addr=", ret.address);
|
||||
updateArray({
|
||||
name: "CFHero",
|
||||
type: "erc721",
|
||||
json: "assets/contracts/CFNFTGame.json",
|
||||
address: ret.address,
|
||||
network: hre.network.name,
|
||||
});
|
||||
};
|
||||
|
||||
deployNFTForGame.tags = ["CFNFTGame"];
|
||||
|
||||
export default deployNFTForGame;
|
@ -1,22 +0,0 @@
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { DeployFunction } from "hardhat-deploy/types";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
|
||||
const deployNFTClaim: DeployFunction =
|
||||
async function (hre: HardhatRuntimeEnvironment) {
|
||||
const provider = ethers.provider;
|
||||
const from = await (await provider.getSigner()).getAddress();
|
||||
|
||||
console.log(from);
|
||||
const ret = await hre.deployments.deploy("NFTClaimStage2", {
|
||||
from,
|
||||
args: [],
|
||||
log: true,
|
||||
});
|
||||
console.log("==NFTClaimStage2 addr=", ret.address);
|
||||
};
|
||||
|
||||
deployNFTClaim.tags = ["NFTClaimStage2"];
|
||||
|
||||
export default deployNFTClaim;
|
@ -1,22 +0,0 @@
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { DeployFunction } from "hardhat-deploy/types";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
|
||||
const deployNFTForGame: DeployFunction =
|
||||
async function (hre: HardhatRuntimeEnvironment) {
|
||||
const provider = ethers.provider;
|
||||
const from = await (await provider.getSigner()).getAddress();
|
||||
|
||||
console.log(from);
|
||||
const ret = await hre.deployments.deploy("CFNFTGame", {
|
||||
from,
|
||||
args: [],
|
||||
log: true,
|
||||
});
|
||||
console.log("==CFNFTGame addr=", ret.address);
|
||||
};
|
||||
|
||||
deployNFTForGame.tags = ["CFNFTGame"];
|
||||
|
||||
export default deployNFTForGame;
|
38
deploy/2_deploy_nftclaimer.ts
Normal file
38
deploy/2_deploy_nftclaimer.ts
Normal file
@ -0,0 +1,38 @@
|
||||
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();
|
||||
|
||||
console.log(from);
|
||||
const nftAddress = '0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D';
|
||||
const verifier = '0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888'
|
||||
const mintConfig = [
|
||||
1000,
|
||||
2000,
|
||||
'0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D',
|
||||
100,
|
||||
'0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888'
|
||||
]
|
||||
const ret = await hre.deployments.deploy("NFTClaimStage2", {
|
||||
from,
|
||||
args: [nftAddress, verifier, mintConfig],
|
||||
log: true,
|
||||
});
|
||||
console.log("==NFTClaimStage2 addr=", ret.address);
|
||||
updateArray({
|
||||
name: "NFTClaimStage2",
|
||||
type: "logic",
|
||||
json: "assets/contracts/NFTClaimStage2.json",
|
||||
address: ret.address,
|
||||
network: hre.network.name,
|
||||
});
|
||||
};
|
||||
|
||||
deployNFTClaim.tags = ["NFTClaimStage2"];
|
||||
|
||||
export default deployNFTClaim;
|
30
deploy/3_1_deploy_test_tokon.ts
Normal file
30
deploy/3_1_deploy_test_tokon.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
||||
import { DeployFunction } from "hardhat-deploy/types";
|
||||
import { updateArray } from "../scripts/utils"
|
||||
|
||||
const deployNFTForGame: 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 owner = from;
|
||||
const name = "Test Token";
|
||||
const symbol = "TETH";
|
||||
const ret = await hre.deployments.deploy("ImmutableERC20MinterBurnerPermit", {
|
||||
from,
|
||||
args: [owner, owner, owner, name, symbol, '10000000000000000000000000'],
|
||||
log: true,
|
||||
});
|
||||
console.log("==ImmutableERC20MinterBurnerPermit addr=", ret.address);
|
||||
updateArray({
|
||||
name: "TestToken",
|
||||
type: "erc20",
|
||||
json: "assets/contracts/ImmutableERC20MinterBurnerPermit.json",
|
||||
address: ret.address,
|
||||
network: hre.network.name,
|
||||
});
|
||||
};
|
||||
|
||||
deployNFTForGame.tags = ["TestToken"];
|
||||
|
||||
export default deployNFTForGame;
|
707
deployments/imtbl_test/NFTClaimStage2.json
Normal file
707
deployments/imtbl_test/NFTClaimStage2.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,6 +7,9 @@
|
||||
"@openzeppelin/contracts/security/ReentrancyGuard.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == _ENTERED;\n }\n}\n"
|
||||
},
|
||||
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n"
|
||||
},
|
||||
"@openzeppelin/contracts/utils/Context.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n"
|
||||
},
|
||||
@ -22,17 +25,11 @@
|
||||
"@openzeppelin/contracts/utils/Strings.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./math/Math.sol\";\nimport \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\n */\n function toString(int256 value) internal pure returns (string memory) {\n return string(abi.encodePacked(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value))));\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n\n /**\n * @dev Returns true if the two strings are equal.\n */\n function equal(string memory a, string memory b) internal pure returns (bool) {\n return keccak256(bytes(a)) == keccak256(bytes(b));\n }\n}\n"
|
||||
},
|
||||
"contracts/activity/TreasureHunt.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.23;\nimport \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport \"../core/HasSignature.sol\";\nimport \"../utils/TimeChecker.sol\";\n\n\ncontract TreasureHunt is HasSignature, ReentrancyGuard, TimeChecker{\n mapping(address => mapping(uint256 => uint256)) public checkinHistory;\n mapping(address => mapping(uint256 => uint256)) public exploreHistory;\n mapping(address => mapping(uint256 => uint256)) public enhanceHistory;\n mapping(address => mapping(uint256 => uint256)) public claimTaskHistory;\n mapping(address => mapping(uint256 => uint256)) public openBoxHistory;\n uint256 private immutable _CACHED_CHAIN_ID;\n address private immutable _CACHED_THIS;\n address private verifier;\n\n bool public isPaused = false;\n\n event ActionEvent(\n address indexed user,\n uint256 indexed action,\n uint256 value\n );\n\n event StateUpdated(bool isPaused);\n event VerifierUpdated(address indexed verifier);\n\n constructor() {\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_THIS = address(this);\n }\n\n modifier whenNotPaused() {\n require(!isPaused, \"TreasureHunt: paused\");\n _;\n }\n\n function updatePaused(bool _isPaused) external onlyOwner {\n isPaused = _isPaused;\n emit StateUpdated(_isPaused);\n }\n\n /**\n * @dev update verifier address\n */\n function updateVerifier(address _verifier) external onlyOwner {\n require(_verifier != address(0), \"TreasureHunt: address can not be zero\");\n verifier = _verifier;\n emit VerifierUpdated(_verifier);\n }\n \n // daily checkin\n function dailyCheckin() external whenNotPaused {\n address user = _msgSender();\n uint256 day = block.timestamp / 1 days;\n require(checkinHistory[user][day] == 0, \"TreasureHunt: already checked in\");\n checkinHistory[user][day] = 1;\n emit ActionEvent(user, 1, day);\n }\n\n // explore\n function explore(\n uint256 step\n ) external whenNotPaused {\n address user = _msgSender();\n exploreHistory[user][step] = 1;\n emit ActionEvent(user, 2, step);\n } \n\n // enhance box\n function enhanceBox(\n uint256 boxId\n ) external whenNotPaused {\n address user = _msgSender();\n require(enhanceHistory[user][boxId] == 0, \"TreasureHunt: already enhanced\");\n enhanceHistory[user][boxId] = 1;\n emit ActionEvent(user, 3, boxId);\n } \n\n // open box\n function openBox(\n uint256 boxId\n ) external whenNotPaused {\n address user = _msgSender();\n require(openBoxHistory[user][boxId] == 0, \"TreasureHunt: already opened\");\n openBoxHistory[user][boxId] = 1;\n emit ActionEvent(user, 4, boxId);\n }\n\n // claim task reward\n function claimTaskReward(\n uint256 taskId\n ) external whenNotPaused{\n address user = _msgSender();\n require(claimTaskHistory[user][taskId] == 0, \"TreasureHunt: already claimed\");\n claimTaskHistory[user][taskId] = 1;\n emit ActionEvent(user, 5, taskId);\n } \n\n function generalAction(\n uint256 actionType,\n uint256 val,\n uint256 signTime,\n uint256 saltNonce,\n bytes calldata signature\n ) external nonReentrant whenNotPaused timeValid(signTime){\n address user = _msgSender();\n bytes32 criteriaMessageHash = getMessageHash(\n user,\n actionType,\n val,\n _CACHED_THIS,\n _CACHED_CHAIN_ID,\n signTime,\n saltNonce\n );\n checkSigner(verifier, criteriaMessageHash, signature);\n if (actionType == 1) {\n require(checkinHistory[user][val] == 0, \"TreasureHunt: already checked in\");\n checkinHistory[user][val] = 1;\n } else if (actionType == 2) {\n require(exploreHistory[user][val] == 0, \"TreasureHunt: already explored\");\n exploreHistory[user][val] = 1;\n } else if (actionType == 3) {\n require(enhanceHistory[user][val] == 0, \"TreasureHunt: already enhanced\");\n enhanceHistory[user][val] = 1;\n } else if (actionType == 4) {\n require(openBoxHistory[user][val] == 0, \"TreasureHunt: already opened\");\n openBoxHistory[user][val] = 1;\n } else if (actionType == 5) {\n require(claimTaskHistory[user][val] == 0, \"TreasureHunt: already claimed\");\n claimTaskHistory[user][val] = 1;\n } else {\n revert(\"TreasureHunt: invalid action type\");\n }\n emit ActionEvent(user, actionType, val);\n }\n\n function getMessageHash(\n address _user,\n uint256 _type,\n uint256 _val,\n address _contract,\n uint256 _chainId,\n uint256 _signTime,\n uint256 _saltNonce\n ) public pure returns (bytes32) {\n bytes memory encoded = abi.encodePacked(\n _user,\n _type,\n _val,\n _contract,\n _chainId,\n _signTime,\n _saltNonce\n );\n return keccak256(encoded);\n }\n}\n"
|
||||
"contracts/activity/NFTClaimStage2.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\n\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {HasSignature} from \"../core/HasSignature.sol\";\n\n/**\n * Contract for the activity of NFT claim stage 2.\n */\ninterface IClaimAbleNFT {\n function safeMint(address to, uint256 tokenID) external;\n}\n\ncontract NFTClaimStage2 is HasSignature, ReentrancyGuard {\n struct MintConfig {\n uint256 parse1MaxSupply; // max supply for phase1\n uint256 maxSupply; // max supply for phase2\n address currency; // token address which user must pay to mint\n uint256 mintPrice; // in wei\n address feeToAddress; // wallet address to receive mint fee\n }\n // parse: 0: not open or end, 1: phase1, 2: phase2\n uint256 public mintParse = 0;\n\n uint256 public immutable _CACHED_CHAIN_ID;\n address public immutable _CACHED_THIS;\n address public immutable nftAddress;\n\n address public verifier;\n MintConfig public mintConfig;\n uint256 public parse1Count;\n uint256 public totalCount;\n\n event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids);\n\n event ParseUpdated(uint256 _parse);\n event MintConfigUpdated(MintConfig config);\n event VerifierUpdated(address indexed verifier);\n\n constructor(address _nftAddress, address _verifier, MintConfig memory _mintConfig) {\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_THIS = address(this);\n nftAddress = _nftAddress;\n verifier = _verifier;\n mintConfig = _mintConfig;\n }\n\n modifier whenNotPaused() {\n require(mintParse > 0, \"NFTClaimer: not begin or ended\");\n _;\n }\n\n function updateMintParse(uint256 _mintParse) external onlyOwner {\n mintParse = _mintParse;\n emit ParseUpdated(_mintParse);\n }\n\n function updateMintConfig(MintConfig calldata config) external onlyOwner {\n mintConfig = config;\n emit MintConfigUpdated(config);\n }\n\n /**\n * @dev update verifier address\n */\n function updateVerifier(address _verifier) external onlyOwner {\n require(_verifier != address(0), \"NFTClaimer: address can not be zero\");\n verifier = _verifier;\n emit VerifierUpdated(_verifier);\n }\n\n /**\n * @dev claim NFT\n * Get whitelist signature from a third-party service, then call this method to claim NFT\n * @param saltNonce nonce\n * @param signature signature\n */\n function claim(\n uint256[] memory ids,\n uint256 tokenAmount,\n uint256 saltNonce,\n bytes calldata signature\n ) external nonReentrant whenNotPaused {\n // get current parse;\n uint256 count = ids.length;\n require(count > 0, \"NFTClaimer: ids length must be greater than 0\");\n if (mintParse == 1) {\n require(count <= mintConfig.parse1MaxSupply - parse1Count, \"NFTClaimer: exceed parse 1 max supply\");\n } else {\n require(count <= mintConfig.maxSupply - totalCount, \"NFTClaimer: exceed max supply\");\n }\n require(tokenAmount >= mintConfig.mintPrice * count, \"NFTClaimer: insufficient token amount\");\n address to = _msgSender();\n bytes32 criteriaMessageHash = getMessageHash(to, nftAddress, ids, tokenAmount, _CACHED_THIS, _CACHED_CHAIN_ID, saltNonce);\n checkSigner(verifier, criteriaMessageHash, signature);\n IERC20(mintConfig.currency).transferFrom(to, mintConfig.feeToAddress, tokenAmount);\n for (uint256 i = 0; i < count; ++i) {\n IClaimAbleNFT(nftAddress).safeMint(to, ids[i]);\n }\n // require(count > 2, \"run to here\");\n totalCount += count;\n if (mintParse == 1) {\n parse1Count += count;\n }\n _useSignature(signature);\n emit NFTClaimed(nftAddress, to, ids);\n }\n\n function getMessageHash(\n address _to,\n address _address,\n uint256[] memory _ids,\n uint256 _tokenAmount,\n address _contract,\n uint256 _chainId,\n uint256 _saltNonce\n ) public pure returns (bytes32) {\n bytes memory encoded = abi.encodePacked(_to, _address, _tokenAmount, _contract, _chainId, _saltNonce);\n for (uint256 i = 0; i < _ids.length; ++i) {\n encoded = bytes.concat(encoded, abi.encodePacked(_ids[i]));\n }\n return keccak256(encoded);\n }\n}\n"
|
||||
},
|
||||
"contracts/core/HasSignature.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.23;\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract HasSignature is Ownable {\n mapping(bytes => bool) private _usedSignatures;\n\n function checkSigner(\n address signer,\n bytes32 hash,\n bytes memory signature\n ) public pure {\n bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);\n\n address recovered = ECDSA.recover(ethSignedMessageHash, signature);\n require(recovered == signer, \"[BE] invalid signature\");\n }\n\n modifier signatureValid(bytes calldata signature) {\n require(\n !_usedSignatures[signature],\n \"[BE] signature used. please send another transaction with new signature\"\n );\n _;\n }\n\n function _useSignature(bytes calldata signature) internal {\n if (!_usedSignatures[signature]) {\n _usedSignatures[signature] = true;\n }\n }\n}\n"
|
||||
},
|
||||
"contracts/Lock.sol": {
|
||||
"content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.8.23;\n\n// Uncomment this line to use console.log\n// import \"hardhat/console.sol\";\n\ncontract Lock {\n uint public unlockTime;\n address payable public owner;\n\n event Withdrawal(uint amount, uint when);\n\n constructor(uint _unlockTime) payable {\n require(\n block.timestamp < _unlockTime,\n \"Unlock time should be in the future\"\n );\n\n unlockTime = _unlockTime;\n owner = payable(msg.sender);\n }\n\n function withdraw() public {\n // Uncomment this line, and the import of \"hardhat/console.sol\", to print a log in your terminal\n // console.log(\"Unlock time is %o and block timestamp is %o\", unlockTime, block.timestamp);\n\n require(block.timestamp >= unlockTime, \"You can't withdraw yet\");\n require(msg.sender == owner, \"You aren't the owner\");\n\n emit Withdrawal(address(this).balance, block.timestamp);\n\n owner.transfer(address(this).balance);\n }\n}\n"
|
||||
},
|
||||
"contracts/utils/TimeChecker.sol": {
|
||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.23;\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract TimeChecker is Ownable {\n uint256 public duration;\n uint256 public minDuration;\n\n event DurationUpdated(uint256 indexed duration);\n\n constructor() {\n duration = 1 days;\n minDuration = 30 minutes;\n }\n\n /**\n * @dev Check if the time is valid\n */\n modifier timeValid(uint256 time) {\n require(\n time + duration >= block.timestamp,\n \"expired, please send another transaction with new signature\"\n );\n _;\n }\n\n\n /**\n * @dev Change duration value\n */\n function updateDuation(uint256 valNew) external onlyOwner {\n require(valNew > minDuration, \"duration too short\");\n duration = valNew;\n emit DurationUpdated(valNew);\n }\n}\n"
|
||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract HasSignature is Ownable {\n mapping(bytes signature => bool status) private _usedSignatures;\n\n function checkSigner(\n address signer,\n bytes32 hash,\n bytes memory signature\n ) public pure {\n bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);\n\n address recovered = ECDSA.recover(ethSignedMessageHash, signature);\n require(recovered == signer, \"invalid signature\");\n }\n\n modifier signatureValid(bytes calldata signature) {\n require(\n !_usedSignatures[signature],\n \"signature used. please send another transaction with new signature\"\n );\n _;\n }\n\n function _useSignature(bytes calldata signature) internal {\n if (!_usedSignatures[signature]) {\n _usedSignatures[signature] = true;\n }\n }\n}\n"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@ -40,7 +37,7 @@
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"evmVersion": "paris",
|
||||
"viaIR": true,
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
@ -8,15 +8,6 @@ dotenv.config();
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
compilers: [
|
||||
{
|
||||
version: "0.8.23",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.8.19",
|
||||
settings: {
|
||||
@ -24,6 +15,7 @@ const config: HardhatUserConfig = {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
viaIR: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
20
out/imtbl_test_dev.json
Normal file
20
out/imtbl_test_dev.json
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"name": "CFHero",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/CFNFTGame.json",
|
||||
"address": "0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D"
|
||||
},
|
||||
{
|
||||
"name": "TestToken",
|
||||
"type": "erc20",
|
||||
"json": "assets/contracts/ImmutableERC20MinterBurnerPermit.json",
|
||||
"address": "0xFd42bfb03212dA7e1A4608a44d7658641D99CF34"
|
||||
},
|
||||
{
|
||||
"name": "NFTClaimStage2",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/NFTClaimStage2.json",
|
||||
"address": "0xf45702180314187a3549FEDac3B78349b47ca6A0"
|
||||
}
|
||||
]
|
@ -5,10 +5,12 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "hardhat test",
|
||||
"compile": "hardhat compile",
|
||||
"compile": "hardhat compile --show-stack-traces",
|
||||
"clean": "hardhat clean",
|
||||
"deploy": "hardhat deploy --network imtbl_test",
|
||||
"deploy:nftclaim": "hardhat deploy --tags NFTClaimStage2 --network imtbl_test --reset",
|
||||
"deploy:nft": "hardhat deploy --tags CFNFTGame --network imtbl_test --reset",
|
||||
"deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",
|
||||
"solhint": "solhint --config ./.solhint.json"
|
||||
},
|
||||
"author": "",
|
||||
@ -34,6 +36,7 @@
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ethers": "^6.12.1",
|
||||
"fs-jetpack": "^5.1.0",
|
||||
"hardhat": "^2.22.4",
|
||||
"hardhat-deploy": "^0.12.4",
|
||||
"hardhat-deploy-ethers": "^0.4.2",
|
||||
|
29
scripts/utils.ts
Normal file
29
scripts/utils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
import { read, write } from "fs-jetpack";
|
||||
|
||||
export const updateArray = ({ name, type, json, address, network }: { name: string, type: string, json: string, address: string, network: string }) => {
|
||||
let env = process.env.NODE_ENV || "dev";
|
||||
const filename = `./out/${network}_${env}.json`;
|
||||
let cfgs = read(filename, "json");
|
||||
cfgs = cfgs || [];
|
||||
if (cfgs.find((item: any) => item.name === name)) {
|
||||
cfgs.splice(
|
||||
cfgs.findIndex((item: any) => item.name === name),
|
||||
1,
|
||||
);
|
||||
}
|
||||
cfgs.push({
|
||||
name,
|
||||
type,
|
||||
json,
|
||||
address,
|
||||
});
|
||||
write(filename, cfgs);
|
||||
return cfgs;
|
||||
};
|
||||
|
||||
export const loadData = function ({ network }: { network: string }) {
|
||||
let env = process.env.NODE_ENV || "dev";
|
||||
const filename = `./out/${network}_${env}.json`;
|
||||
return read(filename, "json");
|
||||
};
|
260
test/testNFTClaim.ts
Normal file
260
test/testNFTClaim.ts
Normal file
@ -0,0 +1,260 @@
|
||||
import { expect } from 'chai'
|
||||
import hre from "hardhat";
|
||||
import {
|
||||
getBytes,
|
||||
solidityPackedKeccak256,
|
||||
} from 'ethers'
|
||||
import {
|
||||
loadFixture,
|
||||
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
|
||||
|
||||
describe('NFTClaimStage2', 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("NFTClaimStage2");
|
||||
const mintConfig = [
|
||||
1000,
|
||||
2000,
|
||||
ft.target,
|
||||
100,
|
||||
owner.address
|
||||
]
|
||||
const nftClaimer = await NFTClaimStage2.deploy( nftAddress, verifier, mintConfig);
|
||||
await nft.grantMinterRole(nftClaimer.target)
|
||||
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 } = await loadFixture(deployOneContract);
|
||||
expect((await nftClaimer.mintConfig()).mintPrice).to.equal(100);
|
||||
});
|
||||
it('should deploy NFTClaimStage2 with the correct verifier', async function() {
|
||||
const { nftClaimer, verifier } = await loadFixture(deployOneContract);
|
||||
expect(await nftClaimer.verifier()).to.equal(verifier);
|
||||
});
|
||||
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 = [
|
||||
1000,
|
||||
2000,
|
||||
'0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D',
|
||||
200,
|
||||
'0x5Ab03Aa79Ab91B7420b5CFF134a4188388888888'
|
||||
]
|
||||
await nftClaimer.updateMintConfig(mintConfig);
|
||||
expect((await nftClaimer.mintConfig()).mintPrice).to.equal(200);
|
||||
});
|
||||
it('should update verifier', async function() {
|
||||
const { nftClaimer, otherAccount } = await loadFixture(deployOneContract);
|
||||
await nftClaimer.updateVerifier(otherAccount.address);
|
||||
expect(await nftClaimer.verifier()).to.equal(otherAccount.address);
|
||||
});
|
||||
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);
|
||||
const price = 100
|
||||
//use ft.connect() to send a transaction from another account
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, price)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
|
||||
const contractMsgHash = await nftClaimer.getMessageHash(
|
||||
otherAccount.address, nft.target, ids, price, nftClaimer.target, chainId, nonce
|
||||
)
|
||||
expect(localMsgHash).equal(contractMsgHash);
|
||||
|
||||
expect(await nftClaimer.verifier()).equal(owner.address);
|
||||
expect(await nftClaimer.nftAddress()).equal(nft.target);
|
||||
const signature = await owner.signMessage(getBytes(contractMsgHash));
|
||||
// @ts-ignore
|
||||
await nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature);
|
||||
expect(await nft.balanceOf(otherAccount.address)).to.equal(1);
|
||||
expect(await nftClaimer.totalCount()).to.equal(1);
|
||||
});
|
||||
it('should claim NFTS', async function() {
|
||||
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
|
||||
await nftClaimer.updateMintParse(1);
|
||||
const price = 100
|
||||
const ids = ['1002', '1003', '1004']
|
||||
const tokenAmount = price * ids.length
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, tokenAmount)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, tokenAmount, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await nftClaimer.connect(otherAccount).claim(ids, tokenAmount, nonce, signature);
|
||||
expect(await nft.balanceOf(otherAccount.address)).to.equal(ids.length);
|
||||
expect(await ft.balanceOf(owner.address)).to.equal(tokenAmount)
|
||||
});
|
||||
it('should revert claim NFT if the signature error', 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)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
const nonce2 = (Math.random() * 1000) | 0;
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce2, signature)).to.be.revertedWith(
|
||||
"invalid signature"
|
||||
);
|
||||
});
|
||||
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 = [
|
||||
1000,
|
||||
2000,
|
||||
ft.target,
|
||||
price,
|
||||
owner.address
|
||||
]
|
||||
await nftClaimer.updateMintConfig(mintConfig);
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, price)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).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)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith(
|
||||
"ERC20: insufficient allowance"
|
||||
);
|
||||
});
|
||||
it('should revert claim NFT if the price not correct', async function() {
|
||||
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
|
||||
await nftClaimer.updateMintParse(1);
|
||||
const price = 99
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, price)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith(
|
||||
"NFTClaimer: insufficient token amount"
|
||||
);
|
||||
});
|
||||
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)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
const ids = ['1002']
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith(
|
||||
"NFTClaimer: not begin or ended"
|
||||
);
|
||||
});
|
||||
it('should revert claim NFT if the exceed parse1 max num', async function() {
|
||||
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
|
||||
await nftClaimer.updateMintParse(1);
|
||||
const price = 100
|
||||
const mintConfig = [
|
||||
1,
|
||||
2000,
|
||||
ft.target,
|
||||
price,
|
||||
owner.address
|
||||
]
|
||||
await nftClaimer.updateMintConfig(mintConfig);
|
||||
const ids = ['1002', '1003']
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, price * ids.length)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price * ids.length, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith(
|
||||
"NFTClaimer: exceed parse 1 max supply"
|
||||
);
|
||||
});
|
||||
it('should revert claim NFT if the exceed total num', async function() {
|
||||
const { nftClaimer, owner, otherAccount, ft, nft, chainId } = await loadFixture(deployOneContract);
|
||||
await nftClaimer.updateMintParse(2);
|
||||
const price = 100
|
||||
const mintConfig = [
|
||||
1000,
|
||||
1,
|
||||
ft.target,
|
||||
price,
|
||||
owner.address
|
||||
]
|
||||
await nftClaimer.updateMintConfig(mintConfig);
|
||||
const ids = ['1002', '1003']
|
||||
// @ts-ignore
|
||||
await ft.connect(otherAccount).approve(nftClaimer.target, price * ids.length)
|
||||
const nonce = (Math.random() * 1000) | 0;
|
||||
|
||||
let localMsgHash = solidityPackedKeccak256(["address", "address", "uint256", "address", "uint256", "uint256", ...ids.map(() => "uint256")],
|
||||
[otherAccount.address, nft.target, price * ids.length, nftClaimer.target, chainId, nonce, ...ids]);
|
||||
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||
// @ts-ignore
|
||||
await expect(nftClaimer.connect(otherAccount).claim(ids, price, nonce, signature)).to.be.revertedWith(
|
||||
"NFTClaimer: exceed max supply"
|
||||
);
|
||||
});
|
||||
});
|
||||
})
|
@ -2420,6 +2420,13 @@ fs-extra@^9.1.0:
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs-jetpack@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-jetpack/-/fs-jetpack-5.1.0.tgz#dcd34d709b69007c9dc2420a6f2b9e8f986cff0d"
|
||||
integrity sha512-Xn4fDhLydXkuzepZVsr02jakLlmoARPy+YWIclo4kh0GyNGUHnTqeH/w/qIsVn50dFxtp8otPL2t/HcPJBbxUA==
|
||||
dependencies:
|
||||
minimatch "^5.1.0"
|
||||
|
||||
fs-readdir-recursive@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
|
||||
@ -3228,7 +3235,7 @@ minimatch@5.0.1:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
minimatch@^5.0.1, minimatch@^5.1.0:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
|
Loading…
x
Reference in New Issue
Block a user