// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/governance/TimelockController.sol"; import "../core/HasSignature.sol"; contract BEBoxMall is Ownable, HasSignature, TimelockController { using SafeERC20 for IERC20; using Address for address; uint256 public constant MIN_DELAY = 2 days; uint256 public constant MAX_DELAY = 16 days; uint256 private _minDelay; bool public address_initialized; constructor(address[] memory proposers, address[] memory executors) TimelockController(MIN_DELAY, proposers, executors) HasSignature("BEBoxMall", "1") { _minDelay = MIN_DELAY; address_initialized = false; } event BEBoxPaid( uint256 indexed boxId, address indexed buyer, uint256 boxType, uint256 price, address paymentToken ); address public paymentReceivedAddress; function setPaymentReceivedAddress(address _paymentReceivedAddress) public { require( _paymentReceivedAddress != address(0), "BEBoxMall::setPaymentReceivedAddress: payment received address can not be zero" ); if (address_initialized) { require( msg.sender == address(this), "BEBoxMall::setPaymentReceivedAddress: Call must come from BEBoxMall." ); } else { require( msg.sender == owner(), "BEBoxMall::setPaymentReceivedAddress: First call must come from owner." ); address_initialized = true; } paymentReceivedAddress = _paymentReceivedAddress; } /** * @dev BE box payment function */ function payForBoxWithSignature( uint256 boxId, uint256 _type, address userAddress, uint256 price, address paymentErc20, uint256 saltNonce, bytes calldata signature ) external onlyOwner signatureValid(signature) { require( !userAddress.isContract(), "BEBoxPayment: Only user address is allowed to buy box" ); require(_type > 0, "BEBoxPayment: Invalid box type"); require(price > 0, "BEBoxPayment: Invalid payment amount"); bytes32 criteriaMessageHash = getMessageHash( _type, paymentErc20, price, saltNonce ); checkSigner712(userAddress, criteriaMessageHash, signature); IERC20 paymentToken = IERC20(paymentErc20); uint256 allowToPayAmount = paymentToken.allowance( userAddress, address(this) ); require(allowToPayAmount >= price, "BEBoxPayment: Invalid token allowance"); // Transfer payment paymentToken.safeTransferFrom(userAddress, paymentReceivedAddress, price); _useSignature(signature); // Emit payment event emit BEBoxPaid(boxId, userAddress, _type, price, paymentErc20); } function getMessageHash( uint256 _boxType, address _paymentErc20, uint256 _price, uint256 _saltNonce ) public pure returns (bytes32) { return keccak256( abi.encode( keccak256( "set(uint256 item,address token,uint256 price,uint256 salt)" ), _boxType, _paymentErc20, _price, _saltNonce ) ); } /** * @dev Returns the minimum delay for an operation to become valid. * * This value can be changed by executing an operation that calls `updateDelay`. */ function getMinDelay() public view virtual override returns (uint256 duration) { return _minDelay; } /** * @dev Changes the minimum timelock duration for future operations. * * Emits a {MinDelayChange} event. * * Requirements: * * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing * an operation where the timelock is the target and the data is the ABI-encoded call to this function. */ function updateDelay(uint256 newDelay) external virtual override { require(msg.sender == address(this), "BEBoxMall: caller must be timelock"); require( newDelay >= MIN_DELAY, "BEBoxMall: newDelay must greater than or equal to MIN_DELAY" ); require( newDelay <= MAX_DELAY, "BEBoxMall: newDelay must less than or equal to MAX_DELAY" ); emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; } }