Source Code
Overview
HYPE Balance
HYPE Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
MultiFeeDistribution
Compiler Version
v0.8.27+commit.40a35a09
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {RecoverERC20} from "../libraries/RecoverERC20.sol";
import {IChefIncentivesController} from "../../interfaces/IChefIncentivesController.sol";
import {IMiddleFeeDistribution} from "../../interfaces/IMiddleFeeDistribution.sol";
import {IBountyManager} from "../../interfaces/IBountyManager.sol";
import {IMultiFeeDistribution, IFeeDistribution} from "../../interfaces/IMultiFeeDistribution.sol";
import {IMintableToken} from "../../interfaces/IMintableToken.sol";
import {LockedBalance, Balances, Reward, EarnedBalance} from "../../interfaces/LockedBalance.sol";
import {IPriceProvider} from "../../interfaces/IPriceProvider.sol";
/// @title Multi Fee Distribution Contract
/// @author Prime
/// @notice This contract is responsible for distributing rewards to stakers.
/// @dev This contract is used to distribute rewards to stakers.
/// Rewards are distributed to stakers based on the amount of tokens they have locked.
/// Stakers can lock their tokens for a specified period of time, and receive rewards based on the lock period.
/// Stakers can also earn rewards by vesting their tokens.
contract MultiFeeDistribution is
IMultiFeeDistribution,
Initializable,
PausableUpgradeable,
OwnableUpgradeable,
RecoverERC20
{
using SafeERC20 for IERC20;
using SafeERC20 for IMintableToken;
address private _priceProvider;
/********************** Constants ***********************/
uint256 public constant QUART = 25000; // 25%
uint256 public constant HALF = 65000; // 65%
uint256 public constant WHOLE = 100000; // 100%
// Maximum slippage allowed to be set by users (used for compounding).
uint256 public constant MAX_SLIPPAGE = 9500; //5%
uint256 public constant PERCENT_DIVISOR = 10000; //100%
/// @notice Proportion of burn amount
uint256 public burn;
/// @notice Duration that rewards are streamed over
uint256 public rewardsDuration;
/// @notice Duration that rewards loop back
uint256 public rewardsLookback;
/// @notice Default lock index
uint256 public constant DEFAULT_LOCK_INDEX = 1;
/// @notice Duration of lock/earned penalty period, used for earnings
uint256 public defaultLockDuration;
/// @notice Duration of vesting PRFI
uint256 public vestDuration;
/// @notice Returns reward converter
address public rewardConverter;
/********************** Contract Addresses ***********************/
/// @notice Address of Middle Fee Distribution Contract
IMiddleFeeDistribution public middleFeeDistribution;
/// @notice Address of CIC contract
IChefIncentivesController public incentivesController;
/// @notice Address of PRFI
IMintableToken public prfiToken;
/// @notice Address of LP token
address public stakingToken;
// Address of Lock Fliker
address internal _flik;
/********************** Lock & Earn Info ***********************/
// Private mappings for balance data
mapping(address => Balances) private _balances;
mapping(address => LockedBalance[]) internal _userLocks;
mapping(address => LockedBalance[]) private _userEarnings;
mapping(address => bool) public autocompoundEnabled;
mapping(address => uint256) public lastAutocompound;
/// @notice Total locked value
uint256 public lockedSupply;
/// @notice Total locked value in multipliers
uint256 public lockedSupplyWithMultiplier;
// Time lengths
uint256[] internal _lockPeriod;
// Multipliers
uint256[] internal _rewardMultipliers;
/********************** Reward Info ***********************/
/// @notice Reward tokens being distributed
address[] public rewardTokens;
/// @notice Reward data per token
mapping(address => Reward) public rewardData;
/// @notice user -> reward token -> rpt; RPT for paid amount
mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid;
/// @notice user -> reward token -> amount; used to store reward amount
mapping(address => mapping(address => uint256)) public rewards;
/********************** Other Info ***********************/
/// @notice DAO wallet
address public daoTreasury;
/// @notice treasury wallet
address public starfleetTreasury;
/// @notice Addresses approved to call mint
mapping(address => bool) public minters;
// Addresses to relock
mapping(address => bool) public autoRelockDisabled;
// Default lock index for relock
mapping(address => uint256) public defaultLockIndex;
/// @notice Flag to prevent more minter addings
bool public mintersAreSet;
/// @notice Last claim time of the user
mapping(address => uint256) public lastClaimTime;
/// @notice Bounty manager contract
address public bountyManager;
/// @notice Maximum slippage for each trade excepted by the individual user when performing compound trades
mapping(address => uint256) public userSlippage;
/********************** Events ***********************/
event Locked(address indexed user, uint256 amount, uint256 lockedBalance, uint256 indexed lockLength, bool isLP);
event Withdrawn(
address indexed user,
uint256 receivedAmount,
uint256 lockedBalance,
uint256 penalty,
uint256 burn,
bool isLP
);
event RewardPaid(address indexed user, address indexed rewardToken, uint256 reward);
event Relocked(address indexed user, uint256 amount, uint256 lockIndex);
event BountyManagerUpdated(address indexed _bounty);
event RewardConverterUpdated(address indexed _rewardConverter);
event LockTypeInfoUpdated(uint256[] lockPeriod, uint256[] rewardMultipliers);
event AddressesUpdated(
IChefIncentivesController _controller,
IMiddleFeeDistribution _middleFeeDistribution,
address indexed _treasury
);
event LPTokenUpdated(address indexed _stakingToken);
event RewardAdded(address indexed _rewardToken);
event LockerAdded(address indexed locker);
event LockerRemoved(address indexed locker);
/********************** Errors ***********************/
error AddressZero();
error AmountZero();
error InvalidBurn();
error InvalidLookback();
error MintersSet();
error InvalidLockPeriod();
error InsufficientPermission();
error AlreadyAdded();
error AlreadySet();
error InvalidType();
error ActiveReward();
error InvalidAmount();
error InvalidEarned();
error InvalidTime();
error InvalidPeriod();
error UnlockTimeNotFound();
error InvalidAddress();
constructor() {
_disableInitializers();
}
/**
* @dev Initializer
* First reward MUST be the PRFI token or things will break
* related to the 50% penalty and distribution to locked balances.
* @param prfiToken_ PRFI token address
* @param flik_ Flik contract address
* @param dao_ DAO address
* @param priceProvider_ PriceProvider contract address
* @param rewardsDuration_ Duration that rewards are streamed over
* @param rewardsLookback_ Duration that rewards loop back
* @param lockDuration_ lock duration
* @param burnRatio_ Proportion of burn amount
* @param vestDuration_ vest duration
*/
function initialize(
address prfiToken_,
address flik_,
address dao_,
address priceProvider_,
uint256 rewardsDuration_,
uint256 rewardsLookback_,
uint256 lockDuration_,
uint256 burnRatio_,
uint256 vestDuration_
) public initializer {
require(
prfiToken_ != address(0) && flik_ != address(0) && dao_ != address(0) && priceProvider_ != address(0),
AddressZero()
);
require(rewardsDuration_ > 0 && rewardsLookback_ > 0 && lockDuration_ > 0 && vestDuration_ > 0, AmountZero());
require(burnRatio_ <= WHOLE, InvalidBurn());
require(rewardsLookback_ <= rewardsDuration_, InvalidLookback());
__Pausable_init();
__Ownable_init(_msgSender());
prfiToken = IMintableToken(prfiToken_);
_flik = flik_;
daoTreasury = dao_;
_priceProvider = priceProvider_;
rewardTokens.push(prfiToken_);
rewardData[prfiToken_].lastUpdateTime = block.timestamp;
rewardsDuration = rewardsDuration_;
rewardsLookback = rewardsLookback_;
defaultLockDuration = lockDuration_;
burn = burnRatio_;
vestDuration = vestDuration_;
}
/********************** Setters ***********************/
/**
* @notice Set minters
* @dev Can be called only once
* @param minters_ array of address
*/
function setMinters(address[] calldata minters_) external onlyOwner {
if (mintersAreSet) revert MintersSet();
uint256 length = minters_.length;
for (uint256 i; i < length; ) {
if (minters_[i] == address(0)) revert AddressZero();
minters[minters_[i]] = true;
unchecked {
i++;
}
}
mintersAreSet = true;
}
/**
* @notice Sets bounty manager contract.
* @param bounty contract address
*/
function setBountyManager(address bounty) external onlyOwner {
if (bounty == address(0)) revert AddressZero();
bountyManager = bounty;
minters[bounty] = true;
emit BountyManagerUpdated(bounty);
}
/**
* @notice Sets reward converter contract.
* @param rewardConverter_ contract address
*/
function addRewardConverter(address rewardConverter_) external onlyOwner {
if (rewardConverter_ == address(0)) revert AddressZero();
rewardConverter = rewardConverter_;
emit RewardConverterUpdated(rewardConverter_);
}
/**
* @notice Sets lock period and reward multipliers.
* @param lockPeriod_ lock period array
* @param rewardMultipliers_ multipliers per lock period
*/
function setLockTypeInfo(uint256[] calldata lockPeriod_, uint256[] calldata rewardMultipliers_) external onlyOwner {
if (lockPeriod_.length != rewardMultipliers_.length) revert InvalidLockPeriod();
delete _lockPeriod;
delete _rewardMultipliers;
uint256 length = lockPeriod_.length;
for (uint256 i; i < length; ) {
_lockPeriod.push(lockPeriod_[i]);
_rewardMultipliers.push(rewardMultipliers_[i]);
unchecked {
i++;
}
}
emit LockTypeInfoUpdated(lockPeriod_, rewardMultipliers_);
}
/**
* @notice Set CIC, MFD and Treasury.
* @param controller_ CIC address
* @param middleFeeDistribution_ address
* @param treasury_ address
*/
function setAddresses(
IChefIncentivesController controller_,
IMiddleFeeDistribution middleFeeDistribution_,
address treasury_
) external onlyOwner {
if (address(controller_) == address(0)) revert AddressZero();
if (address(middleFeeDistribution_) == address(0)) revert AddressZero();
incentivesController = controller_;
middleFeeDistribution = middleFeeDistribution_;
starfleetTreasury = treasury_;
emit AddressesUpdated(controller_, middleFeeDistribution_, treasury_);
}
/**
* @notice Set LP token.
* @param stakingToken_ LP token address
*/
function setLPToken(address stakingToken_) external onlyOwner {
if (stakingToken_ == address(0)) revert AddressZero();
if (stakingToken != address(0)) revert AlreadySet();
stakingToken = stakingToken_;
emit LPTokenUpdated(stakingToken_);
}
/**
* @notice Add a new reward token to be distributed to stakers.
* @param rewardToken address
*/
function addReward(address rewardToken) external {
if (rewardToken == address(0)) revert AddressZero();
if (!minters[msg.sender]) revert InsufficientPermission();
if (rewardData[rewardToken].lastUpdateTime != 0) revert AlreadyAdded();
rewardTokens.push(rewardToken);
Reward storage rd = rewardData[rewardToken];
rd.lastUpdateTime = block.timestamp;
rd.periodFinish = block.timestamp;
emit RewardAdded(rewardToken);
}
/**
* @notice Remove an existing reward token.
* @param _rewardToken address to be removed
*/
function removeReward(address _rewardToken) external {
if (!minters[msg.sender]) revert InsufficientPermission();
bool isTokenFound;
uint256 indexToRemove;
uint256 length = rewardTokens.length;
for (uint256 i; i < length; ) {
if (rewardTokens[i] == _rewardToken) {
isTokenFound = true;
indexToRemove = i;
break;
}
unchecked {
i++;
}
}
if (!isTokenFound) revert InvalidAddress();
// Reward token order is changed, but that doesn't have an impact
if (indexToRemove < length - 1) {
rewardTokens[indexToRemove] = rewardTokens[length - 1];
}
rewardTokens.pop();
// Scrub historical reward token data
delete rewardData[_rewardToken];
}
/**
* @notice Set default lock type index for user relock.
* @param index of default lock length
*/
function setDefaultRelockTypeIndex(uint256 index) external {
require(index < _lockPeriod.length, InvalidType());
defaultLockIndex[msg.sender] = index;
}
/**
* @notice Sets option if auto compound is enabled.
* @param status true if auto compounding is enabled.
* @param slippage the maximum amount of slippage that the user will incur for each compounding trade
*/
function setAutocompound(bool status, uint256 slippage) external {
require(slippage >= MAX_SLIPPAGE && slippage < PERCENT_DIVISOR, InvalidAmount());
autocompoundEnabled[msg.sender] = status;
userSlippage[msg.sender] = slippage;
}
/**
* @notice Set relock status
* @param status true if auto relock is enabled.
*/
function setRelock(bool status) external virtual {
autoRelockDisabled[msg.sender] = !status;
}
/**
* @notice Sets the lookback period
* @param lookback in seconds
*/
function setLookback(uint256 lookback) external onlyOwner {
require(lookback > 0, AmountZero());
require(lookback <= rewardsDuration, InvalidLookback());
rewardsLookback = lookback;
}
/********************** External functions ***********************/
/**
* @notice Stake tokens to receive rewards.
* @dev Locked tokens cannot be withdrawn for defaultLockDuration and are eligible to receive rewards.
* @param amount to stake.
* @param onBehalfOf address for staking.
* @param typeIndex lock type index.
*/
function stake(uint256 amount, address onBehalfOf, uint256 typeIndex) external {
_stake(amount, onBehalfOf, typeIndex, false);
}
/**
* @notice Add to earnings
* @dev Minted tokens receive rewards normally but incur a 50% penalty when
* withdrawn before vestDuration has passed.
* @param user vesting owner.
* @param amount to vest.
* @param withPenalty does this bear penalty?
*/
function vestTokens(address user, uint256 amount, bool withPenalty) external whenNotPaused {
require(minters[msg.sender], InsufficientPermission());
if (amount == 0) return;
if (user == address(this)) {
// minting to this contract adds the new tokens as incentives for lockers
_notifyReward(address(prfiToken), amount);
return;
}
Balances storage bal = _balances[user];
bal.total = bal.total + amount;
if (withPenalty) {
bal.earned = bal.earned + amount;
LockedBalance[] storage earnings = _userEarnings[user];
uint256 lastIndex = earnings.length > 0 ? earnings.length - 1 : 0;
uint256 currentDayVestingEnd = (block.timestamp + vestDuration) / 1 days;
// We check if an entry for the current day already exists. If yes, add new amount to that entry
if (earnings.length > 0 && (earnings[lastIndex].unlockTime / 1 days) == currentDayVestingEnd) {
earnings[lastIndex].amount = earnings[lastIndex].amount + amount;
} else {
// If there is no entry for the current day, create a new one
uint256 unlockTime = block.timestamp + vestDuration;
earnings.push(
LockedBalance({amount: amount, unlockTime: unlockTime, multiplier: 1, duration: vestDuration})
);
}
} else {
bal.unlocked = bal.unlocked + amount;
}
}
/**
* @notice Withdraw tokens from earnings and unlocked.
* @dev First withdraws unlocked tokens, then earned tokens. Withdrawing earned tokens
* incurs a 50% penalty which is distributed based on locked balances.
* @param amount for withdraw
*/
function withdraw(uint256 amount) external {
address _address = msg.sender;
if (amount == 0) revert AmountZero();
uint256 penaltyAmount;
uint256 burnAmount;
Balances storage bal = _balances[_address];
if (amount <= bal.unlocked) {
bal.unlocked = bal.unlocked - amount;
} else {
uint256 remaining = amount - bal.unlocked;
require(bal.earned >= remaining, InvalidEarned());
bal.unlocked = 0;
uint256 sumEarned = bal.earned;
uint256 i;
LockedBalance[] storage earnings = _userEarnings[_address];
for (;;) {
uint256 earnedAmount = earnings[i].amount;
if (earnedAmount == 0) continue;
(
uint256 withdrawAmount,
uint256 penaltyFactor,
uint256 newPenaltyAmount,
uint256 newBurnAmount
) = _penaltyInfo(earnings[i]);
uint256 requiredAmount = earnedAmount;
if (remaining >= withdrawAmount) {
remaining = remaining - withdrawAmount;
if (remaining == 0) i++;
} else {
requiredAmount = (remaining * WHOLE) / (WHOLE - penaltyFactor);
earnings[i].amount = earnedAmount - requiredAmount;
remaining = 0;
newPenaltyAmount = (requiredAmount * penaltyFactor) / WHOLE;
newBurnAmount = (newPenaltyAmount * burn) / WHOLE;
}
sumEarned = sumEarned - requiredAmount;
penaltyAmount = penaltyAmount + newPenaltyAmount;
burnAmount = burnAmount + newBurnAmount;
if (remaining == 0) {
break;
} else {
if (sumEarned == 0) revert InvalidEarned();
}
unchecked {
i++;
}
}
if (i > 0) {
uint256 length = earnings.length;
for (uint256 j = i; j < length; ) {
earnings[j - i] = earnings[j];
unchecked {
j++;
}
}
for (uint256 j = 0; j < i; ) {
earnings.pop();
unchecked {
j++;
}
}
}
bal.earned = sumEarned;
}
// Update values
bal.total = bal.total - amount - penaltyAmount;
_withdrawTokens(_address, amount, penaltyAmount, burnAmount, false);
}
/**
* @notice Withdraw individual unlocked balance and earnings, optionally claim pending rewards.
* @param claimRewards true to claim rewards when exit
* @param unlockTime of earning
*/
function individualEarlyExit(bool claimRewards, uint256 unlockTime) external {
address onBehalfOf = msg.sender;
require(unlockTime > block.timestamp, InvalidTime());
(uint256 amount, uint256 penaltyAmount, uint256 burnAmount, uint256 index) = _ieeWithdrawableBalance(
onBehalfOf,
0,
unlockTime
);
LockedBalance[] storage earnings = _userEarnings[onBehalfOf];
uint256 length = earnings.length;
for (uint256 i = index + 1; i < length; ) {
earnings[i - 1] = earnings[i];
unchecked {
i++;
}
}
earnings.pop();
Balances storage bal = _balances[onBehalfOf];
bal.total = bal.total - amount - penaltyAmount;
bal.earned = bal.earned - amount - penaltyAmount;
_withdrawTokens(onBehalfOf, amount, penaltyAmount, burnAmount, claimRewards);
}
/**
* @notice Withdraw full unlocked balance and earnings, optionally claim pending rewards.
* @param claimRewards true to claim rewards when exit
*/
function exit(bool claimRewards) external {
address onBehalfOf = msg.sender;
(uint256 amount, uint256 penaltyAmount, uint256 burnAmount) = withdrawableBalance(onBehalfOf);
delete _userEarnings[onBehalfOf];
Balances storage bal = _balances[onBehalfOf];
bal.total = bal.total - bal.unlocked - bal.earned;
bal.unlocked = 0;
bal.earned = 0;
_withdrawTokens(onBehalfOf, amount, penaltyAmount, burnAmount, claimRewards);
}
/**
* @notice Claim all pending staking rewards.
*/
function getAllRewards() external {
return getReward(rewardTokens);
}
/**
* @notice Withdraw expired locks with options
* @param address_ for withdraw
* @param limit_ of lock length for withdraw
* @param isRelockAction_ option to relock
* @return withdraw amount
*/
function withdrawExpiredLocksForWithOptions(
address address_,
uint256 limit_,
bool isRelockAction_
) external returns (uint256) {
if (limit_ == 0) limit_ = _userLocks[address_].length;
return _withdrawExpiredLocksFor(address_, isRelockAction_, true, limit_);
}
/**
* @notice Flik vesting PRFI tokens to LP
* @param user address
* @return flikped amount
*/
function flikVestingToLp(address user) external returns (uint256 flikped) {
if (msg.sender != _flik) revert InsufficientPermission();
_updateReward(user);
uint256 currentTimestamp = block.timestamp;
LockedBalance[] storage earnings = _userEarnings[user];
for (uint256 i = earnings.length; i > 0; ) {
LockedBalance storage earning = earnings[i - 1];
if (earning.unlockTime > currentTimestamp) {
flikped = flikped + earning.amount;
earnings.pop();
} else {
break;
}
unchecked {
i--;
}
}
prfiToken.safeTransfer(_flik, flikped);
Balances storage bal = _balances[user];
bal.earned = bal.earned - flikped;
bal.total = bal.total - flikped;
IPriceProvider(_priceProvider).update();
return flikped;
}
/**
* @notice Claim rewards by converter.
* @dev Rewards are transfered to converter. In the Prime Capital protocol
* the role of the Converter is taken over by Compounder.sol.
* @param onBehalf address to claim.
*/
function claimFromConverter(address onBehalf) external whenNotPaused {
if (msg.sender != rewardConverter) revert InsufficientPermission();
_updateReward(onBehalf);
middleFeeDistribution.forwardReward(rewardTokens);
uint256 length = rewardTokens.length;
mapping(address => uint256) storage userRewards = rewards[onBehalf];
for (uint256 i; i < length; ) {
address token = rewardTokens[i];
if (token != address(prfiToken)) {
_notifyUnseenReward(token);
Reward storage rd = rewardData[token];
uint256 tokenReward = userRewards[token];
uint256 reward = tokenReward / 1e12;
if (reward > 0) {
delete userRewards[token];
rd.balance = rd.balance - reward;
IERC20(token).safeTransfer(rewardConverter, reward);
emit RewardPaid(onBehalf, token, reward);
}
}
unchecked {
i++;
}
}
IPriceProvider(_priceProvider).update();
lastClaimTime[onBehalf] = block.timestamp;
}
/**
* @notice Withdraw and restake assets.
*/
function relock() external virtual {
uint256 amount = _withdrawExpiredLocksFor(msg.sender, true, true, _userLocks[msg.sender].length);
emit Relocked(msg.sender, amount, defaultLockIndex[msg.sender]);
}
/**
* @notice Requalify user
*/
function requalify() external {
requalifyFor(msg.sender);
}
/**
* @notice Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders.
* @param tokenAddress to recover.
* @param tokenAmount to recover.
*/
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
_recoverERC20(tokenAddress, tokenAmount);
}
/********************** External View functions ***********************/
/**
* @notice Return lock duration.
*/
function getLockDurations() external view returns (uint256[] memory) {
return _lockPeriod;
}
/**
* @notice Return reward multipliers.
*/
function getLockMultipliers() external view returns (uint256[] memory) {
return _rewardMultipliers;
}
/**
* @notice Returns all locks of a user.
* @param user address.
* @return lockInfo of the user.
*/
function lockInfo(address user) external view returns (LockedBalance[] memory) {
return _userLocks[user];
}
/**
* @notice Total balance of an account, including unlocked, locked and earned tokens.
* @param user address.
*/
function totalBalance(address user) external view returns (uint256) {
Balances storage bal = _balances[user];
if (stakingToken == address(prfiToken)) {
return bal.total;
}
return bal.locked;
}
/**
* @notice Returns price provider address
*/
function getPriceProvider() external view returns (address) {
return _priceProvider;
}
/**
* @notice Reward amount of the duration.
* @param rewardToken for the reward
* @return reward amount for duration
*/
function getRewardForDuration(address rewardToken) external view returns (uint256) {
return (rewardData[rewardToken].rewardPerSecond * rewardsDuration) / 1e12;
}
/**
* @notice Total balance of an account, including unlocked, locked and earned tokens.
* @param user address of the user for which the balances are fetched
*/
function getBalances(address user) external view returns (Balances memory) {
return _balances[user];
}
/********************** Public functions ***********************/
/**
* @notice Claims bounty.
* @dev Remove expired locks
* @param user address
* @param execute true if this is actual execution
* @return issueBaseBounty true if needs to issue base bounty
*/
function claimBounty(address user, bool execute) public whenNotPaused returns (bool issueBaseBounty) {
if (msg.sender != address(bountyManager)) revert InsufficientPermission();
(, uint256 unlockable, , , ) = lockedBalances(user);
if (unlockable == 0) {
return (false);
} else {
issueBaseBounty = true;
}
if (!execute) {
return (issueBaseBounty);
}
// Withdraw the user's expried locks
_withdrawExpiredLocksFor(user, false, true, _userLocks[user].length);
}
/**
* @notice Claim all pending staking rewards.
* @param rewardTokens_ array of reward tokens
*/
function getReward(address[] memory rewardTokens_) public {
_updateReward(msg.sender);
_getReward(msg.sender, rewardTokens_);
IPriceProvider(_priceProvider).update();
}
/**
* @notice Pause MFD functionalities
*/
function pause() public onlyOwner {
_pause();
}
/**
* @notice Resume MFD functionalities
*/
function unpause() public onlyOwner {
_unpause();
}
/**
* @notice Requalify user for reward elgibility
* @param user address
*/
function requalifyFor(address user) public {
incentivesController.afterLockUpdate(user);
}
/**
* @notice Information on a user's lockings
* @return total balance of locks
* @return unlockable balance
* @return locked balance
* @return lockedWithMultiplier
* @return lockData which is an array of locks
*/
function lockedBalances(
address user
)
public
view
returns (
uint256 total,
uint256 unlockable,
uint256 locked,
uint256 lockedWithMultiplier,
LockedBalance[] memory lockData
)
{
LockedBalance[] storage locks = _userLocks[user];
uint256 j;
uint256 length = locks.length;
for (uint256 i; i < length; ) {
if (locks[i].unlockTime > block.timestamp) {
if (j == 0) {
lockData = new LockedBalance[](locks.length - i);
}
lockData[j] = locks[i];
locked = locked + locks[i].amount;
lockedWithMultiplier = lockedWithMultiplier + (locks[i].amount * locks[i].multiplier);
unchecked {
j++;
}
} else {
unlockable = unlockable + locks[i].amount;
}
unchecked {
i++;
}
}
total = _balances[user].locked;
}
/**
* @notice Reward locked amount of the user.
* @param user address
* @return locked amount
*/
function lockedBalance(address user) public view returns (uint256 locked) {
LockedBalance[] storage locks = _userLocks[user];
uint256 length = locks.length;
uint256 currentTimestamp = block.timestamp;
for (uint256 i; i < length; ) {
if (locks[i].unlockTime > currentTimestamp) {
locked = locked + locks[i].amount;
}
unchecked {
i++;
}
}
}
/**
* @notice Earnings which are vesting, and earnings which have vested for full duration.
* @dev Earned balances may be withdrawn immediately, but will incur a penalty between 25-90%, based on a linear schedule of elapsed time.
* @return totalVesting sum of vesting tokens
* @return unlocked earnings
* @return earningsData which is an array of all infos
*/
function earnedBalances(
address user
) public view returns (uint256 totalVesting, uint256 unlocked, EarnedBalance[] memory earningsData) {
unlocked = _balances[user].unlocked;
LockedBalance[] storage earnings = _userEarnings[user];
uint256 j;
uint256 length = earnings.length;
uint256 currentTimestamp = block.timestamp;
for (uint256 i; i < length; ) {
LockedBalance memory earning = earnings[i];
if (earning.unlockTime > currentTimestamp) {
if (j == 0) {
earningsData = new EarnedBalance[](earnings.length - i);
}
(, uint256 penaltyAmount, , ) = _ieeWithdrawableBalance(user, i, earning.unlockTime);
earningsData[j].amount = earning.amount;
earningsData[j].unlockTime = earning.unlockTime;
earningsData[j].penalty = penaltyAmount;
totalVesting = totalVesting + earning.amount;
unchecked {
j++;
}
} else {
unlocked = unlocked + earning.amount;
}
unchecked {
i++;
}
}
return (totalVesting, unlocked, earningsData);
}
/**
* @notice Final balance received and penalty balance paid by user upon calling exit.
* @dev This is earnings, not locks.
* @param user address.
* @return amount total withdrawable amount.
* @return penaltyAmount penalty amount.
* @return burnAmount amount to burn.
*/
function withdrawableBalance(
address user
) public view returns (uint256 amount, uint256 penaltyAmount, uint256 burnAmount) {
Balances storage bal = _balances[user];
uint256 earned = bal.earned;
if (earned > 0) {
LockedBalance[] storage earnings = _userEarnings[user];
uint256 length = earnings.length;
for (uint256 i; i < length; ) {
LockedBalance storage earning = earnings[i];
uint256 earnedAmount = earning.amount;
if (earnedAmount == 0) continue;
(, , uint256 newPenaltyAmount, uint256 newBurnAmount) = _penaltyInfo(earning);
penaltyAmount = penaltyAmount + newPenaltyAmount;
burnAmount = burnAmount + newBurnAmount;
unchecked {
i++;
}
}
}
amount = bal.unlocked + earned - penaltyAmount;
return (amount, penaltyAmount, burnAmount);
}
/**
* @notice Returns reward applicable timestamp.
* @param rewardToken for the reward
* @return end time of reward period
*/
function lastTimeRewardApplicable(address rewardToken) public view returns (uint256) {
uint256 periodFinish = rewardData[rewardToken].periodFinish;
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
/**
* @notice Reward amount per token
* @dev Reward is distributed only for locks.
* @param rewardToken for reward
* @return rptStored current RPT with accumulated rewards
*/
function rewardPerToken(address rewardToken) public view returns (uint256 rptStored) {
rptStored = rewardData[rewardToken].rewardPerTokenStored;
if (lockedSupplyWithMultiplier > 0) {
uint256 newReward = (lastTimeRewardApplicable(rewardToken) - rewardData[rewardToken].lastUpdateTime) *
rewardData[rewardToken].rewardPerSecond;
rptStored = rptStored + ((newReward * 1e18) / lockedSupplyWithMultiplier);
}
}
/**
* @notice Address and claimable amount of all reward tokens for the given account.
* @param account for rewards
* @return rewardsData array of rewards
*/
function claimableRewards(address account) public view returns (IFeeDistribution.RewardData[] memory rewardsData) {
rewardsData = new IFeeDistribution.RewardData[](rewardTokens.length);
uint256 length = rewardTokens.length;
for (uint256 i; i < length; ) {
rewardsData[i].token = rewardTokens[i];
rewardsData[i].amount =
_earned(
account,
rewardsData[i].token,
_balances[account].lockedWithMultiplier,
rewardPerToken(rewardsData[i].token)
) /
1e12;
unchecked {
i++;
}
}
return rewardsData;
}
/********************** Internal functions ***********************/
/**
* @notice Stake tokens to receive rewards.
* @dev Locked tokens cannot be withdrawn for defaultLockDuration and are eligible to receive rewards.
* @param amount to stake.
* @param onBehalfOf address for staking.
* @param typeIndex lock type index.
* @param isRelock true if this is with relock enabled.
*/
function _stake(uint256 amount, address onBehalfOf, uint256 typeIndex, bool isRelock) internal whenNotPaused {
if (amount == 0) return;
if (bountyManager != address(0)) {
if (amount < IBountyManager(bountyManager).minDLPBalance()) revert InvalidAmount();
}
if (typeIndex >= _lockPeriod.length) revert InvalidType();
_updateReward(onBehalfOf);
LockedBalance[] memory userLocks = _userLocks[onBehalfOf];
uint256 userLocksLength = userLocks.length;
Balances storage bal = _balances[onBehalfOf];
bal.total = bal.total + amount;
bal.locked = bal.locked + amount;
lockedSupply = lockedSupply + amount;
uint256 rewardMultiplier = _rewardMultipliers[typeIndex];
bal.lockedWithMultiplier = bal.lockedWithMultiplier + (amount * rewardMultiplier);
lockedSupplyWithMultiplier = lockedSupplyWithMultiplier + (amount * rewardMultiplier);
uint256 lockDurationDays = _lockPeriod[typeIndex] / 1 days;
uint256 unlockTime = block.timestamp + (lockDurationDays * 1 days);
uint256 lastIndex = userLocksLength > 0 ? userLocksLength - 1 : 0;
if (userLocksLength > 0) {
LockedBalance memory lastUserLock = userLocks[lastIndex];
if (
(lastUserLock.unlockTime / 1 days == (block.timestamp / 1 days) + lockDurationDays) &&
lastUserLock.multiplier == rewardMultiplier
) {
_userLocks[onBehalfOf][lastIndex].amount = lastUserLock.amount + amount;
} else {
_insertLock(
onBehalfOf,
LockedBalance({
amount: amount,
unlockTime: unlockTime,
multiplier: rewardMultiplier,
duration: _lockPeriod[typeIndex]
})
);
emit LockerAdded(onBehalfOf);
}
} else {
_insertLock(
onBehalfOf,
LockedBalance({
amount: amount,
unlockTime: unlockTime,
multiplier: rewardMultiplier,
duration: _lockPeriod[typeIndex]
})
);
emit LockerAdded(onBehalfOf);
}
if (!isRelock) {
IERC20(stakingToken).safeTransferFrom(msg.sender, address(this), amount);
}
incentivesController.afterLockUpdate(onBehalfOf);
emit Locked(
onBehalfOf,
amount,
_balances[onBehalfOf].locked,
_lockPeriod[typeIndex],
stakingToken != address(prfiToken)
);
}
/**
* @notice Add new lockings
* @dev We keep the array to be sorted by unlock time.
* @param user address of locker.
* @param newLock new lock info.
*/
function _insertLock(address user, LockedBalance memory newLock) internal {
LockedBalance[] storage locks = _userLocks[user];
uint256 length = locks.length;
uint256 i = _binarySearch(locks, length, newLock.unlockTime);
locks.push();
for (uint256 j = length; j > i; ) {
locks[j] = locks[j - 1];
unchecked {
j--;
}
}
locks[i] = newLock;
}
/**
* @notice Update user reward info.
* @param account address
*/
function _updateReward(address account) internal {
uint256 balance = _balances[account].lockedWithMultiplier;
uint256 length = rewardTokens.length;
for (uint256 i; i < length; ) {
address token = rewardTokens[i];
uint256 rpt = rewardPerToken(token);
Reward storage r = rewardData[token];
r.rewardPerTokenStored = rpt;
r.lastUpdateTime = lastTimeRewardApplicable(token);
if (account != address(this)) {
rewards[account][token] = _earned(account, token, balance, rpt);
userRewardPerTokenPaid[account][token] = rpt;
}
unchecked {
i++;
}
}
}
/**
* @notice Add new reward.
* @dev If prev reward period is not done, then it resets `rewardPerSecond` and restarts period
* @param rewardToken address
* @param reward amount
*/
function _notifyReward(address rewardToken, uint256 reward) internal {
Reward storage r = rewardData[rewardToken];
if (block.timestamp >= r.periodFinish) {
r.rewardPerSecond = (reward * 1e12) / rewardsDuration;
} else {
uint256 remaining = r.periodFinish - block.timestamp;
uint256 leftover = (remaining * r.rewardPerSecond) / 1e12;
r.rewardPerSecond = ((reward + leftover) * 1e12) / rewardsDuration;
}
r.lastUpdateTime = block.timestamp;
r.periodFinish = block.timestamp + rewardsDuration;
r.balance = r.balance + reward;
}
/**
* @notice Notify unseen rewards.
* @dev for rewards other than PRFI token, every 24 hours we check if new
* rewards were sent to the contract or accrued via aToken interest.
* @param token address
*/
function _notifyUnseenReward(address token) internal {
require(token != address(0), AddressZero());
if (token == address(prfiToken)) {
return;
}
Reward storage r = rewardData[token];
uint256 periodFinish = r.periodFinish;
require(periodFinish > 0, InvalidPeriod());
if (periodFinish < block.timestamp + rewardsDuration - rewardsLookback) {
uint256 unseen = IERC20(token).balanceOf(address(this)) - r.balance;
if (unseen > 0) {
_notifyReward(token, unseen);
}
}
}
/**
* @notice User gets reward
* @param user address
* @param rewardTokens_ array of reward tokens
*/
function _getReward(address user, address[] memory rewardTokens_) internal whenNotPaused {
middleFeeDistribution.forwardReward(rewardTokens_);
uint256 length = rewardTokens_.length;
mapping(address => uint256) storage userRewards = rewards[user];
for (uint256 i; i < length; ) {
address token = rewardTokens_[i];
Reward storage r = rewardData[token];
uint256 tokenReward = userRewards[token];
_notifyUnseenReward(token);
uint256 reward = tokenReward / 1e12;
if (reward > 0) {
delete userRewards[token];
r.balance = r.balance - reward;
IERC20(token).safeTransfer(user, reward);
emit RewardPaid(user, token, reward);
}
unchecked {
i++;
}
}
}
/**
* @notice Withdraw tokens from MFD
* @param onBehalfOf address to withdraw
* @param amount of withdraw
* @param penaltyAmount penalty applied amount
* @param burnAmount amount to burn
* @param claimRewards option to claim rewards
*/
function _withdrawTokens(
address onBehalfOf,
uint256 amount,
uint256 penaltyAmount,
uint256 burnAmount,
bool claimRewards
) internal {
if (onBehalfOf != msg.sender) revert InsufficientPermission();
_updateReward(onBehalfOf);
prfiToken.safeTransfer(onBehalfOf, amount);
if (penaltyAmount > 0) {
if (burnAmount > 0) {
prfiToken.safeTransfer(starfleetTreasury, burnAmount);
}
prfiToken.safeTransfer(daoTreasury, penaltyAmount - burnAmount);
}
if (claimRewards) {
_getReward(onBehalfOf, rewardTokens);
lastClaimTime[onBehalfOf] = block.timestamp;
}
IPriceProvider(_priceProvider).update();
emit Withdrawn(onBehalfOf, amount, _balances[onBehalfOf].locked, penaltyAmount, burnAmount, false);
}
/**
* @notice Withdraw all lockings tokens where the unlock time has passed
* @param user address
* @param limit limit for looping operation
* @return lockAmount withdrawable lock amount
* @return lockAmountWithMultiplier withdraw amount with multiplier
*/
function _cleanWithdrawableLocks(
address user,
uint256 limit
) internal returns (uint256 lockAmount, uint256 lockAmountWithMultiplier) {
LockedBalance[] storage locks = _userLocks[user];
if (locks.length != 0) {
uint256 length = locks.length <= limit ? locks.length : limit;
uint256 i;
LockedBalance memory lock = locks[i];
while (i < length && lock.unlockTime <= block.timestamp) {
lockAmount = lockAmount + lock.amount;
lockAmountWithMultiplier = lockAmountWithMultiplier + (lock.amount * lock.multiplier);
unchecked {
i++;
}
if (i < length) {
lock = locks[i];
}
}
uint256 locksLength = locks.length;
for (uint256 j = i; j < locksLength; ) {
locks[j - i] = locks[j];
unchecked {
j++;
}
}
for (uint256 j = 0; j < i; ) {
locks.pop();
unchecked {
j++;
}
}
if (locks.length == 0) {
emit LockerRemoved(user);
}
}
}
/**
* @notice Withdraw all currently locked tokens where the unlock time has passed.
* @param address_ of the user.
* @param isRelockAction true if withdraw with relock
* @param doTransfer true to transfer tokens to user
* @param limit limit for looping operation
* @return amount for withdraw
*/
function _withdrawExpiredLocksFor(
address address_,
bool isRelockAction,
bool doTransfer,
uint256 limit
) internal whenNotPaused returns (uint256 amount) {
if (isRelockAction && address_ != msg.sender && _flik != msg.sender) revert InsufficientPermission();
_updateReward(address_);
uint256 amountWithMultiplier;
Balances storage bal = _balances[address_];
(amount, amountWithMultiplier) = _cleanWithdrawableLocks(address_, limit);
bal.locked = bal.locked - amount;
bal.lockedWithMultiplier = bal.lockedWithMultiplier - amountWithMultiplier;
bal.total = bal.total - amount;
lockedSupply = lockedSupply - amount;
lockedSupplyWithMultiplier = lockedSupplyWithMultiplier - amountWithMultiplier;
if (isRelockAction || (address_ != msg.sender && !autoRelockDisabled[address_])) {
_stake(amount, address_, defaultLockIndex[address_], true);
} else {
if (doTransfer) {
IERC20(stakingToken).safeTransfer(address_, amount);
incentivesController.afterLockUpdate(address_);
emit Withdrawn(address_, amount, _balances[address_].locked, 0, 0, stakingToken != address(prfiToken));
}
}
return amount;
}
/********************** Internal View functions ***********************/
/**
* @notice Returns withdrawable balance at exact unlock time
* @param user address for withdraw
* @param unlockTime exact unlock time
* @return amount total withdrawable amount
* @return penaltyAmount penalty amount
* @return burnAmount amount to burn
* @return index of earning
*/
function _ieeWithdrawableBalance(
address user,
uint256 indexToStart,
uint256 unlockTime
) internal view returns (uint256 amount, uint256 penaltyAmount, uint256 burnAmount, uint256 index) {
LockedBalance[] storage earnings = _userEarnings[user];
uint256 length = earnings.length;
for (index = indexToStart; index < length; ) {
LockedBalance storage earning = earnings[index];
if (earning.unlockTime == unlockTime) {
(amount, , penaltyAmount, burnAmount) = _penaltyInfo(earning);
return (amount, penaltyAmount, burnAmount, index);
}
unchecked {
index++;
}
}
revert UnlockTimeNotFound();
}
/**
* @notice Calculate earnings.
* @param user address of earning owner
* @param rewardToken address
* @param balance of the user
* @param currentRewardPerToken current RPT
* @return earnings amount
*/
function _earned(
address user,
address rewardToken,
uint256 balance,
uint256 currentRewardPerToken
) internal view returns (uint256 earnings) {
earnings = rewards[user][rewardToken];
uint256 userReward = userRewardPerTokenPaid[user][rewardToken];
uint256 realRPT = currentRewardPerToken - userReward;
earnings = earnings + ((balance * realRPT) / 1e18);
}
/**
* @notice Penalty information of individual earning
* @param earning earning info.
* @return amount of available earning.
* @return penaltyFactor penalty rate.
* @return penaltyAmount amount of penalty.
* @return burnAmount amount to burn.
*/
function _penaltyInfo(
LockedBalance memory earning
) internal view returns (uint256 amount, uint256 penaltyFactor, uint256 penaltyAmount, uint256 burnAmount) {
if (earning.unlockTime > block.timestamp) {
// 90% on day 1, decays to 25% on day 90
penaltyFactor = ((earning.unlockTime - block.timestamp) * HALF) / vestDuration + QUART; // 25% + timeLeft/vestDuration * 65%
penaltyAmount = (earning.amount * penaltyFactor) / WHOLE;
burnAmount = (penaltyAmount * burn) / WHOLE;
}
amount = earning.amount - penaltyAmount;
}
/********************** Private functions ***********************/
function _binarySearch(
LockedBalance[] storage locks,
uint256 length,
uint256 unlockTime
) private view returns (uint256) {
uint256 low = 0;
uint256 high = length;
while (low < high) {
uint256 mid = (low + high) / 2;
LockedBalance storage lock = locks[mid];
if (lock.unlockTime < unlockTime) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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.
*
* The initial owner is set to the address provided by the deployer. 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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @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) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling 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 {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
interface IBountyManager {
function quote(address _param) external returns (uint256 bounty);
function claim(address _param) external returns (uint256 bounty);
function minDLPBalance() external view returns (uint256 amt);
function executeBounty(
address _user,
bool _execute,
uint256 _actionType
) external returns (uint256 bounty, uint256 actionType);
}// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.27;
pragma experimental ABIEncoderV2;
interface IChefIncentivesController {
/**
* @dev Called by the corresponding asset on any update that affects the rewards distribution
* @param user The address of the user
**/
function handleActionBefore(address user) external;
/**
* @dev Called by the corresponding asset on any update that affects the rewards distribution
* @param user The address of the user
* @param userBalance The balance of the user of the asset in the lending pool
* @param totalSupply The total supply of the asset in the lending pool
**/
function handleActionAfter(address user, uint256 userBalance, uint256 totalSupply) external;
/**
* @dev Called by the locking contracts after locking or unlocking happens
* @param user The address of the user
**/
function beforeLockUpdate(address user) external;
/**
* @notice Hook for lock update.
* @dev Called by the locking contracts after locking or unlocking happens
*/
function afterLockUpdate(address _user) external;
function addPool(address _token, uint256 _allocPoint) external;
function claim(address _user, address[] calldata _tokens) external;
function setClaimReceiver(address _user, address _receiver) external;
function getRegisteredTokens() external view returns (address[] memory);
function disqualifyUser(address _user, address _hunter) external returns (uint256 bounty);
function bountyForUser(address _user) external view returns (uint256 bounty);
function allPendingRewards(address _user) external view returns (uint256 pending);
function claimAll(address _user) external;
function claimBounty(uint32 _eid, address _user, bool _execute) external returns (bool issueBaseBounty);
function setEligibilityExempt(address _address, bool _value) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import "./LockedBalance.sol";
interface IFeeDistribution {
struct RewardData {
address token;
uint256 amount;
}
function addReward(address rewardsToken) external;
function removeReward(address _rewardToken) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import "./LockedBalance.sol";
import {IFeeDistribution} from "./IMultiFeeDistribution.sol";
interface IMiddleFeeDistribution is IFeeDistribution {
function forwardReward(address[] memory _rewardTokens) external;
function getPrfiTokenAddress() external view returns (address);
function getMultiFeeDistributionAddress() external view returns (address);
function operationExpenseRatio() external view returns (uint256);
function operationExpenses() external view returns (address);
function isRewardToken(address) external view returns (bool);
}// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.27;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IMintableToken is IERC20 {
function mint(address _receiver, uint256 _amount) external returns (bool);
function burn(uint256 _amount) external returns (bool);
function setMinter(address _minter) external returns (bool);
function priceProvider() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import "./LockedBalance.sol";
import "./IFeeDistribution.sol";
import "./IMintableToken.sol";
interface IMultiFeeDistribution is IFeeDistribution {
function exit(bool claimRewards) external;
function stake(uint256 amount, address onBehalfOf, uint256 typeIndex) external;
function prfiToken() external view returns (IMintableToken);
function getPriceProvider() external view returns (address);
function lockInfo(address user) external view returns (LockedBalance[] memory);
function autocompoundEnabled(address user) external view returns (bool);
function defaultLockIndex(address _user) external view returns (uint256);
function autoRelockDisabled(address user) external view returns (bool);
function totalBalance(address user) external view returns (uint256);
function lockedBalance(address user) external view returns (uint256);
function lockedBalances(
address user
) external view returns (uint256, uint256, uint256, uint256, LockedBalance[] memory);
function getBalances(address _user) external view returns (Balances memory);
function flikVestingToLp(address _address) external returns (uint256);
function claimableRewards(address account) external view returns (IFeeDistribution.RewardData[] memory rewards);
function setDefaultRelockTypeIndex(uint256 _index) external;
function daoTreasury() external view returns (address);
function stakingToken() external view returns (address);
function userSlippage(address) external view returns (uint256);
function claimFromConverter(address) external;
function vestTokens(address user, uint256 amount, bool withPenalty) external;
}
interface IMFDPlus is IMultiFeeDistribution {
function getLastClaimTime(address _user) external returns (uint256);
function claimBounty(address _user, bool _execute) external returns (bool issueBaseBounty);
function claimCompound(address _user, bool _execute, uint256 _slippage) external returns (uint256 bountyAmt);
function setAutocompound(bool _newVal) external;
function getAutocompoundEnabled(address _user) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
interface IPriceProvider {
function getTokenPrice() external view returns (uint256);
function getTokenPriceUsd() external view returns (uint256);
function getLpTokenPrice() external view returns (uint256);
function getLpTokenPriceUsd() external view returns (uint256);
function decimals() external view returns (uint256);
function update() external;
function baseAssetChainlinkAdapter() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
struct LockedBalance {
uint256 amount;
uint256 unlockTime;
uint256 multiplier;
uint256 duration;
}
struct EarnedBalance {
uint256 amount;
uint256 unlockTime;
uint256 penalty;
}
struct Reward {
uint256 periodFinish;
uint256 rewardPerSecond;
uint256 lastUpdateTime;
uint256 rewardPerTokenStored;
// tracks already-added balances to handle accrued interest in aToken rewards
// for the stakingToken this value is unused and will always be 0
uint256 balance;
}
struct Balances {
uint256 total; // sum of earnings and lockings; no use when LP and PRFI is different
uint256 unlocked; // PRFI token
uint256 locked; // LP token or PRFI token
uint256 lockedWithMultiplier; // Multiplied locked amount
uint256 earned; // PRFI token
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title RecoverERC20 contract
/// @author Prime Devs
/// @dev All function calls are currently implemented without side effects
contract RecoverERC20 {
using SafeERC20 for IERC20;
/// @notice Emitted when ERC20 token is recovered
event Recovered(address indexed token, uint256 amount);
/**
* @notice Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
*/
function _recoverERC20(address tokenAddress, uint256 tokenAmount) internal {
IERC20(tokenAddress).safeTransfer(msg.sender, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
}{
"evmVersion": "shanghai",
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": [],
"viaIR": true,
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ActiveReward","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AlreadyAdded","type":"error"},{"inputs":[],"name":"AlreadySet","type":"error"},{"inputs":[],"name":"AmountZero","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InsufficientPermission","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidBurn","type":"error"},{"inputs":[],"name":"InvalidEarned","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidLockPeriod","type":"error"},{"inputs":[],"name":"InvalidLookback","type":"error"},{"inputs":[],"name":"InvalidPeriod","type":"error"},{"inputs":[],"name":"InvalidTime","type":"error"},{"inputs":[],"name":"InvalidType","type":"error"},{"inputs":[],"name":"MintersSet","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"UnlockTimeNotFound","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IChefIncentivesController","name":"_controller","type":"address"},{"indexed":false,"internalType":"contract IMiddleFeeDistribution","name":"_middleFeeDistribution","type":"address"},{"indexed":true,"internalType":"address","name":"_treasury","type":"address"}],"name":"AddressesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_bounty","type":"address"}],"name":"BountyManagerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_stakingToken","type":"address"}],"name":"LPTokenUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"lockPeriod","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"rewardMultipliers","type":"uint256[]"}],"name":"LockTypeInfoUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockedBalance","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"lockLength","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLP","type":"bool"}],"name":"Locked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"locker","type":"address"}],"name":"LockerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"locker","type":"address"}],"name":"LockerRemoved","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":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockIndex","type":"uint256"}],"name":"Relocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_rewardToken","type":"address"}],"name":"RewardAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_rewardConverter","type":"address"}],"name":"RewardConverterUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"receivedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockedBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"burn","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLP","type":"bool"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"DEFAULT_LOCK_INDEX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HALF","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SLIPPAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERCENT_DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"QUART","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WHOLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"}],"name":"addReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardConverter_","type":"address"}],"name":"addRewardConverter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"autoRelockDisabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"autocompoundEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bountyManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burn","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bool","name":"execute","type":"bool"}],"name":"claimBounty","outputs":[{"internalType":"bool","name":"issueBaseBounty","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"onBehalf","type":"address"}],"name":"claimFromConverter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"claimableRewards","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct IFeeDistribution.RewardData[]","name":"rewardsData","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"daoTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"defaultLockIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"earnedBalances","outputs":[{"internalType":"uint256","name":"totalVesting","type":"uint256"},{"internalType":"uint256","name":"unlocked","type":"uint256"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"unlockTime","type":"uint256"},{"internalType":"uint256","name":"penalty","type":"uint256"}],"internalType":"struct EarnedBalance[]","name":"earningsData","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"claimRewards","type":"bool"}],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"flikVestingToLp","outputs":[{"internalType":"uint256","name":"flikped","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getBalances","outputs":[{"components":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"unlocked","type":"uint256"},{"internalType":"uint256","name":"locked","type":"uint256"},{"internalType":"uint256","name":"lockedWithMultiplier","type":"uint256"},{"internalType":"uint256","name":"earned","type":"uint256"}],"internalType":"struct Balances","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockDurations","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockMultipliers","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPriceProvider","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"rewardTokens_","type":"address[]"}],"name":"getReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"}],"name":"getRewardForDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incentivesController","outputs":[{"internalType":"contract IChefIncentivesController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"claimRewards","type":"bool"},{"internalType":"uint256","name":"unlockTime","type":"uint256"}],"name":"individualEarlyExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"prfiToken_","type":"address"},{"internalType":"address","name":"flik_","type":"address"},{"internalType":"address","name":"dao_","type":"address"},{"internalType":"address","name":"priceProvider_","type":"address"},{"internalType":"uint256","name":"rewardsDuration_","type":"uint256"},{"internalType":"uint256","name":"rewardsLookback_","type":"uint256"},{"internalType":"uint256","name":"lockDuration_","type":"uint256"},{"internalType":"uint256","name":"burnRatio_","type":"uint256"},{"internalType":"uint256","name":"vestDuration_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastAutocompound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastClaimTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"}],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"lockInfo","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"unlockTime","type":"uint256"},{"internalType":"uint256","name":"multiplier","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"internalType":"struct LockedBalance[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"lockedBalance","outputs":[{"internalType":"uint256","name":"locked","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"lockedBalances","outputs":[{"internalType":"uint256","name":"total","type":"uint256"},{"internalType":"uint256","name":"unlockable","type":"uint256"},{"internalType":"uint256","name":"locked","type":"uint256"},{"internalType":"uint256","name":"lockedWithMultiplier","type":"uint256"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"unlockTime","type":"uint256"},{"internalType":"uint256","name":"multiplier","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"internalType":"struct LockedBalance[]","name":"lockData","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockedSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockedSupplyWithMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"middleFeeDistribution","outputs":[{"internalType":"contract IMiddleFeeDistribution","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"minters","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintersAreSet","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"prfiToken","outputs":[{"internalType":"contract IMintableToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"relock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewardToken","type":"address"}],"name":"removeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requalify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"requalifyFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardConverter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardData","outputs":[{"internalType":"uint256","name":"periodFinish","type":"uint256"},{"internalType":"uint256","name":"rewardPerSecond","type":"uint256"},{"internalType":"uint256","name":"lastUpdateTime","type":"uint256"},{"internalType":"uint256","name":"rewardPerTokenStored","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken","type":"address"}],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"rptStored","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewardTokens","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsLookback","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChefIncentivesController","name":"controller_","type":"address"},{"internalType":"contract IMiddleFeeDistribution","name":"middleFeeDistribution_","type":"address"},{"internalType":"address","name":"treasury_","type":"address"}],"name":"setAddresses","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"status","type":"bool"},{"internalType":"uint256","name":"slippage","type":"uint256"}],"name":"setAutocompound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"bounty","type":"address"}],"name":"setBountyManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"setDefaultRelockTypeIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"stakingToken_","type":"address"}],"name":"setLPToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"lockPeriod_","type":"uint256[]"},{"internalType":"uint256[]","name":"rewardMultipliers_","type":"uint256[]"}],"name":"setLockTypeInfo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lookback","type":"uint256"}],"name":"setLookback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"minters_","type":"address[]"}],"name":"setMinters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"status","type":"bool"}],"name":"setRelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"uint256","name":"typeIndex","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"starfleetTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"totalBalance","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":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"userRewardPerTokenPaid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userSlippage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vestDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"withPenalty","type":"bool"}],"name":"vestTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"address_","type":"address"},{"internalType":"uint256","name":"limit_","type":"uint256"},{"internalType":"bool","name":"isRelockAction_","type":"bool"}],"name":"withdrawExpiredLocksForWithOptions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"withdrawableBalance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"penaltyAmount","type":"uint256"},{"internalType":"uint256","name":"burnAmount","type":"uint256"}],"stateMutability":"view","type":"function"}]Contract Creation Code
6080806040523460aa575f5160206152e95f395f51905f525460ff8160401c16609b576002600160401b03196001600160401b038216016049575b60405161523a90816100af8239f35b6001600160401b0319166001600160401b039081175f5160206152e95f395f51905f525581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80603a565b63f92ee8a960e01b5f5260045ffd5b5f80fdfe60806040526004361015610011575f80fd5b5f5f3560e01c8062a4173a1461311057806302b62938146130da5780630483a7f61461308a57806309eba3431461306d5780630a065fb6146130485780630c14930f146130225780630f63107f14612fae5780630fd6699b14612f915780631285deee14612f7657806317053a7e14612f3e57806320212eaa14612ec757806321cc01c914612ea15780632e1a7d4d14612b155780632e88caa114612add57806332c991b514612ac0578063363bf964146129f65780633824c76a146129cf578063386a9525146129b15780633b26664f1461279b5780633f4ba83a146126dc57806344df8e70146126be57806345b35f561461268e5780634870dd9a1461267157806348e5d9f81461260c578063525abb2b146125d4578063547d0096146124e95780635c975abb146124a7578063638634ee146124835780636904fd83146123245780636a678a9c146122845780636bd3b87c146121f25780636e9c931c146121c15780636eacd3981461219d5780636fa75e6d1461215f5780637035ab9814612111578063715018a61461207857806372f702f31461205157806376520c6d14611fcc5780637654b1b914611fb257806379022a9f14611f8b5780637ab8955d14611f465780637bb7bed114611f035780637fd7d06214611e665780638232e5f714611e485780638456cb5914611daf5780638980f11f14611d475780638da5cb5b14611d015780638f40aab1146119845780638fd9f2ca14611961578063914f6e6d1461184157806394761e2a1461182357806398387a8d146117e65780639ae697bf146117615780639bd4ef50146117445780639c9b2e21146116655780639fe6386214611643578063a4d5e67c146114be578063ada71bc214611481578063af1df2551461145a578063b2d6e66d14611237578063b36b9ffd14611211578063b77cf9c6146111d9578063bba63ae41461115f578063bcd1101414611111578063beb3e38214610d9b578063c53b573d14610b5a578063c84aae1714610aa3578063ca5c7b9114610a85578063dc01f60d146108ee578063df379876146106eb578063e6af230d1461069b578063e6c91a15146105a7578063e70b9e2714610559578063e8999f431461053b578063f12297771461050f578063f2fde38b146104e5578063f4252d59146104be578063f46eccc414610481578063f7ad360114610443578063f9759518146104265763ff66a30514610394575f80fd5b34610423576020366003190112610423576001600160a01b036103b5613131565b6103bd613f19565b16801561041457806001600160a01b03196020541617602055808252601b60205260408220600160ff198254161790557f399c0ee7188568b38d4bbb76666ae7a03da799c64e2a69ece47a0543021fa8d38280a280f35b600482639fabe1c160e01b8152fd5b80fd5b5034610423578060031936011261042357602060405161251c8152f35b50346104235760203660031901126104235761047e610460613173565b15338352601c602052604083209060ff801983541691151516179055565b80f35b50346104235760203660031901126104235760ff60406020926001600160a01b036104aa613131565b168152601b84522054166040519015158152f35b503461042357806003193601126104235760206001600160a01b03601a5416604051908152f35b50346104235760203660031901126104235761047e610502613131565b61050a613f19565b613caa565b503461042357602036600319011261042357602061053361052e613131565b613c11565b604051908152f35b50346104235780600319360112610423576020601254604051908152f35b5034610423576040366003190112610423576001600160a01b03604061057d613131565b928261058761315d565b9416815260186020522091165f52602052602060405f2054604051908152f35b5034610423576020366003190112610423576105c1613173565b906105cb336135c3565b90939193338452600e60205260408420948554958581558661062a575b5061047e949550338652600c602052856040812081815461060f6001840191825490613378565b926106206004820194855490613378565b9055555533613f78565b7f3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff87168703610687578552602085209560021b8601955b868110156105e85760049086815586600182015586600282015586600382015501610661565b602486634e487b7160e01b81526011600452fd5b5034610423576020366003190112610423576004356106b8613f19565b80156106dc5760025481116106cd5760035580f35b60048263387cf9e560e11b8152fd5b6004826365e52d5160e11b8152fd5b503461042357602036600319011261042357610705613131565b816060906001600160a01b03831692838252600c6020526001604083200154938252600e602052604082208291815484915b8183106107a95750505050506040519384936060850191855260208501526060604085015282518091526020608085019301915b818110610779575050500390f35b9193509160206060600192604087518051835284810151858401520151604082015201940191019184939261076b565b909192939596976107c36107bd85876133b5565b50613591565b60208101428151115f146108d657881561083a575b600192839282604061081d8d8f6107f58d6108279951908d614da5565b50509590508651610806848461366c565b5152516020610815848461366c565b51015261366c565b510152519061336b565b9701935b01939293919097969597610737565b90919998506108498585613378565b61085281613353565b906108606040519283613331565b80825261086f601f1991613353565b01885b8181106108865750999a90995091906107d8565b6040516060810181811067ffffffffffffffff8211176108c25790602092916040528b81528b838201528b604082015282828601015201610872565b60248c634e487b7160e01b81526041600452fd5b506001919794996108e891519061336b565b9861082b565b503461042357602036600319011261042357610908613131565b60155461091481613353565b916109226040519384613331565b818352601f1961093183613353565b01845b818110610a3b5750506001600160a01b038116845b8381106109ab578486604051918291602083016020845282518091526020604085019301915b81811061097d575050500390f35b825180516001600160a01b03168552602090810151818601528695506040909401939092019160010161096f565b806001600160a01b036109bf600193613274565b90549060031b1c166109d1828861366c565b515264e8d4a51000610a256001600160a01b036109ee848a61366c565b515116858a52600c602052600360408b200154610a1e6001600160a01b03610a16878d61366c565b515116613c11565b9188614f10565b046020610a32838961366c565b51015201610949565b6040516040810181811067ffffffffffffffff821117610a71579060209291604052878152878382015282828801015201610934565b602488634e487b7160e01b81526041600452fd5b50346104235780600319360112610423576020601154604051908152f35b503461042357602036600319011261042357604060a0916001600160a01b03610aca613131565b8260808551610ad8816132e5565b82815282602082015282878201528260608201520152168152600c60205220604051610b03816132e5565b81549182825260018101546020830190815260028201549060408401918252608060046003850154946060870195865201549401938452604051948552516020850152516040840152516060830152516080820152f35b5034610423578060031936011261042357338152600d6020526040812054610b80613d65565b610b8933614292565b338252600c6020526040822082908390338552600d602052604085209384549081610c60575b5050610bfd92935060028101610bc6858254613378565b905560038101610bd7838254613378565b9055610be4848254613378565b9055610bf283601154613378565b601155601254613378565b601255338252601d602052610c1f6040832054610c18613d65565b33836147bc565b338252601d602052604082205460405191825260208201527f4c46641520b3fedde433247f82a421df0d7a83ce24df3f229e9df96f386c8b0c60403392a280f35b91939091808311610d9057509094859392905b8093610c816107bd886133a3565b935b88861080610d82575b15610ce157610cb9610ca260019287519061336b565b94610cb38751604089015190613680565b9061336b565b950195888710610ccc575b959492610c83565b9350610cdb6107bd87896133b5565b93610cc4565b95919697509250929092815b818110610d52575050845b818110610d40575050610bfd9293945415610d16575b83925f610baf565b337f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df48680a2610d0e565b600190610d4c88613822565b01610cf8565b80610d7c610d626001938b6133b5565b50610d76610d708785613378565b8c6133b5565b906137ee565b01610ced565b506020850151421015610c8c565b959392909193610c73565b50346104235761012036600319011261042357610db6613131565b610dbe61315d565b610dc6613147565b91606435916001600160a01b03831680930361110d5760843560a4359060c4359260e4359461010435967ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00549860ff8a60401c16159967ffffffffffffffff811680159081611105575b60011490816110fb575b1590816110f2575b506110ca5767ffffffffffffffff1981166001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00558a61108b575b506001600160a01b0383169384151580611079575b80611067575b8061105e575b1561104f5785151580611046575b8061103d575b80611034575b1561102557620186a08911610ffd57858711610fee57610f4a9392916001600160a01b038092610ee7615121565b610eef615121565b610ef7615121565b610f0033613caa565b876001600160a01b03196009541617600955166001600160a01b0319600b541617600b55166001600160a01b031960195416176019556001600160a01b03198b5416178a55613b95565b87526016602052426002604089200155600255600355600455600155600555610f705780f35b68ff0000000000000000197ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a180f35b60048c63387cf9e560e11b8152fd5b60048c7feeddf6ab000000000000000000000000000000000000000000000000000000008152fd5b60048c6365e52d5160e11b8152fd5b50891515610eb9565b50871515610eb3565b50861515610ead565b60048c639fabe1c160e01b8152fd5b50821515610e9f565b506001600160a01b0382161515610e99565b506001600160a01b0381161515610e93565b68ffffffffffffffffff191668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00555f610e7e565b60048c7ff92ee8a9000000000000000000000000000000000000000000000000000000008152fd5b9050155f610e42565b303b159150610e3a565b8c9150610e30565b8480fd5b50346104235760203660031901126104235764e8d4a51000611156600160406020946001600160a01b03611143613131565b1681526016865220015460025490613680565b04604051908152f35b503461042357604036600319011261042357611179613173565b6024359061251c821015806111ce575b156111bf576111af90338452600f602052604084209060ff801983541691151516179055565b3382526021602052604082205580f35b60048363162908e360e11b8152fd5b506127108210611189565b50346104235760203660031901126104235760406020916001600160a01b03611200613131565b168152601f83522054604051908152f35b503461042357806003193601126104235760206001600160a01b03815416604051908152f35b503461042357602036600319011261042357611251613131565b611259613d65565b6001600160a01b0360065416330361144b5761127481614292565b816001600160a01b0360075416803b15611340578160405180926308c51c6560e01b8252602060048301528183816112ae602482016138b5565b03925af1801561134457611436575b50506001600160a01b03601554911690818352601860205260408320835b82811061134f57505050816001600160a01b03815416803b156113405781809160046040518094819363a2e6204560e01b83525af180156113445761132b575b5052601f60205242604082205580f35b8161133591613331565b61134057815f61131b565b5080fd5b6040513d84823e3d90fd5b806001600160a01b03611363600193613274565b90549060031b1c166001600160a01b03600954168103611385575b50016112db565b61138e81614df5565b808752601660205285604088206001600160a01b0383165f528560205264e8d4a5100060405f20540490816113c6575b50505061137e565b7f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9160046020926001600160a01b0387165f528984528c60405f20550161140e828254613378565b9055611426816001600160a01b036006541687614370565b604051908152a35f8581806113be565b8161144091613331565b61134057815f6112bd565b600482630deda90360e41b8152fd5b503461042357806003193601126104235760206001600160a01b0360085416604051908152f35b50346104235760203660031901126104235760ff60406020926001600160a01b036114aa613131565b168152600f84522054166040519015158152f35b5034610423576020366003190112610423576114d8613131565b338252601b60205260ff6040832054161561144b578182601554916001600160a01b03859416935b838110611611575b50156115e9575f1982019182116115d55781811061158a575b50506015548015611576575f190161153881613274565b6001600160a01b0382549160031b1b191690556015558152601660205280600460408220828155826001820155826002820155826003820155015580f35b602483634e487b7160e01b81526031600452fd5b6115af6001600160a01b036115a16115ce94613274565b90549060031b1c1691613274565b9091906001600160a01b038084549260031b9316831b921b1916179055565b5f80611521565b602484634e487b7160e01b81526011600452fd5b6004847fe6c4247b000000000000000000000000000000000000000000000000000000008152fd5b846001600160a01b0361162383613274565b90549060031b1c161461163857600101611500565b91505060015f611508565b50346104235760203660031901126104235761047e611660613131565b613bbf565b50346104235760203660031901126104235761167f613131565b6001600160a01b03811690811561173557338352601b60205260ff6040842054161561172657818352601660205260026040842001546116fe576116c290613b95565b8082526016602052604082204260028201554290557fb13fd610fe4e1b384966826794a9b2f6100ad031f352cc5ec6f22667f60749808280a280f35b6004837ff411c327000000000000000000000000000000000000000000000000000000008152fd5b600483630deda90360e41b8152fd5b600483639fabe1c160e01b8152fd5b503461042357806003193601126104235760206040516161a88152f35b50346104235760203660031901126104235761177b613131565b6001600160a01b03168152600d60205260408120805490825b8281106117a657602084604051908152f35b6117b081836133b5565b50600142910154116117c5575b600101611794565b926117de6001916117d686856133b5565b50549061336b565b9390506117bd565b50346104235760203660031901126104235760ff60406020926001600160a01b0361180f613131565b168152601c84522054166040519015158152f35b50346104235780600319360112610423576020600554604051908152f35b50346104235760403660031901126104235761185b613173565b9060243542811115611939576118719033614d3c565b9492919091338552600e602052604085209586549060018101809111611925575b8181106118e45750506118a861047e9596613822565b338652600c6020526004604087206118ca846118c5858454613378565b613378565b8155016118dc836118c5848454613378565b905533613f78565b6118ee81896133b5565b505f1982018281116119115760019291610d7661190b928c6133b5565b01611892565b602489634e487b7160e01b81526011600452fd5b602487634e487b7160e01b81526011600452fd5b6004827f6f7eac26000000000000000000000000000000000000000000000000000000008152fd5b5034610423578060031936011261042357602060ff601e54166040519015158152f35b50346104235761199336613182565b8192919215611ce4575b6119a5613d65565b8080611cd1575b80611cbc575b611cad576119bf82614292565b6001600160a01b03821690818552600c602052604085209385948691848852600d602052604088209081549081611b70575b50505090816002611a349301611a08888254613378565b905560038101611a19838254613378565b9055611a26878254613378565b9055610bf286601154613378565b6012558015611b4e575b15611a6457906040846105339360209652601d8652205490611a5e613d65565b836147bc565b9082611a7b916001600160a01b03600a5416614370565b826001600160a01b0360085416803b1561134057819060246040518094819363075b9bf960e31b83528760048401525af18015611b4357611b2a575b507fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca60a0848360209652600c86526002604082200154906001600160a01b03600a54166001600160a01b0360095416141590604051928784528884015280604084015260608301526080820152a2610533565b611b35848092613331565b611b3f575f611ab7565b8280fd5b6040513d86823e3d90fd5b50338114158015611a3e5750808452601c60205260ff60408520541615611a3e565b8993979298949691959950808611155f14611ca25750849896959394965b8396611b9c6107bd8b6133a3565b965b8b891080611c94575b15611bf657611bce611bbd6001928a519061336b565b97610cb38a5160408c015190613680565b9801988b8a10611be1575b989795611b9e565b9650611bf06107bd8a8c6133b5565b96611bd9565b999398919650939950959095815b818110611c6a575050885b818110611c5857505090611a3492915415611c2e575b90915f806119f1565b847f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df48980a2611c25565b600190611c6484613822565b01611c0f565b80611c8e611c7a600193876133b5565b50610d76611c888785613378565b886133b5565b01611c04565b506020880151421015611ba7565b989695939496611b8e565b600484630deda90360e41b8152fd5b506001600160a01b03600b54163314156119b2565b50336001600160a01b03831614156119ac565b6001600160a01b0382168452600d6020526040842054925061199d565b503461042357806003193601126104235760206001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b503461042357604036600319011261042357611d61613131565b7f8c1256b8896378cd5044f80c202f9772b9d77dc85c8a6eb51967210b09bfaa2860206001600160a01b0360243593611d98613f19565b1692611da5813386614370565b604051908152a280f35b5034610423578060031936011261042357611dc8613f19565b611dd0613d65565b600160ff197fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005416177fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346104235780600319360112610423576020600354604051908152f35b50346104235760203660031901126104235760043567ffffffffffffffff81116113405736602382011215611340578060040135611ea381613353565b91611eb16040519384613331565b8183526024602084019260051b8201019036821161110d57602401915b818310611edf578461047e856139c7565b82356001600160a01b0381168103611eff57815260209283019201611ece565b5f80fd5b503461042357602036600319011261042357600435906015548210156104235760206001600160a01b03611f3684613274565b90549060031b1c16604051908152f35b503461042357604036600319011261042357611f60613131565b602435918215158303610423576020611f818484611f7c613d65565b61396d565b6040519015158152f35b503461042357806003193601126104235760206001600160a01b0360195416604051908152f35b503461042357806003193601126104235761047e33613bbf565b503461042357806003193601126104235760405180916020601354928381520191601382527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090915b81811061203b576120378561202b81870382613331565b6040519182918261320a565b0390f35b8254845260209093019260019283019201612014565b503461042357806003193601126104235760206001600160a01b03600a5416604051908152f35b5034610423578060031936011261042357612091613f19565b806001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031981167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5034610423576040366003190112610423576001600160a01b036040612135613131565b928261213f61315d565b9416815260176020522091165f52602052602060405f2054604051908152f35b50346104235760203660031901126104235760043560135481101561218e57338252601d602052604082205580f35b60048263b968846160e01b8152fd5b50346104235760203660031901126104235760206105336121bc613131565b613931565b50346104235760603660031901126104235761047e6121de61315d565b6121e6613d65565b604435906004356143c5565b5034610423576020366003190112610423576001600160a01b03612214613131565b168152600d6020526040812080549061222c82613353565b9261223a6040519485613331565b82845290815260208082209084015b8383106122665760405160208082528190612037908201886131b5565b6004602060019261227685613591565b815201920192019190612249565b5034610423576020366003190112610423576001600160a01b036122a6613131565b6122ae613f19565b16801561041457600a546001600160a01b0381166122fc576001600160a01b0319168117600a557ff9d1c90c609623055381582a51735b38df3243eb09629a9170b15c2ab6bc07398280a280f35b6004837fa741a045000000000000000000000000000000000000000000000000000000008152fd5b50346104235760203660031901126104235761233e613131565b81906001600160a01b03600b5416330361172657806123646001600160a01b0392614292565b16808352600e60205260408320805490815b612428575b505061239f826001600160a01b03600954166001600160a01b03600b541690614370565b8252600c60205260408220600481016123b9838254613378565b90556123c6828254613378565b9055816001600160a01b03815416803b1561134057819060046040518094819363a2e6204560e01b83525af1801561241d57612408575b602082604051908152f35b612413838092613331565b61134057816123fd565b6040513d85823e3d90fd5b5f198201939091841161246f5761243f84836133b5565b50426001820154115f1461246857546124579161336b565b9261246182613822565b9081612376565b509261237b565b602485634e487b7160e01b81526011600452fd5b50346104235760203660031901126104235760206105336124a2613131565b61390b565b5034610423578060031936011261042357602060ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330054166040519015158152f35b50346104235760203660031901126104235760043567ffffffffffffffff81116113405761251b903690600401613243565b612523613f19565b60ff601e54166125ac57825b8181106125475783600160ff19601e541617601e5580f35b6001600160a01b0361256261255d838587613868565b6138f7565b161561259d57806001600160a01b0361258161255d6001948688613868565b168552601b60205260408520805460ff1916831790550161252f565b600484639fabe1c160e01b8152fd5b6004837f4cd4ee53000000000000000000000000000000000000000000000000000000008152fd5b50346104235760203660031901126104235760406020916001600160a01b036125fb613131565b168152602183522054604051908152f35b503461042357602036600319011261042357604060a0916001600160a01b03612633613131565b168152601660205220805490600181015490600281015460046003830154920154926040519485526020850152604084015260608301526080820152f35b503461042357806003193601126104235760206040516127108152f35b503461042357806003193601126104235761047e6040516126b9816126b2816138b5565b0382613331565b6139c7565b50346104235780600319360112610423576020600154604051908152f35b50346104235780600319360112610423576126f5613f19565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff8116156127735760ff19167fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b6004827f8dfc202b000000000000000000000000000000000000000000000000000000008152fd5b50346104235760403660031901126104235760043567ffffffffffffffff8111611340576127cd903690600401613243565b9060243567ffffffffffffffff81116129ad576127ee903690600401613243565b6127f6613f19565b808403612985576013548560135580612943575b506014548560145580612901575b50845b84811061287557507f3dd1633e862937bc1494dac711b194b9438e3d57bc5ea37f2821484aecadc65a939161286f91612861604051958695604087526040870191613878565b918483036020860152613878565b0390a180f35b612880818686613868565b3560135468010000000000000000811015610a7157906128ab8260016128c3940160135560136132d0565b90919082549060031b91821b915f19901b1916179055565b6128ce818385613868565b3560145468010000000000000000811015610a7157600192916128ab82856128fb940160145560146132d0565b0161281b565b601486527fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec908101905b8181106129385750612818565b86815560010161292b565b601386527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090908101905b81811061297a575061280a565b86815560010161296d565b6004857f2a82a34f000000000000000000000000000000000000000000000000000000008152fd5b8380fd5b50346104235780600319360112610423576020600254604051908152f35b503461042357806003193601126104235760206001600160a01b0360065416604051908152f35b5034610423576060366003190112610423576004356001600160a01b03811680910361134057602435906001600160a01b038216809203611b3f57612a39613147565b612a41613f19565b811561259d57821561259d57816001600160a01b036040927f97a48c4be738bdf3e84e8f3927df6d4e52ccc94c58e96a666faca603e456d490946001600160a01b03196008541617600855856001600160a01b031960075416176007551693846001600160a01b0319601a541617601a5582519182526020820152a280f35b5034610423578060031936011261042357602060405161fde88152f35b50346104235760203660031901126104235760406020916001600160a01b03612b04613131565b168152601d83522054604051908152f35b34611eff576020366003190112611eff576004358015612e92575f905f335f52600c60205260405f20600181018054808511155f14612c9c5750612b5a848254613378565b90555b612b6c846118c5858454613378565b90555f92612b7933614292565b612b8f83336001600160a01b0360095416614370565b80612c3f575b6001600160a01b035f5416803b15611eff575f809160046040518094819363a2e6204560e01b83525af18015612c3457612c1f575b50338452600c60205260026040852001546040519384526020840152604083015260608201528160808201527fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca60a03392a280f35b612c2c9194505f90613331565b5f9284612bca565b6040513d5f823e3d90fd5b81612c75575b612c706001600160a01b03600954166001600160a01b0360195416612c6a8585613378565b91614370565b612b95565b612c97826001600160a01b03600954166001600160a01b03601a541690614370565b612c45565b612caa908596929496613378565b926004830190815490858210612db4575f9055935f90335f52600e60205260405f20905b612cd883836133b5565b50548015612e8c57612cf5612cf06107bd86866133b5565b613e8b565b949093929183838310612e0a57505090612d0e91613378565b988915612ddc575b91612d28612d2e92612d349594613378565b9761336b565b9961336b565b9580612da1575081612d49575b505055612b5d565b8054825b818110612d775750505f5b828110612d655750612d41565b600190612d7183613822565b01612d58565b80612d9b612d87600193866133b5565b50610d76612d958885613378565b876133b5565b01612d4d565b918496989415612db45760010191612cce565b7f1c243dc1000000000000000000000000000000000000000000000000000000005f5260045ffd5b9492915f198414612df65760019390930194919291612d16565b634e487b7160e01b5f52601160045260245ffd5b95509a9350809250620186a091500290808204620186a01490151715612df65788620186a00392620186a08411612df657612e55612e4e612d3495612d2e94613385565b8092613378565b612e5f87876133b5565b5055612d28620186a0612e735f9c84613680565b0491620186a0612e8560015485613680565b0494613378565b50612cce565b6365e52d5160e11b5f5260045ffd5b34611eff575f366003190112611eff5760206001600160a01b0360075416604051908152f35b34611eff576020366003190112611eff576001600160a01b03612ee8613131565b612ef0613f19565b168015612f2f57806001600160a01b031960065416176006557fe3b68397b3e7baa0682f1ed87243f231cbc0e0a383587123479edb959ddf6b885f80a2005b639fabe1c160e01b5f5260045ffd5b34611eff576020366003190112611eff576001600160a01b03612f5f613131565b165f526010602052602060405f2054604051908152f35b34611eff575f366003190112611eff57602060405160018152f35b34611eff575f366003190112611eff576020600454604051908152f35b34611eff575f366003190112611eff5760405180602060145491828152019060145f527fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec905f5b81811061300c576120378561202b81870382613331565b8254845260209093019260019283019201612ff5565b34611eff575f366003190112611eff5760206001600160a01b0360095416604051908152f35b34611eff575f366003190112611eff5760206001600160a01b035f5416604051908152f35b34611eff575f366003190112611eff576020604051620186a08152f35b34611eff576020366003190112611eff576120376130ae6130a9613131565b613693565b91604095939551958695865260208601526040850152606084015260a0608084015260a08301906131b5565b34611eff576020366003190112611eff5760606130fd6130f8613131565b6135c3565b9060405192835260208301526040820152f35b34611eff5761312f61312136613182565b9161312a613d65565b61340b565b005b600435906001600160a01b0382168203611eff57565b604435906001600160a01b0382168203611eff57565b602435906001600160a01b0382168203611eff57565b600435908115158203611eff57565b6060906003190112611eff576004356001600160a01b0381168103611eff5790602435906044358015158103611eff5790565b90602080835192838152019201905f5b8181106131d25750505090565b9091926020608060019260608751805183528481015185840152604081015160408401520151606082015201940191019190916131c5565b60206040818301928281528451809452019201905f5b81811061322d5750505090565b8251845260209384019390920191600101613220565b9181601f84011215611eff5782359167ffffffffffffffff8311611eff576020808501948460051b010111611eff57565b60155481101561328c5760155f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b60145481101561328c5760145f5260205f2001905f90565b60135481101561328c5760135f5260205f2001905f90565b805482101561328c575f5260205f2001905f90565b60a0810190811067ffffffffffffffff82111761330157604052565b634e487b7160e01b5f52604160045260245ffd5b6080810190811067ffffffffffffffff82111761330157604052565b90601f8019910116810190811067ffffffffffffffff82111761330157604052565b67ffffffffffffffff81116133015760051b60200190565b91908201809211612df657565b91908203918211612df657565b811561338f570490565b634e487b7160e01b5f52601260045260245ffd5b80541561328c575f5260205f20905f90565b805482101561328c575f5260205f209060021b01905f90565b91906133f85760608160039251845560208101516001850155604081015160028501550151910155565b634e487b7160e01b5f525f60045260245ffd5b919091335f52601b60205260ff60405f2054161561358257821561357d576001600160a01b03169030821461356657815f52600c60205260405f209061345284835461336b565b8255156135525760040161346783825461336b565b90555f52600e60205260405f209081549081151591825f1461354b575f198101818111612df657925b60055490620151806134a2834261336b565b048161352e575b50156134d5575050906134ca6134d0926134c383866133b5565b505461336b565b926133b5565b50555b565b90939192506134e4814261336b565b604051936134f185613315565b845260208401526001604084015260608301526801000000000000000083101561330157826135289160016134d3950181556133b5565b906133ce565b905062015180600161354087896133b5565b50015404145f6134a9565b5f92613490565b613562915060010191825461336b565b9055565b50506134d3906001600160a01b0360095416613db8565b505050565b630deda90360e41b5f5260045ffd5b9060405161359e81613315565b6060600382948054845260018101546020850152600281015460408501520154910152565b5f916001600160a01b035f9216805f52600c6020528360405f2060048101549283613600575b506135fb9260016118c592015461336b565b929190565b9091505f52600e60205260405f2080545f915b8183106136245750869291506135e9565b9091959661363287836133b5565b5080541561366357600191612d2e61364f612cf061365994613591565b949150915061336b565b9601915b90613613565b5096959161365d565b805182101561328c5760209160051b010190565b81810292918115918404141715612df657565b5f905f905f906001600160a01b0360609116805f52600d60205260405f205f9080545f915b8183106136d557505050505f52600c602052600260405f20015494565b90919295966136e484836133b5565b50600101544210156137d457861561375f575b600161375361372e829361370e6107bd89886133b5565b6137188c8c61366c565b526137238b8b61366c565b506117d688876133b5565b99610cb361373c88876133b5565b5054600261374a8a896133b5565b50015490613680565b9701935b0191906136b8565b945061376b8383613378565b61377481613353565b906137826040519283613331565b808252613791601f1991613353565b015f5b8181106137a55750909590506136f7565b6020906040516137b481613315565b5f81525f838201525f60408201525f606082015282828601015201613794565b969592976137e86001916117d68b856133b5565b98613757565b91906133f8578082036137ff575050565b600381819254845560018101546001850155600281015460028501550154910155565b80548015613854575f19019061383882826133b5565b6133f8576003815f809355826001820155826002820155015555565b634e487b7160e01b5f52603160045260245ffd5b919081101561328c5760051b0190565b90918281527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311611eff5760209260051b809284830137010190565b602060155491828152019060155f5260205f20905f5b8181106138d85750505090565b82546001600160a01b03168452602090930192600192830192016138cb565b356001600160a01b0381168103611eff5790565b6001600160a01b03165f52601660205260405f20548042105f1461392e57504290565b90565b6001600160a01b03165f52600c60205260405f206001600160a01b03600a54166001600160a01b036009541614613969576002015490565b5490565b9190916001600160a01b036020541633036135825761398b81613693565b5050501515905061399c57505f9150565b600192156139c457806001600160a01b036139c492165f52600d60205260405f205490614aab565b50565b6139d033614292565b6139d8613d65565b600754906001600160a01b035f9216803b15611eff57604051906308c51c6560e01b82528160248101602060048301528451809152604482019060208601905f5b818110613b735750505091815f81819503925af18015612c3457613b5e575b5080519033835260186020526040832090835b838110613aa057505050506001600160a01b03815416803b156113405781809160046040518094819363a2e6204560e01b83525af1801561134457613a8e575050565b613a99828092613331565b6104235750565b806001600160a01b03613ab56001938561366c565b51168087526016602052604087206001600160a01b0382165f528560205264e8d4a5100060405f2054613ae784614df5565b049081613af8575b50505001613a4b565b6004906001600160a01b0384165f52876020528960405f205501613b1d828254613378565b9055613b2a813384614370565b6040519081527f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e60203392a35f8080613aef565b613b6b9192505f90613331565b5f905f613a38565b82516001600160a01b0316845286945060209384019390920191600101613a19565b6015549068010000000000000000821015613301576115af8260016134d3940160155560156132d0565b6001600160a01b036008541690813b15611eff576001600160a01b0360245f9283604051958694859363075b9bf960e31b85521660048401525af18015612c3457613c075750565b5f6134d391613331565b906001600160a01b03821691825f526016602052600360405f200154926012549182613c3c57505050565b90613c66613c50613c7c939694959661390b565b825f526016602052600260405f20015490613378565b905f526016602052600160405f20015490613680565b670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612df65761392e92610cb391613385565b6001600160a01b03168015613d39576001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054826001600160a01b03198216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b60ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005416613d9057565b7fd93c0665000000000000000000000000000000000000000000000000000000005f5260045ffd5b6001600160a01b03165f52601660205260405f208054804210155f14613e29575064e8d4a51000820282810464e8d4a510001483151715612df65761356291613e0660049260025490613385565b60018201555b426002820155613e1e6002544261336b565b81550191825461336b565b613e34904290613378565b90613e5564e8d4a51000613e4e6001840194855490613680565b048461336b565b9064e8d4a5100082029180830464e8d4a510001490151715612df65761356292613e8460049360025490613385565b9055613e0c565b5f905f905f906020810151428111613ead575b5082613eaa9151613378565b93565b91509250613ebd91504290613378565b61fde881029080820461fde81490151715612df657600554613ede91613385565b906161a88201809211612df657620186a0613efa838351613680565b0490613eaa82620186a0613f1060015483613680565b04929150613e9e565b6001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303613f4c57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b93919290926001600160a01b0385169433860361358257613f9881614292565b60095493613fb186836001600160a01b035f9816614370565b8261423b575b61405c575b506001600160a01b03835416803b156129ad5783809160046040518094819363a2e6204560e01b83525af18015611b4357614047575b50917fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca939160a093868352600c60205260026040842001546040519485526020850152604084015260608301526080820152a2565b614052848092613331565b611b3f575f613ff2565b60405190816020601554918281520190838260155f5260205f20925f5b81811061421957505061408e92500384613331565b614096613d65565b6001600160a01b0360075416803b15611eff5760405180926308c51c6560e01b8252602482016020600484015286518091526044830191905f5b8181106141f75750505091815f81819503925af18015612c34576141e2575b5081519187865260186020526040862090865b848110614122575050505050848352601f6020524260408420555f613fbc565b806001600160a01b036141376001938561366c565b5116808a5260166020528b8a604081206001600160a01b0384165f528760205264e8d4a5100060405f205461416b86614df5565b048061417d575b505050505001614102565b60046020927f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e946001600160a01b0388165f528b855260405f2055016141c4828254613378565b90556141d1818b87614370565b604051908152a35f8b8a8280614172565b6141ef9195505f90613331565b5f935f6140ef565b82516001600160a01b03168452869450602093840193909201916001016140d0565b84546001600160a01b0316835260019485019488945060209093019201614079565b8361426b575b6142666001600160a01b03600954166001600160a01b0360195416612c6a8787613378565b613fb7565b61428d846001600160a01b03600954166001600160a01b03601a541690614370565b614241565b6001600160a01b03811690815f52600c602052600360405f20015490601554925f9230821415935b8581106142c957505050505050565b806001600160a01b036142dd600193613274565b90549060031b1c166142ee81613c11565b90805f5260166020528760405f20836003820155600261430d8461390b565b91015561431d575b5050016142ba565b6143298286838a614f10565b865f52601860205260405f206001600160a01b0383165f5260205260405f2055855f5260176020526001600160a01b0360405f2091165f5260205260405f20555f80614315565b6134d3926001600160a01b03604051937fa9059cbb0000000000000000000000000000000000000000000000000000000060208601521660248401526044830152604482526143c0606483613331565b614f83565b5f81156147b6576001600160a01b036020541680614742575b50601354841015614733576143f283614292565b6001600160a01b03831692835f52600d60205260405f2090815461441581613353565b926144236040519485613331565b81845260208401905f5260205f205f915b8383106147155750505050815190855f52600c60205260405f2061445986825461336b565b81556002810161446a87825461336b565b90556144788660115461336b565b6011556003614486896132a0565b905490821b1c910161449d8154610cb3848a613680565b90556144af601254610cb38389613680565b601255620151806144bf896132b8565b90549060031b1c0491620151808302838104620151801484151715612df6576144e8904261336b565b918415159588875f1461470c57505f198601958611612df6578895965b156146f857866145149161366c565b519361452f620151806020870151049162015180420461336b565b14806146eb575b156146825750505061455e9161454c915161336b565b91855f52600d60205260405f206133b5565b50555b6145bb6001600160a01b03600a5416604051907f23b872dd000000000000000000000000000000000000000000000000000000006020830152336024830152306044830152846064830152606482526143c0608483613331565b6001600160a01b036008541691823b15611eff575f809360246040518096819363075b9bf960e31b83528960048401525af1918215612c34577f913e1ccde4cbddce589104382f519cff2be9fa901ece8f615cee557d1a5efd699360609361466e575b506002604082876146389452600c602052200154956132b8565b90549060031b1c946001600160a01b03600a54166001600160a01b036009541614159060405192835260208301526040820152a3565b61467a91505f90613331565b5f600261461e565b91935091506146c19350614695886132b8565b90549060031b1c90604051936146aa85613315565b87855260208501526040840152606083015261504d565b827f7c5af8d36d8be103bc583da8e01d3e98f15216cc7ef38832c7550b34e8feb43a5f80a2614561565b5080604085015114614536565b5091935091506146c19350614695886132b8565b95505f96614505565b6004602060019261472585613591565b815201920192019190614434565b63b968846160e01b5f5260045ffd5b60206004916040519283809263d9cdd51360e01b82525afa908115612c34575f91614784575b508210614775575f6143de565b63162908e360e11b5f5260045ffd5b90506020813d6020116147ae575b8161479f60209383613331565b81010312611eff57515f614768565b3d9150614792565b50505050565b5f81156147b6576001600160a01b036020541680614a46575b50601354841015614733576147e983614292565b6001600160a01b03831692835f52600d60205260405f2090815461480c81613353565b9261481a6040519485613331565b81845260208401905f5260205f205f915b838310614a285750505050815190855f52600c60205260405f2061485086825461336b565b81556002810161486187825461336b565b905561486f8660115461336b565b601155600361487d896132a0565b905490821b1c91016148948154610cb3848a613680565b90556148a6601254610cb38389613680565b601255620151806148b6896132b8565b90549060031b1c0491620151808302838104620151801484151715612df6576148df904261336b565b918415159588875f14614a1f57505f198601958611612df6578895965b15614a0b578661490b9161366c565b5193614926620151806020870151049162015180420461336b565b14806149fe575b156149c1575050506149439161454c915161336b565b50556001600160a01b036008541691823b15611eff575f809360246040518096819363075b9bf960e31b83528960048401525af1918215612c34577f913e1ccde4cbddce589104382f519cff2be9fa901ece8f615cee557d1a5efd699360609361466e57506002604082876146389452600c602052200154956132b8565b91935091506149d49350614695886132b8565b827f7c5af8d36d8be103bc583da8e01d3e98f15216cc7ef38832c7550b34e8feb43a5f80a26145bb565b508060408501511461492d565b5091935091506149d49350614695886132b8565b95505f966148fc565b60046020600192614a3885613591565b81520192019201919061482b565b60206004916040519283809263d9cdd51360e01b82525afa908115612c34575f91614a79575b508210614775575f6147d5565b90506020813d602011614aa3575b81614a9460209383613331565b81010312611eff57515f614a6c565b3d9150614a87565b614ab3613d65565b5f90614abe81614292565b6001600160a01b03811691825f52600c60205260405f20935f945f91855f52600d60205260405f209081549081614c24575b50505090816002614b079301611a08888254613378565b601255833384141580614c0b575b15614b3457508061392e9360409252601d602052205490611a5e613d65565b614b4a91926001600160a01b03600a5416614370565b6001600160a01b0360085416803b15611eff575f809160246040518094819363075b9bf960e31b83528860048401525af18015612c3457614bf7575b5060a081837fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca9352600c6020526002604082200154906001600160a01b03600a54166001600160a01b036009541614159060405192878452602084015280604084015260608301526080820152a290565b614c0391505f90613331565b5f60a0614b86565b5050825f52601c6020528360ff60405f20541615614b15565b9498919792969395909492939192808611614d315750849896959394965b5f96614c506107bd8b6133a3565b965b8b891080614d23575b15614c9957614c71611bbd6001928a519061336b565b9801988b8a10614c84575b989795614c52565b9650614c936107bd8a8c6133b5565b96614c7c565b999398919650939950959095815b818110614d0d5750505f5b818110614cfb57505090614b0792915415614cd1575b90915f80614af0565b857f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df45f80a2614cc8565b600190614d0784613822565b01614cb2565b80614d1d611c7a600193876133b5565b01614ca7565b506020880151421015614c5b565b989695939496614c42565b6001600160a01b03165f52600e60205260405f20908154915f5b838110614d6c57635a8cb77960e01b5f5260045ffd5b614d7681836133b5565b5083600182015414614d8b5750600101614d56565b909350614d9d9250612cf09150613591565b929490939150565b6001600160a01b03165f908152600e60205260409020805492915b838110614dd657635a8cb77960e01b5f5260045ffd5b614de081836133b5565b5083600182015414614d8b5750600101614dc0565b6001600160a01b0381168015612f2f576001600160a01b03600954168114614ea657805f52601660205260405f2080548015614ee857614e43614e3a6002544261336b565b60035490613378565b11614e4d57505050565b6020602492604051938480927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa918215612c34575f92614eb3575b5060040154614ea091613378565b80614eaa575b5050565b6134d391613db8565b91506020823d602011614ee0575b81614ece60209383613331565b81010312611eff579051906004614e92565b3d9150614ec1565b7f17479ac8000000000000000000000000000000000000000000000000000000005f5260045ffd5b670de0b6b3a76400009192614f7661392e956001600160a01b03614f7c941695865f52601860205260405f206001600160a01b0382165f5260205260405f2054965f5260176020526001600160a01b0360405f2091165f5260205260405f205490613378565b90613680565b049061336b565b5f6001600160a01b038192169260208151910182855af13d15615041573d67ffffffffffffffff811161330157614fdc9160405191614fcc6020601f19601f8401160184613331565b82523d5f602084013e5b83615178565b805190811515918261501d575b5050614ff25750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8192509060209181010312611eff5760200151801590811503611eff575f80614fe9565b614fdc90606090614fd6565b6001600160a01b0390929192165f52600d60205260405f209182546020820151905f9181905b8184106150e0575050680100000000000000008110156133015760018101855561509d81866133b5565b50505b8181116150b65750613528906134d393946133b5565b5f19810190808211612df6576150db90610d766150d384896133b5565b5091886133b5565b6150a0565b90926150ec848261336b565b60011c908260016150fd848b6133b5565b500154101561511a575060018101809111612df657925b90615073565b9350615114565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561515057565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b906151b5575080511561518d57805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b815115806151fb575b6151c6575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156151be56fea2646970667358221220b77ee7029740f66bfe6dcf7542db42fcbe5b249c33ccf81dcf3c92103cc4974f64736f6c634300081b0033f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00
Deployed Bytecode
0x60806040526004361015610011575f80fd5b5f5f3560e01c8062a4173a1461311057806302b62938146130da5780630483a7f61461308a57806309eba3431461306d5780630a065fb6146130485780630c14930f146130225780630f63107f14612fae5780630fd6699b14612f915780631285deee14612f7657806317053a7e14612f3e57806320212eaa14612ec757806321cc01c914612ea15780632e1a7d4d14612b155780632e88caa114612add57806332c991b514612ac0578063363bf964146129f65780633824c76a146129cf578063386a9525146129b15780633b26664f1461279b5780633f4ba83a146126dc57806344df8e70146126be57806345b35f561461268e5780634870dd9a1461267157806348e5d9f81461260c578063525abb2b146125d4578063547d0096146124e95780635c975abb146124a7578063638634ee146124835780636904fd83146123245780636a678a9c146122845780636bd3b87c146121f25780636e9c931c146121c15780636eacd3981461219d5780636fa75e6d1461215f5780637035ab9814612111578063715018a61461207857806372f702f31461205157806376520c6d14611fcc5780637654b1b914611fb257806379022a9f14611f8b5780637ab8955d14611f465780637bb7bed114611f035780637fd7d06214611e665780638232e5f714611e485780638456cb5914611daf5780638980f11f14611d475780638da5cb5b14611d015780638f40aab1146119845780638fd9f2ca14611961578063914f6e6d1461184157806394761e2a1461182357806398387a8d146117e65780639ae697bf146117615780639bd4ef50146117445780639c9b2e21146116655780639fe6386214611643578063a4d5e67c146114be578063ada71bc214611481578063af1df2551461145a578063b2d6e66d14611237578063b36b9ffd14611211578063b77cf9c6146111d9578063bba63ae41461115f578063bcd1101414611111578063beb3e38214610d9b578063c53b573d14610b5a578063c84aae1714610aa3578063ca5c7b9114610a85578063dc01f60d146108ee578063df379876146106eb578063e6af230d1461069b578063e6c91a15146105a7578063e70b9e2714610559578063e8999f431461053b578063f12297771461050f578063f2fde38b146104e5578063f4252d59146104be578063f46eccc414610481578063f7ad360114610443578063f9759518146104265763ff66a30514610394575f80fd5b34610423576020366003190112610423576001600160a01b036103b5613131565b6103bd613f19565b16801561041457806001600160a01b03196020541617602055808252601b60205260408220600160ff198254161790557f399c0ee7188568b38d4bbb76666ae7a03da799c64e2a69ece47a0543021fa8d38280a280f35b600482639fabe1c160e01b8152fd5b80fd5b5034610423578060031936011261042357602060405161251c8152f35b50346104235760203660031901126104235761047e610460613173565b15338352601c602052604083209060ff801983541691151516179055565b80f35b50346104235760203660031901126104235760ff60406020926001600160a01b036104aa613131565b168152601b84522054166040519015158152f35b503461042357806003193601126104235760206001600160a01b03601a5416604051908152f35b50346104235760203660031901126104235761047e610502613131565b61050a613f19565b613caa565b503461042357602036600319011261042357602061053361052e613131565b613c11565b604051908152f35b50346104235780600319360112610423576020601254604051908152f35b5034610423576040366003190112610423576001600160a01b03604061057d613131565b928261058761315d565b9416815260186020522091165f52602052602060405f2054604051908152f35b5034610423576020366003190112610423576105c1613173565b906105cb336135c3565b90939193338452600e60205260408420948554958581558661062a575b5061047e949550338652600c602052856040812081815461060f6001840191825490613378565b926106206004820194855490613378565b9055555533613f78565b7f3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff87168703610687578552602085209560021b8601955b868110156105e85760049086815586600182015586600282015586600382015501610661565b602486634e487b7160e01b81526011600452fd5b5034610423576020366003190112610423576004356106b8613f19565b80156106dc5760025481116106cd5760035580f35b60048263387cf9e560e11b8152fd5b6004826365e52d5160e11b8152fd5b503461042357602036600319011261042357610705613131565b816060906001600160a01b03831692838252600c6020526001604083200154938252600e602052604082208291815484915b8183106107a95750505050506040519384936060850191855260208501526060604085015282518091526020608085019301915b818110610779575050500390f35b9193509160206060600192604087518051835284810151858401520151604082015201940191019184939261076b565b909192939596976107c36107bd85876133b5565b50613591565b60208101428151115f146108d657881561083a575b600192839282604061081d8d8f6107f58d6108279951908d614da5565b50509590508651610806848461366c565b5152516020610815848461366c565b51015261366c565b510152519061336b565b9701935b01939293919097969597610737565b90919998506108498585613378565b61085281613353565b906108606040519283613331565b80825261086f601f1991613353565b01885b8181106108865750999a90995091906107d8565b6040516060810181811067ffffffffffffffff8211176108c25790602092916040528b81528b838201528b604082015282828601015201610872565b60248c634e487b7160e01b81526041600452fd5b506001919794996108e891519061336b565b9861082b565b503461042357602036600319011261042357610908613131565b60155461091481613353565b916109226040519384613331565b818352601f1961093183613353565b01845b818110610a3b5750506001600160a01b038116845b8381106109ab578486604051918291602083016020845282518091526020604085019301915b81811061097d575050500390f35b825180516001600160a01b03168552602090810151818601528695506040909401939092019160010161096f565b806001600160a01b036109bf600193613274565b90549060031b1c166109d1828861366c565b515264e8d4a51000610a256001600160a01b036109ee848a61366c565b515116858a52600c602052600360408b200154610a1e6001600160a01b03610a16878d61366c565b515116613c11565b9188614f10565b046020610a32838961366c565b51015201610949565b6040516040810181811067ffffffffffffffff821117610a71579060209291604052878152878382015282828801015201610934565b602488634e487b7160e01b81526041600452fd5b50346104235780600319360112610423576020601154604051908152f35b503461042357602036600319011261042357604060a0916001600160a01b03610aca613131565b8260808551610ad8816132e5565b82815282602082015282878201528260608201520152168152600c60205220604051610b03816132e5565b81549182825260018101546020830190815260028201549060408401918252608060046003850154946060870195865201549401938452604051948552516020850152516040840152516060830152516080820152f35b5034610423578060031936011261042357338152600d6020526040812054610b80613d65565b610b8933614292565b338252600c6020526040822082908390338552600d602052604085209384549081610c60575b5050610bfd92935060028101610bc6858254613378565b905560038101610bd7838254613378565b9055610be4848254613378565b9055610bf283601154613378565b601155601254613378565b601255338252601d602052610c1f6040832054610c18613d65565b33836147bc565b338252601d602052604082205460405191825260208201527f4c46641520b3fedde433247f82a421df0d7a83ce24df3f229e9df96f386c8b0c60403392a280f35b91939091808311610d9057509094859392905b8093610c816107bd886133a3565b935b88861080610d82575b15610ce157610cb9610ca260019287519061336b565b94610cb38751604089015190613680565b9061336b565b950195888710610ccc575b959492610c83565b9350610cdb6107bd87896133b5565b93610cc4565b95919697509250929092815b818110610d52575050845b818110610d40575050610bfd9293945415610d16575b83925f610baf565b337f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df48680a2610d0e565b600190610d4c88613822565b01610cf8565b80610d7c610d626001938b6133b5565b50610d76610d708785613378565b8c6133b5565b906137ee565b01610ced565b506020850151421015610c8c565b959392909193610c73565b50346104235761012036600319011261042357610db6613131565b610dbe61315d565b610dc6613147565b91606435916001600160a01b03831680930361110d5760843560a4359060c4359260e4359461010435967ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00549860ff8a60401c16159967ffffffffffffffff811680159081611105575b60011490816110fb575b1590816110f2575b506110ca5767ffffffffffffffff1981166001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00558a61108b575b506001600160a01b0383169384151580611079575b80611067575b8061105e575b1561104f5785151580611046575b8061103d575b80611034575b1561102557620186a08911610ffd57858711610fee57610f4a9392916001600160a01b038092610ee7615121565b610eef615121565b610ef7615121565b610f0033613caa565b876001600160a01b03196009541617600955166001600160a01b0319600b541617600b55166001600160a01b031960195416176019556001600160a01b03198b5416178a55613b95565b87526016602052426002604089200155600255600355600455600155600555610f705780f35b68ff0000000000000000197ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a180f35b60048c63387cf9e560e11b8152fd5b60048c7feeddf6ab000000000000000000000000000000000000000000000000000000008152fd5b60048c6365e52d5160e11b8152fd5b50891515610eb9565b50871515610eb3565b50861515610ead565b60048c639fabe1c160e01b8152fd5b50821515610e9f565b506001600160a01b0382161515610e99565b506001600160a01b0381161515610e93565b68ffffffffffffffffff191668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00555f610e7e565b60048c7ff92ee8a9000000000000000000000000000000000000000000000000000000008152fd5b9050155f610e42565b303b159150610e3a565b8c9150610e30565b8480fd5b50346104235760203660031901126104235764e8d4a51000611156600160406020946001600160a01b03611143613131565b1681526016865220015460025490613680565b04604051908152f35b503461042357604036600319011261042357611179613173565b6024359061251c821015806111ce575b156111bf576111af90338452600f602052604084209060ff801983541691151516179055565b3382526021602052604082205580f35b60048363162908e360e11b8152fd5b506127108210611189565b50346104235760203660031901126104235760406020916001600160a01b03611200613131565b168152601f83522054604051908152f35b503461042357806003193601126104235760206001600160a01b03815416604051908152f35b503461042357602036600319011261042357611251613131565b611259613d65565b6001600160a01b0360065416330361144b5761127481614292565b816001600160a01b0360075416803b15611340578160405180926308c51c6560e01b8252602060048301528183816112ae602482016138b5565b03925af1801561134457611436575b50506001600160a01b03601554911690818352601860205260408320835b82811061134f57505050816001600160a01b03815416803b156113405781809160046040518094819363a2e6204560e01b83525af180156113445761132b575b5052601f60205242604082205580f35b8161133591613331565b61134057815f61131b565b5080fd5b6040513d84823e3d90fd5b806001600160a01b03611363600193613274565b90549060031b1c166001600160a01b03600954168103611385575b50016112db565b61138e81614df5565b808752601660205285604088206001600160a01b0383165f528560205264e8d4a5100060405f20540490816113c6575b50505061137e565b7f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9160046020926001600160a01b0387165f528984528c60405f20550161140e828254613378565b9055611426816001600160a01b036006541687614370565b604051908152a35f8581806113be565b8161144091613331565b61134057815f6112bd565b600482630deda90360e41b8152fd5b503461042357806003193601126104235760206001600160a01b0360085416604051908152f35b50346104235760203660031901126104235760ff60406020926001600160a01b036114aa613131565b168152600f84522054166040519015158152f35b5034610423576020366003190112610423576114d8613131565b338252601b60205260ff6040832054161561144b578182601554916001600160a01b03859416935b838110611611575b50156115e9575f1982019182116115d55781811061158a575b50506015548015611576575f190161153881613274565b6001600160a01b0382549160031b1b191690556015558152601660205280600460408220828155826001820155826002820155826003820155015580f35b602483634e487b7160e01b81526031600452fd5b6115af6001600160a01b036115a16115ce94613274565b90549060031b1c1691613274565b9091906001600160a01b038084549260031b9316831b921b1916179055565b5f80611521565b602484634e487b7160e01b81526011600452fd5b6004847fe6c4247b000000000000000000000000000000000000000000000000000000008152fd5b846001600160a01b0361162383613274565b90549060031b1c161461163857600101611500565b91505060015f611508565b50346104235760203660031901126104235761047e611660613131565b613bbf565b50346104235760203660031901126104235761167f613131565b6001600160a01b03811690811561173557338352601b60205260ff6040842054161561172657818352601660205260026040842001546116fe576116c290613b95565b8082526016602052604082204260028201554290557fb13fd610fe4e1b384966826794a9b2f6100ad031f352cc5ec6f22667f60749808280a280f35b6004837ff411c327000000000000000000000000000000000000000000000000000000008152fd5b600483630deda90360e41b8152fd5b600483639fabe1c160e01b8152fd5b503461042357806003193601126104235760206040516161a88152f35b50346104235760203660031901126104235761177b613131565b6001600160a01b03168152600d60205260408120805490825b8281106117a657602084604051908152f35b6117b081836133b5565b50600142910154116117c5575b600101611794565b926117de6001916117d686856133b5565b50549061336b565b9390506117bd565b50346104235760203660031901126104235760ff60406020926001600160a01b0361180f613131565b168152601c84522054166040519015158152f35b50346104235780600319360112610423576020600554604051908152f35b50346104235760403660031901126104235761185b613173565b9060243542811115611939576118719033614d3c565b9492919091338552600e602052604085209586549060018101809111611925575b8181106118e45750506118a861047e9596613822565b338652600c6020526004604087206118ca846118c5858454613378565b613378565b8155016118dc836118c5848454613378565b905533613f78565b6118ee81896133b5565b505f1982018281116119115760019291610d7661190b928c6133b5565b01611892565b602489634e487b7160e01b81526011600452fd5b602487634e487b7160e01b81526011600452fd5b6004827f6f7eac26000000000000000000000000000000000000000000000000000000008152fd5b5034610423578060031936011261042357602060ff601e54166040519015158152f35b50346104235761199336613182565b8192919215611ce4575b6119a5613d65565b8080611cd1575b80611cbc575b611cad576119bf82614292565b6001600160a01b03821690818552600c602052604085209385948691848852600d602052604088209081549081611b70575b50505090816002611a349301611a08888254613378565b905560038101611a19838254613378565b9055611a26878254613378565b9055610bf286601154613378565b6012558015611b4e575b15611a6457906040846105339360209652601d8652205490611a5e613d65565b836147bc565b9082611a7b916001600160a01b03600a5416614370565b826001600160a01b0360085416803b1561134057819060246040518094819363075b9bf960e31b83528760048401525af18015611b4357611b2a575b507fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca60a0848360209652600c86526002604082200154906001600160a01b03600a54166001600160a01b0360095416141590604051928784528884015280604084015260608301526080820152a2610533565b611b35848092613331565b611b3f575f611ab7565b8280fd5b6040513d86823e3d90fd5b50338114158015611a3e5750808452601c60205260ff60408520541615611a3e565b8993979298949691959950808611155f14611ca25750849896959394965b8396611b9c6107bd8b6133a3565b965b8b891080611c94575b15611bf657611bce611bbd6001928a519061336b565b97610cb38a5160408c015190613680565b9801988b8a10611be1575b989795611b9e565b9650611bf06107bd8a8c6133b5565b96611bd9565b999398919650939950959095815b818110611c6a575050885b818110611c5857505090611a3492915415611c2e575b90915f806119f1565b847f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df48980a2611c25565b600190611c6484613822565b01611c0f565b80611c8e611c7a600193876133b5565b50610d76611c888785613378565b886133b5565b01611c04565b506020880151421015611ba7565b989695939496611b8e565b600484630deda90360e41b8152fd5b506001600160a01b03600b54163314156119b2565b50336001600160a01b03831614156119ac565b6001600160a01b0382168452600d6020526040842054925061199d565b503461042357806003193601126104235760206001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b503461042357604036600319011261042357611d61613131565b7f8c1256b8896378cd5044f80c202f9772b9d77dc85c8a6eb51967210b09bfaa2860206001600160a01b0360243593611d98613f19565b1692611da5813386614370565b604051908152a280f35b5034610423578060031936011261042357611dc8613f19565b611dd0613d65565b600160ff197fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005416177fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346104235780600319360112610423576020600354604051908152f35b50346104235760203660031901126104235760043567ffffffffffffffff81116113405736602382011215611340578060040135611ea381613353565b91611eb16040519384613331565b8183526024602084019260051b8201019036821161110d57602401915b818310611edf578461047e856139c7565b82356001600160a01b0381168103611eff57815260209283019201611ece565b5f80fd5b503461042357602036600319011261042357600435906015548210156104235760206001600160a01b03611f3684613274565b90549060031b1c16604051908152f35b503461042357604036600319011261042357611f60613131565b602435918215158303610423576020611f818484611f7c613d65565b61396d565b6040519015158152f35b503461042357806003193601126104235760206001600160a01b0360195416604051908152f35b503461042357806003193601126104235761047e33613bbf565b503461042357806003193601126104235760405180916020601354928381520191601382527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090915b81811061203b576120378561202b81870382613331565b6040519182918261320a565b0390f35b8254845260209093019260019283019201612014565b503461042357806003193601126104235760206001600160a01b03600a5416604051908152f35b5034610423578060031936011261042357612091613f19565b806001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031981167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5034610423576040366003190112610423576001600160a01b036040612135613131565b928261213f61315d565b9416815260176020522091165f52602052602060405f2054604051908152f35b50346104235760203660031901126104235760043560135481101561218e57338252601d602052604082205580f35b60048263b968846160e01b8152fd5b50346104235760203660031901126104235760206105336121bc613131565b613931565b50346104235760603660031901126104235761047e6121de61315d565b6121e6613d65565b604435906004356143c5565b5034610423576020366003190112610423576001600160a01b03612214613131565b168152600d6020526040812080549061222c82613353565b9261223a6040519485613331565b82845290815260208082209084015b8383106122665760405160208082528190612037908201886131b5565b6004602060019261227685613591565b815201920192019190612249565b5034610423576020366003190112610423576001600160a01b036122a6613131565b6122ae613f19565b16801561041457600a546001600160a01b0381166122fc576001600160a01b0319168117600a557ff9d1c90c609623055381582a51735b38df3243eb09629a9170b15c2ab6bc07398280a280f35b6004837fa741a045000000000000000000000000000000000000000000000000000000008152fd5b50346104235760203660031901126104235761233e613131565b81906001600160a01b03600b5416330361172657806123646001600160a01b0392614292565b16808352600e60205260408320805490815b612428575b505061239f826001600160a01b03600954166001600160a01b03600b541690614370565b8252600c60205260408220600481016123b9838254613378565b90556123c6828254613378565b9055816001600160a01b03815416803b1561134057819060046040518094819363a2e6204560e01b83525af1801561241d57612408575b602082604051908152f35b612413838092613331565b61134057816123fd565b6040513d85823e3d90fd5b5f198201939091841161246f5761243f84836133b5565b50426001820154115f1461246857546124579161336b565b9261246182613822565b9081612376565b509261237b565b602485634e487b7160e01b81526011600452fd5b50346104235760203660031901126104235760206105336124a2613131565b61390b565b5034610423578060031936011261042357602060ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330054166040519015158152f35b50346104235760203660031901126104235760043567ffffffffffffffff81116113405761251b903690600401613243565b612523613f19565b60ff601e54166125ac57825b8181106125475783600160ff19601e541617601e5580f35b6001600160a01b0361256261255d838587613868565b6138f7565b161561259d57806001600160a01b0361258161255d6001948688613868565b168552601b60205260408520805460ff1916831790550161252f565b600484639fabe1c160e01b8152fd5b6004837f4cd4ee53000000000000000000000000000000000000000000000000000000008152fd5b50346104235760203660031901126104235760406020916001600160a01b036125fb613131565b168152602183522054604051908152f35b503461042357602036600319011261042357604060a0916001600160a01b03612633613131565b168152601660205220805490600181015490600281015460046003830154920154926040519485526020850152604084015260608301526080820152f35b503461042357806003193601126104235760206040516127108152f35b503461042357806003193601126104235761047e6040516126b9816126b2816138b5565b0382613331565b6139c7565b50346104235780600319360112610423576020600154604051908152f35b50346104235780600319360112610423576126f5613f19565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff8116156127735760ff19167fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b6004827f8dfc202b000000000000000000000000000000000000000000000000000000008152fd5b50346104235760403660031901126104235760043567ffffffffffffffff8111611340576127cd903690600401613243565b9060243567ffffffffffffffff81116129ad576127ee903690600401613243565b6127f6613f19565b808403612985576013548560135580612943575b506014548560145580612901575b50845b84811061287557507f3dd1633e862937bc1494dac711b194b9438e3d57bc5ea37f2821484aecadc65a939161286f91612861604051958695604087526040870191613878565b918483036020860152613878565b0390a180f35b612880818686613868565b3560135468010000000000000000811015610a7157906128ab8260016128c3940160135560136132d0565b90919082549060031b91821b915f19901b1916179055565b6128ce818385613868565b3560145468010000000000000000811015610a7157600192916128ab82856128fb940160145560146132d0565b0161281b565b601486527fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec908101905b8181106129385750612818565b86815560010161292b565b601386527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090908101905b81811061297a575061280a565b86815560010161296d565b6004857f2a82a34f000000000000000000000000000000000000000000000000000000008152fd5b8380fd5b50346104235780600319360112610423576020600254604051908152f35b503461042357806003193601126104235760206001600160a01b0360065416604051908152f35b5034610423576060366003190112610423576004356001600160a01b03811680910361134057602435906001600160a01b038216809203611b3f57612a39613147565b612a41613f19565b811561259d57821561259d57816001600160a01b036040927f97a48c4be738bdf3e84e8f3927df6d4e52ccc94c58e96a666faca603e456d490946001600160a01b03196008541617600855856001600160a01b031960075416176007551693846001600160a01b0319601a541617601a5582519182526020820152a280f35b5034610423578060031936011261042357602060405161fde88152f35b50346104235760203660031901126104235760406020916001600160a01b03612b04613131565b168152601d83522054604051908152f35b34611eff576020366003190112611eff576004358015612e92575f905f335f52600c60205260405f20600181018054808511155f14612c9c5750612b5a848254613378565b90555b612b6c846118c5858454613378565b90555f92612b7933614292565b612b8f83336001600160a01b0360095416614370565b80612c3f575b6001600160a01b035f5416803b15611eff575f809160046040518094819363a2e6204560e01b83525af18015612c3457612c1f575b50338452600c60205260026040852001546040519384526020840152604083015260608201528160808201527fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca60a03392a280f35b612c2c9194505f90613331565b5f9284612bca565b6040513d5f823e3d90fd5b81612c75575b612c706001600160a01b03600954166001600160a01b0360195416612c6a8585613378565b91614370565b612b95565b612c97826001600160a01b03600954166001600160a01b03601a541690614370565b612c45565b612caa908596929496613378565b926004830190815490858210612db4575f9055935f90335f52600e60205260405f20905b612cd883836133b5565b50548015612e8c57612cf5612cf06107bd86866133b5565b613e8b565b949093929183838310612e0a57505090612d0e91613378565b988915612ddc575b91612d28612d2e92612d349594613378565b9761336b565b9961336b565b9580612da1575081612d49575b505055612b5d565b8054825b818110612d775750505f5b828110612d655750612d41565b600190612d7183613822565b01612d58565b80612d9b612d87600193866133b5565b50610d76612d958885613378565b876133b5565b01612d4d565b918496989415612db45760010191612cce565b7f1c243dc1000000000000000000000000000000000000000000000000000000005f5260045ffd5b9492915f198414612df65760019390930194919291612d16565b634e487b7160e01b5f52601160045260245ffd5b95509a9350809250620186a091500290808204620186a01490151715612df65788620186a00392620186a08411612df657612e55612e4e612d3495612d2e94613385565b8092613378565b612e5f87876133b5565b5055612d28620186a0612e735f9c84613680565b0491620186a0612e8560015485613680565b0494613378565b50612cce565b6365e52d5160e11b5f5260045ffd5b34611eff575f366003190112611eff5760206001600160a01b0360075416604051908152f35b34611eff576020366003190112611eff576001600160a01b03612ee8613131565b612ef0613f19565b168015612f2f57806001600160a01b031960065416176006557fe3b68397b3e7baa0682f1ed87243f231cbc0e0a383587123479edb959ddf6b885f80a2005b639fabe1c160e01b5f5260045ffd5b34611eff576020366003190112611eff576001600160a01b03612f5f613131565b165f526010602052602060405f2054604051908152f35b34611eff575f366003190112611eff57602060405160018152f35b34611eff575f366003190112611eff576020600454604051908152f35b34611eff575f366003190112611eff5760405180602060145491828152019060145f527fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec905f5b81811061300c576120378561202b81870382613331565b8254845260209093019260019283019201612ff5565b34611eff575f366003190112611eff5760206001600160a01b0360095416604051908152f35b34611eff575f366003190112611eff5760206001600160a01b035f5416604051908152f35b34611eff575f366003190112611eff576020604051620186a08152f35b34611eff576020366003190112611eff576120376130ae6130a9613131565b613693565b91604095939551958695865260208601526040850152606084015260a0608084015260a08301906131b5565b34611eff576020366003190112611eff5760606130fd6130f8613131565b6135c3565b9060405192835260208301526040820152f35b34611eff5761312f61312136613182565b9161312a613d65565b61340b565b005b600435906001600160a01b0382168203611eff57565b604435906001600160a01b0382168203611eff57565b602435906001600160a01b0382168203611eff57565b600435908115158203611eff57565b6060906003190112611eff576004356001600160a01b0381168103611eff5790602435906044358015158103611eff5790565b90602080835192838152019201905f5b8181106131d25750505090565b9091926020608060019260608751805183528481015185840152604081015160408401520151606082015201940191019190916131c5565b60206040818301928281528451809452019201905f5b81811061322d5750505090565b8251845260209384019390920191600101613220565b9181601f84011215611eff5782359167ffffffffffffffff8311611eff576020808501948460051b010111611eff57565b60155481101561328c5760155f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b60145481101561328c5760145f5260205f2001905f90565b60135481101561328c5760135f5260205f2001905f90565b805482101561328c575f5260205f2001905f90565b60a0810190811067ffffffffffffffff82111761330157604052565b634e487b7160e01b5f52604160045260245ffd5b6080810190811067ffffffffffffffff82111761330157604052565b90601f8019910116810190811067ffffffffffffffff82111761330157604052565b67ffffffffffffffff81116133015760051b60200190565b91908201809211612df657565b91908203918211612df657565b811561338f570490565b634e487b7160e01b5f52601260045260245ffd5b80541561328c575f5260205f20905f90565b805482101561328c575f5260205f209060021b01905f90565b91906133f85760608160039251845560208101516001850155604081015160028501550151910155565b634e487b7160e01b5f525f60045260245ffd5b919091335f52601b60205260ff60405f2054161561358257821561357d576001600160a01b03169030821461356657815f52600c60205260405f209061345284835461336b565b8255156135525760040161346783825461336b565b90555f52600e60205260405f209081549081151591825f1461354b575f198101818111612df657925b60055490620151806134a2834261336b565b048161352e575b50156134d5575050906134ca6134d0926134c383866133b5565b505461336b565b926133b5565b50555b565b90939192506134e4814261336b565b604051936134f185613315565b845260208401526001604084015260608301526801000000000000000083101561330157826135289160016134d3950181556133b5565b906133ce565b905062015180600161354087896133b5565b50015404145f6134a9565b5f92613490565b613562915060010191825461336b565b9055565b50506134d3906001600160a01b0360095416613db8565b505050565b630deda90360e41b5f5260045ffd5b9060405161359e81613315565b6060600382948054845260018101546020850152600281015460408501520154910152565b5f916001600160a01b035f9216805f52600c6020528360405f2060048101549283613600575b506135fb9260016118c592015461336b565b929190565b9091505f52600e60205260405f2080545f915b8183106136245750869291506135e9565b9091959661363287836133b5565b5080541561366357600191612d2e61364f612cf061365994613591565b949150915061336b565b9601915b90613613565b5096959161365d565b805182101561328c5760209160051b010190565b81810292918115918404141715612df657565b5f905f905f906001600160a01b0360609116805f52600d60205260405f205f9080545f915b8183106136d557505050505f52600c602052600260405f20015494565b90919295966136e484836133b5565b50600101544210156137d457861561375f575b600161375361372e829361370e6107bd89886133b5565b6137188c8c61366c565b526137238b8b61366c565b506117d688876133b5565b99610cb361373c88876133b5565b5054600261374a8a896133b5565b50015490613680565b9701935b0191906136b8565b945061376b8383613378565b61377481613353565b906137826040519283613331565b808252613791601f1991613353565b015f5b8181106137a55750909590506136f7565b6020906040516137b481613315565b5f81525f838201525f60408201525f606082015282828601015201613794565b969592976137e86001916117d68b856133b5565b98613757565b91906133f8578082036137ff575050565b600381819254845560018101546001850155600281015460028501550154910155565b80548015613854575f19019061383882826133b5565b6133f8576003815f809355826001820155826002820155015555565b634e487b7160e01b5f52603160045260245ffd5b919081101561328c5760051b0190565b90918281527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311611eff5760209260051b809284830137010190565b602060155491828152019060155f5260205f20905f5b8181106138d85750505090565b82546001600160a01b03168452602090930192600192830192016138cb565b356001600160a01b0381168103611eff5790565b6001600160a01b03165f52601660205260405f20548042105f1461392e57504290565b90565b6001600160a01b03165f52600c60205260405f206001600160a01b03600a54166001600160a01b036009541614613969576002015490565b5490565b9190916001600160a01b036020541633036135825761398b81613693565b5050501515905061399c57505f9150565b600192156139c457806001600160a01b036139c492165f52600d60205260405f205490614aab565b50565b6139d033614292565b6139d8613d65565b600754906001600160a01b035f9216803b15611eff57604051906308c51c6560e01b82528160248101602060048301528451809152604482019060208601905f5b818110613b735750505091815f81819503925af18015612c3457613b5e575b5080519033835260186020526040832090835b838110613aa057505050506001600160a01b03815416803b156113405781809160046040518094819363a2e6204560e01b83525af1801561134457613a8e575050565b613a99828092613331565b6104235750565b806001600160a01b03613ab56001938561366c565b51168087526016602052604087206001600160a01b0382165f528560205264e8d4a5100060405f2054613ae784614df5565b049081613af8575b50505001613a4b565b6004906001600160a01b0384165f52876020528960405f205501613b1d828254613378565b9055613b2a813384614370565b6040519081527f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e60203392a35f8080613aef565b613b6b9192505f90613331565b5f905f613a38565b82516001600160a01b0316845286945060209384019390920191600101613a19565b6015549068010000000000000000821015613301576115af8260016134d3940160155560156132d0565b6001600160a01b036008541690813b15611eff576001600160a01b0360245f9283604051958694859363075b9bf960e31b85521660048401525af18015612c3457613c075750565b5f6134d391613331565b906001600160a01b03821691825f526016602052600360405f200154926012549182613c3c57505050565b90613c66613c50613c7c939694959661390b565b825f526016602052600260405f20015490613378565b905f526016602052600160405f20015490613680565b670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612df65761392e92610cb391613385565b6001600160a01b03168015613d39576001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054826001600160a01b03198216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b60ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005416613d9057565b7fd93c0665000000000000000000000000000000000000000000000000000000005f5260045ffd5b6001600160a01b03165f52601660205260405f208054804210155f14613e29575064e8d4a51000820282810464e8d4a510001483151715612df65761356291613e0660049260025490613385565b60018201555b426002820155613e1e6002544261336b565b81550191825461336b565b613e34904290613378565b90613e5564e8d4a51000613e4e6001840194855490613680565b048461336b565b9064e8d4a5100082029180830464e8d4a510001490151715612df65761356292613e8460049360025490613385565b9055613e0c565b5f905f905f906020810151428111613ead575b5082613eaa9151613378565b93565b91509250613ebd91504290613378565b61fde881029080820461fde81490151715612df657600554613ede91613385565b906161a88201809211612df657620186a0613efa838351613680565b0490613eaa82620186a0613f1060015483613680565b04929150613e9e565b6001600160a01b037f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303613f4c57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b93919290926001600160a01b0385169433860361358257613f9881614292565b60095493613fb186836001600160a01b035f9816614370565b8261423b575b61405c575b506001600160a01b03835416803b156129ad5783809160046040518094819363a2e6204560e01b83525af18015611b4357614047575b50917fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca939160a093868352600c60205260026040842001546040519485526020850152604084015260608301526080820152a2565b614052848092613331565b611b3f575f613ff2565b60405190816020601554918281520190838260155f5260205f20925f5b81811061421957505061408e92500384613331565b614096613d65565b6001600160a01b0360075416803b15611eff5760405180926308c51c6560e01b8252602482016020600484015286518091526044830191905f5b8181106141f75750505091815f81819503925af18015612c34576141e2575b5081519187865260186020526040862090865b848110614122575050505050848352601f6020524260408420555f613fbc565b806001600160a01b036141376001938561366c565b5116808a5260166020528b8a604081206001600160a01b0384165f528760205264e8d4a5100060405f205461416b86614df5565b048061417d575b505050505001614102565b60046020927f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e946001600160a01b0388165f528b855260405f2055016141c4828254613378565b90556141d1818b87614370565b604051908152a35f8b8a8280614172565b6141ef9195505f90613331565b5f935f6140ef565b82516001600160a01b03168452869450602093840193909201916001016140d0565b84546001600160a01b0316835260019485019488945060209093019201614079565b8361426b575b6142666001600160a01b03600954166001600160a01b0360195416612c6a8787613378565b613fb7565b61428d846001600160a01b03600954166001600160a01b03601a541690614370565b614241565b6001600160a01b03811690815f52600c602052600360405f20015490601554925f9230821415935b8581106142c957505050505050565b806001600160a01b036142dd600193613274565b90549060031b1c166142ee81613c11565b90805f5260166020528760405f20836003820155600261430d8461390b565b91015561431d575b5050016142ba565b6143298286838a614f10565b865f52601860205260405f206001600160a01b0383165f5260205260405f2055855f5260176020526001600160a01b0360405f2091165f5260205260405f20555f80614315565b6134d3926001600160a01b03604051937fa9059cbb0000000000000000000000000000000000000000000000000000000060208601521660248401526044830152604482526143c0606483613331565b614f83565b5f81156147b6576001600160a01b036020541680614742575b50601354841015614733576143f283614292565b6001600160a01b03831692835f52600d60205260405f2090815461441581613353565b926144236040519485613331565b81845260208401905f5260205f205f915b8383106147155750505050815190855f52600c60205260405f2061445986825461336b565b81556002810161446a87825461336b565b90556144788660115461336b565b6011556003614486896132a0565b905490821b1c910161449d8154610cb3848a613680565b90556144af601254610cb38389613680565b601255620151806144bf896132b8565b90549060031b1c0491620151808302838104620151801484151715612df6576144e8904261336b565b918415159588875f1461470c57505f198601958611612df6578895965b156146f857866145149161366c565b519361452f620151806020870151049162015180420461336b565b14806146eb575b156146825750505061455e9161454c915161336b565b91855f52600d60205260405f206133b5565b50555b6145bb6001600160a01b03600a5416604051907f23b872dd000000000000000000000000000000000000000000000000000000006020830152336024830152306044830152846064830152606482526143c0608483613331565b6001600160a01b036008541691823b15611eff575f809360246040518096819363075b9bf960e31b83528960048401525af1918215612c34577f913e1ccde4cbddce589104382f519cff2be9fa901ece8f615cee557d1a5efd699360609361466e575b506002604082876146389452600c602052200154956132b8565b90549060031b1c946001600160a01b03600a54166001600160a01b036009541614159060405192835260208301526040820152a3565b61467a91505f90613331565b5f600261461e565b91935091506146c19350614695886132b8565b90549060031b1c90604051936146aa85613315565b87855260208501526040840152606083015261504d565b827f7c5af8d36d8be103bc583da8e01d3e98f15216cc7ef38832c7550b34e8feb43a5f80a2614561565b5080604085015114614536565b5091935091506146c19350614695886132b8565b95505f96614505565b6004602060019261472585613591565b815201920192019190614434565b63b968846160e01b5f5260045ffd5b60206004916040519283809263d9cdd51360e01b82525afa908115612c34575f91614784575b508210614775575f6143de565b63162908e360e11b5f5260045ffd5b90506020813d6020116147ae575b8161479f60209383613331565b81010312611eff57515f614768565b3d9150614792565b50505050565b5f81156147b6576001600160a01b036020541680614a46575b50601354841015614733576147e983614292565b6001600160a01b03831692835f52600d60205260405f2090815461480c81613353565b9261481a6040519485613331565b81845260208401905f5260205f205f915b838310614a285750505050815190855f52600c60205260405f2061485086825461336b565b81556002810161486187825461336b565b905561486f8660115461336b565b601155600361487d896132a0565b905490821b1c91016148948154610cb3848a613680565b90556148a6601254610cb38389613680565b601255620151806148b6896132b8565b90549060031b1c0491620151808302838104620151801484151715612df6576148df904261336b565b918415159588875f14614a1f57505f198601958611612df6578895965b15614a0b578661490b9161366c565b5193614926620151806020870151049162015180420461336b565b14806149fe575b156149c1575050506149439161454c915161336b565b50556001600160a01b036008541691823b15611eff575f809360246040518096819363075b9bf960e31b83528960048401525af1918215612c34577f913e1ccde4cbddce589104382f519cff2be9fa901ece8f615cee557d1a5efd699360609361466e57506002604082876146389452600c602052200154956132b8565b91935091506149d49350614695886132b8565b827f7c5af8d36d8be103bc583da8e01d3e98f15216cc7ef38832c7550b34e8feb43a5f80a26145bb565b508060408501511461492d565b5091935091506149d49350614695886132b8565b95505f966148fc565b60046020600192614a3885613591565b81520192019201919061482b565b60206004916040519283809263d9cdd51360e01b82525afa908115612c34575f91614a79575b508210614775575f6147d5565b90506020813d602011614aa3575b81614a9460209383613331565b81010312611eff57515f614a6c565b3d9150614a87565b614ab3613d65565b5f90614abe81614292565b6001600160a01b03811691825f52600c60205260405f20935f945f91855f52600d60205260405f209081549081614c24575b50505090816002614b079301611a08888254613378565b601255833384141580614c0b575b15614b3457508061392e9360409252601d602052205490611a5e613d65565b614b4a91926001600160a01b03600a5416614370565b6001600160a01b0360085416803b15611eff575f809160246040518094819363075b9bf960e31b83528860048401525af18015612c3457614bf7575b5060a081837fa236f2dcd2b940fd86168787a5f820805cdbd85131f7192d9d9c418556876fca9352600c6020526002604082200154906001600160a01b03600a54166001600160a01b036009541614159060405192878452602084015280604084015260608301526080820152a290565b614c0391505f90613331565b5f60a0614b86565b5050825f52601c6020528360ff60405f20541615614b15565b9498919792969395909492939192808611614d315750849896959394965b5f96614c506107bd8b6133a3565b965b8b891080614d23575b15614c9957614c71611bbd6001928a519061336b565b9801988b8a10614c84575b989795614c52565b9650614c936107bd8a8c6133b5565b96614c7c565b999398919650939950959095815b818110614d0d5750505f5b818110614cfb57505090614b0792915415614cd1575b90915f80614af0565b857f95266445d018e5b30f957c915e91b04bb4a19bf0f8f21020a08dad9be7931df45f80a2614cc8565b600190614d0784613822565b01614cb2565b80614d1d611c7a600193876133b5565b01614ca7565b506020880151421015614c5b565b989695939496614c42565b6001600160a01b03165f52600e60205260405f20908154915f5b838110614d6c57635a8cb77960e01b5f5260045ffd5b614d7681836133b5565b5083600182015414614d8b5750600101614d56565b909350614d9d9250612cf09150613591565b929490939150565b6001600160a01b03165f908152600e60205260409020805492915b838110614dd657635a8cb77960e01b5f5260045ffd5b614de081836133b5565b5083600182015414614d8b5750600101614dc0565b6001600160a01b0381168015612f2f576001600160a01b03600954168114614ea657805f52601660205260405f2080548015614ee857614e43614e3a6002544261336b565b60035490613378565b11614e4d57505050565b6020602492604051938480927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa918215612c34575f92614eb3575b5060040154614ea091613378565b80614eaa575b5050565b6134d391613db8565b91506020823d602011614ee0575b81614ece60209383613331565b81010312611eff579051906004614e92565b3d9150614ec1565b7f17479ac8000000000000000000000000000000000000000000000000000000005f5260045ffd5b670de0b6b3a76400009192614f7661392e956001600160a01b03614f7c941695865f52601860205260405f206001600160a01b0382165f5260205260405f2054965f5260176020526001600160a01b0360405f2091165f5260205260405f205490613378565b90613680565b049061336b565b5f6001600160a01b038192169260208151910182855af13d15615041573d67ffffffffffffffff811161330157614fdc9160405191614fcc6020601f19601f8401160184613331565b82523d5f602084013e5b83615178565b805190811515918261501d575b5050614ff25750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8192509060209181010312611eff5760200151801590811503611eff575f80614fe9565b614fdc90606090614fd6565b6001600160a01b0390929192165f52600d60205260405f209182546020820151905f9181905b8184106150e0575050680100000000000000008110156133015760018101855561509d81866133b5565b50505b8181116150b65750613528906134d393946133b5565b5f19810190808211612df6576150db90610d766150d384896133b5565b5091886133b5565b6150a0565b90926150ec848261336b565b60011c908260016150fd848b6133b5565b500154101561511a575060018101809111612df657925b90615073565b9350615114565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561515057565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b906151b5575080511561518d57805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b815115806151fb575b6151c6575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156151be56fea2646970667358221220b77ee7029740f66bfe6dcf7542db42fcbe5b249c33ccf81dcf3c92103cc4974f64736f6c634300081b0033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in HYPE
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
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.