111 lines
3.1 KiB
Solidity
111 lines
3.1 KiB
Solidity
// 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);
|
|
}
|
|
}
|