增加erc20空投合约
This commit is contained in:
parent
77b430fce1
commit
a8adc5929e
128
contracts/activity/TokenClaim.sol
Normal file
128
contracts/activity/TokenClaim.sol
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||||
|
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
|
||||||
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import {HasSignature} from "../core/HasSignature.sol";
|
||||||
|
import {TimeChecker} from "../utils/TimeChecker.sol";
|
||||||
|
|
||||||
|
contract TokenClaim is HasSignature, ReentrancyGuard, Pausable, TimeChecker {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
uint256 public immutable _CACHED_CHAIN_ID;
|
||||||
|
address public immutable _CACHED_THIS;
|
||||||
|
address public verifier;
|
||||||
|
|
||||||
|
mapping(address token => address wallet) public erc20Wallets;
|
||||||
|
|
||||||
|
// store user's claimed amount
|
||||||
|
mapping(address user => mapping(address token => uint256 amount)) public claimedAmount;
|
||||||
|
|
||||||
|
event EventERC20Wallet(address erc20, address wallet);
|
||||||
|
|
||||||
|
event EventVerifierUpdated(address indexed verifier);
|
||||||
|
event EventTokenClaimed(
|
||||||
|
address indexed user,
|
||||||
|
address indexed token,
|
||||||
|
address passport,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 nonce
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(address _wallet, address _token, address _verifier, uint256 _duration) TimeChecker(_duration) {
|
||||||
|
_CACHED_CHAIN_ID = block.chainid;
|
||||||
|
_CACHED_THIS = address(this);
|
||||||
|
erc20Wallets[_token] = _wallet;
|
||||||
|
verifier = _verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev update verifier address
|
||||||
|
*/
|
||||||
|
function updateVerifier(address _verifier) external onlyOwner {
|
||||||
|
require(_verifier != address(0), "TokenClaimer: address can not be zero");
|
||||||
|
verifier = _verifier;
|
||||||
|
emit EventVerifierUpdated(_verifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev update pause state
|
||||||
|
*/
|
||||||
|
function pause() external onlyOwner {
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev update unpause state
|
||||||
|
*/
|
||||||
|
function unpause() external onlyOwner {
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev update ERC20 wallet
|
||||||
|
*/
|
||||||
|
function updateERC20Wallet(address erc20, address wallet) external onlyOwner {
|
||||||
|
require(erc20Wallets[erc20] != wallet, "TokenClaimer: ERC20 wallet not changed");
|
||||||
|
erc20Wallets[erc20] = wallet;
|
||||||
|
emit EventERC20Wallet(erc20, wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev claim CEC with signature
|
||||||
|
*/
|
||||||
|
|
||||||
|
function claim(
|
||||||
|
address passport,
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 signTime,
|
||||||
|
uint256 saltNonce,
|
||||||
|
bytes calldata signature
|
||||||
|
) external signatureValid(signature) timeValid(signTime) nonReentrant whenNotPaused {
|
||||||
|
require(passport != address(0), "TokenClaimer: passport address can not be zero");
|
||||||
|
require(erc20Wallets[token] != address(0), "TokenClaimer: token is not supported");
|
||||||
|
require(amount > 0, "TokenClaimer: amount is zero");
|
||||||
|
address user = _msgSender();
|
||||||
|
bytes32 criteriaMessageHash = getMessageHash(
|
||||||
|
user,
|
||||||
|
passport,
|
||||||
|
token,
|
||||||
|
amount,
|
||||||
|
_CACHED_THIS,
|
||||||
|
_CACHED_CHAIN_ID,
|
||||||
|
signTime,
|
||||||
|
saltNonce
|
||||||
|
);
|
||||||
|
checkSigner(verifier, criteriaMessageHash, signature);
|
||||||
|
_useSignature(signature);
|
||||||
|
claimedAmount[user][token] += amount;
|
||||||
|
IERC20(token).safeTransferFrom(erc20Wallets[token], user, amount);
|
||||||
|
emit EventTokenClaimed(user, token, passport, amount, saltNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageHash(
|
||||||
|
address _user,
|
||||||
|
address _passport,
|
||||||
|
address _token,
|
||||||
|
uint256 _amount,
|
||||||
|
address _contract,
|
||||||
|
uint256 _chainId,
|
||||||
|
uint256 _signTime,
|
||||||
|
uint256 _saltNonce
|
||||||
|
) public pure returns (bytes32) {
|
||||||
|
bytes memory encoded = abi.encodePacked(
|
||||||
|
_user,
|
||||||
|
_passport,
|
||||||
|
_token,
|
||||||
|
_amount,
|
||||||
|
_contract,
|
||||||
|
_chainId,
|
||||||
|
_signTime,
|
||||||
|
_saltNonce
|
||||||
|
);
|
||||||
|
return keccak256(encoded);
|
||||||
|
}
|
||||||
|
}
|
29
deploy/7_deploy_tokenclaim.ts
Normal file
29
deploy/7_deploy_tokenclaim.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 { mallFeeAddress, paymentTokens, verifier } = config.market
|
||||||
|
const ret = await hre.deployments.deploy("TokenClaim", {
|
||||||
|
from,
|
||||||
|
args: [mallFeeAddress, paymentTokens[0], verifier, 3600],
|
||||||
|
log: true,
|
||||||
|
});
|
||||||
|
console.log("==TokenClaim addr=", ret.address);
|
||||||
|
updateArray({
|
||||||
|
name: "TokenClaim",
|
||||||
|
type: "logic",
|
||||||
|
json: "assets/contracts/TokenClaim.json",
|
||||||
|
address: ret.address,
|
||||||
|
network: hre.network.name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deployNFTClaim.tags = ["TokenClaim"];
|
||||||
|
|
||||||
|
export default deployNFTClaim;
|
File diff suppressed because one or more lines are too long
702
deployments/bsc_test/TokenClaim.json
Normal file
702
deployments/bsc_test/TokenClaim.json
Normal file
File diff suppressed because one or more lines are too long
@ -4,6 +4,9 @@
|
|||||||
"@openzeppelin/contracts/access/Ownable.sol": {
|
"@openzeppelin/contracts/access/Ownable.sol": {
|
||||||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
|
||||||
},
|
},
|
||||||
|
"@openzeppelin/contracts/security/Pausable.sol": {
|
||||||
|
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"
|
||||||
|
},
|
||||||
"@openzeppelin/contracts/security/ReentrancyGuard.sol": {
|
"@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"
|
"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"
|
||||||
},
|
},
|
||||||
@ -34,15 +37,12 @@
|
|||||||
"@openzeppelin/contracts/utils/Strings.sol": {
|
"@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"
|
"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/TokenClaim.sol": {
|
||||||
|
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\n\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport {Pausable} from \"@openzeppelin/contracts/security/Pausable.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {HasSignature} from \"../core/HasSignature.sol\";\nimport {TimeChecker} from \"../utils/TimeChecker.sol\";\n\ncontract TokenClaim is HasSignature, ReentrancyGuard, Pausable, TimeChecker {\n using SafeERC20 for IERC20;\n\n uint256 public immutable _CACHED_CHAIN_ID;\n address public immutable _CACHED_THIS;\n address public verifier;\n\n mapping(address token => address wallet) public erc20Wallets;\n \n // store user's claimed amount\n mapping(address user => mapping(address token => uint256 amount)) public claimedAmount;\n\n event EventERC20Wallet(address erc20, address wallet);\n \n event EventVerifierUpdated(address indexed verifier);\n event EventTokenClaimed(\n address indexed user, \n address indexed token, \n address passport, \n uint256 amount, \n uint256 nonce\n );\n\n constructor(address _wallet, address _token, address _verifier, uint256 _duration) TimeChecker(_duration) {\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_THIS = address(this);\n erc20Wallets[_token] = _wallet;\n verifier = _verifier;\n }\n\n /**\n * @dev update verifier address\n */\n function updateVerifier(address _verifier) external onlyOwner {\n require(_verifier != address(0), \"TokenClaimer: address can not be zero\");\n verifier = _verifier;\n emit EventVerifierUpdated(_verifier);\n }\n\n /**\n * @dev update pause state\n */\n function pause() external onlyOwner {\n _pause();\n }\n\n /**\n * @dev update unpause state\n */\n function unpause() external onlyOwner {\n _unpause();\n }\n\n /**\n * @dev update ERC20 wallet\n */\n function updateERC20Wallet(address erc20, address wallet) external onlyOwner {\n require(erc20Wallets[erc20] != wallet, \"TokenClaimer: ERC20 wallet not changed\");\n erc20Wallets[erc20] = wallet;\n emit EventERC20Wallet(erc20, wallet);\n }\n\n /**\n * @dev claim CEC with signature\n */\n\n function claim(\n address passport,\n address token,\n uint256 amount,\n uint256 signTime,\n uint256 saltNonce,\n bytes calldata signature\n ) external signatureValid(signature) timeValid(signTime) nonReentrant whenNotPaused {\n require(passport != address(0), \"TokenClaimer: passport address can not be zero\");\n require(erc20Wallets[token] != address(0), \"TokenClaimer: token is not supported\");\n require(amount > 0, \"TokenClaimer: amount is zero\");\n address user = _msgSender();\n bytes32 criteriaMessageHash = getMessageHash(\n user,\n passport,\n token,\n amount,\n _CACHED_THIS,\n _CACHED_CHAIN_ID,\n signTime,\n saltNonce\n );\n checkSigner(verifier, criteriaMessageHash, signature);\n _useSignature(signature);\n claimedAmount[user][token] += amount;\n IERC20(token).safeTransferFrom(erc20Wallets[token], user, amount);\n emit EventTokenClaimed(user, token, passport, amount, saltNonce);\n }\n\n function getMessageHash(\n address _user,\n address _passport,\n address _token,\n uint256 _amount,\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 _passport,\n _token,\n _amount,\n _contract,\n _chainId,\n _signTime,\n _saltNonce\n );\n return keccak256(encoded);\n }\n}"
|
||||||
|
},
|
||||||
"contracts/core/HasSignature.sol": {
|
"contracts/core/HasSignature.sol": {
|
||||||
"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"
|
"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"
|
||||||
},
|
},
|
||||||
"contracts/mall/GameItemMall.sol": {
|
|
||||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport {HasSignature} from \"../core/HasSignature.sol\";\nimport {TimeChecker} from \"../utils/TimeChecker.sol\";\nimport {MallBase} from \"./MallBase.sol\";\n\n/**\n * @title GameItemMall\n * @dev GameItemMall is a contract for managing centralized game items sale,\n * allowing users to buy item in game.\n */\ncontract GameItemMall is MallBase, ReentrancyGuard, HasSignature, TimeChecker {\n using SafeERC20 for IERC20;\n\n mapping(uint256 itemId => address user) public orderIdUsed;\n\n event ItemSoldOut(\n address indexed buyer,\n address indexed passport,\n uint256 indexed orderId,\n address currency,\n uint256 amount\n );\n\n constructor(address _currency, address _feeToAddress, address _verifier, uint256 _duration) \n TimeChecker(_duration)\n MallBase(_currency, _feeToAddress, _verifier){\n }\n\n function buy(\n address passport,\n uint256 orderId,\n address currency,\n uint256 amount,\n uint256 signTime,\n uint256 saltNonce,\n bytes calldata signature\n ) external nonReentrant signatureValid(signature) timeValid(signTime) {\n require(passport != address(0), \"passport address can not be zero\");\n // check if orderId is used\n require(orderIdUsed[orderId] == address(0), \"orderId is used\");\n // check if currency is supported\n require(erc20Supported[currency], \"currency is not supported\");\n // check if amount is valid\n require(amount > 0, \"amount is zero\");\n address buyer = _msgSender();\n bytes32 criteriaMessageHash = getMessageHash(\n buyer,\n passport,\n orderId,\n currency,\n amount,\n _CACHED_THIS,\n _CACHED_CHAIN_ID,\n signTime,\n saltNonce\n );\n checkSigner(verifier, criteriaMessageHash, signature);\n IERC20 paymentContract = IERC20(currency);\n _useSignature(signature);\n orderIdUsed[orderId] = buyer;\n paymentContract.safeTransferFrom(buyer, feeToAddress, amount);\n emit ItemSoldOut(buyer, passport, orderId, currency, amount);\n }\n\n function getMessageHash(\n address _buyer,\n address _passport,\n uint256 _orderId,\n address _currency,\n uint256 _amount,\n address _contract,\n uint256 _chainId,\n uint256 _signTime,\n uint256 _saltNonce\n ) public pure returns (bytes32) {\n bytes memory encoded = abi.encodePacked(\n _buyer,\n _passport,\n _orderId,\n _currency,\n _amount,\n _contract,\n _chainId,\n _signTime,\n _saltNonce\n );\n return keccak256(encoded);\n }\n}"
|
|
||||||
},
|
|
||||||
"contracts/mall/MallBase.sol": {
|
|
||||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\n\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\n\nabstract contract MallBase is Ownable {\n address public verifier;\n // Address to receive transaction fee\n address public feeToAddress;\n\n uint256 public immutable _CACHED_CHAIN_ID;\n address public immutable _CACHED_THIS;\n\n mapping(address token => bool status) public erc20Supported;\n event AddERC20Suppout(address erc20);\n event RemoveERC20Suppout(address erc20);\n event VerifierUpdated(address indexed verifier);\n event FeeToAddressUpdated(address indexed feeToAddress);\n\n constructor(address _currency, address _feeToAddress, address _verifier) {\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_THIS = address(this);\n verifier = _verifier;\n erc20Supported[_currency] = true;\n feeToAddress = _feeToAddress;\n }\n\n function addERC20Support(address erc20) external onlyOwner {\n require(erc20 != address(0), \"ERC20 address can not be zero\");\n erc20Supported[erc20] = true;\n emit AddERC20Suppout(erc20);\n }\n\n function removeERC20Support(address erc20) external onlyOwner {\n erc20Supported[erc20] = false;\n emit RemoveERC20Suppout(erc20);\n }\n\n /**\n * @dev update verifier address\n */\n function updateVerifier(address _verifier) external onlyOwner {\n require(_verifier != address(0), \"address can not be zero\");\n verifier = _verifier;\n emit VerifierUpdated(_verifier);\n }\n\n function setFeeToAddress(address _feeToAddress) external onlyOwner {\n require(\n _feeToAddress != address(0),\n \"fee received address can not be zero\"\n );\n feeToAddress = _feeToAddress;\n emit FeeToAddressUpdated(_feeToAddress);\n }\n}\n"
|
|
||||||
},
|
|
||||||
"contracts/utils/TimeChecker.sol": {
|
"contracts/utils/TimeChecker.sol": {
|
||||||
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.19;\nimport {Ownable} from \"@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(uint256 _duration) {\n duration = _duration;\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 {Ownable} from \"@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(uint256 _duration) {\n duration = _duration;\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"
|
||||||
}
|
}
|
@ -65,6 +65,24 @@ const config: HardhatUserConfig = {
|
|||||||
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
|
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sourcify: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
etherscan: {
|
||||||
|
apiKey: {
|
||||||
|
bsc_test: "BUWD4T1ENMK9JUTNVQD4YBDMNRNINEWSUN"
|
||||||
|
},
|
||||||
|
customChains: [
|
||||||
|
{
|
||||||
|
network: "bsc_test",
|
||||||
|
chainId: 97,
|
||||||
|
urls: {
|
||||||
|
apiURL: "https://api-testnet.bscscan.com/api",
|
||||||
|
browserURL: "https://testnet.bscscan.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -16,5 +16,11 @@
|
|||||||
"type": "logic",
|
"type": "logic",
|
||||||
"json": "assets/contracts/GameItemMall.json",
|
"json": "assets/contracts/GameItemMall.json",
|
||||||
"address": "0xaE08adb5278B107D2501e7c61907e41FEf3887D7"
|
"address": "0xaE08adb5278B107D2501e7c61907e41FEf3887D7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TokenClaim",
|
||||||
|
"type": "logic",
|
||||||
|
"json": "assets/contracts/TokenClaim.json",
|
||||||
|
"address": "0xc058411B15E544291765F15B13c88582b7bceaD0"
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -16,8 +16,11 @@
|
|||||||
"deploy:nftlock:main": "hardhat deploy --tags NFTLockMain --network sepolia_test --reset",
|
"deploy:nftlock:main": "hardhat deploy --tags NFTLockMain --network sepolia_test --reset",
|
||||||
"deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",
|
"deploy:testtoken": "hardhat deploy --tags TestToken --network imtbl_test --reset",
|
||||||
"deploy:airdrop": "hardhat deploy --tags AirdropToken --network imtbl_test --reset",
|
"deploy:airdrop": "hardhat deploy --tags AirdropToken --network imtbl_test --reset",
|
||||||
|
"deploy:tokenclaim": "hardhat deploy --tags TokenClaim --network bsc_test --reset",
|
||||||
"deploy:gameitemmall": "hardhat deploy --tags GameItemMall --network imtbl_test --reset",
|
"deploy:gameitemmall": "hardhat deploy --tags GameItemMall --network imtbl_test --reset",
|
||||||
"solhint": "solhint --config ./.solhint.json"
|
"solhint": "solhint --config ./.solhint.json",
|
||||||
|
"show_verify_list": "npx hardhat verify --list-networks",
|
||||||
|
"verify_sample": "npx hardhat verify --network bsc_test --constructor-args ./verify/tokenclaim.js 0xee0044BF2ACEf7C3D7f6781d8f5DC4d2Dd1CE64c"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
105
test/testTokenClaim.ts
Normal file
105
test/testTokenClaim.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import hre from "hardhat";
|
||||||
|
import {
|
||||||
|
getBytes,
|
||||||
|
solidityPackedKeccak256,
|
||||||
|
} from 'ethers'
|
||||||
|
import {
|
||||||
|
loadFixture,
|
||||||
|
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
|
||||||
|
|
||||||
|
describe('TokenClaim', 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 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);
|
||||||
|
const amount = '10000000000000000000000';
|
||||||
|
await ft.mint(owner.address, amount);
|
||||||
|
const TokenClaim = await hre.ethers.getContractFactory("TokenClaim");
|
||||||
|
|
||||||
|
|
||||||
|
const claimer = await TokenClaim.deploy( verifier, ft.target, verifier, 3600);
|
||||||
|
// @ts-ignore
|
||||||
|
await ft.connect(owner).approve(claimer.target, amount);
|
||||||
|
const chainId = hre.network.config.chainId
|
||||||
|
await operatorAllowlist.grantRegistrarRole(owner.address)
|
||||||
|
await operatorAllowlist.addAddressToAllowlist([claimer.target])
|
||||||
|
return { claimer, owner, otherAccount, verifier, ft, chainId };
|
||||||
|
}
|
||||||
|
describe("Deployment", function () {
|
||||||
|
it('should deploy TokenClaim with the correct verifier', async function() {
|
||||||
|
const { claimer, verifier } = await loadFixture(deployOneContract);
|
||||||
|
expect(await claimer.verifier()).to.equal(verifier);
|
||||||
|
});
|
||||||
|
it('should deploy TokenClaim with the correct FT address', async function() {
|
||||||
|
const { claimer, ft, owner } = await loadFixture(deployOneContract);
|
||||||
|
expect(await claimer.erc20Wallets(ft.target)).to.equal(owner.address);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Claim", function () {
|
||||||
|
it('should claim token success', async function() {
|
||||||
|
const { claimer, ft, owner, chainId, otherAccount } = await loadFixture(deployOneContract);
|
||||||
|
const amount = 100;
|
||||||
|
// @ts-ignore
|
||||||
|
// await ft.connect(owner).approve(claimer.target, '10000000000000000000000');
|
||||||
|
const nonce = (Math.random() * 1000) | 0;
|
||||||
|
const now = (Date.now() / 1000) | 0;
|
||||||
|
let localMsgHash = solidityPackedKeccak256(["address","address", "address", "uint256", "address", "uint256", "uint256", "uint256"],
|
||||||
|
[otherAccount.address, otherAccount.address, ft.target, amount, claimer.target, chainId, now, nonce]);
|
||||||
|
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||||
|
// @ts-ignore
|
||||||
|
await expect(claimer.connect(otherAccount).claim(otherAccount.address, ft.target, amount, now, nonce, signature))
|
||||||
|
.to.emit(claimer, "EventTokenClaimed")
|
||||||
|
.withArgs(otherAccount.address, ft.target, otherAccount.address, amount, nonce);
|
||||||
|
expect(await ft.balanceOf(otherAccount.address)).to.equal(amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should revert claim token for signature used', async function() {
|
||||||
|
const { claimer, ft, owner, chainId, otherAccount } = await loadFixture(deployOneContract);
|
||||||
|
const amount = 100;
|
||||||
|
const nonce = (Math.random() * 1000) | 0;
|
||||||
|
const now = (Date.now() / 1000) | 0;
|
||||||
|
let localMsgHash = solidityPackedKeccak256(["address","address", "address", "uint256", "address", "uint256", "uint256", "uint256"],
|
||||||
|
[otherAccount.address, otherAccount.address, ft.target, amount, claimer.target, chainId, now, nonce]);
|
||||||
|
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||||
|
//@ts-ignore
|
||||||
|
await claimer.connect(otherAccount).claim(otherAccount.address, ft.target, amount, now, nonce, signature)
|
||||||
|
// @ts-ignore
|
||||||
|
await expect(claimer.connect(otherAccount).claim(otherAccount.address, ft.target, amount, now, nonce, signature)).to.be.revertedWith("signature used. please send another transaction with new signature");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should revert claim token for timeout', async function() {
|
||||||
|
const { claimer, ft, owner, chainId, otherAccount } = await loadFixture(deployOneContract);
|
||||||
|
const amount = 100;
|
||||||
|
// @ts-ignore
|
||||||
|
// await ft.connect(owner).approve(claimer.target, '10000000000000000000000');
|
||||||
|
const nonce = (Math.random() * 1000) | 0;
|
||||||
|
const now = (Date.now() / 1000 - 3601) | 0;
|
||||||
|
let localMsgHash = solidityPackedKeccak256(["address","address", "address", "uint256", "address", "uint256", "uint256", "uint256"],
|
||||||
|
[otherAccount.address, otherAccount.address, ft.target, amount, claimer.target, chainId, now, nonce]);
|
||||||
|
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||||
|
// @ts-ignore
|
||||||
|
await expect(claimer.connect(otherAccount).claim(otherAccount.address, ft.target, amount, now, nonce, signature)).to.be.revertedWith("expired, please send another transaction with new signature");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should revert claim token for token not support', async function() {
|
||||||
|
const { claimer, ft, owner, chainId, otherAccount } = await loadFixture(deployOneContract);
|
||||||
|
await claimer.updateERC20Wallet(ft.target, '0x0000000000000000000000000000000000000000');
|
||||||
|
const amount = 100;
|
||||||
|
const nonce = (Math.random() * 1000) | 0;
|
||||||
|
const now = (Date.now() / 1000) | 0;
|
||||||
|
let localMsgHash = solidityPackedKeccak256(["address","address", "address", "uint256", "address", "uint256", "uint256", "uint256"],
|
||||||
|
[otherAccount.address, otherAccount.address, ft.target, amount, claimer.target, chainId, now, nonce]);
|
||||||
|
const signature = await owner.signMessage(getBytes(localMsgHash));
|
||||||
|
// @ts-ignore
|
||||||
|
await expect(claimer.connect(otherAccount).claim(otherAccount.address, ft.target, amount, now, nonce, signature)).to.be.revertedWith("TokenClaimer: token is not supported");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
6
verify/tokenclaim.js
Normal file
6
verify/tokenclaim.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = [
|
||||||
|
'0x50A8e60041A206AcaA5F844a1104896224be6F39',
|
||||||
|
'0x8f34a7b59841bc87f7d80f9858490cc1412d50fb',
|
||||||
|
'0x50A8e60041A206AcaA5F844a1104896224be6F39',
|
||||||
|
3600
|
||||||
|
];
|
Loading…
x
Reference in New Issue
Block a user