ETH Price: $3,386.98 (-1.43%)
Gas: 3 Gwei

Contract

0x955a31153e6764FE892757AcE79123ae996B0aFB
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Value
Unstake201961142024-06-29 8:20:594 hrs ago1719649259IN
0x955a3115...e996B0aFB
0 ETH0.00026072.68719194
Unstake201923632024-06-28 19:45:5917 hrs ago1719603959IN
0x955a3115...e996B0aFB
0 ETH0.000373443.27204894
Claim All201889002024-06-28 8:10:2328 hrs ago1719562223IN
0x955a3115...e996B0aFB
0 ETH0.000308883.72114994
Unbond201764092024-06-26 14:17:352 days ago1719411455IN
0x955a3115...e996B0aFB
0 ETH0.0008541914.70922295
Claim All201619912024-06-24 13:58:474 days ago1719237527IN
0x955a3115...e996B0aFB
0 ETH0.000638487.55590576
Unbond201619832024-06-24 13:57:114 days ago1719237431IN
0x955a3115...e996B0aFB
0 ETH0.000411797.0985643
Unstake201572482024-06-23 22:04:235 days ago1719180263IN
0x955a3115...e996B0aFB
0 ETH0.000262512.9622974
Claim All201572412024-06-23 22:02:595 days ago1719180179IN
0x955a3115...e996B0aFB
0 ETH0.00024932.95026665
Claim All201540222024-06-23 11:14:236 days ago1719141263IN
0x955a3115...e996B0aFB
0 ETH0.00015892.35758002
Unstake201496482024-06-22 20:32:356 days ago1719088355IN
0x955a3115...e996B0aFB
0 ETH0.000198212.23644351
Unstake201495282024-06-22 20:08:356 days ago1719086915IN
0x955a3115...e996B0aFB
0 ETH0.000178442.01336495
Unstake201495272024-06-22 20:08:236 days ago1719086903IN
0x955a3115...e996B0aFB
0 ETH0.000178982.01938504
Unbond201462822024-06-22 9:14:237 days ago1719047663IN
0x955a3115...e996B0aFB
0 ETH0.000116131.99983399
Claim All201419902024-06-21 18:49:477 days ago1718995787IN
0x955a3115...e996B0aFB
0 ETH0.000483825.7256447
Unbond201419842024-06-21 18:48:357 days ago1718995715IN
0x955a3115...e996B0aFB
0 ETH0.00033565.78034284
Unbond201023612024-06-16 5:48:3513 days ago1718516915IN
0x955a3115...e996B0aFB
0 ETH0.000155622.81568005
Unbond201023592024-06-16 5:48:1113 days ago1718516891IN
0x955a3115...e996B0aFB
0 ETH0.000155922.8210038
Unbond201023572024-06-16 5:47:4713 days ago1718516867IN
0x955a3115...e996B0aFB
0 ETH0.000163572.95947156
Unbond201023552024-06-16 5:47:2313 days ago1718516843IN
0x955a3115...e996B0aFB
0 ETH0.000155952.82162484
Unbond201023532024-06-16 5:46:5913 days ago1718516819IN
0x955a3115...e996B0aFB
0 ETH0.00015732.84602222
Claim All201023512024-06-16 5:46:3513 days ago1718516795IN
0x955a3115...e996B0aFB
0 ETH0.000489942.91878552
Unbond201023502024-06-16 5:46:2313 days ago1718516783IN
0x955a3115...e996B0aFB
0 ETH0.000155712.68149031
Unstake200995302024-06-15 20:17:5913 days ago1718482679IN
0x955a3115...e996B0aFB
0 ETH0.000305243.44451583
Claim All200995282024-06-15 20:17:3513 days ago1718482655IN
0x955a3115...e996B0aFB
0 ETH0.000222223.29698901
Claim All200970922024-06-15 12:07:5914 days ago1718453279IN
0x955a3115...e996B0aFB
0 ETH0.000387664.6701665
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x763dcfB2...60ABaFd2b
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
CellarStaking

Compiler Version
v0.8.15+commit.e14f2714

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 7 : CellarStaking.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import { ERC20 } from "solmate/src/tokens/ERC20.sol";
import { SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ICellarStaking } from "./interfaces/ICellarStaking.sol";

import "./Errors.sol";

/**
 * @title Sommelier Staking
 * @author Kevin Kennis
 *
 * Staking for Sommelier Cellars.
 *
 * This contract is inspired by the Synthetix staking rewards contract, Ampleforth's
 * token geyser, and Treasure DAO's MAGIC mine. However, there are unique improvements
 * and new features, specifically unbonding, as inspired by LP bonding on Osmosis.
 * Unbonding allows the contract to guarantee deposits for a certain amount of time,
 * increasing predictability and stickiness of TVL for Cellars.
 *
 * *********************************** Funding Flow ***********************************
 *
 * 1) The contract owner calls 'notifyRewardAmount' to specify an initial schedule of rewards
 *    The contract should hold enough the distribution token to fund the
 *    specified reward schedule, where the length of the reward schedule is defined by
 *    epochDuration. This duration can also be changed by the owner, and any change will apply
 *    to future calls to 'notifyRewardAmount' (but will not affect active schedules).
 * 2) At a future time, the contract owner may call 'notifyRewardAmount' again to extend the
 *    staking program with new rewards. These new schedules may distribute more or less
 *    rewards than previous epochs. If a previous epoch is not finished, any leftover rewards
 *    get rolled into the new schedule, increasing the reward rate. Reward schedules always
 *    end exactly 'epochDuration' seconds from the most recent time 'notifyRewardAmount' has been
 *    called.
 *
 * ********************************* Staking Lifecycle ********************************
 *
 * 1) A user may deposit a certain amount of tokens to stake, and is required to lock
 *    those tokens for a specified amount of time. There are three locking options:
 *    one day, one week, or one month. Longer locking times receive larger 'boosts',
 *    that the deposit will receive a larger proportional amount of shares. A user
 *    may not unstake until they choose to unbond, and time defined by the lock has
 *    elapsed during unbonding.
 * 2) When a user wishes to withdraw, they must first "unbond" their stake, which starts
 *    a timer equivalent to the lock time. They still receive their rewards during this
 *    time, but forfeit any locktime boosts. A user may cancel the unbonding period at any
 *    time to regain their boosts, which will set the unbonding timer back to 0.
 * 2) Once the lock has elapsed, a user may unstake their deposit, either partially
 *    or in full. The user will continue to receive the same 'boosted' amount of rewards
 *    until they unstake. The user may unstake all of their deposits at once, as long
 *    as all of the lock times have elapsed. When unstaking, the user will also receive
 *    all eligible rewards for all deposited stakes, which accumulate linearly.
 * 3) At any time, a user may claim their available rewards for their deposits. Rewards
 *    accumulate linearly and can be claimed at any time, whether or not the lock has
 *    for a given deposit has expired. The user can claim rewards for a specific deposit,
 *    or may choose to collect all eligible rewards at once.
 *
 * ************************************ Accounting ************************************
 *
 * The contract uses an accounting mechanism based on the 'rewardPerToken' model,
 * originated by the Synthetix staking rewards contract. First, token deposits are accounted
 * for, with synthetic "boosted" amounts used for reward calculations. As time passes,
 * rewardPerToken continues to accumulate, whereas the value of 'rewardPerToken' will match
 * the reward due to a single token deposited before the first ever rewards were scheduled.
 *
 * At each accounting checkpoint, rewardPerToken will be recalculated, and every time an
 * existing stake is 'touched', this value is used to calculate earned rewards for that
 * stake. Each stake tracks a 'rewardPerTokenPaid' value, which represents the 'rewardPerToken'
 * value the last time the stake calculated "earned" rewards. Every recalculation pays the difference.
 * This ensures no earning is double-counted. When a new stake is deposited, its
 * initial 'rewardPerTokenPaid' is set to the current 'rewardPerToken' in the contract,
 * ensuring it will not receive any rewards emitted during the period before deposit.
 *
 * The following example applies to a given epoch of 100 seconds, with a reward rate
 * of 100 tokens per second:
 *
 * a) User 1 deposits a stake of 50 before the epoch begins
 * b) User 2 deposits a stake of 20 at second 20 of the epoch
 * c) User 3 deposits a stake of 100 at second 50 of the epoch
 *
 * In this case,
 *
 * a) At second 20, before User 2's deposit, rewardPerToken will be 40
 *     (2000 total tokens emitted over 20 seconds / 50 staked).
 * b) At second 50, before User 3's deposit, rewardPerToken will be 82.857
 *     (previous 40 + 3000 tokens emitted over 30 seconds / 70 staked == 42.857)
 * c) At second 100, when the period is over, rewardPerToken will be 112.267
 *     (previous 82.857 + 5000 tokens emitted over 50 seconds / 170 staked == 29.41)
 *
 *
 * Then, each user will receive rewards proportional to the their number of tokens. At second 100:
 * a) User 1 will receive 50 * 112.267 = 5613.35 rewards
 * b) User 2 will receive 20 * (112.267 - 40) = 1445.34
 *       (40 is deducted because it was the current rewardPerToken value on deposit)
 * c) User 3 will receive 100 * (112.267 - 82.857) = 2941
 *       (82.857 is deducted because it was the current rewardPerToken value on deposit)
 *
 * Depending on deposit times, this accumulation may take place over multiple
 * reward periods, and the total rewards earned is simply the sum of rewards earned for
 * each period. A user may also have multiple discrete deposits, which are all
 * accounted for separately due to timelocks and locking boosts. Therefore,
 * a user's total earned rewards are a function of their rewards across
 * the proportional tokens deposited, across different ranges of rewardPerToken.
 *
 * Reward accounting takes place before every operation which may change
 * accounting calculations (minting of new shares on staking, burning of
 * shares on unstaking, or claiming, which decrements eligible rewards).
 * This is gas-intensive but unavoidable, since retroactive accounting
 * based on previous proportionate shares would require a prohibitive
 * amount of storage of historical state. On every accounting run, there
 * are a number of safety checks to ensure that all reward tokens are
 * accounted for and that no accounting time periods have been missed.
 *
 */
contract CellarStaking is ICellarStaking, Ownable {
    using SafeTransferLib for ERC20;

    // ============================================ STATE ==============================================

    // ============== Constants ==============

    uint256 public constant ONE = 1e18;
    uint256 public constant ONE_DAY = 60 * 60 * 24;
    uint256 public constant ONE_WEEK = ONE_DAY * 7;
    uint256 public constant TWO_WEEKS = ONE_WEEK * 2;

    uint256 public immutable SHORT_BOOST;
    uint256 public immutable MEDIUM_BOOST;
    uint256 public immutable LONG_BOOST;

    uint256 public immutable SHORT_BOOST_TIME;
    uint256 public immutable MEDIUM_BOOST_TIME;
    uint256 public immutable LONG_BOOST_TIME;

    // ============ Global State =============

    ERC20 public immutable override stakingToken;
    ERC20 public immutable override distributionToken;
    uint256 public override currentEpochDuration;
    uint256 public override nextEpochDuration;
    uint256 public override rewardsReady;

    uint256 public override minimumDeposit;
    uint256 public override endTimestamp;
    uint256 public override totalDeposits;
    uint256 public override totalDepositsWithBoost;
    uint256 public override rewardRate;
    uint256 public override rewardPerTokenStored;

    uint256 private lastAccountingTimestamp = block.timestamp;

    /// @notice Emergency states in case of contract malfunction.
    bool public override paused;
    bool public override ended;
    bool public override claimable;

    // ============= User State ==============

    /// @notice user => all user's staking positions
    mapping(address => UserStake[]) public stakes;

    // ========================================== CONSTRUCTOR ===========================================

    /**
     * @param _owner                The owner of the staking contract - will immediately receive ownership.
     * @param _stakingToken         The token users will deposit in order to stake.
     * @param _distributionToken    The token the staking contract will distribute as rewards.
     * @param _epochDuration        The length of a reward schedule.
     * @param shortBoost            The boost multiplier for the short unbonding time.
     * @param mediumBoost           The boost multiplier for the medium unbonding time.
     * @param longBoost             The boost multiplier for the long unbonding time.
     * @param shortBoostTime        The short unbonding time.
     * @param mediumBoostTime       The medium unbonding time.
     * @param longBoostTime         The long unbonding time.
     */
    constructor(
        address _owner,
        ERC20 _stakingToken,
        ERC20 _distributionToken,
        uint256 _epochDuration,
        uint256 shortBoost,
        uint256 mediumBoost,
        uint256 longBoost,
        uint256 shortBoostTime,
        uint256 mediumBoostTime,
        uint256 longBoostTime
    ) {
        stakingToken = _stakingToken;
        distributionToken = _distributionToken;
        nextEpochDuration = _epochDuration;

        SHORT_BOOST = shortBoost;
        MEDIUM_BOOST = mediumBoost;
        LONG_BOOST = longBoost;

        SHORT_BOOST_TIME = shortBoostTime;
        MEDIUM_BOOST_TIME = mediumBoostTime;
        LONG_BOOST_TIME = longBoostTime;

        transferOwnership(_owner);
    }

    // ======================================= STAKING OPERATIONS =======================================

    /**
     * @notice  Make a new deposit into the staking contract. Longer locks receive reward boosts.
     * @dev     Specified amount of stakingToken must be approved for withdrawal by the caller.
     * @dev     Valid lock values are 0 (one day), 1 (one week), and 2 (two weeks).
     *
     * @param amount                The amount of the stakingToken to stake.
     * @param lock                  The amount of time to lock stake for.
     */
    function stake(uint256 amount, Lock lock) external override whenNotPaused updateRewards {
        if (amount == 0) revert USR_ZeroDeposit();
        if (amount < minimumDeposit) revert USR_MinimumDeposit(amount, minimumDeposit);

        if (totalDeposits == 0 && rewardsReady > 0) {
            _startProgram(rewardsReady);
            rewardsReady = 0;

            // Need to run updateRewards again
            _updateRewards();
        } else if (block.timestamp > endTimestamp) {
            revert STATE_NoRewardsLeft();
        }

        // Do share accounting and populate user stake information
        (uint256 boost, ) = _getBoost(lock);
        uint256 amountWithBoost = amount + ((amount * boost) / ONE);

        stakes[msg.sender].push(
            UserStake({
                amount: uint112(amount),
                amountWithBoost: uint112(amountWithBoost),
                unbondTimestamp: 0,
                rewardPerTokenPaid: uint112(rewardPerTokenStored),
                rewards: 0,
                lock: lock
            })
        );

        // Update global state
        totalDeposits += amount;
        totalDepositsWithBoost += amountWithBoost;

        stakingToken.safeTransferFrom(msg.sender, address(this), amount);

        emit Stake(msg.sender, stakes[msg.sender].length - 1, amount);
    }

    /**
     * @notice  Unbond a specified amount from a certain deposited stake.
     * @dev     After the unbond time elapses, the deposit can be unstaked.
     *
     * @param depositId             The specified deposit to unstake from.
     *
     */
    function unbond(uint256 depositId) external override whenNotPaused updateRewards {
        _unbond(depositId);
    }

    /**
     * @notice  Unbond all user deposits.
     * @dev     Different deposits may have different timelocks.
     *
     */
    function unbondAll() external override whenNotPaused updateRewards {
        // Individually unbond each deposit
        UserStake[] storage userStakes = stakes[msg.sender];
        for (uint256 i = 0; i < userStakes.length; i++) {
            UserStake storage s = userStakes[i];

            if (s.amount != 0 && s.unbondTimestamp == 0) {
                _unbond(i);
            }
        }
    }

    /**
     * @dev     Contains all logic for processing an unbond operation.
     *          For the given deposit, sets an unlock time, and
     *          reverts boosts to 0.
     *
     * @param depositId             The specified deposit to unbond from.
     */
    function _unbond(uint256 depositId) internal {
        // Fetch stake and make sure it is withdrawable
        UserStake storage s = stakes[msg.sender][depositId];

        uint256 depositAmount = s.amount;
        if (depositAmount == 0) revert USR_NoDeposit(depositId);
        if (s.unbondTimestamp > 0) revert USR_AlreadyUnbonding(depositId);

        _updateRewardForStake(msg.sender, depositId);

        // Remove any lock boosts
        uint256 depositAmountReduced = s.amountWithBoost - depositAmount;
        (, uint256 lockDuration) = _getBoost(s.lock);

        s.amountWithBoost = uint112(depositAmount);
        s.unbondTimestamp = uint32(block.timestamp + lockDuration);

        totalDepositsWithBoost -= uint112(depositAmountReduced);

        emit Unbond(msg.sender, depositId, depositAmount);
    }

    /**
     * @notice  Cancel an unbonding period for a stake that is currently unbonding.
     * @dev     Resets the unbonding timer and reinstates any lock boosts.
     *
     * @param depositId             The specified deposit to unstake from.
     *
     */
    function cancelUnbonding(uint256 depositId) external override whenNotPaused updateRewards {
        _cancelUnbonding(depositId);
    }

    /**
     * @notice  Cancel an unbonding period for all stakes.
     * @dev     Only cancels stakes that are unbonding.
     *
     */
    function cancelUnbondingAll() external override whenNotPaused updateRewards {
        // Individually unbond each deposit
        UserStake[] storage userStakes = stakes[msg.sender];
        for (uint256 i = 0; i < userStakes.length; i++) {
            UserStake storage s = userStakes[i];

            if (s.amount != 0 && s.unbondTimestamp != 0) {
                _cancelUnbonding(i);
            }
        }
    }

    /**
     * @dev     Contains all logic for cancelling an unbond operation.
     *          For the given deposit, resets the unbonding timer, and
     *          reverts boosts to amount determined by lock.
     *
     * @param depositId             The specified deposit to unbond from.
     */
    function _cancelUnbonding(uint256 depositId) internal {
        // Fetch stake and make sure it is withdrawable
        UserStake storage s = stakes[msg.sender][depositId];

        uint256 depositAmount = s.amount;
        if (depositAmount == 0) revert USR_NoDeposit(depositId);
        if (s.unbondTimestamp == 0) revert USR_NotUnbonding(depositId);

        _updateRewardForStake(msg.sender, depositId);

        // Reinstate
        (uint256 boost, ) = _getBoost(s.lock);
        uint256 depositAmountIncreased = (s.amount * boost) / ONE;
        uint256 amountWithBoost = s.amount + depositAmountIncreased;

        s.amountWithBoost = uint112(amountWithBoost);
        s.unbondTimestamp = 0;

        totalDepositsWithBoost += depositAmountIncreased;

        emit CancelUnbond(msg.sender, depositId);
    }

    /**
     * @notice  Unstake a specific deposited stake.
     * @dev     The unbonding time for the specified deposit must have elapsed.
     * @dev     Unstaking automatically claims available rewards for the deposit.
     *
     * @param depositId             The specified deposit to unstake from.
     *
     * @return reward               The amount of accumulated rewards since the last reward claim.
     */
    function unstake(uint256 depositId) external override whenNotPaused updateRewards returns (uint256 reward) {
        return _unstake(depositId);
    }

    /**
     * @notice  Unstake all user deposits.
     * @dev     Only unstakes rewards that are unbonded.
     * @dev     Unstaking automatically claims all available rewards.
     *
     * @return rewards              The amount of accumulated rewards since the last reward claim.
     */
    function unstakeAll() external override whenNotPaused updateRewards returns (uint256[] memory) {
        // Individually unstake each deposit
        UserStake[] storage userStakes = stakes[msg.sender];
        uint256[] memory rewards = new uint256[](userStakes.length);

        for (uint256 i = 0; i < userStakes.length; i++) {
            UserStake storage s = userStakes[i];

            if (s.amount != 0 && s.unbondTimestamp != 0 && block.timestamp >= s.unbondTimestamp) {
                rewards[i] = _unstake(i);
            }
        }

        return rewards;
    }

    /**
     * @dev     Contains all logic for processing an unstake operation.
     *          For the given deposit, does share accounting and burns
     *          shares, returns staking tokens to the original owner,
     *          updates global deposit and share trackers, and claims
     *          rewards for the given deposit.
     *
     * @param depositId             The specified deposit to unstake from.
     */
    function _unstake(uint256 depositId) internal returns (uint256 reward) {
        // Fetch stake and make sure it is withdrawable
        UserStake storage s = stakes[msg.sender][depositId];

        uint256 depositAmount = s.amount;

        if (depositAmount == 0) revert USR_NoDeposit(depositId);
        if (s.unbondTimestamp == 0 || block.timestamp < s.unbondTimestamp) revert USR_StakeLocked(depositId);

        _updateRewardForStake(msg.sender, depositId);

        // Start unstaking
        reward = s.rewards;

        s.amount = 0;
        s.amountWithBoost = 0;
        s.rewards = 0;

        // Update global state
        // Boosted amount same as deposit amount, since we have unbonded
        totalDeposits -= depositAmount;
        totalDepositsWithBoost -= depositAmount;

        // Distribute stake
        stakingToken.safeTransfer(msg.sender, depositAmount);

        // Distribute reward
        distributionToken.safeTransfer(msg.sender, reward);

        emit Unstake(msg.sender, depositId, depositAmount, reward);
    }

    /**
     * @notice  Claim rewards for a given deposit.
     * @dev     Rewards accumulate linearly since deposit.
     *
     * @param depositId             The specified deposit for which to claim rewards.
     *
     * @return reward               The amount of accumulated rewards since the last reward claim.
     */
    function claim(uint256 depositId) external override whenNotPaused updateRewards returns (uint256 reward) {
        return _claim(depositId);
    }

    /**
     * @notice  Claim all available rewards.
     * @dev     Rewards accumulate linearly.
     *
     *
     * @return rewards               The amount of accumulated rewards since the last reward claim.
     *                               Each element of the array specified rewards for the corresponding
     *                               indexed deposit.
     */
    function claimAll() external override whenNotPaused updateRewards returns (uint256[] memory rewards) {
        // Individually claim for each stake
        UserStake[] storage userStakes = stakes[msg.sender];
        rewards = new uint256[](userStakes.length);

        for (uint256 i = 0; i < userStakes.length; i++) {
            rewards[i] = _claim(i);
        }
    }

    /**
     * @dev Contains all logic for processing a claim operation.
     *      Relies on previous reward accounting done before
     *      processing external functions. Updates the amount
     *      of rewards claimed so rewards cannot be claimed twice.
     *
     *
     * @param depositId             The specified deposit to claim rewards for.
     *
     * @return reward               The amount of accumulated rewards since the last reward claim.
     */
    function _claim(uint256 depositId) internal returns (uint256 reward) {
        // Fetch stake and make sure it is valid
        UserStake storage s = stakes[msg.sender][depositId];

        _updateRewardForStake(msg.sender, depositId);

        reward = s.rewards;

        // Distribute reward
        if (reward > 0) {
            s.rewards = 0;

            distributionToken.safeTransfer(msg.sender, reward);

            emit Claim(msg.sender, depositId, reward);
        }
    }

    /**
     * @notice  Unstake and return all staked tokens to the caller.
     * @dev     In emergency mode, staking time locks do not apply.
     */
    function emergencyUnstake() external override {
        if (!ended) revert STATE_NoEmergencyUnstake();

        UserStake[] storage userStakes = stakes[msg.sender];
        for (uint256 i = 0; i < userStakes.length; i++) {
            if (claimable) _updateRewardForStake(msg.sender, i);

            UserStake storage s = userStakes[i];
            uint256 amount = s.amount;

            if (amount > 0) {
                // Update global state
                totalDeposits -= amount;
                totalDepositsWithBoost -= s.amountWithBoost;

                s.amount = 0;
                s.amountWithBoost = 0;

                stakingToken.transfer(msg.sender, amount);

                emit EmergencyUnstake(msg.sender, i, amount);
            }
        }
    }

    /**
     * @notice  Claim any accumulated rewards in emergency mode.
     * @dev     In emergency node, no additional reward accounting is done.
     *          Rewards do not accumulate after emergency mode begins,
     *          so any earned amount is only retroactive to when the contract
     *          was active.
     */
    function emergencyClaim() external override {
        if (!ended) revert STATE_NoEmergencyUnstake();
        if (!claimable) revert STATE_NoEmergencyClaim();

        uint256 reward;

        UserStake[] storage userStakes = stakes[msg.sender];
        for (uint256 i = 0; i < userStakes.length; i++) {
            _updateRewardForStake(msg.sender, i);

            UserStake storage s = userStakes[i];

            reward += s.rewards;
            s.rewards = 0;
        }

        if (reward > 0) {
            distributionToken.safeTransfer(msg.sender, reward);

            // No need for per-stake events like emergencyUnstake:
            // don't need to make sure positions were unwound
            emit EmergencyClaim(msg.sender, reward);
        }
    }

    // ======================================== ADMIN OPERATIONS ========================================

    /**
     * @notice Specify a new schedule for staking rewards. Contract must already hold enough tokens.
     * @dev    Can only be called by reward distributor. Owner must approve distributionToken for withdrawal.
     * @dev    epochDuration must divide reward evenly, otherwise any remainder will be lost.
     *
     * @param reward                The amount of rewards to distribute per second.
     */
    function notifyRewardAmount(uint256 reward) external override onlyOwner updateRewards {
        if (block.timestamp < endTimestamp) {
            uint256 remaining = endTimestamp - block.timestamp;
            uint256 leftover = remaining * rewardRate;
            reward += leftover;
        }

        if (reward < nextEpochDuration) revert USR_ZeroRewardsPerEpoch();

        uint256 rewardBalance = distributionToken.balanceOf(address(this));
        uint256 pendingRewards = reward + rewardsReady;
        if (rewardBalance < pendingRewards) revert STATE_RewardsNotFunded(rewardBalance, pendingRewards);

        // prevent overflow when computing rewardPerToken
        uint256 proposedRewardRate = reward / nextEpochDuration;
        if (proposedRewardRate >= ((type(uint256).max / ONE) / nextEpochDuration)) {
            revert USR_RewardTooLarge();
        }

        if (totalDeposits == 0) {
            // No deposits yet, so keep rewards pending until first deposit
            // Incrementing in case it is called twice
            rewardsReady += reward;
        } else {
            // Ready to start
            _startProgram(reward);
        }

        lastAccountingTimestamp = block.timestamp;
    }

    /**
     * @notice Change the length of a reward epoch for future reward schedules.
     *
     * @param _epochDuration        The new duration for reward schedules.
     */
    function setRewardsDuration(uint256 _epochDuration) external override onlyOwner {
        if (rewardsReady > 0) revert STATE_RewardsReady();

        nextEpochDuration = _epochDuration;
        emit EpochDurationChange(nextEpochDuration);
    }

    /**
     * @notice Specify a minimum deposit for staking.
     * @dev    Can only be called by owner.
     *
     * @param _minimum              The minimum deposit for each new stake.
     */
    function setMinimumDeposit(uint256 _minimum) external override onlyOwner {
        minimumDeposit = _minimum;
    }

    /**
     * @notice Pause the contract. Pausing prevents staking, unstaking, claiming
     *         rewards, and scheduling new rewards. Should only be used
     *         in an emergency.
     *
     * @param _paused               Whether the contract should be paused.
     */
    function setPaused(bool _paused) external override onlyOwner {
        paused = _paused;
    }

    /**
     * @notice Stops the contract - this is irreversible. Should only be used
     *         in an emergency, for example an irreversible accounting bug
     *         or an exploit. Enables all depositors to withdraw their stake
     *         instantly. Also stops new rewards accounting.
     *
     * @param makeRewardsClaimable  Whether any previously accumulated rewards should be claimable.
     */
    function emergencyStop(bool makeRewardsClaimable) external override onlyOwner {
        if (ended) revert STATE_AlreadyShutdown();

        // Update state and put in irreversible emergency mode
        ended = true;
        claimable = makeRewardsClaimable;
        uint256 amountToReturn = distributionToken.balanceOf(address(this));

        if (makeRewardsClaimable) {
            // Update rewards one more time
            _updateRewards();

            // Return any remaining, since new calculation is stopped
            uint256 remaining = endTimestamp > block.timestamp ? (endTimestamp - block.timestamp) * rewardRate : 0;

            // Make sure any rewards except for remaining are kept for claims
            uint256 amountToKeep = rewardRate * currentEpochDuration - remaining;

            amountToReturn -= amountToKeep;
        }

        // Send distribution token back to owner
        distributionToken.transfer(msg.sender, amountToReturn);

        emit EmergencyStop(msg.sender, makeRewardsClaimable);
    }

    // ======================================= STATE INFORMATION =======================================

    /**
     * @notice Returns the latest time to account for in the reward program.
     *
     * @return timestamp           The latest time to calculate.
     */
    function latestRewardsTimestamp() public view override returns (uint256) {
        return block.timestamp < endTimestamp ? block.timestamp : endTimestamp;
    }

    /**
     * @notice Returns the amount of reward to distribute per currently-depostied token.
     *         Will update on changes to total deposit balance or reward rate.
     * @dev    Sets rewardPerTokenStored.
     *
     *
     * @return newRewardPerTokenStored  The new rewards to distribute per token.
     * @return latestTimestamp          The latest time to calculate.
     */
    function rewardPerToken() public view override returns (uint256 newRewardPerTokenStored, uint256 latestTimestamp) {
        latestTimestamp = latestRewardsTimestamp();

        if (totalDeposits == 0) return (rewardPerTokenStored, latestTimestamp);

        uint256 timeElapsed = latestTimestamp - lastAccountingTimestamp;
        uint256 rewardsForTime = timeElapsed * rewardRate;
        uint256 newRewardsPerToken = (rewardsForTime * ONE) / totalDepositsWithBoost;

        newRewardPerTokenStored = rewardPerTokenStored + newRewardsPerToken;
    }

    /**
     * @notice Gets all of a user's stakes.
     * @dev This is provided because Solidity converts public arrays into index getters,
     *      but we need a way to allow external contracts and users to access the whole array.

     * @param user                      The user whose stakes to get.
     *
     * @return stakes                   Array of all user's stakes
     */
    function getUserStakes(address user) public view override returns (UserStake[] memory) {
        return stakes[user];
    }

    // ============================================ HELPERS ============================================

    /**
     * @dev Modifier to apply reward updates before functions that change accounts.
     */
    modifier updateRewards() {
        _updateRewards();
        _;
    }

    /**
     * @dev Blocks calls if contract is paused or killed.
     */
    modifier whenNotPaused() {
        if (paused) revert STATE_ContractPaused();
        if (ended) revert STATE_ContractKilled();
        _;
    }

    /**
     * @dev Update reward accounting for the global state totals.
     */
    function _updateRewards() internal {
        (rewardPerTokenStored, lastAccountingTimestamp) = rewardPerToken();
    }

    /**
     * @dev On initial deposit, start the rewards program.
     *
     * @param reward                    The pending rewards to start distributing.
     */
    function _startProgram(uint256 reward) internal {
        // Assumptions
        // Total deposits are now (mod current tx), no ongoing program
        // Rewards are already funded (since checked in notifyRewardAmount)

        rewardRate = reward / nextEpochDuration;
        endTimestamp = block.timestamp + nextEpochDuration;
        currentEpochDuration = nextEpochDuration;

        emit Funding(reward, endTimestamp);
    }

    /**
     * @dev Update reward for a specific user stake.
     */
    function _updateRewardForStake(address user, uint256 depositId) internal {
        UserStake storage s = stakes[user][depositId];
        if (s.amount == 0) return;

        uint256 earned = _earned(s);
        s.rewards += uint112(earned);

        s.rewardPerTokenPaid = uint112(rewardPerTokenStored);
    }

    /**
     * @dev Return how many rewards a stake has earned and has claimable.
     */
    function _earned(UserStake memory s) internal view returns (uint256) {
        uint256 rewardPerTokenAcc = rewardPerTokenStored - s.rewardPerTokenPaid;
        uint256 newRewards = (s.amountWithBoost * rewardPerTokenAcc) / ONE;

        return newRewards;
    }

    /**
     * @dev Maps Lock enum values to corresponding lengths of time and reward boosts.
     */
    function _getBoost(Lock _lock) internal view returns (uint256 boost, uint256 timelock) {
        if (_lock == Lock.short) {
            return (SHORT_BOOST, SHORT_BOOST_TIME);
        } else if (_lock == Lock.medium) {
            return (MEDIUM_BOOST, MEDIUM_BOOST_TIME);
        } else if (_lock == Lock.long) {
            return (LONG_BOOST, LONG_BOOST_TIME);
        } else {
            revert USR_InvalidLockValue(uint256(_lock));
        }
    }
}

File 2 of 7 : ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

File 3 of 7 : SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

File 4 of 7 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 5 of 7 : ICellarStaking.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import { ERC20 } from "solmate/src/tokens/ERC20.sol";

/**
 * @title Sommelier Staking Interface
 * @author Kevin Kennis
 *
 * @notice Full documentation in implementation contract.
 */
interface ICellarStaking {
    // ===================== Events =======================

    event Funding(uint256 rewardAmount, uint256 rewardEnd);
    event Stake(address indexed user, uint256 depositId, uint256 amount);
    event Unbond(address indexed user, uint256 depositId, uint256 amount);
    event CancelUnbond(address indexed user, uint256 depositId);
    event Unstake(address indexed user, uint256 depositId, uint256 amount, uint256 reward);
    event Claim(address indexed user, uint256 depositId, uint256 amount);
    event EmergencyStop(address owner, bool claimable);
    event EmergencyUnstake(address indexed user, uint256 depositId, uint256 amount);
    event EmergencyClaim(address indexed user, uint256 amount);
    event EpochDurationChange(uint256 duration);

    // ===================== Structs ======================

    enum Lock {
        short,
        medium,
        long
    }

    struct UserStake {
        uint112 amount;
        uint112 amountWithBoost;
        uint32 unbondTimestamp;
        uint112 rewardPerTokenPaid;
        uint112 rewards;
        Lock lock;
    }

    // ============== Public State Variables ==============

    function stakingToken() external returns (ERC20);

    function distributionToken() external returns (ERC20);

    function currentEpochDuration() external returns (uint256);

    function nextEpochDuration() external returns (uint256);

    function rewardsReady() external returns (uint256);

    function minimumDeposit() external returns (uint256);

    function endTimestamp() external returns (uint256);

    function totalDeposits() external returns (uint256);

    function totalDepositsWithBoost() external returns (uint256);

    function rewardRate() external returns (uint256);

    function rewardPerTokenStored() external returns (uint256);

    function paused() external returns (bool);

    function ended() external returns (bool);

    function claimable() external returns (bool);

    // ================ User Functions ================

    function stake(uint256 amount, Lock lock) external;

    function unbond(uint256 depositId) external;

    function unbondAll() external;

    function cancelUnbonding(uint256 depositId) external;

    function cancelUnbondingAll() external;

    function unstake(uint256 depositId) external returns (uint256 reward);

    function unstakeAll() external returns (uint256[] memory rewards);

    function claim(uint256 depositId) external returns (uint256 reward);

    function claimAll() external returns (uint256[] memory rewards);

    function emergencyUnstake() external;

    function emergencyClaim() external;

    // ================ Admin Functions ================

    function notifyRewardAmount(uint256 reward) external;

    function setRewardsDuration(uint256 _epochDuration) external;

    function setMinimumDeposit(uint256 _minimum) external;

    function setPaused(bool _paused) external;

    function emergencyStop(bool makeRewardsClaimable) external;

    // ================ View Functions ================

    function latestRewardsTimestamp() external view returns (uint256);

    function rewardPerToken() external view returns (uint256, uint256);

    function getUserStakes(address user) external view returns (UserStake[] memory);
}

File 6 of 7 : Errors.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

// ========================================== USER ERRORS ===========================================

/**
 * @dev These errors represent invalid user input to functions. Where appropriate, the invalid value
 *      is specified along with constraints. These errors can be resolved by callers updating their
 *      arguments.
 */

/**
 * @notice Attempted an action with zero assets.
 */
error USR_ZeroAssets();

/**
 * @notice Attempted an action with zero shares.
 */
error USR_ZeroShares();

/**
 * @notice Attempted deposit more than the max deposit.
 * @param assets the assets user attempted to deposit
 * @param maxDeposit the max assets that can be deposited
 */
error USR_DepositRestricted(uint256 assets, uint256 maxDeposit);

/**
 * @notice Attempted to transfer more active shares than the user has.
 * @param activeShares amount of shares user has
 * @param attemptedActiveShares amount of shares user tried to transfer
 */
error USR_NotEnoughActiveShares(uint256 activeShares, uint256 attemptedActiveShares);

/**
 * @notice Attempted swap into an asset that is not the current asset of the position.
 * @param assetOut address of the asset attempted to swap to
 * @param currentAsset address of the current asset of position
 */
error USR_InvalidSwap(address assetOut, address currentAsset);

/**
 * @notice Attempted to sweep an asset that is managed by the cellar.
 * @param token address of the token that can't be sweeped
 */
error USR_ProtectedAsset(address token);

/**
 * @notice Attempted rebalance into the same position.
 * @param position address of the position
 */
error USR_SamePosition(address position);

/**
 * @notice Attempted to update the position to one that is not supported by the platform.
 * @param unsupportedPosition address of the unsupported position
 */
error USR_UnsupportedPosition(address unsupportedPosition);

/**
 * @notice Attempted an operation on an untrusted position.
 * @param position address of the position
 */
error USR_UntrustedPosition(address position);

/**
 * @notice Attempted to update a position to an asset that uses an incompatible amount of decimals.
 * @param newDecimals decimals of precision that the new position uses
 * @param maxDecimals maximum decimals of precision for a position to be compatible with the cellar
 */
error USR_TooManyDecimals(uint8 newDecimals, uint8 maxDecimals);

/**
 * @notice User attempted to stake zero amout.
 */
error USR_ZeroDeposit();

/**
 * @notice User attempted to stake an amount smaller than the minimum deposit.
 *
 * @param amount                Amount user attmpted to stake.
 * @param minimumDeposit        The minimum deopsit amount accepted.
 */
error USR_MinimumDeposit(uint256 amount, uint256 minimumDeposit);

/**
 * @notice The specified deposit ID does not exist for the caller.
 *
 * @param depositId             The deposit ID provided for lookup.
 */
error USR_NoDeposit(uint256 depositId);

/**
 * @notice The user is attempting to cancel unbonding for a deposit which is not unbonding.
 *
 * @param depositId             The deposit ID the user attempted to cancel.
 */
error USR_NotUnbonding(uint256 depositId);

/**
 * @notice The user is attempting to unbond a deposit which has already been unbonded.
 *
 * @param depositId             The deposit ID the user attempted to unbond.
 */
error USR_AlreadyUnbonding(uint256 depositId);

/**
 * @notice The user is attempting to unstake a deposit which is still timelocked.
 *
 * @param depositId             The deposit ID the user attempted to unstake.
 */
error USR_StakeLocked(uint256 depositId);

/**
 * @notice The contract owner attempted to update rewards but the new reward rate would cause overflow.
 */
error USR_RewardTooLarge();

/**
 * @notice The reward distributor attempted to update rewards but 0 rewards per epoch.
 *         This can also happen if there is less than 1 wei of rewards per second of the
 *         epoch - due to integer division this will also lead to 0 rewards.
 */
error USR_ZeroRewardsPerEpoch();

/**
 * @notice The caller attempted to stake with a lock value that did not
 *         correspond to a valid staking time.
 *
 * @param lock                  The provided lock value.
 */
error USR_InvalidLockValue(uint256 lock);

/**
 * @notice The caller attempted an signed action with an invalid signature.
 * @param signatureLength length of the signature passed in
 * @param expectedSignatureLength expected length of the signature passed in
 */
error USR_InvalidSignature(uint256 signatureLength, uint256 expectedSignatureLength);

/**
 * @notice Attempted an action by a non-custodian
 */
error USR_NotCustodian();


// ========================================== STATE ERRORS ===========================================

/**
 * @dev These errors represent actions that are being prevented due to current contract state.
 *      These errors do not relate to user input, and may or may not be resolved by other actions
 *      or the progression of time.
 */

/**
 * @notice Attempted an action when cellar is using an asset that has a fee on transfer.
 * @param assetWithFeeOnTransfer address of the asset with fee on transfer
 */
error STATE_AssetUsesFeeOnTransfer(address assetWithFeeOnTransfer);

/**
 * @notice Attempted action was prevented due to contract being shutdown.
 */
error STATE_ContractShutdown();

/**
 * @notice Attempted to shutdown the contract when it was already shutdown.
 */
error STATE_AlreadyShutdown();

/**
 * @notice The caller attempted to start a reward period, but the contract did not have enough tokens
 *         for the specified amount of rewards.
 *
 * @param rewardBalance         The amount of distributionToken held by the contract.
 * @param reward                The amount of rewards the caller attempted to distribute.
 */
error STATE_RewardsNotFunded(uint256 rewardBalance, uint256 reward);

/**
 * @notice Attempted an operation that is prohibited while yield is still being distributed from the last accrual.
 */
error STATE_AccrualOngoing();

/**
 * @notice The caller attempted to change the epoch length, but current reward epochs were active.
 */
error STATE_RewardsOngoing();

/**
 * @notice The caller attempted to change the next epoch duration, but there are rewards ready.
 */
error STATE_RewardsReady();

/**
 * @notice The caller attempted to deposit stake, but there are no remaining rewards to pay out.
 */
error STATE_NoRewardsLeft();

/**
 * @notice The caller attempted to perform an an emergency unstake, but the contract
 *         is not in emergency mode.
 */
error STATE_NoEmergencyUnstake();

/**
 * @notice The caller attempted to perform an an emergency unstake, but the contract
 *         is not in emergency mode, or the emergency mode does not allow claiming rewards.
 */
error STATE_NoEmergencyClaim();

/**
 * @notice The caller attempted to perform a state-mutating action (e.g. staking or unstaking)
 *         while the contract was paused.
 */
error STATE_ContractPaused();

/**
 * @notice The caller attempted to perform a state-mutating action (e.g. staking or unstaking)
 *         while the contract was killed (placed in emergency mode).
 * @dev    Emergency mode is irreversible.
 */
error STATE_ContractKilled();

File 7 of 7 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

Settings
{
  "metadata": {
    "bytecodeHash": "none"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"contract ERC20","name":"_stakingToken","type":"address"},{"internalType":"contract ERC20","name":"_distributionToken","type":"address"},{"internalType":"uint256","name":"_epochDuration","type":"uint256"},{"internalType":"uint256","name":"shortBoost","type":"uint256"},{"internalType":"uint256","name":"mediumBoost","type":"uint256"},{"internalType":"uint256","name":"longBoost","type":"uint256"},{"internalType":"uint256","name":"shortBoostTime","type":"uint256"},{"internalType":"uint256","name":"mediumBoostTime","type":"uint256"},{"internalType":"uint256","name":"longBoostTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"STATE_AlreadyShutdown","type":"error"},{"inputs":[],"name":"STATE_ContractKilled","type":"error"},{"inputs":[],"name":"STATE_ContractPaused","type":"error"},{"inputs":[],"name":"STATE_NoEmergencyClaim","type":"error"},{"inputs":[],"name":"STATE_NoEmergencyUnstake","type":"error"},{"inputs":[],"name":"STATE_NoRewardsLeft","type":"error"},{"inputs":[{"internalType":"uint256","name":"rewardBalance","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"}],"name":"STATE_RewardsNotFunded","type":"error"},{"inputs":[],"name":"STATE_RewardsReady","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"USR_AlreadyUnbonding","type":"error"},{"inputs":[{"internalType":"uint256","name":"lock","type":"uint256"}],"name":"USR_InvalidLockValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minimumDeposit","type":"uint256"}],"name":"USR_MinimumDeposit","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"USR_NoDeposit","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"USR_NotUnbonding","type":"error"},{"inputs":[],"name":"USR_RewardTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"USR_StakeLocked","type":"error"},{"inputs":[],"name":"USR_ZeroDeposit","type":"error"},{"inputs":[],"name":"USR_ZeroRewardsPerEpoch","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"CancelUnbond","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyClaim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"bool","name":"claimable","type":"bool"}],"name":"EmergencyStop","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyUnstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"EpochDurationChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rewardAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardEnd","type":"uint256"}],"name":"Funding","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Stake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unbond","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Unstake","type":"event"},{"inputs":[],"name":"LONG_BOOST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LONG_BOOST_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MEDIUM_BOOST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MEDIUM_BOOST_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_DAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_WEEK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHORT_BOOST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHORT_BOOST_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TWO_WEEKS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"cancelUnbonding","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelUnbondingAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"claim","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimAll","outputs":[{"internalType":"uint256[]","name":"rewards","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentEpochDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"distributionToken","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"makeRewardsClaimable","type":"bool"}],"name":"emergencyStop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyUnstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ended","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserStakes","outputs":[{"components":[{"internalType":"uint112","name":"amount","type":"uint112"},{"internalType":"uint112","name":"amountWithBoost","type":"uint112"},{"internalType":"uint32","name":"unbondTimestamp","type":"uint32"},{"internalType":"uint112","name":"rewardPerTokenPaid","type":"uint112"},{"internalType":"uint112","name":"rewards","type":"uint112"},{"internalType":"enum ICellarStaking.Lock","name":"lock","type":"uint8"}],"internalType":"struct ICellarStaking.UserStake[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRewardsTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minimumDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextEpochDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"name":"notifyRewardAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"newRewardPerTokenStored","type":"uint256"},{"internalType":"uint256","name":"latestTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsReady","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minimum","type":"uint256"}],"name":"setMinimumDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_epochDuration","type":"uint256"}],"name":"setRewardsDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"enum ICellarStaking.Lock","name":"lock","type":"uint8"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakes","outputs":[{"internalType":"uint112","name":"amount","type":"uint112"},{"internalType":"uint112","name":"amountWithBoost","type":"uint112"},{"internalType":"uint32","name":"unbondTimestamp","type":"uint32"},{"internalType":"uint112","name":"rewardPerTokenPaid","type":"uint112"},{"internalType":"uint112","name":"rewards","type":"uint112"},{"internalType":"enum ICellarStaking.Lock","name":"lock","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDeposits","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDepositsWithBoost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"unbond","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unbondAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"unstake","outputs":[{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unstakeAll","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}]

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102955760003560e01c80637b0a47ee11610167578063af38d757116100ce578063cc1a378f11610087578063cc1a378f146105e5578063cd3daf9d146105f8578063d1058e5914610615578063df136d651461061d578063e78ec42e14610626578063f2fde38b1461063957600080fd5b8063af38d75714610563578063b5bc32bd14610576578063b98fe9471461059d578063baaa17f2146105a6578063bdb16286146105af578063c2ee3a08146105d657600080fd5b80638da5cb5b116101205780638da5cb5b146104eb5780638e6f6b77146104fc578063934d1fa4146105045780639d1390621461050c578063a827710014610533578063a85adeab1461055a57600080fd5b80637b0a47ee146104615780637bafd02d1461046a5780637d882097146104915780638220155b1461049a578063842e2981146104c1578063863e76db146104e157600080fd5b806335322f371161020b578063636bfbab116101c4578063636bfbab146103f8578063657bab8814610401578063715018a61461040a57806372f702f3146104125780637589cf2f14610451578063785f51801461045957600080fd5b806335322f3714610383578063379607f5146103985780633c6b16ab146103ab578063584b62a1146103be5780635c975abb146103e35780635ef73e24146103f057600080fd5b806315f7b4021161025d57806315f7b4021461031357806316c38b3c1461031b578063190ad7631461032e57806327de9e32146103555780632b8278ca146103685780632e17de781461037057600080fd5b80630a72f2ea1461029a5780630ac62e02146102af5780630d06e528146102c257806310087fb1146102de57806312fa6feb146102f1575b600080fd5b6102ad6102a8366004612505565b61064c565b005b6102ad6102bd36600461252c565b6106ad565b6102cb60035481565b6040519081526020015b60405180910390f35b6102ad6102ec366004612550565b6108ec565b600b5461030390610100900460ff1681565b60405190151581526020016102d5565b6102ad610c29565b6102ad61032936600461252c565b610d09565b6102cb7f0000000000000000000000000000000000000000000000000429d069189e000081565b6102ad610363366004612505565b610d46565b6102ad610da4565b6102cb61037e366004612505565b610efd565b61038b610f66565b6040516102d59190612584565b6102cb6103a6366004612505565b6110d0565b6102ad6103b9366004612505565b611131565b6103d16103cc3660046125df565b611319565b6040516102d596959493929190612641565b600b546103039060ff1681565b6102cb611389565b6102cb60045481565b6102cb60025481565b6102ad6113a0565b6104397f000000000000000000000000b5b29320d2dde5ba5bafa1ebcd270052070483ec81565b6040516001600160a01b0390911681526020016102d5565b6102ad6113d6565b6102ad61159e565b6102cb60085481565b6102cb7f00000000000000000000000000000000000000000000000006f05b59d3b2000081565b6102cb60065481565b6102cb7f000000000000000000000000000000000000000000000000000000000012750081565b6104d46104cf36600461268d565b61167b565b6040516102d591906126a8565b6102cb6201518081565b6000546001600160a01b0316610439565b6102cb611774565b6102cb611785565b6104397f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd181565b6102cb7f00000000000000000000000000000000000000000000000000000000001baf8081565b6102cb60055481565b600b546103039062010000900460ff1681565b6102cb7f0000000000000000000000000000000000000000000000000000000000093a8081565b6102cb60075481565b6102cb60015481565b6102cb7f000000000000000000000000000000000000000000000000016345785d8a000081565b6102cb670de0b6b3a764000081565b6102ad6105f3366004612505565b61179e565b610600611825565b604080519283526020830191909152016102d5565b61038b6118a5565b6102cb60095481565b6102ad610634366004612505565b61199b565b6102ad61064736600461268d565b6119ca565b600b5460ff161561067057604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156106995760405163eebee04f60e01b815260040160405180910390fd5b6106a1611a62565b6106aa81611a72565b50565b6000546001600160a01b031633146106e05760405162461bcd60e51b81526004016106d790612744565b60405180910390fd5b600b54610100900460ff16156107095760405163e6c1c8bf60e01b815260040160405180910390fd5b600b805461010062ffff00199091166201000084151502171790556040516370a0823160e01b81523060048201526000907f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b0316906370a0823190602401602060405180830381865afa15801561078b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107af9190612779565b9050811561081c576107bf611a62565b600042600554116107d15760006107ec565b600854426005546107e291906127a8565b6107ec91906127bf565b905060008160015460085461080191906127bf565b61080b91906127a8565b905061081781846127a8565b925050505b60405163a9059cbb60e01b8152336004820152602481018290527f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610889573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108ad91906127de565b506040805133815283151560208201527f156a2b3940a4c3c2a905ee35163815d4146391134c92d0b6b53f33afe993f718910160405180910390a15050565b600b5460ff161561091057604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156109395760405163eebee04f60e01b815260040160405180910390fd5b610941611a62565b81600003610962576040516344c7244760e11b815260040160405180910390fd5b6004548210156109905760048054604051630b30907560e01b815291820184905260248201526044016106d7565b6006541580156109a257506000600354115b156109c4576109b2600354611bf5565b60006003556109bf611a62565b6109e7565b6005544211156109e757604051636e6b983360e01b815260040160405180910390fd5b60006109f282611c54565b5090506000670de0b6b3a7640000610a0a83866127bf565b610a1491906127fb565b610a1e908561281d565b9050600c6000336001600160a01b03166001600160a01b031681526020019081526020016000206040518060c00160405280866001600160701b03168152602001836001600160701b03168152602001600063ffffffff1681526020016009546001600160701b0316815260200160006001600160701b03168152602001856002811115610aae57610aae612609565b905281546001808201845560009384526020938490208351600293840290910180549585015160408601516001600160701b039384166001600160e01b031998891617600160701b9285168302176001600160e01b0316600160e01b63ffffffff909216820217835560608701519483018054608089015196861699169890981794909316029290921780865560a0850151949592949360ff60e01b1990911691908490811115610b6157610b61612609565b021790555050508360066000828254610b7a919061281d565b925050819055508060076000828254610b93919061281d565b90915550610bce90506001600160a01b037f000000000000000000000000b5b29320d2dde5ba5bafa1ebcd270052070483ec16333087611dad565b336000818152600c60205260409020547f5af417134f72a9d41143ace85b0a26dce6f550f894f2cbc1eeee8810603d91b690610c0c906001906127a8565b60408051918252602082018890520160405180910390a250505050565b600b5460ff1615610c4d57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610c765760405163eebee04f60e01b815260040160405180910390fd5b610c7e611a62565b336000908152600c60205260408120905b8154811015610d05576000828281548110610cac57610cac612835565b6000918252602090912060029091020180549091506001600160701b031615801590610ce457508054600160e01b900463ffffffff16155b15610cf257610cf282611e37565b5080610cfd8161284b565b915050610c8f565b5050565b6000546001600160a01b03163314610d335760405162461bcd60e51b81526004016106d790612744565b600b805460ff1916911515919091179055565b600b5460ff1615610d6a57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610d935760405163eebee04f60e01b815260040160405180910390fd5b610d9b611a62565b6106aa81611e37565b600b54610100900460ff16610dcc57604051630809727360e01b815260040160405180910390fd5b600b5462010000900460ff16610df5576040516399f0c45d60e01b815260040160405180910390fd5b336000908152600c60205260408120815b8154811015610e8957610e193382611fc5565b6000828281548110610e2d57610e2d612835565b600091825260209091206002909102016001810154909150610e5f90600160701b90046001600160701b03168561281d565b60019091018054600160701b600160e01b0319169055925080610e818161284b565b915050610e06565b508115610d0557610ec46001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163384612126565b60405182815233907f07181ceaa5c61f1818da3a082bd8f1f5a85817f2f0ff49e19e3b6a8b30822f559060200160405180910390a25050565b600b5460009060ff1615610f2457604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610f4d5760405163eebee04f60e01b815260040160405180910390fd5b610f55611a62565b610f5e826121a4565b90505b919050565b600b5460609060ff1615610f8d57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610fb65760405163eebee04f60e01b815260040160405180910390fd5b610fbe611a62565b336000908152600c60205260408120805490919067ffffffffffffffff811115610fea57610fea612864565b604051908082528060200260200182016040528015611013578160200160208202803683370190505b50905060005b82548110156110c957600083828154811061103657611036612835565b6000918252602090912060029091020180549091506001600160701b03161580159061106f57508054600160e01b900463ffffffff1615155b801561108957508054600160e01b900463ffffffff164210155b156110b657611097826121a4565b8383815181106110a9576110a9612835565b6020026020010181815250505b50806110c18161284b565b915050611019565b5091505090565b600b5460009060ff16156110f757604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156111205760405163eebee04f60e01b815260040160405180910390fd5b611128611a62565b610f5e82612379565b6000546001600160a01b0316331461115b5760405162461bcd60e51b81526004016106d790612744565b611163611a62565b6005544210156111a05760004260055461117d91906127a8565b905060006008548261118f91906127bf565b905061119b818461281d565b925050505b6002548110156111c35760405163c2bdb92560e01b815260040160405180910390fd5b6040516370a0823160e01b81523060048201526000907f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b0316906370a0823190602401602060405180830381865afa15801561122a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061124e9190612779565b9050600060035483611260919061281d565b90508082101561128d57604051635e6e7a3760e01b815260048101839052602481018290526044016106d7565b60006002548461129d91906127fb565b6002549091506112b7670de0b6b3a76400006000196127fb565b6112c191906127fb565b81106112df576040516207703f60e81b815260040160405180910390fd5b6006546000036113065783600360008282546112fb919061281d565b9091555061130f9050565b61130f84611bf5565b505042600a555050565b600c602052816000526040600020818154811061133557600080fd5b6000918252602090912060029091020180546001909101546001600160701b038083169450600160701b8084048216945063ffffffff600160e01b948590041693838316939182049092169160ff91041686565b6000600554421061139b575060055490565b504290565b6000546001600160a01b031633146113ca5760405162461bcd60e51b81526004016106d790612744565b6113d4600061245f565b565b600b54610100900460ff166113fe57604051630809727360e01b815260040160405180910390fd5b336000908152600c60205260408120905b8154811015610d0557600b5462010000900460ff1615611433576114333382611fc5565b600082828154811061144757611447612835565b6000918252602090912060029091020180549091506001600160701b0316801561158957806006600082825461147d91906127a8565b9091555050815460078054600160701b9092046001600160701b0316916000906114a89084906127a8565b909155505081546001600160e01b031916825560405163a9059cbb60e01b8152336004820152602481018290527f000000000000000000000000b5b29320d2dde5ba5bafa1ebcd270052070483ec6001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015611528573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061154c91906127de565b50604080518481526020810183905233917f4c363bde70ba6f3710164df779019cbdf717067dd1c615ccc164601c05168a36910160405180910390a25b505080806115969061284b565b91505061140f565b600b5460ff16156115c257604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156115eb5760405163eebee04f60e01b815260040160405180910390fd5b6115f3611a62565b336000908152600c60205260408120905b8154811015610d0557600082828154811061162157611621612835565b6000918252602090912060029091020180549091506001600160701b03161580159061165a57508054600160e01b900463ffffffff1615155b156116685761166882611a72565b50806116738161284b565b915050611604565b6001600160a01b0381166000908152600c60209081526040808320805482518185028101850190935280835260609492939192909184015b828210156117695760008481526020908190206040805160c081018252600286810290930180546001600160701b038082168452600160701b808304821697850197909752600160e01b9182900463ffffffff16948401949094526001820154808516606085015295860490931660808301529093909260a085019290910460ff169081111561174557611745612609565b600281111561175657611756612609565b81525050815260200190600101906116b3565b505050509050919050565b6117826201518060076127bf565b81565b6117936201518060076127bf565b6117829060026127bf565b6000546001600160a01b031633146117c85760405162461bcd60e51b81526004016106d790612744565b600354156117e95760405163116eb83560e11b815260040160405180910390fd5b60028190556040518181527f66b9213ecbabaff7979ad49112aa2a6835a4ad4307c6d562d1af7529d9efa523906020015b60405180910390a150565b600080611830611389565b90506006546000036118455760095491509091565b6000600a548261185591906127a8565b905060006008548261186791906127bf565b90506000600754670de0b6b3a76400008361188291906127bf565b61188c91906127fb565b90508060095461189c919061281d565b94505050509091565b600b5460609060ff16156118cc57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156118f55760405163eebee04f60e01b815260040160405180910390fd5b6118fd611a62565b336000908152600c60205260409020805467ffffffffffffffff81111561192657611926612864565b60405190808252806020026020018201604052801561194f578160200160208202803683370190505b50915060005b81548110156119965761196781612379565b83828151811061197957611979612835565b60209081029190910101528061198e8161284b565b915050611955565b505090565b6000546001600160a01b031633146119c55760405162461bcd60e51b81526004016106d790612744565b600455565b6000546001600160a01b031633146119f45760405162461bcd60e51b81526004016106d790612744565b6001600160a01b038116611a595760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016106d7565b6106aa8161245f565b611a6a611825565b600a55600955565b336000908152600c60205260408120805483908110611a9357611a93612835565b60009182526020822060029091020180549092506001600160701b031690819003611ad45760405163102c472960e11b8152600481018490526024016106d7565b8154600160e01b900463ffffffff16600003611b0657604051631211d1cb60e31b8152600481018490526024016106d7565b611b103384611fc5565b6001820154600090611b2b90600160e01b900460ff16611c54565b508354909150600090670de0b6b3a764000090611b529084906001600160701b03166127bf565b611b5c91906127fb565b8454909150600090611b789083906001600160701b031661281d565b85546001600160e01b036001600160701b03808416600160701b02919091169116178655600780549192508391600090611bb390849061281d565b909155505060405186815233907fe13d5cf5271bd721532059c5883b639ba871a2b135c24caf452a8de615213fd49060200160405180910390a2505050505050565b600254611c0290826127fb565b600855600254611c12904261281d565b60058190556002546001556040805183815260208101929092527fff92011f5b6637357c4904d41ea3f9d759723fe605d64ac0e2c47541d288dc03910161181a565b60008080836002811115611c6a57611c6a612609565b03611cb957507f000000000000000000000000000000000000000000000000016345785d8a0000927f0000000000000000000000000000000000000000000000000000000000093a8092509050565b6001836002811115611ccd57611ccd612609565b03611d1c57507f0000000000000000000000000000000000000000000000000429d069189e0000927f000000000000000000000000000000000000000000000000000000000012750092509050565b6002836002811115611d3057611d30612609565b03611d7f57507f00000000000000000000000000000000000000000000000006f05b59d3b20000927f00000000000000000000000000000000000000000000000000000000001baf8092509050565b826002811115611d9157611d91612609565b60405163208a64e760e11b81526004016106d791815260200190565b60006040516323b872dd60e01b81528460048201528360248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080611e305760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b60448201526064016106d7565b5050505050565b336000908152600c60205260408120805483908110611e5857611e58612835565b60009182526020822060029091020180549092506001600160701b031690819003611e995760405163102c472960e11b8152600481018490526024016106d7565b8154600160e01b900463ffffffff1615611ec957604051630c7820cd60e31b8152600481018490526024016106d7565b611ed33384611fc5565b8154600090611ef3908390600160701b90046001600160701b03166127a8565b90506000611f1184600101601c9054906101000a900460ff16611c54565b8554600160701b600160e01b031916600160701b6001600160701b038716021786559150611f419050814261281d565b845463ffffffff91909116600160e01b026001600160e01b03909116178455600780546001600160701b0384169190600090611f7e9084906127a8565b9091555050604080518681526020810185905233917f7659747cd8571f1071eea946fdc648adcf181cad916f32a05f82c3a525976048910160405180910390a25050505050565b6001600160a01b0382166000908152600c60205260408120805483908110611fef57611fef612835565b60009182526020822060029091020180549092506001600160701b0316900361201757505050565b6040805160c08101825282546001600160701b038082168352600160701b8083048216602085015263ffffffff600160e01b938490041694840194909452600185015480821660608501529384041660808301526000926120a99291859160a084019160ff910416600281111561209057612090612609565b60028111156120a1576120a1612609565b9052506124af565b90508082600101600e8282829054906101000a90046001600160701b03166120d1919061287a565b92506101000a8154816001600160701b0302191690836001600160701b031602179055506009548260010160006101000a8154816001600160701b0302191690836001600160701b0316021790555050505050565b600060405163a9059cbb60e01b8152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061219e5760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b60448201526064016106d7565b50505050565b336000908152600c602052604081208054829190849081106121c8576121c8612835565b60009182526020822060029091020180549092506001600160701b0316908190036122095760405163102c472960e11b8152600481018590526024016106d7565b8154600160e01b900463ffffffff16158061223157508154600160e01b900463ffffffff1642105b1561225257604051633a49774d60e11b8152600481018590526024016106d7565b61225c3385611fc5565b60018201805483546001600160e01b0319168455600160701b600160e01b0319811690915560068054600160701b9092046001600160701b0316945082916000906122a89084906127a8565b9250508190555080600760008282546122c191906127a8565b909155506122fb90506001600160a01b037f000000000000000000000000b5b29320d2dde5ba5bafa1ebcd270052070483ec163383612126565b61232f6001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163385612126565b604080518581526020810183905290810184905233907ffbd65cfd6de1493db337385c0712095397ecbd0504df64b861cdfceb80c7b4229060600160405180910390a25050919050565b336000908152600c6020526040812080548291908490811061239d5761239d612835565b906000526020600020906002020190506123b73384611fc5565b6001810154600160701b90046001600160701b03169150811561245957600181018054600160701b600160e01b031916905561241d6001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163384612126565b604080518481526020810184905233917f34fcbac0073d7c3d388e51312faf357774904998eeb8fca628b9e6f65ee1cbf7910160405180910390a25b50919050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008082606001516001600160701b03166009546124cd91906127a8565b90506000670de0b6b3a76400008285602001516001600160701b03166124f391906127bf565b6124fd91906127fb565b949350505050565b60006020828403121561251757600080fd5b5035919050565b80151581146106aa57600080fd5b60006020828403121561253e57600080fd5b81356125498161251e565b9392505050565b6000806040838503121561256357600080fd5b8235915060208301356003811061257957600080fd5b809150509250929050565b6020808252825182820181905260009190848201906040850190845b818110156125bc578351835292840192918401916001016125a0565b50909695505050505050565b80356001600160a01b0381168114610f6157600080fd5b600080604083850312156125f257600080fd5b6125fb836125c8565b946020939093013593505050565b634e487b7160e01b600052602160045260246000fd5b6003811061263d57634e487b7160e01b600052602160045260246000fd5b9052565b6001600160701b038781168252868116602083015263ffffffff8616604083015284811660608301528316608082015260c0810161268260a083018461261f565b979650505050505050565b60006020828403121561269f57600080fd5b612549826125c8565b602080825282518282018190526000919060409081850190868401855b8281101561273757815180516001600160701b039081168652878201518116888701528682015163ffffffff16878701526060808301518216908701526080808301519091169086015260a090810151906127228187018361261f565b505060c09390930192908501906001016126c5565b5091979650505050505050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60006020828403121561278b57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b6000828210156127ba576127ba612792565b500390565b60008160001904831182151516156127d9576127d9612792565b500290565b6000602082840312156127f057600080fd5b81516125498161251e565b60008261281857634e487b7160e01b600052601260045260246000fd5b500490565b6000821982111561283057612830612792565b500190565b634e487b7160e01b600052603260045260246000fd5b60006001820161285d5761285d612792565b5060010190565b634e487b7160e01b600052604160045260246000fd5b60006001600160701b0380831681851680830382111561289c5761289c612792565b0194935050505056fea164736f6c634300080f000a

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.