编译器
0.8.19+commit.7dd6d404
文件 1 的 5:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 2 的 5:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
文件 3 的 5:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 4 的 5:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
}
function _nonReentrantAfter() private {
_status = _NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
文件 5 的 5:WaitStaking.sol
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract WaitStaking is Ownable, ReentrancyGuard {
struct StakingPlan {
uint256 multiplier;
uint256 duration;
}
struct StakingInfo {
uint256 amount;
uint256 planId;
uint256 startTime;
uint256 accumulatedReward;
}
event Staked(address indexed user, uint256 amount, uint256 planId);
event Unstaked(address indexed user, uint256 amount);
IERC20 public waitToken;
uint256 public constant FREEZING_DURATION = 7 * 24 * 60 * 60 seconds;
mapping(uint256 => StakingPlan) public stakingPlans;
mapping(address => StakingInfo) public stakingInfos;
constructor(address _waitToken) {
waitToken = IERC20(_waitToken);
}
function addStakingPlan(uint256 _planId, uint256 _multiplier, uint256 _duration) external onlyOwner {
stakingPlans[_planId] = StakingPlan(_multiplier, _duration);
}
function removeStakingPlan(uint256 _planId) external onlyOwner {
delete stakingPlans[_planId];
}
function stake(uint256 _amount, uint256 _planId) external {
require(_amount >= 1_000e9, "WaitStaking: Must stake at least 1000");
require(_amount < 1_000_000e9, "WaitStaking: Must stake less than 1000000");
require(stakingPlans[_planId].multiplier != 0, "WaitStaking: Invalid staking plan");
(bool isStaking, bool isInFreezingPeriod) = getUserStatus(msg.sender);
require(!isStaking, "WaitStaking: Already staking");
if (isInFreezingPeriod) {
require(
_amount >= stakingInfos[msg.sender].amount,
"WaitStaking: Must stake at least previously staked amount"
);
stakingInfos[msg.sender].accumulatedReward += getCurrentReward(msg.sender);
} else {
stakingInfos[msg.sender].accumulatedReward = 0;
}
if (_amount > stakingInfos[msg.sender].amount) {
waitToken.transferFrom(msg.sender, address(this), _amount - stakingInfos[msg.sender].amount);
}
stakingInfos[msg.sender].amount = _amount;
stakingInfos[msg.sender].planId = _planId;
stakingInfos[msg.sender].startTime = block.timestamp;
emit Staked(msg.sender, _amount, _planId);
}
function unstake() external nonReentrant {
(bool isStaking, ) = getUserStatus(msg.sender);
require(!isStaking, "WaitStaking: Cannot unstake during active staking");
require(stakingInfos[msg.sender].amount != 0, "WaitStaking: Not staked");
waitToken.transfer(msg.sender, stakingInfos[msg.sender].amount);
delete stakingInfos[msg.sender];
emit Unstaked(msg.sender, stakingInfos[msg.sender].amount);
}
function getCurrentReward(address _user) public view returns (uint256) {
(bool isStaking, bool isInFreezingPeriod) = getUserStatus(_user);
if (!isStaking && !isInFreezingPeriod) {
return 0;
}
StakingInfo memory info = stakingInfos[_user];
uint256 stakedDays = (block.timestamp - info.startTime) / 1 days;
uint256 currentReward = (info.amount * stakingPlans[info.planId].multiplier * stakedDays) / 10000;
return currentReward + info.accumulatedReward;
}
function getUserStatus(address _user) public view returns (bool isStaking, bool isInFreezingPeriod) {
StakingInfo memory info = stakingInfos[_user];
if (info.amount == 0) {
return (false, false);
}
uint256 userStakingEndTime = info.startTime + stakingPlans[info.planId].duration;
if (block.timestamp <= userStakingEndTime) {
return (true, false);
}
if (block.timestamp <= userStakingEndTime + FREEZING_DURATION) {
return (false, true);
}
return (false, false);
}
}
{
"compilationTarget": {
"contracts/WaitStaking.sol": "WaitStaking"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 7777
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_waitToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"planId","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"FREEZING_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_planId","type":"uint256"},{"internalType":"uint256","name":"_multiplier","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"addStakingPlan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getCurrentReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getUserStatus","outputs":[{"internalType":"bool","name":"isStaking","type":"bool"},{"internalType":"bool","name":"isInFreezingPeriod","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_planId","type":"uint256"}],"name":"removeStakingPlan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_planId","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakingInfos","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"planId","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"accumulatedReward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakingPlans","outputs":[{"internalType":"uint256","name":"multiplier","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"waitToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"}]