// 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 TokenMall 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; mapping(address currency => bool status) public currencySupported; mapping(uint256 code => uint256 count) public codeUsed; event EventERC20Wallet(address erc20, address wallet); event EventVerifierUpdated(address indexed verifier); event EventCurrencySupportUpdated(address indexed currency, bool status); event EventTokenBuy(address indexed user, address indexed token, address currency,uint256 amount, uint256 currencyAmount, uint256 code, 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), "TokenClaim: address can not be zero"); verifier = _verifier; emit EventVerifierUpdated(_verifier); } function updateCurrencySupport(address currency, bool status) external onlyOwner { require(currency != address(0), "currency address can not be zero"); currencySupported[currency] = status; emit EventCurrencySupportUpdated(currency, status); } /** * @dev update pause state */ function pause() external onlyOwner { _pause(); } /** * @dev update unpause state */ function unpause() external onlyOwner { _unpause(); } function withdrawToken(address erc20, address to, uint256 amount) external onlyOwner { IERC20(erc20).safeTransfer(to, amount); } /** * @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 buy Token with signature * @param token address of token * @param currency address of currency * @param vals amount, currencyAmount, code, maxUseCount, signTime, nonce * @param signature signature of buy */ function buy( address token, address currency, uint256[6] calldata vals, bytes calldata signature ) external signatureValid(signature) timeValid(vals[4]) nonReentrant whenNotPaused { require(erc20Wallets[token] != address(0), "TokenClaim: token is not supported"); require(vals[0] > 0, "TokenClaim: amount is zero"); require(currencySupported[currency], "TokenClaim: currency is not supported"); require(codeUsed[vals[2]] <= vals[3], "TokenClaim: code is used"); address user = _msgSender(); bytes32 criteriaMessageHash = getMessageHash(user, token, currency, _CACHED_THIS, _CACHED_CHAIN_ID, vals); checkSigner(verifier, criteriaMessageHash, signature); _useSignature(signature); codeUsed[vals[2]] += 1; IERC20(currency).safeTransferFrom(user, _CACHED_THIS, vals[1]); IERC20(token).safeTransferFrom(erc20Wallets[token], user, vals[0]); emit EventTokenBuy(user, token, currency, vals[0], vals[1], vals[2], vals[5]); } function getMessageHash( address _user, address _token, address _currency, address _contract, uint256 _chainId, uint256[6] calldata _vals ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked(_user, _token, _currency, _contract, _chainId); for (uint256 i = 0; i < _vals.length; i++) { encoded = bytes.concat(encoded, abi.encodePacked(_vals[i])); } return keccak256(encoded); } }