账户
0xbe...a1c7
MIND Ecosystem Staking A

MIND Ecosystem Staking A

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.0+commit.c7dfd78e
语言
Solidity
合同源代码
文件 1 的 2:IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see `ERC20Detailed`.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a `Transfer` event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through `transferFrom`. This is
     * zero by default.
     *
     * This value changes when `approve` or `transferFrom` are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * > Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an `Approval` event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a `Transfer` event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to `approve`. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
合同源代码
文件 2 的 2:StakingCampaign.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";

contract StakingCampaign {
   
    struct StackingInfo {
        uint seq;
        uint amount;
        uint reward;
        bool isPayout;
        uint unlockTime;
    }

    event Deposited (
        address indexed sender,
        uint seq,
        uint amount,
        uint256 timestamp
    );

    event Claimed (
        address indexed sender,
        uint seq,
        uint amount,
        uint reward,
        uint256 timestamp
    );

    address public owner;
    modifier onlyAdmin {
        require(msg.sender == owner, 'Caller is not owner');
        _;
    }

    // ERC20 token for staking campaign
    IERC20 public token;
    // campaign name
    string public name;
    // total day for staking (in second)
    uint public duration;
    // annual percentage rate
    uint public apr;
    // total cap for campaign, stop campaign if cap is reached
    uint public maxCap;
    // expired time of campaign, no more staking is accepted (in second)
    uint public expiredTime; 
    // min amount for one staking deposit
    uint public minTransactionAmount;
    // max amount for one staking deposit
    uint public maxTransactionAmount;
    // total amount already payout for staker (payout = staking amount + reward)
    uint public totalPayoutAmount;
    // total reward need for campaign
    uint public totalCampaignReward;
    // total staked amount
    uint public totalStakedAmount;
    //
    bool public isMaxCapReached = false;

    mapping(address => StackingInfo[]) internal stakingList;

    /**
     * 
     */
    constructor (IERC20 _token, string memory _campaignName, uint _expiredTime, 
                uint _maxCap, uint _maxTransactionAmount, uint _minTransactionAmount,
                uint _duration, uint _apr) {
        owner = msg.sender;
        token = _token;
        name = _campaignName;
        expiredTime = block.timestamp + _expiredTime;
        maxCap = _maxCap;
        maxTransactionAmount = _maxTransactionAmount;
        minTransactionAmount = _minTransactionAmount;
        duration = _duration;
        apr = _apr;
    }

    /**
     * Deposit amount of token to stack
     */
    function deposit(uint _amount, address _userAddr) external {
        require(totalStakedAmount + _amount <= maxCap, "Total cap is reached");
        require(_amount >= minTransactionAmount, "Staking amount is too small");
        require(_amount <= maxTransactionAmount, "Staking amount is too big");
        require(block.timestamp < expiredTime, "Campaign is over");

        token.transferFrom(_userAddr, address(this), _amount);
        uint unlockTime = block.timestamp + duration;
        uint seq = stakingList[_userAddr].length + 1;
        uint reward = _amount*apr*duration/(365*24*60*60*100);

        StackingInfo memory staking = StackingInfo(seq, _amount, reward, false, unlockTime);
        stakingList[_userAddr].push(staking);
       
        totalStakedAmount += _amount;
        totalCampaignReward += reward;

        isMaxCapReached = (totalStakedAmount == maxCap || totalStakedAmount + minTransactionAmount > maxCap);

        emit Deposited(_userAddr, seq, _amount, block.timestamp);
    }

    function claim(uint _seq, address _userAddr) public {
        StackingInfo[] memory userStakings = stakingList[_userAddr];
        require(_seq > 0 && userStakings.length >= _seq, "Invalid index");

        uint idx = _seq - 1;
        
        StackingInfo memory staking = userStakings[idx];

        require(!staking.isPayout, "Stake is already payout");
        require(staking.unlockTime < block.timestamp, "Staking is in lock period");
        
        uint payout = staking.amount + staking.reward;

        token.transfer(_userAddr, payout);
        totalPayoutAmount += payout;
        
        stakingList[_userAddr][idx].isPayout = true;
        
        emit Claimed(_userAddr, _seq, staking.amount, staking.reward, block.timestamp);
    }
    
    function claimRemainingReward(address _userAddr) public onlyAdmin {
        require(block.timestamp > expiredTime, "Campaign is not over yet");

        uint remainingPayoutAmount = totalStakedAmount + totalCampaignReward - totalPayoutAmount;
        uint balance = token.balanceOf(address(this));

        token.transfer(_userAddr, balance - remainingPayoutAmount);
    }

    function getClaimableRemainningReward() public view returns (uint) {
        if(block.timestamp < expiredTime) return 0;
        else {
            uint remainingPayoutAmount = totalStakedAmount + totalCampaignReward - totalPayoutAmount;
            uint balance = token.balanceOf(address(this));
            return balance - remainingPayoutAmount;
        }
    }
    
    function getStakings(address _staker) public view returns (uint[] memory _seqs, uint[] memory _amounts, uint[] memory _rewards, bool[] memory _isPayouts, uint[] memory _timestamps) {
        StackingInfo[] memory userStakings = stakingList[_staker];
        
        uint length = userStakings.length;
        
        uint256[] memory seqList = new uint256[](length);
        uint256[] memory amountList = new uint256[](length);
        uint256[] memory rewardList = new uint256[](length);
        bool[] memory isPayoutList = new bool[](length);
        uint256[] memory timeList = new uint256[](length);
        
        for(uint idx = 0; idx < length; idx++) {
            StackingInfo memory stackingInfo = userStakings[idx];
            
            seqList[idx] = stackingInfo.seq;
            amountList[idx] = stackingInfo.amount;
            rewardList[idx] = stackingInfo.reward;
            isPayoutList[idx] = stackingInfo.isPayout;
            timeList[idx] = stackingInfo.unlockTime;
        }
        
        return (seqList, amountList, rewardList, isPayoutList, timeList);
    }
    
    function getCampaignInfo() public view returns (
            IERC20 _token, string memory _campaignName, uint _expiredTime, 
            uint _maxCap, uint _maxTransactionAmount, uint _minTransactionAmount,
            uint _duration, uint _apr, uint _stakedAmount,uint _totalPayoutAmount) {

        return (token, name, expiredTime, maxCap, maxTransactionAmount, minTransactionAmount, duration, apr, totalStakedAmount, totalPayoutAmount);
    }

    function transferOwnership(address _newOwner) public onlyAdmin {
        owner = _newOwner;
    }
}
设置
{
  "compilationTarget": {
    "StakingCampaign.sol": "StakingCampaign"
  },
  "evmVersion": "istanbul",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"string","name":"_campaignName","type":"string"},{"internalType":"uint256","name":"_expiredTime","type":"uint256"},{"internalType":"uint256","name":"_maxCap","type":"uint256"},{"internalType":"uint256","name":"_maxTransactionAmount","type":"uint256"},{"internalType":"uint256","name":"_minTransactionAmount","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"},{"internalType":"uint256","name":"_apr","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"seq","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"seq","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Deposited","type":"event"},{"inputs":[],"name":"apr","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_seq","type":"uint256"},{"internalType":"address","name":"_userAddr","type":"address"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_userAddr","type":"address"}],"name":"claimRemainingReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_userAddr","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"duration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expiredTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCampaignInfo","outputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"string","name":"_campaignName","type":"string"},{"internalType":"uint256","name":"_expiredTime","type":"uint256"},{"internalType":"uint256","name":"_maxCap","type":"uint256"},{"internalType":"uint256","name":"_maxTransactionAmount","type":"uint256"},{"internalType":"uint256","name":"_minTransactionAmount","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"},{"internalType":"uint256","name":"_apr","type":"uint256"},{"internalType":"uint256","name":"_stakedAmount","type":"uint256"},{"internalType":"uint256","name":"_totalPayoutAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getClaimableRemainningReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"getStakings","outputs":[{"internalType":"uint256[]","name":"_seqs","type":"uint256[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"_rewards","type":"uint256[]"},{"internalType":"bool[]","name":"_isPayouts","type":"bool[]"},{"internalType":"uint256[]","name":"_timestamps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isMaxCapReached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxCap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTransactionAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minTransactionAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalCampaignReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalPayoutAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]