More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 82 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Unstake | 19904487 | 100 days ago | IN | 0 ETH | 0.00046246 | ||||
Unstake | 19778965 | 118 days ago | IN | 0 ETH | 0.00040205 | ||||
Unstake | 19760826 | 120 days ago | IN | 0 ETH | 0.00145312 | ||||
Unbond | 19747212 | 122 days ago | IN | 0 ETH | 0.00042186 | ||||
Unbond | 19728572 | 125 days ago | IN | 0 ETH | 0.00037257 | ||||
Claim All | 19728568 | 125 days ago | IN | 0 ETH | 0.00057733 | ||||
Unstake | 19724582 | 125 days ago | IN | 0 ETH | 0.00133768 | ||||
Unstake | 19708368 | 127 days ago | IN | 0 ETH | 0.00059123 | ||||
Unstake | 19648599 | 136 days ago | IN | 0 ETH | 0.00205613 | ||||
Unbond | 19617767 | 140 days ago | IN | 0 ETH | 0.00122983 | ||||
Claim All | 19615199 | 140 days ago | IN | 0 ETH | 0.00179663 | ||||
Unbond | 19610800 | 141 days ago | IN | 0 ETH | 0.00100232 | ||||
Unstake | 19610111 | 141 days ago | IN | 0 ETH | 0.00160142 | ||||
Claim All | 19575545 | 146 days ago | IN | 0 ETH | 0.00270355 | ||||
Unbond | 19575542 | 146 days ago | IN | 0 ETH | 0.00191466 | ||||
Unstake | 19542784 | 151 days ago | IN | 0 ETH | 0.00204131 | ||||
Unstake | 19538854 | 151 days ago | IN | 0 ETH | 0.00201077 | ||||
Unbond | 19489901 | 158 days ago | IN | 0 ETH | 0.00120726 | ||||
Unstake | 19486805 | 159 days ago | IN | 0 ETH | 0.00182758 | ||||
Unstake | 19486789 | 159 days ago | IN | 0 ETH | 0.00191185 | ||||
Unstake | 19479849 | 160 days ago | IN | 0 ETH | 0.00288333 | ||||
Unbond | 19460707 | 162 days ago | IN | 0 ETH | 0.00127234 | ||||
Claim All | 19446853 | 164 days ago | IN | 0 ETH | 0.00226311 | ||||
Unbond | 19446850 | 164 days ago | IN | 0 ETH | 0.00199103 | ||||
Unstake | 19419897 | 168 days ago | IN | 0 ETH | 0.00852523 |
Advanced mode: Intended for advanced users or developers and will display all Internal Transactions including zero value transfers. Name tag integration is not available in advanced view.
Latest 25 internal transactions (View All)
Advanced mode:
Parent Transaction Hash | Block | From | To | ||||
---|---|---|---|---|---|---|---|
19904487 | 100 days ago | 0 ETH | |||||
19904487 | 100 days ago | 0 ETH | |||||
19778965 | 118 days ago | 0 ETH | |||||
19778965 | 118 days ago | 0 ETH | |||||
19760826 | 120 days ago | 0 ETH | |||||
19760826 | 120 days ago | 0 ETH | |||||
19728568 | 125 days ago | 0 ETH | |||||
19724582 | 125 days ago | 0 ETH | |||||
19724582 | 125 days ago | 0 ETH | |||||
19708368 | 127 days ago | 0 ETH | |||||
19708368 | 127 days ago | 0 ETH | |||||
19648599 | 136 days ago | 0 ETH | |||||
19648599 | 136 days ago | 0 ETH | |||||
19615199 | 140 days ago | 0 ETH | |||||
19610111 | 141 days ago | 0 ETH | |||||
19610111 | 141 days ago | 0 ETH | |||||
19575545 | 146 days ago | 0 ETH | |||||
19542784 | 151 days ago | 0 ETH | |||||
19542784 | 151 days ago | 0 ETH | |||||
19538854 | 151 days ago | 0 ETH | |||||
19538854 | 151 days ago | 0 ETH | |||||
19486805 | 159 days ago | 0 ETH | |||||
19486805 | 159 days ago | 0 ETH | |||||
19486789 | 159 days ago | 0 ETH | |||||
19486789 | 159 days ago | 0 ETH |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0xB12761bF...5Bd230aC6 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.21+commit.d9974bed
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.21; import { ERC20 } from "@solmate/tokens/ERC20.sol"; import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ICellarStaking } from "src/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)); } } }
// 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); } }
// 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; /// @solidity memory-safe-assembly 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; /// @solidity memory-safe-assembly 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; /// @solidity memory-safe-assembly 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; /// @solidity memory-safe-assembly 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"); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (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 Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { 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); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.21; import { ERC20 } from "@solmate/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); // ===================== Errors ======================= /** * @notice Attempted to shutdown the contract when it was already shutdown. */ error CellarStaking__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 CellarStaking__RewardsNotFunded(uint256 rewardBalance, uint256 reward); /** * @notice The caller attempted to change the next epoch duration, but there are rewards ready. */ error CellarStaking__RewardsReady(); /** * @notice The caller attempted to deposit stake, but there are no remaining rewards to pay out. */ error CellarStaking__NoRewardsLeft(); /** * @notice The caller attempted to perform an an emergency unstake, but the contract * is not in emergency mode. */ error CellarStaking__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 CellarStaking__NoEmergencyClaim(); /** * @notice The caller attempted to perform a state-mutating action (e.g. staking or unstaking) * while the contract was paused. */ error CellarStaking__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 CellarStaking__ContractKilled(); /** * @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 CellarStaking__InvalidLockValue(uint256 lock); /** * @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 CellarStaking__ZeroRewardsPerEpoch(); /** * @notice The contract owner attempted to update rewards but the new reward rate would cause overflow. */ error CellarStaking__RewardTooLarge(); /** * @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 CellarStaking__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 CellarStaking__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 CellarStaking__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 CellarStaking__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 CellarStaking__StakeLocked(uint256 depositId); /** * @notice User attempted to stake zero amount. */ error CellarStaking__ZeroDeposit(); // ===================== 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); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.21; // ========================================== 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();
// 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; } }
{ "remappings": [ "@solmate/=lib/solmate/src/", "@forge-std/=lib/forge-std/src/", "@ds-test/=lib/forge-std/lib/ds-test/src/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@openzeppelin/=lib/openzeppelin-contracts/", "@uniswap/v3-periphery/=lib/v3-periphery/", "@uniswap/v3-core/=lib/v3-core/", "@chainlink/=lib/chainlink/", "@uniswapV3P/=lib/v3-periphery/contracts/", "@uniswapV3C/=lib/v3-core/contracts/", "@balancer/=lib/balancer-v2-monorepo/pkg/", "@balancer-labs/=lib/balancer-v2-monorepo/../../node_modules/@balancer-labs/", "@ensdomains/=node_modules/@ensdomains/", "axelar-gmp-sdk-solidity/=lib/axelar-gmp-sdk-solidity/contracts/", "balancer-v2-monorepo/=lib/balancer-v2-monorepo/", "ccip/=lib/ccip/contracts/", "chainlink/=lib/chainlink/integration-tests/contracts/ethereum/src/", "eth-gas-reporter/=node_modules/eth-gas-reporter/", "forge-std/=lib/forge-std/src/", "hardhat/=node_modules/hardhat/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "pendle-core-v2-public/=lib/pendle-core-v2-public/contracts/", "solmate/=lib/solmate/src/", "v3-core/=lib/v3-core/contracts/", "v3-periphery/=lib/v3-periphery/contracts/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"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":"CellarStaking__AlreadyShutdown","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"CellarStaking__AlreadyUnbonding","type":"error"},{"inputs":[],"name":"CellarStaking__ContractKilled","type":"error"},{"inputs":[],"name":"CellarStaking__ContractPaused","type":"error"},{"inputs":[{"internalType":"uint256","name":"lock","type":"uint256"}],"name":"CellarStaking__InvalidLockValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minimumDeposit","type":"uint256"}],"name":"CellarStaking__MinimumDeposit","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"CellarStaking__NoDeposit","type":"error"},{"inputs":[],"name":"CellarStaking__NoEmergencyClaim","type":"error"},{"inputs":[],"name":"CellarStaking__NoEmergencyUnstake","type":"error"},{"inputs":[],"name":"CellarStaking__NoRewardsLeft","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"CellarStaking__NotUnbonding","type":"error"},{"inputs":[],"name":"CellarStaking__RewardTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"rewardBalance","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"}],"name":"CellarStaking__RewardsNotFunded","type":"error"},{"inputs":[],"name":"CellarStaking__RewardsReady","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"CellarStaking__StakeLocked","type":"error"},{"inputs":[],"name":"CellarStaking__ZeroDeposit","type":"error"},{"inputs":[],"name":"CellarStaking__ZeroRewardsPerEpoch","type":"error"},{"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
0x608060405234801561000f575f80fd5b506004361061028c575f3560e01c80637b0a47ee11610161578063af38d757116100ca578063cc1a378f11610084578063cc1a378f146105da578063cd3daf9d146105ed578063d1058e591461060a578063df136d6514610612578063e78ec42e1461061b578063f2fde38b1461062e575f80fd5b8063af38d75714610558578063b5bc32bd1461056b578063b98fe94714610592578063baaa17f21461059b578063bdb16286146105a4578063c2ee3a08146105cb575f80fd5b80638da5cb5b1161011b5780638da5cb5b146104e15780638e6f6b77146104f1578063934d1fa4146104f95780639d13906214610501578063a827710014610528578063a85adeab1461054f575f80fd5b80637b0a47ee146104575780637bafd02d146104605780637d882097146104875780638220155b14610490578063842e2981146104b7578063863e76db146104d7575f80fd5b806335322f3711610203578063636bfbab116101bd578063636bfbab146103ee578063657bab88146103f7578063715018a61461040057806372f702f3146104085780637589cf2f14610447578063785f51801461044f575f80fd5b806335322f3714610379578063379607f51461038e5780633c6b16ab146103a1578063584b62a1146103b45780635c975abb146103d95780635ef73e24146103e6575f80fd5b806315f7b4021161025457806315f7b4021461030957806316c38b3c14610311578063190ad7631461032457806327de9e321461034b5780632b8278ca1461035e5780632e17de7814610366575f80fd5b80630a72f2ea146102905780630ac62e02146102a55780630d06e528146102b857806310087fb1146102d457806312fa6feb146102e7575b5f80fd5b6102a361029e3660046123f8565b610641565b005b6102a36102b336600461241c565b6106a2565b6102c160035481565b6040519081526020015b60405180910390f35b6102a36102e236600461243e565b6108ad565b600b546102f990610100900460ff1681565b60405190151581526020016102cb565b6102a3610be2565b6102a361031f36600461241c565b610cbf565b6102c17f0000000000000000000000000000000000000000000000000429d069189e000081565b6102a36103593660046123f8565b610cda565b6102a3610d38565b6102c16103743660046123f8565b610e8e565b610381610ef6565b6040516102cb919061246f565b6102c161039c3660046123f8565b61105c565b6102a36103af3660046123f8565b6110bc565b6103c76103c23660046124c8565b611278565b6040516102cb96959493929190612524565b600b546102f99060ff1681565b6102c16112e4565b6102c160045481565b6102c160025481565b6102a36112fa565b61042f7f000000000000000000000000cf4b531b4cde95bd35d71926e09b2b54c564f5b681565b6040516001600160a01b0390911681526020016102cb565b6102a361130d565b6102a36114cd565b6102c160085481565b6102c17f00000000000000000000000000000000000000000000000006f05b59d3b2000081565b6102c160065481565b6102c17f000000000000000000000000000000000000000000000000000000000012750081565b6104ca6104c5366004612570565b6115a7565b6040516102cb9190612589565b6102c16201518081565b5f546001600160a01b031661042f565b6102c161169e565b6102c16116af565b61042f7f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd181565b6102c17f00000000000000000000000000000000000000000000000000000000001baf8081565b6102c160055481565b600b546102f99062010000900460ff1681565b6102c17f0000000000000000000000000000000000000000000000000000000000093a8081565b6102c160075481565b6102c160015481565b6102c17f000000000000000000000000000000000000000000000000016345785d8a000081565b6102c1670de0b6b3a764000081565b6102a36105e83660046123f8565b6116c8565b6105f561172d565b604080519283526020830191909152016102cb565b6103816117a8565b6102c160095481565b6102a36106293660046123f8565b61189c565b6102a361063c366004612570565b6118a9565b600b5460ff161561066557604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff161561068e5760405163eebee04f60e01b815260040160405180910390fd5b61069661191f565b61069f8161192f565b50565b6106aa611aab565b600b54610100900460ff16156106d35760405163e6c1c8bf60e01b815260040160405180910390fd5b600b805461010062ffff00199091166201000084151502171790556040516370a0823160e01b81523060048201525f907f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b0316906370a0823190602401602060405180830381865afa158015610752573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107769190612624565b905081156107e05761078661191f565b5f4260055411610796575f6107b1565b600854426005546107a7919061264f565b6107b19190612668565b90505f816001546008546107c59190612668565b6107cf919061264f565b90506107db818461264f565b925050505b60405163a9059cbb60e01b8152336004820152602481018290527f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b03169063a9059cbb906044016020604051808303815f875af115801561084a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061086e919061267f565b506040805133815283151560208201527f156a2b3940a4c3c2a905ee35163815d4146391134c92d0b6b53f33afe993f718910160405180910390a15050565b600b5460ff16156108d157604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156108fa5760405163eebee04f60e01b815260040160405180910390fd5b61090261191f565b815f03610922576040516344c7244760e11b815260040160405180910390fd5b6004548210156109555760048054604051630b30907560e01b815291820184905260248201526044015b60405180910390fd5b60065415801561096657505f600354115b1561098757610976600354611b04565b5f60035561098261191f565b6109aa565b6005544211156109aa57604051636e6b983360e01b815260040160405180910390fd5b5f6109b482611b63565b5090505f670de0b6b3a76400006109cb8386612668565b6109d5919061269a565b6109df90856126b9565b9050600c5f336001600160a01b03166001600160a01b031681526020019081526020015f206040518060c00160405280866001600160701b03168152602001836001600160701b031681526020015f63ffffffff1681526020016009546001600160701b031681526020015f6001600160701b03168152602001856002811115610a6b57610a6b6124f0565b90528154600180820184555f9384526020938490208351600293840290910180549585015160408601516001600160701b039384166001600160e01b031998891617600160701b9285168302176001600160e01b0316600160e01b63ffffffff909216820217835560608701519483018054608089015196861699169890981794909316029290921780865560a0850151949592949360ff60e01b1990911691908490811115610b1d57610b1d6124f0565b021790555050508360065f828254610b3591906126b9565b925050819055508060075f828254610b4d91906126b9565b90915550610b8890506001600160a01b037f000000000000000000000000cf4b531b4cde95bd35d71926e09b2b54c564f5b616333087611cbb565b335f818152600c60205260409020547f5af417134f72a9d41143ace85b0a26dce6f550f894f2cbc1eeee8810603d91b690610bc59060019061264f565b60408051918252602082018890520160405180910390a250505050565b600b5460ff1615610c0657604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610c2f5760405163eebee04f60e01b815260040160405180910390fd5b610c3761191f565b335f908152600c60205260408120905b8154811015610cbb575f828281548110610c6357610c636126cc565b5f918252602090912060029091020180549091506001600160701b031615801590610c9a57508054600160e01b900463ffffffff16155b15610ca857610ca882611d41565b5080610cb3816126e0565b915050610c47565b5050565b610cc7611aab565b600b805460ff1916911515919091179055565b600b5460ff1615610cfe57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610d275760405163eebee04f60e01b815260040160405180910390fd5b610d2f61191f565b61069f81611d41565b600b54610100900460ff16610d6057604051630809727360e01b815260040160405180910390fd5b600b5462010000900460ff16610d89576040516399f0c45d60e01b815260040160405180910390fd5b335f908152600c60205260408120815b8154811015610e1a57610dac3382611eca565b5f828281548110610dbf57610dbf6126cc565b5f91825260209091206002909102016001810154909150610df090600160701b90046001600160701b0316856126b9565b60019091018054600160701b600160e01b0319169055925080610e12816126e0565b915050610d99565b508115610cbb57610e556001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163384612027565b60405182815233907f07181ceaa5c61f1818da3a082bd8f1f5a85817f2f0ff49e19e3b6a8b30822f559060200160405180910390a25050565b600b545f9060ff1615610eb457604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610edd5760405163eebee04f60e01b815260040160405180910390fd5b610ee561191f565b610eee826120a1565b90505b919050565b600b5460609060ff1615610f1d57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff1615610f465760405163eebee04f60e01b815260040160405180910390fd5b610f4e61191f565b335f908152600c60205260408120805490919067ffffffffffffffff811115610f7957610f796126f8565b604051908082528060200260200182016040528015610fa2578160200160208202803683370190505b5090505f5b8254811015611055575f838281548110610fc357610fc36126cc565b5f918252602090912060029091020180549091506001600160701b031615801590610ffb57508054600160e01b900463ffffffff1615155b801561101557508054600160e01b900463ffffffff164210155b1561104257611023826120a1565b838381518110611035576110356126cc565b6020026020010181815250505b508061104d816126e0565b915050610fa7565b5091505090565b600b545f9060ff161561108257604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156110ab5760405163eebee04f60e01b815260040160405180910390fd5b6110b361191f565b610eee82612272565b6110c4611aab565b6110cc61191f565b600554421015611107575f426005546110e5919061264f565b90505f600854826110f69190612668565b905061110281846126b9565b925050505b60025481101561112a5760405163c2bdb92560e01b815260040160405180910390fd5b6040516370a0823160e01b81523060048201525f907f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd16001600160a01b0316906370a0823190602401602060405180830381865afa15801561118e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111b29190612624565b90505f600354836111c391906126b9565b9050808210156111f057604051635e6e7a3760e01b8152600481018390526024810182905260440161094c565b5f600254846111ff919061269a565b600254909150611218670de0b6b3a76400005f1961269a565b611222919061269a565b8110611240576040516207703f60e81b815260040160405180910390fd5b6006545f03611265578360035f82825461125a91906126b9565b9091555061126e9050565b61126e84611b04565b505042600a555050565b600c602052815f5260405f208181548110611291575f80fd5b5f918252602090912060029091020180546001909101546001600160701b038083169450600160701b8084048216945063ffffffff600160e01b948590041693838316939182049092169160ff91041686565b5f60055442106112f5575060055490565b504290565b611302611aab565b61130b5f612355565b565b600b54610100900460ff1661133557604051630809727360e01b815260040160405180910390fd5b335f908152600c60205260408120905b8154811015610cbb57600b5462010000900460ff1615611369576113693382611eca565b5f82828154811061137c5761137c6126cc565b5f918252602090912060029091020180549091506001600160701b031680156114b8578060065f8282546113b0919061264f565b9091555050815460078054600160701b9092046001600160701b0316915f906113da90849061264f565b909155505081546001600160e01b031916825560405163a9059cbb60e01b8152336004820152602481018290527f000000000000000000000000cf4b531b4cde95bd35d71926e09b2b54c564f5b66001600160a01b03169063a9059cbb906044016020604051808303815f875af1158015611457573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061147b919061267f565b50604080518481526020810183905233917f4c363bde70ba6f3710164df779019cbdf717067dd1c615ccc164601c05168a36910160405180910390a25b505080806114c5906126e0565b915050611345565b600b5460ff16156114f157604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff161561151a5760405163eebee04f60e01b815260040160405180910390fd5b61152261191f565b335f908152600c60205260408120905b8154811015610cbb575f82828154811061154e5761154e6126cc565b5f918252602090912060029091020180549091506001600160701b03161580159061158657508054600160e01b900463ffffffff1615155b15611594576115948261192f565b508061159f816126e0565b915050611532565b6001600160a01b0381165f908152600c60209081526040808320805482518185028101850190935280835260609492939192909184015b82821015611693575f8481526020908190206040805160c081018252600286810290930180546001600160701b038082168452600160701b808304821697850197909752600160e01b9182900463ffffffff16948401949094526001820154808516606085015295860490931660808301529093909260a085019290910460ff169081111561166f5761166f6124f0565b6002811115611680576116806124f0565b81525050815260200190600101906115de565b505050509050919050565b6116ac620151806007612668565b81565b6116bd620151806007612668565b6116ac906002612668565b6116d0611aab565b600354156116f15760405163116eb83560e11b815260040160405180910390fd5b60028190556040518181527f66b9213ecbabaff7979ad49112aa2a6835a4ad4307c6d562d1af7529d9efa523906020015b60405180910390a150565b5f806117376112e4565b90506006545f0361174b5760095491509091565b5f600a548261175a919061264f565b90505f6008548261176b9190612668565b90505f600754670de0b6b3a7640000836117859190612668565b61178f919061269a565b90508060095461179f91906126b9565b94505050509091565b600b5460609060ff16156117cf57604051630d6819e560e11b815260040160405180910390fd5b600b54610100900460ff16156117f85760405163eebee04f60e01b815260040160405180910390fd5b61180061191f565b335f908152600c60205260409020805467ffffffffffffffff811115611828576118286126f8565b604051908082528060200260200182016040528015611851578160200160208202803683370190505b5091505f5b81548110156118975761186881612272565b83828151811061187a5761187a6126cc565b60209081029190910101528061188f816126e0565b915050611856565b505090565b6118a4611aab565b600455565b6118b1611aab565b6001600160a01b0381166119165760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161094c565b61069f81612355565b61192761172d565b600a55600955565b335f908152600c6020526040812080548390811061194f5761194f6126cc565b5f9182526020822060029091020180549092506001600160701b03169081900361198f5760405163102c472960e11b81526004810184905260240161094c565b8154600160e01b900463ffffffff165f036119c057604051631211d1cb60e31b81526004810184905260240161094c565b6119ca3384611eca565b60018201545f906119e490600160e01b900460ff16611b63565b5083549091505f90670de0b6b3a764000090611a0a9084906001600160701b0316612668565b611a14919061269a565b84549091505f90611a2f9083906001600160701b03166126b9565b85546001600160e01b036001600160701b03808416600160701b029190911691161786556007805491925083915f90611a699084906126b9565b909155505060405186815233907fe13d5cf5271bd721532059c5883b639ba871a2b135c24caf452a8de615213fd49060200160405180910390a2505050505050565b5f546001600160a01b0316331461130b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161094c565b600254611b11908261269a565b600855600254611b2190426126b9565b60058190556002546001556040805183815260208101929092527fff92011f5b6637357c4904d41ea3f9d759723fe605d64ac0e2c47541d288dc039101611722565b5f8080836002811115611b7857611b786124f0565b03611bc757507f000000000000000000000000000000000000000000000000016345785d8a0000927f0000000000000000000000000000000000000000000000000000000000093a8092509050565b6001836002811115611bdb57611bdb6124f0565b03611c2a57507f0000000000000000000000000000000000000000000000000429d069189e0000927f000000000000000000000000000000000000000000000000000000000012750092509050565b6002836002811115611c3e57611c3e6124f0565b03611c8d57507f00000000000000000000000000000000000000000000000006f05b59d3b20000927f00000000000000000000000000000000000000000000000000000000001baf8092509050565b826002811115611c9f57611c9f6124f0565b60405163208a64e760e11b815260040161094c91815260200190565b5f6040516323b872dd60e01b815284600482015283602482015282604482015260205f6064835f8a5af13d15601f3d1160015f511416171691505080611d3a5760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b604482015260640161094c565b5050505050565b335f908152600c60205260408120805483908110611d6157611d616126cc565b5f9182526020822060029091020180549092506001600160701b031690819003611da15760405163102c472960e11b81526004810184905260240161094c565b8154600160e01b900463ffffffff1615611dd157604051630c7820cd60e31b81526004810184905260240161094c565b611ddb3384611eca565b81545f90611dfa908390600160701b90046001600160701b031661264f565b90505f611e1784600101601c9054906101000a900460ff16611b63565b8554600160701b600160e01b031916600160701b6001600160701b038716021786559150611e47905081426126b9565b845463ffffffff91909116600160e01b026001600160e01b03909116178455600780546001600160701b03841691905f90611e8390849061264f565b9091555050604080518681526020810185905233917f7659747cd8571f1071eea946fdc648adcf181cad916f32a05f82c3a525976048910160405180910390a25050505050565b6001600160a01b0382165f908152600c60205260408120805483908110611ef357611ef36126cc565b5f9182526020822060029091020180549092506001600160701b03169003611f1a57505050565b6040805160c08101825282546001600160701b038082168352600160701b8083048216602085015263ffffffff600160e01b938490041694840194909452600185015480821660608501529384041660808301525f92611fab9291859160a084019160ff9104166002811115611f9257611f926124f0565b6002811115611fa357611fa36124f0565b9052506123a4565b90508082600101600e8282829054906101000a90046001600160701b0316611fd3919061270c565b92506101000a8154816001600160701b0302191690836001600160701b03160217905550600954826001015f6101000a8154816001600160701b0302191690836001600160701b0316021790555050505050565b5f60405163a9059cbb60e01b815283600482015282602482015260205f6044835f895af13d15601f3d1160015f51141617169150508061209b5760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b604482015260640161094c565b50505050565b335f908152600c602052604081208054829190849081106120c4576120c46126cc565b5f9182526020822060029091020180549092506001600160701b0316908190036121045760405163102c472960e11b81526004810185905260240161094c565b8154600160e01b900463ffffffff16158061212c57508154600160e01b900463ffffffff1642105b1561214d57604051633a49774d60e11b81526004810185905260240161094c565b6121573385611eca565b60018201805483546001600160e01b0319168455600160701b600160e01b0319811690915560068054600160701b9092046001600160701b0316945082915f906121a290849061264f565b925050819055508060075f8282546121ba919061264f565b909155506121f490506001600160a01b037f000000000000000000000000cf4b531b4cde95bd35d71926e09b2b54c564f5b6163383612027565b6122286001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163385612027565b604080518581526020810183905290810184905233907ffbd65cfd6de1493db337385c0712095397ecbd0504df64b861cdfceb80c7b4229060600160405180910390a25050919050565b335f908152600c60205260408120805482919084908110612295576122956126cc565b905f5260205f20906002020190506122ad3384611eca565b6001810154600160701b90046001600160701b03169150811561234f57600181018054600160701b600160e01b03191690556123136001600160a01b037f000000000000000000000000a670d7237398238de01267472c6f13e5b8010fd1163384612027565b604080518481526020810184905233917f34fcbac0073d7c3d388e51312faf357774904998eeb8fca628b9e6f65ee1cbf7910160405180910390a25b50919050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f8082606001516001600160701b03166009546123c1919061264f565b90505f670de0b6b3a76400008285602001516001600160701b03166123e69190612668565b6123f0919061269a565b949350505050565b5f60208284031215612408575f80fd5b5035919050565b801515811461069f575f80fd5b5f6020828403121561242c575f80fd5b81356124378161240f565b9392505050565b5f806040838503121561244f575f80fd5b82359150602083013560038110612464575f80fd5b809150509250929050565b602080825282518282018190525f9190848201906040850190845b818110156124a65783518352928401929184019160010161248a565b50909695505050505050565b80356001600160a01b0381168114610ef1575f80fd5b5f80604083850312156124d9575f80fd5b6124e2836124b2565b946020939093013593505050565b634e487b7160e01b5f52602160045260245ffd5b6003811061252057634e487b7160e01b5f52602160045260245ffd5b9052565b6001600160701b038781168252868116602083015263ffffffff8616604083015284811660608301528316608082015260c0810161256560a0830184612504565b979650505050505050565b5f60208284031215612580575f80fd5b612437826124b2565b602080825282518282018190525f919060409081850190868401855b8281101561261757815180516001600160701b039081168652878201518116888701528682015163ffffffff16878701526060808301518216908701526080808301519091169086015260a0908101519061260281870183612504565b505060c09390930192908501906001016125a5565b5091979650505050505050565b5f60208284031215612634575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b818103818111156126625761266261263b565b92915050565b80820281158282048414176126625761266261263b565b5f6020828403121561268f575f80fd5b81516124378161240f565b5f826126b457634e487b7160e01b5f52601260045260245ffd5b500490565b808201808211156126625761266261263b565b634e487b7160e01b5f52603260045260245ffd5b5f600182016126f1576126f161263b565b5060010190565b634e487b7160e01b5f52604160045260245ffd5b6001600160701b0381811683821601908082111561272c5761272c61263b565b509291505056fea2646970667358221220b5ded92582d432b343370b5a28e9f77a079c403530f03a5adfbd8822ae01fd2164736f6c63430008150033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 26 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | 100.00% | $0.023465 | 170.0395 | $3.99 |
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.