账户
0xe0...01b3
0xE0...01B3

0xE0...01B3

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.19+commit.7dd6d404
语言
Solidity
合同源代码
文件 1 的 5:Context.sol
合同源代码
文件 2 的 5:IERC20.sol
合同源代码
文件 3 的 5:IPlanetRewards.sol
// SPDX-License-Identifier: MIT
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();
}
合同源代码
文件 4 的 5:Ownable.sol
合同源代码
文件 5 的 5:PlanetRewards.sol
// SPDX-License-Identifier: MIT
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;
    }


    /* -------------------------------------------------------------------------- */
    /*                         OWNER RESTRICTED FUNCTIONS                         */
    /* -------------------------------------------------------------------------- */

    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": []
}
ABI
[{"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"}]