编译器
0.8.19+commit.7dd6d404
文件 1 的 4: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 的 4: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 的 4: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 的 4:PlanetRewards.sol
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract PlanetRewards is Ownable {
bool public stakingEnabled;
address public immutable PLANET;
uint256 public rewardRate = 10000;
uint256 public firstStakeAmount = 1_000_000 ether;
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;
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);
event SetMinStake(uint256 value);
event SetFirstStakeAmount(uint256 value);
event SetMinStakeDuration(uint256 value);
event SetRewardRate(uint256 value);
event SetMinStakeDurationWithoutNewEntry(uint256 value);
event SetCooldown(uint256 value);
error InvalidStakingAmount();
error NoStakedTokens();
error StakingNotEnabled();
error UnstakingNotPermitted();
error ZeroValue();
constructor(address planet) {
PLANET = planet;
}
function stake(uint256 amount) public {
if (!stakingEnabled) revert StakingNotEnabled();
if (amount % minStake != 0) revert InvalidStakingAmount();
IERC20(PLANET).transferFrom(msg.sender, address(this), amount);
uint256 stakedBefore = account[msg.sender].totalStaked;
if (stakedBefore == 0) {
if (amount < firstStakeAmount) revert InvalidStakingAmount();
totalUniqueStakers++;
}
globalTotalStaked += amount;
uint256 points = 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();
}
totalUniqueStakers--;
globalTotalStaked -= stakedTokens;
account[msg.sender].creditedPoints = totalPoints(msg.sender);
delete account[msg.sender].lastStakedAt;
delete account[msg.sender].totalStaked;
IERC20(PLANET).transfer(msg.sender, stakedTokens);
emit Unstake(msg.sender, stakedTokens, block.timestamp);
}
function totalPoints(address wallet) public view returns (uint256) {
uint256 timePassed = block.timestamp - account[wallet].lastStakedAt;
uint256 accumulated;
if (timePassed != block.timestamp) {
if (timePassed >= maxStakeDurationWithoutNewEntry) {
return 0;
}
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 addToLastWinners(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;
lastWinners.push(winners[i]);
emit ResetWinner(winners[i]);
}
}
function setMinStake(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
minStake = newValue;
emit SetMinStake(newValue);
}
function setFirstStakeAmount(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
firstStakeAmount = newValue;
emit SetFirstStakeAmount(newValue);
}
function setMinStakeDuration(uint256 newValue) external onlyOwner {
minStakeDuration = newValue;
emit SetMinStakeDuration(newValue);
}
function setRewardRate(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
rewardRate = newValue;
emit SetRewardRate(newValue);
}
function setStakingEnabled(bool newValue) external onlyOwner {
stakingEnabled = newValue;
emit StakingEnabled(newValue);
}
function setMaxStakePeriodWithoutNewEntry(uint256 newValue) external onlyOwner {
maxStakeDurationWithoutNewEntry = newValue;
emit SetMinStakeDurationWithoutNewEntry(newValue);
}
function setCooldown(uint256 newValue) external onlyOwner {
if (newValue == 0) revert ZeroValue();
cooldown = newValue;
emit SetCooldown(newValue);
}
}
{
"compilationTarget": {
"src/PlanetRewards.sol": "PlanetRewards"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@chainlink/contracts/=node_modules/@chainlink/contracts/",
":@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/"
]
}
[{"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":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetCooldown","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetFirstStakeAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetMinStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetMinStakeDuration","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetMinStakeDurationWithoutNewEntry","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetRewardRate","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":[{"internalType":"address[]","name":"winners","type":"address[]"}],"name":"addToLastWinners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"firstStakeAmount","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":"setFirstStakeAmount","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"}]