编译器
0.8.23+commit.f704f362
文件 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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 2 的 5:FairStaking.sol
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract FairStaking is ReentrancyGuard, Ownable {
struct User {
uint256 stakedAmount;
uint256 stakingTimestamp;
uint256 paidOutRewards;
uint lockupPeriod;
}
mapping(address => User) public users;
IERC20 public fairToken;
uint256 public totalStakedAmount;
uint256 public totalWeight;
uint256 public stakingRewardsTotal;
uint256 public stakingRewardsRemaining;
uint256 public stakingStartTimestamp;
uint256 public stakingEndTimestamp;
uint256 public minLockupPeriod = 7 days;
uint256 public bonus30Days = 50;
uint256 public bonus90Days = 100;
uint public constant LOCKUP_WEEK = 0;
uint public constant LOCKUP_MONTH = 1;
uint public constant LOCKUP_QUARTER = 2;
mapping(uint => uint256) public lockupTimes;
mapping(uint => uint256) public rewardBonuses;
uint256 public constant SECONDS_IN_YEAR = 365 days;
event Staked(address indexed user, uint256 amount, uint lockupPeriod);
event Unstaked(address indexed user, uint256 amount);
event RewardClaimed(address indexed user, uint256 amount);
event RewardTokensDeposited(address indexed user, uint256 amount);
constructor(address _fairToken) {
fairToken = IERC20(_fairToken);
stakingRewardsRemaining = 0;
stakingRewardsTotal = 0;
lockupTimes[LOCKUP_WEEK] = 7 days;
lockupTimes[LOCKUP_MONTH] = 30 days;
lockupTimes[LOCKUP_QUARTER] = 90 days;
rewardBonuses[LOCKUP_WEEK] = 0;
rewardBonuses[LOCKUP_MONTH] = 50;
rewardBonuses[LOCKUP_QUARTER] = 100;
stakingStartTimestamp = block.timestamp;
stakingEndTimestamp = block.timestamp + SECONDS_IN_YEAR;
}
function depositRewardTokens(uint256 _amount) external {
fairToken.transferFrom(msg.sender, address(this), _amount);
stakingRewardsTotal += _amount;
stakingRewardsRemaining += _amount;
emit RewardTokensDeposited(msg.sender, _amount);
}
function stake(uint256 _amount, uint _lockupPeriod) external nonReentrant {
require(_amount > 0, "Amount must be greater than 0");
require(lockupTimes[_lockupPeriod] > 0, "Invalid lockup period");
require(_lockupPeriod >= users[msg.sender].lockupPeriod, "Cannot decrease lockup period");
if (users[msg.sender].stakedAmount > 0) {
claimRewards();
}
fairToken.transferFrom(msg.sender, address(this), _amount);
totalWeight -= getUserWeight(msg.sender);
users[msg.sender].stakedAmount += _amount;
users[msg.sender].lockupPeriod = _lockupPeriod;
totalWeight += getUserWeight(msg.sender);
users[msg.sender].stakingTimestamp = block.timestamp;
users[msg.sender].paidOutRewards = 0;
totalStakedAmount += _amount;
emit Staked(msg.sender, _amount, _lockupPeriod);
}
function unstake() external nonReentrant {
require(users[msg.sender].stakedAmount > 0, "No stake to unstake");
require(block.timestamp >= users[msg.sender].stakingTimestamp + lockupTimes[users[msg.sender].lockupPeriod], "Lockup period not ended");
claimRewards();
uint256 amountToUnstake = users[msg.sender].stakedAmount;
totalWeight -= getUserWeight(msg.sender);
totalStakedAmount -= amountToUnstake;
users[msg.sender].stakedAmount = 0;
users[msg.sender].stakingTimestamp = 0;
users[msg.sender].lockupPeriod = 0;
users[msg.sender].paidOutRewards = 0;
fairToken.transfer(msg.sender, amountToUnstake);
emit Unstaked(msg.sender, amountToUnstake);
}
function claimRewards() public {
User storage user = users[msg.sender];
require(user.stakedAmount > 0, "No stake to claim rewards");
require(stakingEndTimestamp > block.timestamp, "Staking period ended");
uint256 pendingRewards = calculateRewards(msg.sender);
if (pendingRewards == 0) {
return;
}
user.paidOutRewards += pendingRewards;
stakingRewardsRemaining -= pendingRewards;
fairToken.transfer(msg.sender, pendingRewards);
emit RewardClaimed(msg.sender, pendingRewards);
}
function calculateRewards(address _user) public view returns (uint256) {
User storage user = users[_user];
if (user.stakedAmount == 0) {
return 0;
}
uint256 userStakingDuration = block.timestamp - user.stakingTimestamp;
uint256 totalStakingDuration = stakingEndTimestamp - stakingStartTimestamp;
uint256 userStakingDurationShare = (userStakingDuration * 1e9) / totalStakingDuration;
uint256 userWeight = getUserWeight(_user);
uint256 userWeightShare = (userWeight * 1e9) / totalWeight;
uint256 accrued = userStakingDurationShare * userWeightShare * stakingRewardsTotal / 1e18;
if (accrued > user.paidOutRewards)
return accrued - user.paidOutRewards;
else
return 0;
}
function getUserWeight(address _user) public view returns (uint256) {
User storage user = users[_user];
if (user.stakedAmount == 0) {
return 0;
}
return user.stakedAmount * (100 + rewardBonuses[user.lockupPeriod]);
}
function setLockupTime(uint _lockupPeriod, uint256 _period) external onlyOwner {
lockupTimes[_lockupPeriod] = _period;
}
function setRewardBonus(uint _lockupPeriod, uint256 _bonus) external onlyOwner {
rewardBonuses[_lockupPeriod] = _bonus;
}
function setStakingEndTimestamp(uint256 _timestamp) external onlyOwner {
require(_timestamp > block.timestamp, "End timestamp must be in the future");
stakingEndTimestamp = _timestamp;
}
function currentBaseApy() public view returns (uint256) {
return apyForRatio(stakingRewardsTotal, totalStakedAmount);
}
function getUserApy(address user_address) public view returns (uint256) {
uint256 userWeight = getUserWeight(user_address);
uint256 userWeightShare = (userWeight * 1e9) / totalWeight;
return apyForRatio(
userWeightShare * stakingRewardsTotal / 1e9,
users[user_address].stakedAmount
);
}
function apyForRatio(uint256 rewards, uint256 balance) public view returns (uint256) {
if (balance == 0) {
return 0;
}
return (rewards * 1e9 / balance) *
(SECONDS_IN_YEAR * 1e9 / (stakingEndTimestamp - stakingStartTimestamp)) / 1e14;
}
}
文件 3 的 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);
}
文件 4 的 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);
}
}
文件 5 的 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;
}
}
{
"compilationTarget": {
"contracts/FairStaking.sol": "FairStaking"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_fairToken","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"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardTokensDeposited","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":"lockupPeriod","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":"LOCKUP_MONTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCKUP_QUARTER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCKUP_WEEK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_IN_YEAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"rewards","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"apyForRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonus30Days","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonus90Days","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"calculateRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentBaseApy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"depositRewardTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fairToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user_address","type":"address"}],"name":"getUserApy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getUserWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"lockupTimes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minLockupPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewardBonuses","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lockupPeriod","type":"uint256"},{"internalType":"uint256","name":"_period","type":"uint256"}],"name":"setLockupTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lockupPeriod","type":"uint256"},{"internalType":"uint256","name":"_bonus","type":"uint256"}],"name":"setRewardBonus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"setStakingEndTimestamp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_lockupPeriod","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingEndTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRewardsRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRewardsTotal","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingStartTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","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":[{"internalType":"address","name":"","type":"address"}],"name":"users","outputs":[{"internalType":"uint256","name":"stakedAmount","type":"uint256"},{"internalType":"uint256","name":"stakingTimestamp","type":"uint256"},{"internalType":"uint256","name":"paidOutRewards","type":"uint256"},{"internalType":"uint256","name":"lockupPeriod","type":"uint256"}],"stateMutability":"view","type":"function"}]