2024-08-22 15:18:47 +08:00

122 lines
3.8 KiB
Solidity

// 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 status
mapping(address user => mapping(address token => uint256 claimedBit)) public claimedBitMap;
event EventERC20Wallet(address erc20, address wallet);
event EventVerifierUpdated(address indexed verifier);
event EventTokenClaimed(address indexed user, address indexed token, address account, uint256 amount, uint256 bit);
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), "TokenClaim: 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, "TokenClaim: ERC20 wallet not changed");
erc20Wallets[erc20] = wallet;
emit EventERC20Wallet(erc20, wallet);
}
/**
* @dev claim CEC with signature
* @param account address which eligible to claim, only for event log
* @param token address of token
* @param vals array of amount, bit, signTime, saltNonce
* @param signature signature of claim
*/
function claim(
address account,
address token,
uint256[4] calldata vals, // amount, bit, signTime, saltNonce
bytes calldata signature
) external signatureValid(signature) timeValid(vals[2]) nonReentrant whenNotPaused {
require(erc20Wallets[token] != address(0), "TokenClaim: token is not supported");
require(vals[0] > 0, "TokenClaim: amount is zero");
uint256 current = claimedBitMap[account][token];
require(current & vals[1] == 0, "TokenClaim: condition check failed");
address user = _msgSender();
bytes32 criteriaMessageHash = getMessageHash(
user,
account,
token,
_CACHED_THIS,
_CACHED_CHAIN_ID,
vals
);
checkSigner(verifier, criteriaMessageHash, signature);
_useSignature(signature);
claimedBitMap[account][token] = current | vals[1];
IERC20(token).safeTransferFrom(erc20Wallets[token], user, vals[0]);
emit EventTokenClaimed(user, token, account, vals[0], vals[1]);
}
function getMessageHash(
address _user,
address _account,
address _token,
address _contract,
uint256 _chainId,
uint256[4] calldata _vals
) public pure returns (bytes32) {
bytes memory encoded = abi.encodePacked(
_user,
_account,
_token,
_contract,
_chainId
);
for (uint256 i = 0; i < _vals.length; i++) {
encoded = bytes.concat(encoded, abi.encodePacked(_vals[i]));
}
return keccak256(encoded);
}
}