Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x7F4FE6776a9617847485d43db0d3A9b734e459C5
Balance 0 ETH
Nonce 1
Code Size 9083 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

9083 bytes
0x608060405234801561001057600080fd5b50600436106100df5760003560e01c8063aa5791541161008c578063ea2c38ae11610066578063ea2c38ae14610278578063f3fef3a314610326578063f4a4341d14610352578063f7888aec1461035a576100df565b8063aa5791541461021f578063b97dd9e214610268578063ce58a2a814610270576100df565b80634ff0876a116100bd5780634ff0876a146101b15780636ff1c9bc146101b95780638c028dd0146101df576100df565b80632ca32d7e146100e457806347e7ef241461012b5780634be41dba14610159575b600080fd5b610119600480360360408110156100fa57600080fd5b5080356001600160a01b031690602001356001600160801b0316610388565b60408051918252519081900360200190f35b6101576004803603604081101561014157600080fd5b506001600160a01b038135169060200135610462565b005b6101956004803603608081101561016f57600080fd5b508035906001600160801b036020820135811691604081013591606090910135166110c6565b604080516001600160801b039092168252519081900360200190f35b61011961114a565b610157600480360360208110156101cf57600080fd5b50356001600160a01b0316611150565b610119600480360360608110156101f557600080fd5b5080356001600160a01b0390811691602081013590911690604001356001600160801b031661131a565b6102546004803603604081101561023557600080fd5b5080356001600160a01b031690602001356001600160801b03166114ab565b604080519115158252519081900360200190f35b6101956114e4565b610195611511565b6101576004803603604081101561028e57600080fd5b8101906020810181356401000000008111156102a957600080fd5b8201836020820111156102bb57600080fd5b803590602001918460208302840111640100000000831117156102dd57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505090356001600160801b031691506115589050565b6101576004803603604081101561033c57600080fd5b506001600160a01b0381351690602001356117e7565b610119612097565b6101196004803603604081101561037057600080fd5b506001600160a01b038135811691602001351661209d565b600061039483836114ab565b156103cc57506001600160a01b03821660009081526004602090815260408083206001600160801b038516845290915290205461045c565b6103d78360006114ab565b6103e35750600061045c565b604080516370a0823160e01b8152306004820152905184916001600160a01b038316916370a0823191602480820192602092909190829003018186803b15801561042c57600080fd5b505afa158015610440573d6000803e3d6000fd5b505050506040513d602081101561045657600080fd5b50519150505b92915050565b600260005414156104ba576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260005580610511576040805162461bcd60e51b815260206004820152601b60248201527f5374616b696e673a20416d6f756e74206d757374206265203e20300000000000604482015290519081900360640190fd5b604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152905183916000916001600160a01b0384169163dd62ed3e916044808301926020929190829003018186803b15801561057b57600080fd5b505afa15801561058f573d6000803e3d6000fd5b505050506040513d60208110156105a557600080fd5b50519050828110156105e85760405162461bcd60e51b81526004018080602001828103825260228152602001806122dc6022913960400191505060405180910390fd5b3360009081526003602090815260408083206001600160a01b038816845290915290205461061690846120c8565b3360008181526003602090815260408083206001600160a01b038a811685529083528184209590955580517f23b872dd00000000000000000000000000000000000000000000000000000000815260048101949094523060248501526044840188905251938616936323b872dd93606480820194918390030190829087803b1580156106a157600080fd5b505af11580156106b5573d6000803e3d6000fd5b505050506040513d60208110156106cb57600080fd5b50600090506106d86114e4565b905060006106e4611511565b90506106f086836114ab565b6107505760408051600180825281830190925260609160208083019080368337019050509050868160008151811061072457fe5b60200260200101906001600160a01b031690816001600160a01b03168152505061074e8184611558565b505b6001600160a01b0380871660009081526004602081815260408084206001600160801b036001890116855282529283902083516370a0823160e01b81523093810193909352925192938816926370a08231926024808201939291829003018186803b1580156107be57600080fd5b505afa1580156107d2573d6000803e3d6000fd5b505050506040513d60208110156107e857600080fd5b505181556001808201805460ff191690911790553360008181526005602090815260408083206001600160a01b038c16845290915281209161082b908a8761131a565b82549091506109e057816040518060800160405280876001600160801b03168152602001866001600160801b03168152602001600081526020018a815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b0316021790555060408201518160010155606082015181600201555050816040518060800160405280876001016001600160801b03168152602001670de0b6b3a76400006001600160801b031681526020018a81526020016000815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b0316021790555060408201518160010155606082015181600201555050611000565b81546000198101906001600160801b038716908490839081106109ff57fe5b60009182526020909120600390910201546001600160801b03161015610c9c576000610a98610a88858481548110610a3357fe5b600091825260209182902060408051608081018252600390930290910180546001600160801b038082168552600160801b90910416938301939093526001830154908201526002909101546060820152612122565b670de0b6b3a76400008c896110c6565b9050836040518060800160405280896001600160801b03168152602001836001600160801b03168152602001610ad3878681548110610a3357fe5b81526020018c815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b0316021790555060408201518160010155606082015181600201555050836040518060800160405280896001016001600160801b03168152602001670de0b6b3a76400006001600160801b0316815260200160036000336001600160a01b03166001600160a01b0316815260200190815260200160002060008f6001600160a01b03166001600160a01b031681526020019081526020016000205481526020016000815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b031602179055506040820151816001015560608201518160020155505050610ffe565b856001600160801b0316838281548110610cb257fe5b60009182526020909120600390910201546001600160801b03161415610ed057610d18610ce4848381548110610a3357fe5b848381548110610cf057fe5b6000918252602090912060039091020154600160801b90046001600160801b03168b886110c6565b838281548110610d2457fe5b906000526020600020906003020160000160106101000a8154816001600160801b0302191690836001600160801b03160217905550610d8989848381548110610d6957fe5b9060005260206000209060030201600201546120c890919063ffffffff16565b838281548110610d9557fe5b906000526020600020906003020160020181905550826040518060800160405280886001016001600160801b03168152602001670de0b6b3a76400006001600160801b0316815260200160036000336001600160a01b03166001600160a01b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b031681526020019081526020016000205481526020016000815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b0316021790555060408201518160010155606082015181600201555050610ffe565b60018110158015610f115750856001600160801b0316836001830381548110610ef557fe5b60009182526020909120600390910201546001600160801b0316145b15610fb657610f3a610f2b846001840381548110610a3357fe5b846001840381548110610cf057fe5b836001830381548110610f4957fe5b906000526020600020906003020160000160106101000a8154816001600160801b0302191690836001600160801b03160217905550610f9189846001840381548110610d6957fe5b836001830381548110610fa057fe5b9060005260206000209060030201600201819055505b3360009081526003602090815260408083206001600160a01b038e1684529091529020548354849083908110610fe857fe5b9060005260206000209060030201600101819055505b505b600061100d338b8861131a565b905061104f61101c828461213f565b6001600160a01b038c1660009081526004602090815260408083206001600160801b038c168452909152902054906120c8565b6001600160a01b038b1660008181526004602090815260408083206001600160801b038c1684528252918290209390935580518c81529051919233927f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f629281900390910190a3505060016000555050505050505050565b6000806110ee670de0b6b3a76400006110e8886001600160801b03891661219c565b906121f5565b90506000611111670de0b6b3a76400006110e8876001600160801b03881661219c565b9050600061113e61112289886120c8565b6110e8670de0b6b3a764000061113887876120c8565b9061219c565b98975050505050505050565b60025481565b6001600160a01b038116600090815260066020526040902054600a906001600160801b031661117d6114e4565b036001600160801b031610156111c45760405162461bcd60e51b815260040180806020018281038252602c81526020018061228e602c913960400191505060405180910390fd5b3360009081526003602090815260408083206001600160a01b03851684529091529020548061123a576040805162461bcd60e51b815260206004820152601260248201527f416d6f756e74206d757374206265203e20300000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602090815260408083206001600160a01b038716808552908352818420849055815163a9059cbb60e01b815260048101959095526024850186905290518694919363a9059cbb93604480850194919392918390030190829087803b1580156112a957600080fd5b505af11580156112bd573d6000803e3d6000fd5b505050506040513d60208110156112d357600080fd5b50506040805183815290516001600160a01b0385169133917ff24ef89f38eadc1bde50701ad6e4d6d11a2dc24f7cf834a486991f38833285049181900360200190a3505050565b6001600160a01b03808416600090815260056020908152604080832093861683529290529081208054158061137857508060008154811061135757fe5b60009182526020909120600390910201546001600160801b03908116908416105b156113875760009150506114a4565b805460009060001981019083908290811061139e57fe5b60009182526020909120600390910201546001600160801b0390811690861610611430576114268382815481106113d157fe5b600091825260209182902060408051608081018252600390930290910180546001600160801b038082168552600160801b9091041693830193909352600183015490820152600290910154606082015261225c565b93505050506114a4565b8181111561148f5760006002600183850101049050856001600160801b031684828154811061145b57fe5b60009182526020909120600390910201546001600160801b03161161148257809250611489565b6001810391505b50611430565b61149e8383815481106113d157fe5b93505050505b9392505050565b6001600160a01b03821660009081526004602090815260408083206001600160801b038516845290915290206001015460ff1692915050565b60006001544210156114f85750600061150e565b60025460015442038161150757fe5b0460010190505b90565b60008061151c6114e4565b6002546001549192506001600160801b03831681029091019042820390600090670de0b6b3a764000083028161154e57fe5b0494505050505090565b6115606114e4565b6001600160801b0316816001600160801b031611156115c6576040805162461bcd60e51b815260206004820152601960248201527f63616e277420696e69742061206675747572652065706f636800000000000000604482015290519081900360640190fd5b60005b8251811015611759576000600460008584815181106115e457fe5b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160801b038716808352935220915061163b57600081556001808201805460ff19169091179055611750565b61165884838151811061164a57fe5b6020026020010151846114ab565b156116945760405162461bcd60e51b81526004018080602001828103825260228152602001806122ba6022913960400191505060405180910390fd5b6116b48483815181106116a357fe5b6020026020010151600185036114ab565b6116ef5760405162461bcd60e51b81526004018080602001828103825260278152602001806122fe6027913960400191505060405180910390fd5b600460008584815181106116ff57fe5b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160801b03600019880116825290925290205481556001818101805460ff191690911790555b506001016115c9565b50806001600160801b0316336001600160a01b03167fb85c32b8d9cecc81feba78646289584a693e9a8afea40ab2fd31efae4408429f846040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156117d05781810151838201526020016117b8565b505050509050019250505060405180910390a35050565b6002600054141561183f576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260009081553381526003602090815260408083206001600160a01b03861684529091529020548111156118bb576040805162461bcd60e51b815260206004820152601a60248201527f5374616b696e673a2062616c616e636520746f6f20736d616c6c000000000000604482015290519081900360640190fd5b3360009081526003602090815260408083206001600160a01b03861684529091529020546118e9908261213f565b3360008181526003602090815260408083206001600160a01b03881680855290835281842095909555805163a9059cbb60e01b81526004810194909452602484018690525186949363a9059cbb9360448083019493928390030190829087803b15801561195557600080fd5b505af1158015611969573d6000803e3d6000fd5b505050506040513d602081101561197f57600080fd5b506000905061198c6114e4565b6001600160a01b038516600090815260066020526040902080547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166001600160801b03831617905590506119e184826114ab565b611a4157604080516001808252818301909252606091602080830190803683370190505090508481600081518110611a1557fe5b60200260200101906001600160a01b031690816001600160a01b031681525050611a3f8183611558565b505b6001600160a01b0380851660009081526004602081815260408084206001600160801b036001880116855282529283902083516370a0823160e01b81523093810193909352925192938616926370a08231926024808201939291829003018186803b158015611aaf57600080fd5b505afa158015611ac3573d6000803e3d6000fd5b505050506040513d6020811015611ad957600080fd5b505181556001808201805460ff191690911790553360009081526005602090815260408083206001600160a01b0389168452909152902080546000198101906001600160801b03851690839083908110611b2f57fe5b60009182526020909120600390910201546001600160801b03161015611cf457816040518060800160405280866001600160801b03168152602001670de0b6b3a76400006001600160801b0316815260200160036000336001600160a01b03166001600160a01b0316815260200190815260200160002060008b6001600160a01b03166001600160a01b031681526020019081526020016000205481526020016000815250908060018154018082558091505060019003906000526020600020906003020160009091909190915060008201518160000160006101000a8154816001600160801b0302191690836001600160801b0316021790555060208201518160000160106101000a8154816001600160801b0302191690836001600160801b0316021790555060408201518160010155606082015181600201555050611cc286600460008a6001600160a01b03166001600160a01b031681526020019081526020016000206000876001600160801b031681526020019081526020016000206000015461213f90919063ffffffff16565b6001600160a01b03881660009081526004602090815260408083206001600160801b0389168452909152902055612049565b836001600160801b0316828281548110611d0a57fe5b60009182526020909120600390910201546001600160801b03161415611dfd573360009081526003602090815260408083206001600160a01b038b1684529091529020548254839083908110611d5c57fe5b9060005260206000209060030201600101819055506000828281548110611d7f57fe5b906000526020600020906003020160020181905550670de0b6b3a7640000828281548110611da957fe5b60009182526020808320600390920290910180546001600160801b03948516600160801b029085161790556001600160a01b038a16825260048152604080832093881683529290522054611cc2908761213f565b6000826001830381548110611e0e57fe5b6000918252602080832060408051608081018252600390940290910180546001600160801b038082168652600160801b9091041692840192909252600182015490830152600281015460608301529250611e679061225c565b90508160020154881015611efb576000611eab83600201546110e8670de0b6b3a76400006001600160801b031661113887600101548761213f90919063ffffffff16565b6002840154909150611ebd908a61213f565b600284018190556001840154611edd91670de0b6b3a764000090846110c6565b83546001600160801b03918216600160801b02911617835550611f54565b611f20611f1583600201548a61213f90919063ffffffff16565b60018401549061213f565b60018301556000600283015581546001600160801b0316770de0b6b3a7640000000000000000000000000000000000001782555b6040805160808101825283546001600160801b038082168352600160801b90910416602082015260018401549181019190915260028301546060820152600090611f9d9061225c565b9050611fdf611fac838361213f565b6001600160a01b038c1660009081526004602090815260408083206001600160801b038d1684529091529020549061213f565b6001600160a01b038b1660008181526004602090815260408083206001600160801b038d16845282528083209490945533825260038152838220928252919091522054855486908690811061203057fe5b9060005260206000209060030201600101819055505050505b6040805187815290516001600160a01b0389169133917f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9181900360200190a3505060016000555050505050565b60015481565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b6000828201838110156114a4576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b600061045c826060015183604001516120c890919063ffffffff16565b600082821115612196576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6000826121ab5750600061045c565b828202828482816121b857fe5b04146114a45760405162461bcd60e51b81526004018080602001828103825260218152602001806123256021913960400191505060405180910390fd5b600080821161224b576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b81838161225457fe5b049392505050565b600061045c670de0b6b3a76400006001600160801b03166110e884602001516001600160801b03166111388661212256fe4174206c656173742031302065706f636873206d757374207061737320776974686f757420737563636573735374616b696e673a2065706f636820616c726561647920696e697469616c697a65645374616b696e673a20546f6b656e20616c6c6f77616e636520746f6f20736d616c6c5374616b696e673a2070726576696f75732065706f6368206e6f7420696e697469616c697a6564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a264697066735822122059d4d3a2132e733fa121c6a3707babb176d80b68dfa6b8060fec5586c0d45f9c64736f6c634300060c0033

Verified Source Code Full Match

Compiler: v0.6.12+commit.27d51765 EVM: istanbul Optimization: Yes (1000 runs)
Staking.sol 379 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract Staking is ReentrancyGuard {
    using SafeMath for uint256;

    uint128 constant private BASE_MULTIPLIER = uint128(1 * 10 ** 18);

    // timestamp for the epoch 1
    // everything before that is considered epoch 0 which won't have a reward but allows for the initial stake
    uint256 public epoch1Start;

    // duration of each epoch
    uint256 public epochDuration;

    // holds the current balance of the user for each token
    mapping(address => mapping(address => uint256)) private balances;

    struct Pool {
        uint256 size;
        bool set;
    }

    // for each token, we store the total pool size
    mapping(address => mapping(uint256 => Pool)) private poolSize;

    // a checkpoint of the valid balance of a user for an epoch
    struct Checkpoint {
        uint128 epochId;
        uint128 multiplier;
        uint256 startBalance;
        uint256 newDeposits;
    }

    // balanceCheckpoints[user][token][]
    mapping(address => mapping(address => Checkpoint[])) private balanceCheckpoints;

    mapping(address => uint128) private lastWithdrawEpochId;

    event Deposit(address indexed user, address indexed tokenAddress, uint256 amount);
    event Withdraw(address indexed user, address indexed tokenAddress, uint256 amount);
    event ManualEpochInit(address indexed caller, uint128 indexed epochId, address[] tokens);
    event EmergencyWithdraw(address indexed user, address indexed tokenAddress, uint256 amount);

    constructor (uint256 _epoch1Start, uint256 _epochDuration) public {
        epoch1Start = _epoch1Start;
        epochDuration = _epochDuration;
    }

    /*
     * Stores `amount` of `tokenAddress` tokens for the `user` into the vault
     */
    function deposit(address tokenAddress, uint256 amount) public nonReentrant {
        require(amount > 0, "Staking: Amount must be > 0");

        IERC20 token = IERC20(tokenAddress);
        uint256 allowance = token.allowance(msg.sender, address(this));
        require(allowance >= amount, "Staking: Token allowance too small");

        balances[msg.sender][tokenAddress] = balances[msg.sender][tokenAddress].add(amount);

        token.transferFrom(msg.sender, address(this), amount);

        // epoch logic
        uint128 currentEpoch = getCurrentEpoch();
        uint128 currentMultiplier = currentEpochMultiplier();

        if (!epochIsInitialized(tokenAddress, currentEpoch)) {
            address[] memory tokens = new address[](1);
            tokens[0] = tokenAddress;
            manualEpochInit(tokens, currentEpoch);
        }

        // update the next epoch pool size
        Pool storage pNextEpoch = poolSize[tokenAddress][currentEpoch + 1];
        pNextEpoch.size = token.balanceOf(address(this));
        pNextEpoch.set = true;

        Checkpoint[] storage checkpoints = balanceCheckpoints[msg.sender][tokenAddress];

        uint256 balanceBefore = getEpochUserBalance(msg.sender, tokenAddress, currentEpoch);

        // if there's no checkpoint yet, it means the user didn't have any activity
        // we want to store checkpoints both for the current epoch and next epoch because
        // if a user does a withdraw, the current epoch can also be modified and
        // we don't want to insert another checkpoint in the middle of the array as that could be expensive
        if (checkpoints.length == 0) {
            checkpoints.push(Checkpoint(currentEpoch, currentMultiplier, 0, amount));

            // next epoch => multiplier is 1, epoch deposits is 0
            checkpoints.push(Checkpoint(currentEpoch + 1, BASE_MULTIPLIER, amount, 0));
        } else {
            uint256 last = checkpoints.length - 1;

            // the last action happened in an older epoch (e.g. a deposit in epoch 3, current epoch is >=5)
            if (checkpoints[last].epochId < currentEpoch) {
                uint128 multiplier = computeNewMultiplier(
                    getCheckpointBalance(checkpoints[last]),
                    BASE_MULTIPLIER,
                    amount,
                    currentMultiplier
                );
                checkpoints.push(Checkpoint(currentEpoch, multiplier, getCheckpointBalance(checkpoints[last]), amount));
                checkpoints.push(Checkpoint(currentEpoch + 1, BASE_MULTIPLIER, balances[msg.sender][tokenAddress], 0));
            }
            // the last action happened in the previous epoch
            else if (checkpoints[last].epochId == currentEpoch) {
                checkpoints[last].multiplier = computeNewMultiplier(
                    getCheckpointBalance(checkpoints[last]),
                    checkpoints[last].multiplier,
                    amount,
                    currentMultiplier
                );
                checkpoints[last].newDeposits = checkpoints[last].newDeposits.add(amount);

                checkpoints.push(Checkpoint(currentEpoch + 1, BASE_MULTIPLIER, balances[msg.sender][tokenAddress], 0));
            }
            // the last action happened in the current epoch
            else {
                if (last >= 1 && checkpoints[last - 1].epochId == currentEpoch) {
                    checkpoints[last - 1].multiplier = computeNewMultiplier(
                        getCheckpointBalance(checkpoints[last - 1]),
                        checkpoints[last - 1].multiplier,
                        amount,
                        currentMultiplier
                    );
                    checkpoints[last - 1].newDeposits = checkpoints[last - 1].newDeposits.add(amount);
                }

                checkpoints[last].startBalance = balances[msg.sender][tokenAddress];
            }
        }

        uint256 balanceAfter = getEpochUserBalance(msg.sender, tokenAddress, currentEpoch);

        poolSize[tokenAddress][currentEpoch].size = poolSize[tokenAddress][currentEpoch].size.add(balanceAfter.sub(balanceBefore));

        emit Deposit(msg.sender, tokenAddress, amount);
    }

    /*
     * Removes the deposit of the user and sends the amount of `tokenAddress` back to the `user`
     */
    function withdraw(address tokenAddress, uint256 amount) public nonReentrant {
        require(balances[msg.sender][tokenAddress] >= amount, "Staking: balance too small");

        balances[msg.sender][tokenAddress] = balances[msg.sender][tokenAddress].sub(amount);

        IERC20 token = IERC20(tokenAddress);
        token.transfer(msg.sender, amount);

        // epoch logic
        uint128 currentEpoch = getCurrentEpoch();

        lastWithdrawEpochId[tokenAddress] = currentEpoch;

        if (!epochIsInitialized(tokenAddress, currentEpoch)) {
            address[] memory tokens = new address[](1);
            tokens[0] = tokenAddress;
            manualEpochInit(tokens, currentEpoch);
        }

        // update the pool size of the next epoch to its current balance
        Pool storage pNextEpoch = poolSize[tokenAddress][currentEpoch + 1];
        pNextEpoch.size = token.balanceOf(address(this));
        pNextEpoch.set = true;

        Checkpoint[] storage checkpoints = balanceCheckpoints[msg.sender][tokenAddress];
        uint256 last = checkpoints.length - 1;

        // note: it's impossible to have a withdraw and no checkpoints because the balance would be 0 and revert

        // there was a deposit in an older epoch (more than 1 behind [eg: previous 0, now 5]) but no other action since then
        if (checkpoints[last].epochId < currentEpoch) {
            checkpoints.push(Checkpoint(currentEpoch, BASE_MULTIPLIER, balances[msg.sender][tokenAddress], 0));

            poolSize[tokenAddress][currentEpoch].size = poolSize[tokenAddress][currentEpoch].size.sub(amount);
        }
        // there was a deposit in the `epochId - 1` epoch => we have a checkpoint for the current epoch
        else if (checkpoints[last].epochId == currentEpoch) {
            checkpoints[last].startBalance = balances[msg.sender][tokenAddress];
            checkpoints[last].newDeposits = 0;
            checkpoints[last].multiplier = BASE_MULTIPLIER;

            poolSize[tokenAddress][currentEpoch].size = poolSize[tokenAddress][currentEpoch].size.sub(amount);
        }
        // there was a deposit in the current epoch
        else {
            Checkpoint storage currentEpochCheckpoint = checkpoints[last - 1];

            uint256 balanceBefore = getCheckpointEffectiveBalance(currentEpochCheckpoint);

            // in case of withdraw, we have 2 branches:
            // 1. the user withdraws less than he added in the current epoch
            // 2. the user withdraws more than he added in the current epoch (including 0)
            if (amount < currentEpochCheckpoint.newDeposits) {
                uint128 avgDepositMultiplier = uint128(
                    balanceBefore.sub(currentEpochCheckpoint.startBalance).mul(BASE_MULTIPLIER).div(currentEpochCheckpoint.newDeposits)
                );

                currentEpochCheckpoint.newDeposits = currentEpochCheckpoint.newDeposits.sub(amount);

                currentEpochCheckpoint.multiplier = computeNewMultiplier(
                    currentEpochCheckpoint.startBalance,
                    BASE_MULTIPLIER,
                    currentEpochCheckpoint.newDeposits,
                    avgDepositMultiplier
                );
            } else {
                currentEpochCheckpoint.startBalance = currentEpochCheckpoint.startBalance.sub(
                    amount.sub(currentEpochCheckpoint.newDeposits)
                );
                currentEpochCheckpoint.newDeposits = 0;
                currentEpochCheckpoint.multiplier = BASE_MULTIPLIER;
            }

            uint256 balanceAfter = getCheckpointEffectiveBalance(currentEpochCheckpoint);

            poolSize[tokenAddress][currentEpoch].size = poolSize[tokenAddress][currentEpoch].size.sub(balanceBefore.sub(balanceAfter));

            checkpoints[last].startBalance = balances[msg.sender][tokenAddress];
        }

        emit Withdraw(msg.sender, tokenAddress, amount);
    }

    /*
     * manualEpochInit can be used by anyone to initialize an epoch based on the previous one
     * This is only applicable if there was no action (deposit/withdraw) in the current epoch.
     * Any deposit and withdraw will automatically initialize the current and next epoch.
     */
    function manualEpochInit(address[] memory tokens, uint128 epochId) public {
        require(epochId <= getCurrentEpoch(), "can't init a future epoch");

        for (uint i = 0; i < tokens.length; i++) {
            Pool storage p = poolSize[tokens[i]][epochId];

            if (epochId == 0) {
                p.size = uint256(0);
                p.set = true;
            } else {
                require(!epochIsInitialized(tokens[i], epochId), "Staking: epoch already initialized");
                require(epochIsInitialized(tokens[i], epochId - 1), "Staking: previous epoch not initialized");

                p.size = poolSize[tokens[i]][epochId - 1].size;
                p.set = true;
            }
        }

        emit ManualEpochInit(msg.sender, epochId, tokens);
    }

    function emergencyWithdraw(address tokenAddress) public {
        require((getCurrentEpoch() - lastWithdrawEpochId[tokenAddress]) >= 10, "At least 10 epochs must pass without success");

        uint256 totalUserBalance = balances[msg.sender][tokenAddress];
        require(totalUserBalance > 0, "Amount must be > 0");

        balances[msg.sender][tokenAddress] = 0;

        IERC20 token = IERC20(tokenAddress);
        token.transfer(msg.sender, totalUserBalance);

        emit EmergencyWithdraw(msg.sender, tokenAddress, totalUserBalance);
    }

    /*
     * Returns the valid balance of a user that was taken into consideration in the total pool size for the epoch
     * A deposit will only change the next epoch balance.
     * A withdraw will decrease the current epoch (and subsequent) balance.
     */
    function getEpochUserBalance(address user, address token, uint128 epochId) public view returns (uint256) {
        Checkpoint[] storage checkpoints = balanceCheckpoints[user][token];

        // if there are no checkpoints, it means the user never deposited any tokens, so the balance is 0
        if (checkpoints.length == 0 || epochId < checkpoints[0].epochId) {
            return 0;
        }

        uint min = 0;
        uint max = checkpoints.length - 1;

        // shortcut for blocks newer than the latest checkpoint == current balance
        if (epochId >= checkpoints[max].epochId) {
            return getCheckpointEffectiveBalance(checkpoints[max]);
        }

        // binary search of the value in the array
        while (max > min) {
            uint mid = (max + min + 1) / 2;
            if (checkpoints[mid].epochId <= epochId) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }

        return getCheckpointEffectiveBalance(checkpoints[min]);
    }

    /*
     * Returns the amount of `token` that the `user` has currently staked
     */
    function balanceOf(address user, address token) public view returns (uint256) {
        return balances[user][token];
    }

    /*
     * Returns the id of the current epoch derived from block.timestamp
     */
    function getCurrentEpoch() public view returns (uint128) {
        if (block.timestamp < epoch1Start) {
            return 0;
        }

        return uint128((block.timestamp - epoch1Start) / epochDuration + 1);
    }

    /*
     * Returns the total amount of `tokenAddress` that was locked from beginning to end of epoch identified by `epochId`
     */
    function getEpochPoolSize(address tokenAddress, uint128 epochId) public view returns (uint256) {
        // Premises:
        // 1. it's impossible to have gaps of uninitialized epochs
        // - any deposit or withdraw initialize the current epoch which requires the previous one to be initialized
        if (epochIsInitialized(tokenAddress, epochId)) {
            return poolSize[tokenAddress][epochId].size;
        }

        // epochId not initialized and epoch 0 not initialized => there was never any action on this pool
        if (!epochIsInitialized(tokenAddress, 0)) {
            return 0;
        }

        // epoch 0 is initialized => there was an action at some point but none that initialized the epochId
        // which means the current pool size is equal to the current balance of token held by the staking contract
        IERC20 token = IERC20(tokenAddress);
        return token.balanceOf(address(this));
    }

    /*
     * Returns the percentage of time left in the current epoch
     */
    function currentEpochMultiplier() public view returns (uint128) {
        uint128 currentEpoch = getCurrentEpoch();
        uint256 currentEpochEnd = epoch1Start + currentEpoch * epochDuration;
        uint256 timeLeft = currentEpochEnd - block.timestamp;
        uint128 multiplier = uint128(timeLeft * BASE_MULTIPLIER / epochDuration);

        return multiplier;
    }

    function computeNewMultiplier(uint256 prevBalance, uint128 prevMultiplier, uint256 amount, uint128 currentMultiplier) public pure returns (uint128) {
        uint256 prevAmount = prevBalance.mul(prevMultiplier).div(BASE_MULTIPLIER);
        uint256 addAmount = amount.mul(currentMultiplier).div(BASE_MULTIPLIER);
        uint128 newMultiplier = uint128(prevAmount.add(addAmount).mul(BASE_MULTIPLIER).div(prevBalance.add(amount)));

        return newMultiplier;
    }

    /*
     * Checks if an epoch is initialized, meaning we have a pool size set for it
     */
    function epochIsInitialized(address token, uint128 epochId) public view returns (bool) {
        return poolSize[token][epochId].set;
    }

    function getCheckpointBalance(Checkpoint memory c) internal pure returns (uint256) {
        return c.startBalance.add(c.newDeposits);
    }

    function getCheckpointEffectiveBalance(Checkpoint memory c) internal pure returns (uint256) {
        return getCheckpointBalance(c).mul(c.multiplier).div(BASE_MULTIPLIER);
    }
}
SafeMath.sol 214 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) return (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}
IERC20.sol 77 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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.
     *
     * IMPORTANT: 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);
}
ReentrancyGuard.sol 62 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

Read Contract

balanceOf 0xf7888aec → uint256
computeNewMultiplier 0x4be41dba → uint128
currentEpochMultiplier 0xce58a2a8 → uint128
epoch1Start 0xf4a4341d → uint256
epochDuration 0x4ff0876a → uint256
epochIsInitialized 0xaa579154 → bool
getCurrentEpoch 0xb97dd9e2 → uint128
getEpochPoolSize 0x2ca32d7e → uint256
getEpochUserBalance 0x8c028dd0 → uint256

Write Contract 4 functions

These functions modify contract state and require a wallet transaction to execute.

deposit 0x47e7ef24
address tokenAddress
uint256 amount
emergencyWithdraw 0x6ff1c9bc
address tokenAddress
manualEpochInit 0xea2c38ae
address[] tokens
uint128 epochId
withdraw 0xf3fef3a3
address tokenAddress
uint256 amount

Recent Transactions

No transactions found for this address