账户
0x87...314c
0x87...314C

0x87...314C

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.8+commit.dddeac2f
语言
Solidity
合同源代码
文件 1 的 1:OprivaStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract OprivaStaking {
    IERC20 public stakingToken;
    address public owner;

    struct Stake {
        uint256 amount;
        uint256 timestamp;  
        uint256 lockTimestamp; 
        bool unstakeForced;
        uint256 claimedReward;
    }

    mapping(address => Stake[]) public stakes;
    uint256 public apy1 = 240;
    uint256 public apy2 = 240;
    uint256 public apy3 = 240;
    uint256 public constant FORCE_UNSTAKE_FEE = 4;

    event Staked(address indexed user, uint256 amount, uint256 lockTimestamp);
    event Unstaked(address indexed user, uint256 amount, uint256 reward);
    event ForcedUnstake(address indexed user, uint256 amount, uint256 fee);
    event RewardClaimed(address indexed user, uint256 reward);

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can execute");
        _;
    }

    constructor(address _stakingToken) {
        owner = msg.sender;
        stakingToken = IERC20(_stakingToken);
    }

    function stake(uint256 amount, uint256 lockDuration) external {
        require(amount > 0);
        require(lockDuration == 7 days || lockDuration == 30 days || lockDuration == 90 days);
        require(stakingToken.transferFrom(msg.sender, address(this), amount));

        uint256 lockTimestamp = block.timestamp + lockDuration; 

        stakes[msg.sender].push(Stake({
            amount: amount,
            timestamp: block.timestamp,
            lockTimestamp: lockTimestamp,
            unstakeForced: false,
            claimedReward: 0
        }));

        emit Staked(msg.sender, amount, lockTimestamp);
    }

    function calculateReward(address user, uint256 stakeIndex) public view returns (uint256) {
        Stake storage userStake = stakes[user][stakeIndex];
        if (userStake.amount == 0) {
            return 0;
        }

        uint256 stakedDuration = block.timestamp - userStake.timestamp;
        uint256 apy = getAPYForDuration(userStake.lockTimestamp - userStake.timestamp);

        uint256 reward = (userStake.amount * apy * stakedDuration) / (365 days * 100);
        return reward - userStake.claimedReward; 
    }

    function claimReward(uint256 stakeIndex) external {
        Stake storage userStake = stakes[msg.sender][stakeIndex];
        uint256 reward = calculateReward(msg.sender, stakeIndex);

        require(reward > 0, "No reward to claim");

        userStake.claimedReward += reward;
        require(stakingToken.transfer(msg.sender, reward));

        emit RewardClaimed(msg.sender, reward);
    }

    function unstake(uint256 stakeIndex) external {
        Stake storage userStake = stakes[msg.sender][stakeIndex];
        require(userStake.amount > 0);
        require(block.timestamp >= userStake.lockTimestamp);

        uint256 reward = calculateReward(msg.sender, stakeIndex);
        uint256 totalAmount = userStake.amount + reward;

        userStake.amount = 0;

        require(stakingToken.transfer(msg.sender, totalAmount));

        emit Unstaked(msg.sender, userStake.amount, reward);
    }

    function forcedUnstake(uint256 stakeIndex) external {
        Stake storage userStake = stakes[msg.sender][stakeIndex];
        require(userStake.amount > 0);
        require(!userStake.unstakeForced);

        uint256 reward = calculateReward(msg.sender, stakeIndex);
        uint256 totalAmount = userStake.amount + reward;
        uint256 fee = (totalAmount * FORCE_UNSTAKE_FEE) / 100;
        uint256 amountAfterFee = totalAmount - fee;

        userStake.unstakeForced = true;

        userStake.amount = 0;

        require(stakingToken.transfer(msg.sender, amountAfterFee));

        emit ForcedUnstake(msg.sender, totalAmount, fee);
    }

    function infoUserStake(address user) external view returns (uint256[] memory amounts, uint256[] memory timestamps, uint256[] memory lockTimestamps, uint256[] memory claimedRewards, bool[] memory unstakeForced) {
        uint256 length = stakes[user].length;

        uint256[] memory _amounts = new uint256[](length);
        uint256[] memory _timestamps = new uint256[](length);
        uint256[] memory _lockTimestamps = new uint256[](length);
        uint256[] memory _claimedRewards = new uint256[](length);
        bool[] memory _unstakeForced = new bool[](length);

        for (uint256 i = 0; i < length; i++) {
            Stake storage userStake = stakes[user][i];
            _amounts[i] = userStake.amount;
            _timestamps[i] = userStake.timestamp;
            _lockTimestamps[i] = userStake.lockTimestamp;
            _claimedRewards[i] = userStake.claimedReward;
            _unstakeForced[i] = userStake.unstakeForced;
        }

        return (_amounts, _timestamps, _lockTimestamps, _claimedRewards, _unstakeForced);
    }

    function getAPYForDuration(uint256 lockDuration) public view returns (uint256) {
        if (lockDuration == 7 days) {
            return apy1;
        } else if (lockDuration == 30 days) {
            return apy2;
        } else if (lockDuration == 90 days) {
            return apy3;
        }
        return 0;
    }

    function setAPY(uint256 _apy1, uint256 _apy2, uint256 _apy3) external onlyOwner {
        apy1 = _apy1;
        apy2 = _apy2;
        apy3 = _apy3;
    }

    function withdraw(uint256 _amount) external onlyOwner {
        uint256 contractBalance = stakingToken.balanceOf(address(this));
        require(_amount <= contractBalance, "Insufficient balance");
        require(stakingToken.transfer(owner, _amount), "Transfer failed");
    }

    function contractBalance() external view returns (uint256) {
        return stakingToken.balanceOf(address(this));
    }
}
设置
{
  "compilationTarget": {
    "OprivaStaking.sol": "OprivaStaking"
  },
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": false,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"address","name":"_stakingToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"ForcedUnstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","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"},{"indexed":false,"internalType":"uint256","name":"lockTimestamp","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"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"FORCE_UNSTAKE_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"apy1","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"apy2","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"apy3","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"calculateReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"contractBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"forcedUnstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lockDuration","type":"uint256"}],"name":"getAPYForDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"infoUserStake","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"timestamps","type":"uint256[]"},{"internalType":"uint256[]","name":"lockTimestamps","type":"uint256[]"},{"internalType":"uint256[]","name":"claimedRewards","type":"uint256[]"},{"internalType":"bool[]","name":"unstakeForced","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_apy1","type":"uint256"},{"internalType":"uint256","name":"_apy2","type":"uint256"},{"internalType":"uint256","name":"_apy3","type":"uint256"}],"name":"setAPY","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"lockDuration","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakes","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"lockTimestamp","type":"uint256"},{"internalType":"bool","name":"unstakeForced","type":"bool"},{"internalType":"uint256","name":"claimedReward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakeIndex","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]