编译器
0.8.19+commit.7dd6d404
文件 3 的 5:IPlanetRewards.sol
pragma solidity ^0.8.13;
contract IPlanetRewards {
struct Account {
uint256 lastStakedAt;
uint256 totalStaked;
uint256 creditedPoints;
}
event Stake(address indexed staker, uint256 amount, uint256 totalCreditedPoints, uint256 timestamp);
event Unstake(address indexed staker, uint256 amount, uint256 timestamp);
event StakingEnabled(bool enabled);
event ResetWinner(address winner);
error InvalidStakingAmount();
error NoStakedTokens();
error StakingNotEnabled();
error UnstakingNotPermitted();
error ZeroValue();
}
文件 5 的 5:PlanetRewards.sol
pragma solidity ^0.8.13;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.3/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.3/contracts/access/Ownable.sol";
import "./IPlanetRewards.sol";
contract PlanetRewards is IPlanetRewards, Ownable {
address public immutable PLANET;
bool public stakingEnabled;
uint256 public rewardRate = 100;
uint256 public minStake = 100_000 ether;
uint256 public cooldown = 1 days;
uint256 public maxStakeDurationWithoutNewEntry = 52 weeks;
uint256 public minStakeDuration;
uint256 public globalTotalStaked;
uint256 public totalUniqueStakers;
mapping(address => Account) public account;
address[] public lastWinners;
constructor(address planet) {
PLANET = planet;
}
function stake(uint256 amount) public {
if (!stakingEnabled) revert StakingNotEnabled();
if (amount < minStake) revert InvalidStakingAmount();
IERC20(PLANET).transferFrom(msg.sender, address(this), amount);
uint256 stakedBefore = account[msg.sender].totalStaked;
bool isNewStaker = stakedBefore == 0;
if (isNewStaker) totalUniqueStakers++;
globalTotalStaked += amount;
uint256 points = isNewStaker ? 0 : totalPoints(msg.sender);
account[msg.sender] = Account(
{
lastStakedAt: block.timestamp,
totalStaked: stakedBefore + amount,
creditedPoints: points
}
);
emit Stake(msg.sender, amount, points, block.timestamp);
}
function unstake() public {
uint256 stakedTokens = account[msg.sender].totalStaked;
if (stakedTokens == 0) revert NoStakedTokens();
if (block.timestamp - account[msg.sender].lastStakedAt < minStakeDuration) {
revert UnstakingNotPermitted();
}
IERC20(PLANET).transfer(msg.sender, stakedTokens);
totalUniqueStakers--;
globalTotalStaked -= stakedTokens;
delete account[msg.sender];
emit Unstake(msg.sender, stakedTokens, block.timestamp);
}
function totalPoints(address wallet) public view returns (uint256) {
uint256 timePassed = block.timestamp - account[wallet].lastStakedAt;
if (
timePassed == block.timestamp ||
timePassed >= maxStakeDurationWithoutNewEntry)
{
return 0;
}
uint256 accumulated =
((timePassed / cooldown) * account[wallet].totalStaked) / rewardRate;
return account[wallet].creditedPoints + accumulated;
}
function resetWinners(address[] calldata winners) external onlyOwner {
uint256 length = winners.length;
for (uint256 i = 0; i < length; i++) {
account[winners[i]].lastStakedAt = block.timestamp;
delete account[winners[i]].creditedPoints;
emit ResetWinner(winners[i]);
}
lastWinners = winners;
}
function setMinStake(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
minStake = newValue;
}
function setMinStakeDuration(uint256 newValue) external onlyOwner {
minStakeDuration = newValue;
}
function setRewardRate(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
rewardRate = newValue;
}
function setStakingEnabled(bool newValue) external onlyOwner {
stakingEnabled = newValue;
emit StakingEnabled(newValue);
}
function setMaxStakePeriodWithoutNewEntry(uint256 newValue) external onlyOwner {
maxStakeDurationWithoutNewEntry = newValue;
}
function setCooldown(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
cooldown = newValue;
}
}
{
"compilationTarget": {
"contracts/PlanetRewards.sol": "PlanetRewards"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"planet","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidStakingAmount","type":"error"},{"inputs":[],"name":"NoStakedTokens","type":"error"},{"inputs":[],"name":"StakingNotEnabled","type":"error"},{"inputs":[],"name":"UnstakingNotPermitted","type":"error"},{"inputs":[],"name":"ZeroValue","type":"error"},{"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":false,"internalType":"address","name":"winner","type":"address"}],"name":"ResetWinner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalCreditedPoints","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Stake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"StakingEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Unstake","type":"event"},{"inputs":[],"name":"PLANET","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"account","outputs":[{"internalType":"uint256","name":"lastStakedAt","type":"uint256"},{"internalType":"uint256","name":"totalStaked","type":"uint256"},{"internalType":"uint256","name":"creditedPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalTotalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastWinners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxStakeDurationWithoutNewEntry","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minStakeDuration","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":"address[]","name":"winners","type":"address[]"}],"name":"resetWinners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"setCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"setMaxStakePeriodWithoutNewEntry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"setMinStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"setMinStakeDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"setRewardRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newValue","type":"bool"}],"name":"setStakingEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"totalPoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalUniqueStakers","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"}]