Contract Address Details

0x08A7c8be47773546DC5E173d67B0c38AfFfa4b84

Contract Name
ColdStaking
Creator
0xb7971f–a51f91 at 0x240687–8deb34
Balance
1,180,291,084.373082639193349123 CLO ( )
Tokens
Fetching tokens...
Transactions
259,444 Transactions
Transfers
3 Transfers
Gas Used
26,266,002,502
Last Balance Update
14641077
Contract name:
ColdStaking




Optimization enabled
true
Compiler version
v0.6.12+commit.27d51765




Optimization runs
200
EVM Version
default




Verified at
2022-06-09T14:18:11.131301Z

Contract source code

// SPDX-License-Identifier: No License (None)
pragma solidity ^0.6.0;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint a, uint b) internal pure returns (uint) {
    if (a == 0) {
      return 0;
    }
    uint c = a * b;
    require(c / a == b);
    return c;
  }

  function div(uint a, uint b) internal pure returns (uint) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint a, uint b) internal pure returns (uint) {
    require(b <= a);
    return a - b;
  }

  function add(uint a, uint b) internal pure returns (uint) {
    uint c = a + b;
    require(c >= a);
    return c;
  }
}

contract ColdStaking {
    
    // NOTE: The contract only works for intervals of time > round_interval

    using SafeMath for uint;

    event StartStaking(address addr, uint value, uint amount, uint time, uint end_time);
    event WithdrawStake(address staker, uint amount);
    event Claim(address staker, uint reward);
    event DonationDeposited(address _address, uint value);

    struct Staker
    {
        uint amount;
        uint time;              // Staking start time or last claim rewards
        uint multiplier;        // Rewards multiplier = 0.40 + (0.05 * rounds). [0.45..1] (max rounds 12)
        uint end_time;          // Time when staking ends and user may withdraw. After this time user will not receive rewards.
    }


    uint public LastBlock = block.number;
    uint public Timestamp = now;    //timestamp of the last interaction with the contract.

    uint public TotalStakingWeight; //total weight = sum (each_staking_amount * each_staking_time).
    uint public TotalStakingAmount; //currently frozen amount for Staking.
    uint public StakingRewardPool;  //available amount for paying rewards.
    uint public staking_threshold = 0 ether;

    uint public constant round_interval   = 27 days;     // 1 month.
    uint public constant max_delay        = 365 days;    // 1 year after staking ends.
    uint public constant BlockStartStaking = 7600000;

    uint constant NOMINATOR = 10**18;           // Nominator / denominator used for float point numbers

    //========== TESTNET VALUES ===========
    //uint public constant round_interval   = 1 hours; 
    //uint public constant max_delay        = 2 days;
    //uint public constant BlockStartStaking = 0;
    //========== END TEST VALUES ==========
    
    mapping(address => Staker) public staker;

    receive() external payable
    {
        // No donations accepted to fallback!
        // Consider value deposit is an attempt to become staker.
        // May not accept deposit from other contracts due GAS limit.
        // by default stake for 1 round
        start_staking(1);
    }

    // update TotalStakingAmount value.
    function new_block() internal
    {
        if (block.number > LastBlock)   //run once per block.
        {
            uint _LastBlock = LastBlock;
            LastBlock = block.number;

            StakingRewardPool = address(this).balance.sub(TotalStakingAmount + msg.value);   //fix rewards pool for this block.
            // msg.value here for case new_block() is calling from start_staking(), and msg.value will be added to CurrentBlockDeposits.

            //The consensus protocol enforces block timestamps are always at least +1 from their parent, so a node cannot "lie into the past". 
            if (now > Timestamp) //But with this condition I feel safer :) May be removed.
            {
                uint _blocks = block.number - _LastBlock;
                uint _seconds = now - Timestamp;
                if (_seconds > _blocks * 25) //if time goes far in the future, then use new time as 25 second * blocks.
                {
                    _seconds = _blocks * 25;
                }
                TotalStakingWeight += _seconds.mul(TotalStakingAmount);
                Timestamp += _seconds;
            }
        }
    }

    function start_staking() external payable {
        // by default stake for 1 round
        start_staking(1);
    }

    function start_staking(uint rounds) public staking_available payable
    {
        assert(msg.value >= staking_threshold);
        require(rounds > 0);
        new_block(); //run once per block.
        // to reduce gas cost we will use local variable instead of global
        uint _Timestamp = Timestamp;
        uint staker_amount = staker[msg.sender].amount;
        uint r = rounds;
        if (r > 12) r = 12;
        uint multiplier = (40 + (5 * r)) * NOMINATOR / 100;  // staker multiplier = 0.40 + (0.05 * rounds). [0.45..1]
        uint end_time = _Timestamp.add(round_interval.mul(rounds));
        // claim reward if available.
        if (staker_amount > 0)
        {
            if (_Timestamp >= staker[msg.sender].time + round_interval)
            { 
                _claim(msg.sender); 
            }
            uint staker_end_time = staker[msg.sender].end_time;
            if (staker_end_time > end_time) {
                end_time = staker_end_time;     // Staking end time is the bigger from previous and new one.
                r = (end_time.sub(_Timestamp)).div(round_interval);  // update number of rounds
                if (r > 12) r = 12;
                multiplier = (40 + (5 * r)) * NOMINATOR / 100;  // staker multiplier = 0.40 + (0.05 * rounds). [0.45..1]
            }
            // if there is active staking with bigger multiplier
            if (staker[msg.sender].multiplier > multiplier && staker_end_time > _Timestamp) {
                // recalculate multiplier = (staker.multiplier * staker.amount + new.multiplier * new.amount) / ( staker.amount + new.amount)
                multiplier = ((staker[msg.sender].multiplier.mul(staker_amount)).add(multiplier.mul(msg.value))).div(staker_amount.add(msg.value));
                if (multiplier > NOMINATOR) multiplier = NOMINATOR; // multiplier can't be more then 1
            }
            TotalStakingWeight = TotalStakingWeight.sub((_Timestamp.sub(staker[msg.sender].time)).mul(staker_amount)); // remove from Weight
        }

        TotalStakingAmount = TotalStakingAmount.add(msg.value);
        staker[msg.sender].time = _Timestamp;
        staker[msg.sender].amount = staker_amount.add(msg.value);
        staker[msg.sender].multiplier = multiplier;
        staker[msg.sender].end_time = end_time;

        emit StartStaking(
            msg.sender,
            msg.value,
            staker[msg.sender].amount,
            _Timestamp,
            end_time
        );
    }

    function DEBUG_donation() external payable {
        emit DonationDeposited(msg.sender, msg.value);
    }

    function withdraw_stake() external {
        _withdraw_stake(msg.sender);
    }

    function withdraw_stake(address payable user) external {
        _withdraw_stake(user);
    }

    function _withdraw_stake(address payable user) internal
    {
        new_block(); //run once per block.
        require(Timestamp >= staker[user].end_time); //reject withdrawal before end time.

        uint _amount = staker[user].amount;
        require(_amount != 0);
        // claim reward if available.
        _claim(user); 
        TotalStakingAmount = TotalStakingAmount.sub(_amount);
        TotalStakingWeight = TotalStakingWeight.sub((Timestamp.sub(staker[user].time)).mul(staker[user].amount)); // remove from Weight.
        
        staker[user].amount = 0;
        user.transfer(_amount);
        emit WithdrawStake(user, _amount);
    }

    //claim rewards
    function claim() external only_staker
    {
        _claim(msg.sender);
    }


    function _claim(address payable user) internal
    {
        new_block(); //run once per block
        // to reduce gas cost we will use local variable instead of global
        uint _Timestamp = Timestamp;
        if (_Timestamp > staker[user].end_time) _Timestamp = staker[user].end_time; // rewards calculates until staking ends
        uint _StakingInterval = _Timestamp.sub(staker[user].time);  //time interval of deposit.
        if (_StakingInterval >= round_interval)
        {
            uint _CompleteRoundsInterval = (_StakingInterval / round_interval).mul(round_interval); //only complete rounds.
            uint _StakerWeight = _CompleteRoundsInterval.mul(staker[user].amount); //Weight of completed rounds.
            uint _reward = StakingRewardPool.mul(_StakerWeight).div(TotalStakingWeight);  //StakingRewardPool * _StakerWeight/TotalStakingWeight
            _reward = _reward.mul(staker[user].multiplier) / NOMINATOR;   // reduce rewards if staked on less then 12 rounds.
            StakingRewardPool = StakingRewardPool.sub(_reward);
            TotalStakingWeight = TotalStakingWeight.sub(_StakerWeight); // remove paid Weight.

            staker[user].time = staker[user].time.add(_CompleteRoundsInterval); // reset to paid time, staking continue without a loss of incomplete rounds.
	    
            user.transfer(_reward);
            emit Claim(user, _reward);
        }
    }

    //This function may be used for info only. This can show estimated user reward at current time.
    function stake_reward(address _addr) external view returns (uint _reward)
    {
        require(staker[_addr].amount > 0);

        uint _blocks = block.number - LastBlock;
        uint _seconds = now - Timestamp;
        if (_seconds > _blocks * 25) //if time goes far in the future, then use new time as 25 second * blocks.
        {
            _seconds = _blocks * 25;
        }
        uint _Timestamp = Timestamp + _seconds;
        if (_Timestamp > staker[_addr].end_time) _Timestamp = staker[_addr].end_time; // rewards calculates until staking ends
        uint _TotalStakingWeight = TotalStakingWeight + _seconds.mul(TotalStakingAmount);
        uint _StakingInterval = _Timestamp.sub(staker[_addr].time); //time interval of deposit.
	
        //uint _StakerWeight = _StakingInterval.mul(staker[_addr].amount); //Staker weight.
        uint _CompleteRoundsInterval = (_StakingInterval / round_interval).mul(round_interval); //only complete rounds.
        uint _StakerWeight = _CompleteRoundsInterval.mul(staker[_addr].amount); //Weight of completed rounds.
        uint _StakingRewardPool = address(this).balance.sub(TotalStakingAmount);
        _reward = _StakingRewardPool.mul(_StakerWeight).div(_TotalStakingWeight);  //StakingRewardPool * _StakerWeight/TotalStakingWeight
        _reward = _reward.mul(staker[_addr].multiplier) / NOMINATOR;   // reduce rewards if staked on less then 12 rounds.
    }

    modifier only_staker
    {
        require(staker[msg.sender].amount > 0);
        _;
    }

    modifier staking_available
    {
        require(block.number >= BlockStartStaking);
        _;
    }

    //return deposit to inactive staker after 1 year when staking ends.
    function report_abuse(address payable _addr) public only_staker
    {
        require(staker[_addr].amount > 0);
        new_block(); //run once per block.
        require(Timestamp > staker[_addr].end_time.add(max_delay));
        
        uint _amount = staker[_addr].amount;
        
        TotalStakingAmount = TotalStakingAmount.sub(_amount);
        TotalStakingWeight = TotalStakingWeight.sub((Timestamp.sub(staker[_addr].time)).mul(_amount)); // remove from Weight.

        staker[_addr].amount = 0;
        _addr.transfer(_amount);
    }
}
        

Contract ABI

[{"type":"event","name":"Claim","inputs":[{"type":"address","name":"staker","internalType":"address","indexed":false},{"type":"uint256","name":"reward","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"DonationDeposited","inputs":[{"type":"address","name":"_address","internalType":"address","indexed":false},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"StartStaking","inputs":[{"type":"address","name":"addr","internalType":"address","indexed":false},{"type":"uint256","name":"value","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"uint256","name":"time","internalType":"uint256","indexed":false},{"type":"uint256","name":"end_time","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"WithdrawStake","inputs":[{"type":"address","name":"staker","internalType":"address","indexed":false},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"BlockStartStaking","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"DEBUG_donation","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"LastBlock","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"StakingRewardPool","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"Timestamp","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"TotalStakingAmount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"TotalStakingWeight","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claim","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"max_delay","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"report_abuse","inputs":[{"type":"address","name":"_addr","internalType":"address payable"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"round_interval","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_reward","internalType":"uint256"}],"name":"stake_reward","inputs":[{"type":"address","name":"_addr","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"time","internalType":"uint256"},{"type":"uint256","name":"multiplier","internalType":"uint256"},{"type":"uint256","name":"end_time","internalType":"uint256"}],"name":"staker","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"staking_threshold","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"start_staking","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"start_staking","inputs":[{"type":"uint256","name":"rounds","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdraw_stake","inputs":[{"type":"address","name":"user","internalType":"address payable"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdraw_stake","inputs":[]},{"type":"receive","stateMutability":"payable"}]
            

Deployed ByteCode

0x60806040526004361061010d5760003560e01c806382e4eda411610095578063bf92b4ef11610064578063bf92b4ef146102b4578063cd948855146102e7578063e2d40568146102fc578063e6369e4114610311578063edbbc33b146103265761011e565b806382e4eda4146101fe578063aaee69d914610257578063b3fefcf51461028a578063b43e92fa1461029f5761011e565b80634e71d92d116100dc5780634e71d92d1461016f5780635c854aa9146101845780635d8c85ef146101995780637157c113146101b657806378be0ad4146101cb5761011e565b806314657ef0146101235780631e37de4c1461014a5780631f288efb1461015f5780633a339a4c146101675761011e565b3661011e5761011c600161033b565b005b600080fd5b34801561012f57600080fd5b506101386105a6565b60408051918252519081900360200190f35b34801561015657600080fd5b506101386105ac565b61011c6105b2565b61011c6105be565b34801561017b57600080fd5b5061011c6105fa565b34801561019057600080fd5b5061013861061c565b61011c600480360360208110156101af57600080fd5b503561033b565b3480156101c257600080fd5b50610138610622565b3480156101d757600080fd5b5061011c600480360360208110156101ee57600080fd5b50356001600160a01b0316610629565b34801561020a57600080fd5b506102316004803603602081101561022157600080fd5b50356001600160a01b0316610635565b604080519485526020850193909352838301919091526060830152519081900360800190f35b34801561026357600080fd5b5061011c6004803603602081101561027a57600080fd5b50356001600160a01b031661065c565b34801561029657600080fd5b5061013861077f565b3480156102ab57600080fd5b50610138610785565b3480156102c057600080fd5b50610138600480360360208110156102d757600080fd5b50356001600160a01b031661078b565b3480156102f357600080fd5b5061011c61090c565b34801561030857600080fd5b50610138610915565b34801561031d57600080fd5b5061013861091d565b34801561033257600080fd5b50610138610923565b6273f78043101561034b57600080fd5b60055434101561035757fe5b6000811161036457600080fd5b61036c61092a565b6001543360009081526006602052604090205482600c81111561038d5750600c5b6064670de0b6b3a764000060058302602801020460006103ba6103b36223988088610998565b86906109c8565b905083156105015733600090815260066020526040902060010154622398800185106103e9576103e9336109da565b33600090815260066020526040902060030154818111156104455790508061041e622398806104188389610bd3565b90610be8565b9350600c84111561042e57600c93505b606460286005860201670de0b6b3a7640000020492505b336000908152600660205260409020600201548310801561046557508581115b156104c5576104a761047786346109c8565b6104186104848634610998565b336000908152600660205260409020600201546104a1908a610998565b906109c8565b9250670de0b6b3a76400008311156104c557670de0b6b3a764000092505b336000908152600660205260409020600101546104fc906104f39087906104ed908a90610bd3565b90610998565b60025490610bd3565b600255505b60035461050e90346109c8565b60035533600090815260066020526040902060010185905561053084346109c8565b33600081815260066020908152604091829020848155600281018790556003018590558151928352349083015281810192909252606081018790526080810183905290517f59314c5dd1994b45a3987ac7bace43129e2616c39c4ce9a995c6cc5e4adc5cb49181900360a00190a1505050505050565b60025481565b60045481565b6105bc600161033b565b565b6040805133815234602082015281517fe669e18d16cf72448be9ee29e977628bedcffa0e0c0cde5e8448849fb9426857929181900390910190a1565b3360009081526006602052604090205461061357600080fd5b6105bc336109da565b60055481565b6223988081565b61063281610bfd565b50565b60066020526000908152604090208054600182015460028301546003909301549192909184565b3360009081526006602052604090205461067557600080fd5b6001600160a01b03811660009081526006602052604090205461069757600080fd5b61069f61092a565b6001600160a01b0381166000908152600660205260409020600301546106c9906301e133806109c8565b600154116106d657600080fd5b6001600160a01b0381166000908152600660205260409020546003546106fc9082610bd3565b6003556001600160a01b03821660009081526006602052604090206001908101549054610733916104f39184916104ed9190610bd3565b6002556001600160a01b0382166000818152600660205260408082208290555183156108fc0291849190818181858888f1935050505015801561077a573d6000803e3d6000fd5b505050565b60035481565b60005481565b6001600160a01b0381166000908152600660205260408120546107ad57600080fd5b6000546001544391909103904203601982028111156107cc5750601981025b6001546001600160a01b0385166000908152600660205260409020600301549082019081111561081457506001600160a01b0384166000908152600660205260409020600301545b600061082b6003548461099890919063ffffffff16565b6002546001600160a01b0388166000908152600660205260408120600101549290910192509061085c908490610bd3565b905060006108716223988080845b0490610998565b6001600160a01b03891660009081526006602052604081205491925090610899908390610998565b905060006108b260035447610bd390919063ffffffff16565b90506108c2856104188385610998565b6001600160a01b038b16600090815260066020526040902060020154909950670de0b6b3a7640000906108f6908b90610998565b816108fd57fe5b049a9950505050505050505050565b6105bc33610bfd565b6301e1338081565b60015481565b6273f78081565b6000544311156105bc57600080544390915560035461094c9047903401610bd3565b6004556001544211156106325760015443829003904203601982028111156109745750601981025b600354610982908290610998565b6002805490910190556001805490910190555050565b6000826109a7575060006109c2565b828202828482816109b457fe5b04146109bf57600080fd5b90505b92915050565b6000828201838110156109bf57600080fd5b6109e261092a565b6001546001600160a01b038216600090815260066020526040902060030154811115610a2657506001600160a01b0381166000908152600660205260409020600301545b6001600160a01b038216600090815260066020526040812060010154610a4d908390610bd3565b905062239880811061077a576000610a6962239880808461086a565b6001600160a01b03851660009081526006602052604081205491925090610a91908390610998565b90506000610ab06002546104188460045461099890919063ffffffff16565b6001600160a01b038716600090815260066020526040902060020154909150670de0b6b3a764000090610ae4908390610998565b81610aeb57fe5b049050610b0381600454610bd390919063ffffffff16565b600455600254610b139083610bd3565b6002556001600160a01b038616600090815260066020526040902060010154610b3c90846109c8565b6001600160a01b038716600081815260066020526040808220600101939093559151909183156108fc02918491818181858888f19350505050158015610b86573d6000803e3d6000fd5b50604080516001600160a01b03881681526020810183905281517f47cee97cb7acd717b3c0aa1435d004cd5b3c8c57d70dbceb4e4458bbd60e39d4929181900390910190a1505050505050565b600082821115610be257600080fd5b50900390565b600080828481610bf457fe5b04949350505050565b610c0561092a565b6001600160a01b0381166000908152600660205260409020600301546001541015610c2f57600080fd5b6001600160a01b03811660009081526006602052604090205480610c5257600080fd5b610c5b826109da565b600354610c689082610bd3565b6003556001600160a01b038216600090815260066020526040902080546001918201549154610c9f926104f392916104ed91610bd3565b6002556001600160a01b0382166000818152600660205260408082208290555183156108fc0291849190818181858888f19350505050158015610ce6573d6000803e3d6000fd5b50604080516001600160a01b03841681526020810183905281517f141ef67c4a6d3ec2adfb2f66d33c2b11de5b4f34344757554d430570b18a92ec929181900390910190a1505056fea2646970667358221220657e04f5924264894a178c19b59770cb1d79c43f94ad4d239384d5313e95c9b864736f6c634300060c0033