// 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/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract TokenStaking is Ownable, ReentrancyGuard, Pausable { using SafeERC20 for IERC20; uint256 public cooldownSeconds = 1 days; uint256 orderIndex; struct Staker { address user; address token; uint256 amount; uint64 start; uint64 stakeTime; } mapping(uint256 => Staker) public stakingMap; // User-selectable stake time, in seconds mapping(uint64 => bool) public periods; // User-selectable stake token mapping(address => bool) public erc20Supported; event Staked(address indexed user, Staker info); event Redeem(address indexed user, Staker[] infos); event EditTokenSuppout(address token, bool status); constructor() {} function stake( address token, uint256 amount, uint64 staketime ) external nonReentrant whenNotPaused { require(amount > 0, "TokenStaking: amount is 0"); require( periods[staketime] == true, "TokenStaking: staketime is not supported" ); require( erc20Supported[token] == true, "TokenStaking: token is not supported" ); address to = msg.sender; require( IERC20(token).balanceOf(_msgSender()) >= amount, "TokenStaking: user doesn't have enough token to buy this item" ); require( IERC20(token).allowance(_msgSender(), address(this)) >= amount, "TokenStaking: user doesn't approve TokenStaking to spend amount" ); uint64 _begin = uint64(block.timestamp); uint64 _staketime = _begin + staketime; orderIndex += 1; stakingMap[orderIndex] = Staker(to, token, amount, _begin, _staketime); IERC20(token).safeTransferFrom(to, address(this), amount); emit Staked(to, stakingMap[orderIndex]); } function redeem( address[] calldata tokens, uint256[] calldata ids ) external nonReentrant { require( tokens.length == ids.length, "TokenStaking: tokens length != ids length" ); address to = msg.sender; uint256 amount = 0; // check if ids are valid Staker[] memory _infos = new Staker[](ids.length); for (uint256 i = 0; i < ids.length; i++) { require(stakingMap[ids[i]].amount > 0, "TokenStaking: id is not valid"); require( stakingMap[ids[i]].start + cooldownSeconds <= block.timestamp, "TokenStaking: cooldown time is not reached" ); amount += stakingMap[ids[i]].amount; _infos[i] = stakingMap[ids[i]]; IERC20(stakingMap[ids[i]].token).safeTransfer(to, amount); delete stakingMap[ids[i]]; } emit Redeem(to, _infos); } function updatePeriods(uint64 period, bool status) external onlyOwner { periods[period] = status; } /** * @dev update ERC721 support */ function updateERC20Support(address token, bool status) external onlyOwner { erc20Supported[token] = status; emit EditTokenSuppout(token, status); } }