ETH Price: $2,064.60 (+5.82%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
StakedTokenMTA

Compiler Version
v0.8.6+commit.11564f7e

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
pragma abicoder v2;

import { StakedToken } from "./StakedToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title StakedTokenMTA
 * @dev Derives from StakedToken, and simply adds the functionality specific to the $MTA staking token,
 * for example compounding rewards.
 **/
contract StakedTokenMTA is StakedToken {
    using SafeERC20 for IERC20;

    /**
     * @param _nexus System nexus
     * @param _rewardsToken Token that is being distributed as a reward. eg MTA
     * @param _stakedToken Core token that is staked and tracked (e.g. MTA)
     * @param _cooldownSeconds Seconds a user must wait after she initiates her cooldown before withdrawal is possible
     * @param _unstakeWindow Window in which it is possible to withdraw, following the cooldown period
     */
    constructor(
        address _nexus,
        address _rewardsToken,
        address _questManager,
        address _stakedToken,
        uint256 _cooldownSeconds,
        uint256 _unstakeWindow
    )
        StakedToken(
            _nexus,
            _rewardsToken,
            _questManager,
            _stakedToken,
            _cooldownSeconds,
            _unstakeWindow,
            false
        )
    {}

    function initialize(
        bytes32 _nameArg,
        bytes32 _symbolArg,
        address _rewardsDistributorArg
    ) external initializer {
        __StakedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg);
    }

    /**
     * @dev Allows a staker to compound their rewards IF the Staking token and the Rewards token are the same
     * for example, with $MTA as both staking token and rewards token. Calls 'claimRewards' on the HeadlessStakingRewards
     * before executing a stake here
     */
    function compoundRewards() external nonReentrant {
        require(address(STAKED_TOKEN) == address(REWARDS_TOKEN), "Only for same pairs");

        // 1. claim rewards
        uint256 balBefore = STAKED_TOKEN.balanceOf(address(this));
        _claimReward(address(this));

        // 2. check claim amount
        uint256 balAfter = STAKED_TOKEN.balanceOf(address(this));
        uint256 claimed = balAfter - balBefore;
        require(claimed > 0, "Must compound something");

        // 3. re-invest
        _settleStake(claimed, address(0), false);
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
pragma abicoder v2;

import { IStakedToken } from "./interfaces/IStakedToken.sol";
import { GamifiedVotingToken } from "./GamifiedVotingToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Root } from "../../shared/Root.sol";
import { InitializableReentrancyGuard } from "../../shared/InitializableReentrancyGuard.sol";
import "./deps/GamifiedTokenStructs.sol";

/**
 * @title StakedToken
 * @notice StakedToken is a non-transferrable ERC20 token that allows users to stake and withdraw, earning voting rights.
 * Scaled balance is determined by quests a user completes, and the length of time they keep the raw balance wrapped.
 * Stakers can unstake, after the elapsed cooldown period, and before the end of the unstake window. Users voting/earning
 * power is slashed during this time, and they may face a redemption fee if they leave early.
 * The reason for this unstake window is that this StakedToken acts as a source of insurance value for the mStable system,
 * which can access the funds via the Recollateralisation module, up to the amount defined in `safetyData`.
 * Voting power can be used for a number of things: voting in the mStable DAO/emission dials, boosting rewards, earning
 * rewards here. While a users "balance" is unique to themselves, they can choose to delegate their voting power (which will apply
 * to voting in the mStable DAO and emission dials).
 * @author mStable
 * @dev Only whitelisted contracts can communicate with this contract, in order to avoid having tokenised wrappers that
 * could potentially circumvent our unstaking procedure.
 **/
contract StakedToken is GamifiedVotingToken, InitializableReentrancyGuard {
    using SafeERC20 for IERC20;

    /// @notice Core token that is staked and tracked (e.g. MTA)
    IERC20 public immutable STAKED_TOKEN;
    /// @notice Seconds a user must wait after she initiates her cooldown before withdrawal is possible
    uint256 public immutable COOLDOWN_SECONDS;
    /// @notice Window in which it is possible to withdraw, following the cooldown period
    uint256 public immutable UNSTAKE_WINDOW;
    /// @notice A week
    uint256 private constant ONE_WEEK = 7 days;

    struct SafetyData {
        /// Percentage of collateralisation where 100% = 1e18
        uint128 collateralisationRatio;
        /// Slash % where 100% = 1e18
        uint128 slashingPercentage;
    }

    /// @notice Data relating to the re-collateralisation safety module
    SafetyData public safetyData;

    /// @notice Whitelisted smart contract integrations
    mapping(address => bool) public whitelistedWrappers;

    event Staked(address indexed user, uint256 amount, address delegatee);
    event Withdraw(address indexed user, address indexed to, uint256 amount);
    event Cooldown(address indexed user, uint256 percentage);
    event CooldownExited(address indexed user);
    event SlashRateChanged(uint256 newRate);
    event Recollateralised();
    event WrapperWhitelisted(address wallet);
    event WrapperBlacklisted(address wallet);

    /***************************************
                    INIT
    ****************************************/

    /**
     * @param _nexus System nexus
     * @param _rewardsToken Token that is being distributed as a reward. eg MTA
     * @param _questManager Centralised manager of quests
     * @param _stakedToken Core token that is staked and tracked (e.g. MTA)
     * @param _cooldownSeconds Seconds a user must wait after she initiates her cooldown before withdrawal is possible
     * @param _unstakeWindow Window in which it is possible to withdraw, following the cooldown period
     * @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
     */
    constructor(
        address _nexus,
        address _rewardsToken,
        address _questManager,
        address _stakedToken,
        uint256 _cooldownSeconds,
        uint256 _unstakeWindow,
        bool _hasPriceCoeff
    ) GamifiedVotingToken(_nexus, _rewardsToken, _questManager, _hasPriceCoeff) {
        STAKED_TOKEN = IERC20(_stakedToken);
        COOLDOWN_SECONDS = _cooldownSeconds;
        UNSTAKE_WINDOW = _unstakeWindow;
    }

    /**
     * @param _nameArg Token name
     * @param _symbolArg Token symbol
     * @param _rewardsDistributorArg mStable Rewards Distributor
     */
    function __StakedToken_init(
        bytes32 _nameArg,
        bytes32 _symbolArg,
        address _rewardsDistributorArg
    ) public initializer {
        __GamifiedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg);
        _initializeReentrancyGuard();
        safetyData = SafetyData({ collateralisationRatio: 1e18, slashingPercentage: 0 });
    }

    /**
     * @dev Only the recollateralisation module, as specified in the mStable Nexus, can execute this
     */
    modifier onlyRecollateralisationModule() {
        require(_msgSender() == _recollateraliser(), "Only Recollateralisation Module");
        _;
    }

    /**
     * @dev This protects against fn's being called after a recollateralisation event, when the contract is essentially finished
     */
    modifier onlyBeforeRecollateralisation() {
        _onlyBeforeRecollateralisation();
        _;
    }

    function _onlyBeforeRecollateralisation() internal view {
        require(safetyData.collateralisationRatio == 1e18, "Only while fully collateralised");
    }

    /**
     * @dev Only whitelisted contracts can call core fns. mStable governors can whitelist and de-whitelist wrappers.
     * Access may be given to yield optimisers to boost rewards, but creating unlimited and ungoverned wrappers is unadvised.
     */
    modifier assertNotContract() {
        _assertNotContract();
        _;
    }

    function _assertNotContract() internal view {
        if (_msgSender() != tx.origin) {
            require(whitelistedWrappers[_msgSender()], "Not a whitelisted contract");
        }
    }

    /***************************************
                    ACTIONS
    ****************************************/

    /**
     * @dev Stake an `_amount` of STAKED_TOKEN in the system. This amount is added to the users stake and
     * boosts their voting power.
     * @param _amount Units of STAKED_TOKEN to stake
     */
    function stake(uint256 _amount) external {
        _transferAndStake(_amount, address(0), false);
    }

    /**
     * @dev Stake an `_amount` of STAKED_TOKEN in the system. This amount is added to the users stake and
     * boosts their voting power.
     * @param _amount Units of STAKED_TOKEN to stake
     * @param _exitCooldown Bool signalling whether to take this opportunity to end any outstanding cooldown and
     * return the user back to their full voting power
     */
    function stake(uint256 _amount, bool _exitCooldown) external {
        _transferAndStake(_amount, address(0), _exitCooldown);
    }

    /**
     * @dev Stake an `_amount` of STAKED_TOKEN in the system. This amount is added to the users stake and
     * boosts their voting power. Take the opportunity to change delegatee.
     * @param _amount Units of STAKED_TOKEN to stake
     * @param _delegatee Address of the user to whom the sender would like to delegate their voting power
     */
    function stake(uint256 _amount, address _delegatee) external {
        _transferAndStake(_amount, _delegatee, false);
    }

    /**
     * @dev Transfers tokens from sender before calling `_settleStake`
     */
    function _transferAndStake(
        uint256 _amount,
        address _delegatee,
        bool _exitCooldown
    ) internal {
        STAKED_TOKEN.safeTransferFrom(_msgSender(), address(this), _amount);
        _settleStake(_amount, _delegatee, _exitCooldown);
    }

    /**
     * @dev Internal stake fn. Can only be called by whitelisted contracts/EOAs and only before a recollateralisation event.
     * NOTE - Assumes tokens have already been transferred
     * @param _amount Units of STAKED_TOKEN to stake
     * @param _delegatee Address of the user to whom the sender would like to delegate their voting power
     * @param _exitCooldown Bool signalling whether to take this opportunity to end any outstanding cooldown and
     * return the user back to their full voting power
     */
    function _settleStake(
        uint256 _amount,
        address _delegatee,
        bool _exitCooldown
    ) internal onlyBeforeRecollateralisation assertNotContract {
        require(_amount != 0, "INVALID_ZERO_AMOUNT");

        // 1. Apply the delegate if it has been chosen (else it defaults to the sender)
        if (_delegatee != address(0)) {
            _delegate(_msgSender(), _delegatee);
        }

        // 2. Deal with cooldown
        //      If a user is currently in a cooldown period, re-calculate their cooldown timestamp
        Balance memory oldBalance = _balances[_msgSender()];
        //      If we have missed the unstake window, or the user has chosen to exit the cooldown,
        //      then reset the timestamp to 0
        bool exitCooldown = _exitCooldown ||
            (oldBalance.cooldownTimestamp > 0 &&
                block.timestamp >
                (oldBalance.cooldownTimestamp + COOLDOWN_SECONDS + UNSTAKE_WINDOW));
        if (exitCooldown) {
            emit CooldownExited(_msgSender());
        }

        // 3. Settle the stake by depositing the STAKED_TOKEN and minting voting power
        _mintRaw(_msgSender(), _amount, exitCooldown);

        emit Staked(_msgSender(), _amount, _delegatee);
    }

    /**
     * @dev Withdraw raw tokens from the system, following an elapsed cooldown period.
     * Note - May be subject to a transfer fee, depending on the users weightedTimestamp
     * @param _amount Units of raw token to withdraw
     * @param _recipient Address of beneficiary who will receive the raw tokens
     * @param _amountIncludesFee Is the `_amount` specified inclusive of any applicable redemption fee?
     * @param _exitCooldown Should we take this opportunity to exit the cooldown period?
     **/
    function withdraw(
        uint256 _amount,
        address _recipient,
        bool _amountIncludesFee,
        bool _exitCooldown
    ) external {
        _withdraw(_amount, _recipient, _amountIncludesFee, _exitCooldown);
    }

    /**
     * @dev Withdraw raw tokens from the system, following an elapsed cooldown period.
     * Note - May be subject to a transfer fee, depending on the users weightedTimestamp
     * @param _amount Units of raw token to withdraw
     * @param _recipient Address of beneficiary who will receive the raw tokens
     * @param _amountIncludesFee Is the `_amount` specified inclusive of any applicable redemption fee?
     * @param _exitCooldown Should we take this opportunity to exit the cooldown period?
     **/
    function _withdraw(
        uint256 _amount,
        address _recipient,
        bool _amountIncludesFee,
        bool _exitCooldown
    ) internal assertNotContract {
        require(_amount != 0, "INVALID_ZERO_AMOUNT");

        // Is the contract post-recollateralisation?
        if (safetyData.collateralisationRatio != 1e18) {
            // 1. If recollateralisation has occured, the contract is finished and we can skip all checks
            _burnRaw(_msgSender(), _amount, false, true);
            // 2. Return a proportionate amount of tokens, based on the collateralisation ratio
            STAKED_TOKEN.safeTransfer(
                _recipient,
                (_amount * safetyData.collateralisationRatio) / 1e18
            );
            emit Withdraw(_msgSender(), _recipient, _amount);
        } else {
            // 1. If no recollateralisation has occured, the user must be within their UNSTAKE_WINDOW period in order to withdraw
            Balance memory oldBalance = _balances[_msgSender()];
            require(
                block.timestamp > oldBalance.cooldownTimestamp + COOLDOWN_SECONDS,
                "INSUFFICIENT_COOLDOWN"
            );
            require(
                block.timestamp - (oldBalance.cooldownTimestamp + COOLDOWN_SECONDS) <=
                    UNSTAKE_WINDOW,
                "UNSTAKE_WINDOW_FINISHED"
            );

            // 2. Get current balance
            Balance memory balance = _balances[_msgSender()];

            // 3. Apply redemption fee
            //      e.g. (55e18 / 5e18) - 2e18 = 9e18 / 100 = 9e16
            uint256 feeRate = calcRedemptionFeeRate(balance.weightedTimestamp);
            //      fee = amount * 1e18 / feeRate
            //      totalAmount = amount + fee
            uint256 totalWithdraw = _amountIncludesFee
                ? _amount
                : (_amount * (1e18 + feeRate)) / 1e18;
            uint256 userWithdrawal = (totalWithdraw * 1e18) / (1e18 + feeRate);

            //      Check for percentage withdrawal
            uint256 maxWithdrawal = oldBalance.cooldownUnits;
            require(totalWithdraw <= maxWithdrawal, "Exceeds max withdrawal");

            // 4. Exit cooldown if the user has specified, or if they have withdrawn everything
            // Otherwise, update the percentage remaining proportionately
            bool exitCooldown = _exitCooldown || totalWithdraw == maxWithdrawal;

            // 5. Settle the withdrawal by burning the voting tokens
            _burnRaw(_msgSender(), totalWithdraw, exitCooldown, false);
            //      Log any redemption fee to the rewards contract
            _notifyAdditionalReward(totalWithdraw - userWithdrawal);
            //      Finally transfer tokens back to recipient
            STAKED_TOKEN.safeTransfer(_recipient, userWithdrawal);

            emit Withdraw(_msgSender(), _recipient, _amount);
        }
    }

    /**
     * @dev Enters a cooldown period, after which (and before the unstake window elapses) a user will be able
     * to withdraw part or all of their staked tokens. Note, during this period, a users voting power is significantly reduced.
     * If a user already has a cooldown period, then it will reset to the current block timestamp, so use wisely.
     * @param _units Units of stake to cooldown for
     **/
    function startCooldown(uint256 _units) external {
        _startCooldown(_units);
    }

    /**
     * @dev Ends the cooldown of the sender and give them back their full voting power. This can be used to signal that
     * the user no longer wishes to exit the system. Note, the cooldown can also be reset, more smoothly, as part of a stake or
     * withdraw transaction.
     **/
    function endCooldown() external {
        require(_balances[_msgSender()].cooldownTimestamp != 0, "No cooldown");

        _exitCooldownPeriod(_msgSender());

        emit CooldownExited(_msgSender());
    }

    /**
     * @dev Enters a cooldown period, after which (and before the unstake window elapses) a user will be able
     * to withdraw part or all of their staked tokens. Note, during this period, a users voting power is significantly reduced.
     * If a user already has a cooldown period, then it will reset to the current block timestamp, so use wisely.
     * @param _units Units of stake to cooldown for
     **/
    function _startCooldown(uint256 _units) internal {
        require(balanceOf(_msgSender()) != 0, "INVALID_BALANCE_ON_COOLDOWN");

        _enterCooldownPeriod(_msgSender(), _units);

        emit Cooldown(_msgSender(), _units);
    }

    /***************************************
                    ADMIN
    ****************************************/

    /**
     * @dev This is a write function allowing the whitelisted recollateralisation module to slash stakers here and take
     * the capital to use to recollateralise any lost value in the system. Trusting that the recollateralisation module has
     * sufficient protections put in place. Note, once this has been executed, the contract is now finished, and undercollateralised,
     * meaning that all users must withdraw, and will only receive a proportionate amount back relative to the colRatio.
     **/
    function emergencyRecollateralisation()
        external
        onlyRecollateralisationModule
        onlyBeforeRecollateralisation
    {
        // 1. Change collateralisation rate
        safetyData.collateralisationRatio = 1e18 - safetyData.slashingPercentage;
        // 2. Take slashing percentage
        uint256 balance = STAKED_TOKEN.balanceOf(address(this));
        STAKED_TOKEN.safeTransfer(
            _recollateraliser(),
            (balance * safetyData.slashingPercentage) / 1e18
        );
        // 3. No functions should work anymore because the colRatio has changed
        emit Recollateralised();
    }

    /**
     * @dev Governance can change the slashing percentage here (initially 0). This is the amount of a stakers capital that is at
     * risk in the recollateralisation process.
     * @param _newRate Rate, where 50% == 5e17
     **/
    function changeSlashingPercentage(uint256 _newRate)
        external
        onlyGovernor
        onlyBeforeRecollateralisation
    {
        require(_newRate <= 5e17, "Cannot exceed 50%");

        safetyData.slashingPercentage = SafeCast.toUint128(_newRate);

        emit SlashRateChanged(_newRate);
    }

    /**
     * @dev Allows governance to whitelist a smart contract to interact with the StakedToken (for example a yield aggregator or simply
     * a Gnosis SAFE or other)
     * @param _wrapper Address of the smart contract to list
     **/
    function whitelistWrapper(address _wrapper) external onlyGovernor {
        whitelistedWrappers[_wrapper] = true;

        emit WrapperWhitelisted(_wrapper);
    }

    /**
     * @dev Allows governance to blacklist a smart contract to end it's interaction with the StakedToken
     * @param _wrapper Address of the smart contract to blacklist
     **/
    function blackListWrapper(address _wrapper) external onlyGovernor {
        whitelistedWrappers[_wrapper] = false;

        emit WrapperBlacklisted(_wrapper);
    }

    /***************************************
            BACKWARDS COMPATIBILITY
    ****************************************/

    /**
     * @dev Allows for backwards compatibility with createLock fn, giving basic args to stake
     * @param _value Units to stake
     **/
    function createLock(
        uint256 _value,
        uint256 /* _unlockTime */
    ) external {
        _transferAndStake(_value, address(0), false);
    }

    /**
     * @dev Allows for backwards compatibility with increaseLockAmount fn by simply staking more
     * @param _value Units to stake
     **/
    function increaseLockAmount(uint256 _value) external {
        require(balanceOf(_msgSender()) != 0, "Nothing to increase");
        _transferAndStake(_value, address(0), false);
    }

    /**
     * @dev Backwards compatibility. Previously a lock would run out and a user would call this. Now, it will take 2 calls
     * to exit in order to leave. The first will initiate the cooldown period, and the second will execute a full withdrawal.
     **/
    function exit() external virtual {
        // Since there is no immediate exit here, this can be called twice
        // If there is no cooldown, or the cooldown has passed the unstake window, enter cooldown
        uint128 ts = _balances[_msgSender()].cooldownTimestamp;
        if (ts == 0 || block.timestamp > ts + COOLDOWN_SECONDS + UNSTAKE_WINDOW) {
            (uint256 raw, uint256 cooldownUnits) = rawBalanceOf(_msgSender());
            _startCooldown(raw + cooldownUnits);
        }
        // Else withdraw all available
        else {
            _withdraw(_balances[_msgSender()].cooldownUnits, _msgSender(), true, false);
        }
    }

    /***************************************
                    GETTERS
    ****************************************/

    /**
     * @dev fee = sqrt(300/x)-2.5, where x = weeks since user has staked
     * @param _weightedTimestamp The users weightedTimestamp
     * @return _feeRate where 1% == 1e16
     */
    function calcRedemptionFeeRate(uint32 _weightedTimestamp)
        public
        view
        returns (uint256 _feeRate)
    {
        uint256 weeksStaked = ((block.timestamp - _weightedTimestamp) * 1e18) / ONE_WEEK;
        if (weeksStaked > 3e18) {
            // e.g. weeks = 1  = sqrt(300e18) = 17320508075
            // e.g. weeks = 10 = sqrt(30e18) =   5477225575
            // e.g. weeks = 26 = sqrt(11.5) =    3391164991
            _feeRate = Root.sqrt(300e36 / weeksStaked) * 1e7;
            // e.g. weeks = 1  = 173e15 - 25e15 = 148e15 or 14.8%
            // e.g. weeks = 10 =  55e15 - 25e15 = 30e15 or 3%
            // e.g. weeks = 26 =  34e15 - 25e15 = 9e15 or 0.9%
            _feeRate = _feeRate < 25e15 ? 0 : _feeRate - 25e15;
        } else {
            _feeRate = 75e15;
        }
    }

    uint256[48] private __gap;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../../../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;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @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, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 5 of 32 : IStakedToken.sol
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../deps/GamifiedTokenStructs.sol";

interface IStakedToken {
    // GETTERS
    function COOLDOWN_SECONDS() external view returns (uint256);

    function UNSTAKE_WINDOW() external view returns (uint256);

    function STAKED_TOKEN() external view returns (IERC20);

    function getRewardToken() external view returns (address);

    function pendingAdditionalReward() external view returns (uint256);

    function whitelistedWrappers(address) external view returns (bool);

    function balanceData(address _account) external view returns (Balance memory);

    function balanceOf(address _account) external view returns (uint256);

    function rawBalanceOf(address _account) external view returns (uint256, uint256);

    function calcRedemptionFeeRate(uint32 _weightedTimestamp)
        external
        view
        returns (uint256 _feeRate);

    function safetyData()
        external
        view
        returns (uint128 collateralisationRatio, uint128 slashingPercentage);

    function delegates(address account) external view returns (address);

    function getPastTotalSupply(uint256 blockNumber) external view returns (uint256);

    function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);

    function getVotes(address account) external view returns (uint256);

    // HOOKS/PERMISSIONED
    function applyQuestMultiplier(address _account, uint8 _newMultiplier) external;

    // ADMIN
    function whitelistWrapper(address _wrapper) external;

    function blackListWrapper(address _wrapper) external;

    function changeSlashingPercentage(uint256 _newRate) external;

    function emergencyRecollateralisation() external;

    function setGovernanceHook(address _newHook) external;

    // USER
    function stake(uint256 _amount) external;

    function stake(uint256 _amount, address _delegatee) external;

    function stake(uint256 _amount, bool _exitCooldown) external;

    function withdraw(
        uint256 _amount,
        address _recipient,
        bool _amountIncludesFee,
        bool _exitCooldown
    ) external;

    function delegate(address delegatee) external;

    function startCooldown(uint256 _units) external;

    function endCooldown() external;

    function reviewTimestamp(address _account) external;

    function claimReward() external;

    function claimReward(address _to) external;

    // Backwards compatibility
    function createLock(uint256 _value, uint256) external;

    function exit() external;

    function increaseLockAmount(uint256 _value) external;

    function increaseLockLength(uint256) external;
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import { GamifiedToken } from "./GamifiedToken.sol";
import { IGovernanceHook } from "./interfaces/IGovernanceHook.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

/**
 * @title GamifiedVotingToken
 * @notice GamifiedToken is a checkpointed Voting Token derived from OpenZeppelin "ERC20VotesUpgradable"
 * @author mStable
 * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/f9cdbd7d82d45a614ee98a5dc8c08fb4347d0fea/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol
 * Changes:
 *   - Inherits custom GamifiedToken rather than basic ERC20
 *     - Removal of `Permit` functionality & `delegatebySig`
 *   - Override `delegates` fn as described in their docs
 *   - Prettier formatting
 *   - Addition of `totalSupply` method to get latest totalSupply
 *   - Move totalSupply checkpoints to `afterTokenTransfer`
 *   - Add _governanceHook hook
 */
abstract contract GamifiedVotingToken is Initializable, GamifiedToken {
    struct Checkpoint {
        uint32 fromBlock;
        uint224 votes;
    }

    mapping(address => address) private _delegates;
    mapping(address => Checkpoint[]) private _checkpoints;
    Checkpoint[] private _totalSupplyCheckpoints;

    IGovernanceHook private _governanceHook;

    event GovernanceHookChanged(address indexed hook);

    /**
     * @dev Emitted when an account changes their delegate.
     */
    event DelegateChanged(
        address indexed delegator,
        address indexed fromDelegate,
        address indexed toDelegate
    );

    /**
     * @dev Emitted when a token transfer or delegate change results in changes to an account's voting power.
     */
    event DelegateVotesChanged(
        address indexed delegate,
        uint256 previousBalance,
        uint256 newBalance
    );

    constructor(
        address _nexus,
        address _rewardsToken,
        address _questManager,
        bool _hasPriceCoeff
    ) GamifiedToken(_nexus, _rewardsToken, _questManager, _hasPriceCoeff) {}

    function __GamifiedVotingToken_init() internal initializer {}

    /**
     * @dev
     */
    function setGovernanceHook(address _newHook) external onlyGovernor {
        _governanceHook = IGovernanceHook(_newHook);

        emit GovernanceHookChanged(_newHook);
    }

    /**
     * @dev Get the `pos`-th checkpoint for `account`.
     */
    function checkpoints(address account, uint32 pos)
        public
        view
        virtual
        returns (Checkpoint memory)
    {
        return _checkpoints[account][pos];
    }

    /**
     * @dev Get number of checkpoints for `account`.
     */
    function numCheckpoints(address account) public view virtual returns (uint32) {
        return SafeCast.toUint32(_checkpoints[account].length);
    }

    /**
     * @dev Get the address `account` is currently delegating to.
     */
    function delegates(address account) public view virtual returns (address) {
        // Override as per https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol#L23
        // return _delegates[account];
        address delegatee = _delegates[account];
        return delegatee == address(0) ? account : delegatee;
    }

    /**
     * @dev Gets the current votes balance for `account`
     */
    function getVotes(address account) public view returns (uint256) {
        uint256 pos = _checkpoints[account].length;
        return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
    }

    /**
     * @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
     *
     * Requirements:
     *
     * - `blockNumber` must have been already mined
     */
    function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        return _checkpointsLookup(_checkpoints[account], blockNumber);
    }

    /**
     * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
     * It is but NOT the sum of all the delegated votes!
     *
     * Requirements:
     *
     * - `blockNumber` must have been already mined
     */
    function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
    }

    /**
     * @dev Total sum of all scaled balances
     */
    function totalSupply() public view override returns (uint256) {
        uint256 len = _totalSupplyCheckpoints.length;
        if (len == 0) return 0;
        return _totalSupplyCheckpoints[len - 1].votes;
    }

    /**
     * @dev Lookup a value in a list of (sorted) checkpoints.
     */
    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber)
        private
        view
        returns (uint256)
    {
        // We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
        //
        // During the loop, the index of the wanted checkpoint remains in the range [low, high).
        // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant.
        // - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
        // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
        // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
        // out of bounds (in which case we're looking too far in the past and the result is 0).
        // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
        // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
        // the same.
        uint256 high = ckpts.length;
        uint256 low = 0;
        while (low < high) {
            uint256 mid = MathUpgradeable.average(low, high);
            if (ckpts[mid].fromBlock > blockNumber) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        return high == 0 ? 0 : ckpts[high - 1].votes;
    }

    /**
     * @dev Delegate votes from the sender to `delegatee`.
     */
    function delegate(address delegatee) public virtual {
        return _delegate(_msgSender(), delegatee);
    }

    /**
     * @dev Move voting power when tokens are transferred.
     *
     * Emits a {DelegateVotesChanged} event.
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        super._afterTokenTransfer(from, to, amount);

        // mint or burn, update total supply
        if (from == address(0) || to == address(0)) {
            _writeCheckpoint(_totalSupplyCheckpoints, to == address(0) ? _subtract : _add, amount);
        }

        _moveVotingPower(delegates(from), delegates(to), amount);
    }

    /**
     * @dev Change delegation for `delegator` to `delegatee`.
     *
     * Emits events {DelegateChanged} and {DelegateVotesChanged}.
     */
    function _delegate(address delegator, address delegatee) internal virtual {
        address currentDelegate = delegates(delegator);
        uint256 delegatorBalance = balanceOf(delegator);
        _delegates[delegator] = delegatee;

        emit DelegateChanged(delegator, currentDelegate, delegatee);

        _moveVotingPower(currentDelegate, delegatee, delegatorBalance);
    }

    function _moveVotingPower(
        address src,
        address dst,
        uint256 amount
    ) private {
        if (src != dst && amount > 0) {
            if (src != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
                    _checkpoints[src],
                    _subtract,
                    amount
                );
                emit DelegateVotesChanged(src, oldWeight, newWeight);
            }

            if (dst != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
                    _checkpoints[dst],
                    _add,
                    amount
                );
                emit DelegateVotesChanged(dst, oldWeight, newWeight);
            }

            if (address(_governanceHook) != address(0)) {
                _governanceHook.moveVotingPowerHook(src, dst, amount);
            }
        }
    }

    function _writeCheckpoint(
        Checkpoint[] storage ckpts,
        function(uint256, uint256) view returns (uint256) op,
        uint256 delta
    ) private returns (uint256 oldWeight, uint256 newWeight) {
        uint256 pos = ckpts.length;
        oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
        newWeight = op(oldWeight, delta);

        if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
            ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
        } else {
            ckpts.push(
                Checkpoint({
                    fromBlock: SafeCast.toUint32(block.number),
                    votes: SafeCast.toUint224(newWeight)
                })
            );
        }
    }

    function _add(uint256 a, uint256 b) private pure returns (uint256) {
        return a + b;
    }

    function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
        return a - b;
    }

    uint256[46] private __gap;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {
    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

library Root {
    /**
     * @dev Returns the square root of a given number
     * @param x Input
     * @return y Square root of Input
     */
    function sqrt(uint256 x) internal pure returns (uint256 y) {
        if (x == 0) return 0;
        else {
            uint256 xx = x;
            uint256 r = 1;
            if (xx >= 0x100000000000000000000000000000000) {
                xx >>= 128;
                r <<= 64;
            }
            if (xx >= 0x10000000000000000) {
                xx >>= 64;
                r <<= 32;
            }
            if (xx >= 0x100000000) {
                xx >>= 32;
                r <<= 16;
            }
            if (xx >= 0x10000) {
                xx >>= 16;
                r <<= 8;
            }
            if (xx >= 0x100) {
                xx >>= 8;
                r <<= 4;
            }
            if (xx >= 0x10) {
                xx >>= 4;
                r <<= 2;
            }
            if (xx >= 0x8) {
                r <<= 1;
            }
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1;
            r = (r + x / r) >> 1; // Seven iterations should be enough
            uint256 r1 = x / r;
            return uint256(r < r1 ? r : r1);
        }
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * _Since v2.5.0:_ this module is now much more gas efficient, given net gas
 * metering changes introduced in the Istanbul hardfork.
 */
contract InitializableReentrancyGuard {
    bool private _notEntered;

    function _initializeReentrancyGuard() internal {
        // Storing an initial non-zero value makes deployment a bit more
        // expensive, but in exchange the refund on every call to nonReentrant
        // will be lower in amount. Since refunds are capped to a percetange of
        // the total transaction's gas, it is best to keep them low in cases
        // like this one, to increase the likelihood of the full refund coming
        // into effect.
        _notEntered = true;
    }

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

        // Any calls to nonReentrant after this point will fail
        _notEntered = false;

        _;

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

File 10 of 32 : GamifiedTokenStructs.sol
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

struct Balance {
    /// units of staking token that has been deposited and consequently wrapped
    uint88 raw;
    /// (block.timestamp - weightedTimestamp) represents the seconds a user has had their full raw balance wrapped.
    /// If they deposit or withdraw, the weightedTimestamp is dragged towards block.timestamp proportionately
    uint32 weightedTimestamp;
    /// multiplier awarded for staking for a long time
    uint8 timeMultiplier;
    /// multiplier duplicated from QuestManager
    uint8 questMultiplier;
    /// Time at which the relative cooldown began
    uint32 cooldownTimestamp;
    /// Units up for cooldown
    uint88 cooldownUnits;
}

struct QuestBalance {
    /// last timestamp at which the user made a write action to this contract
    uint32 lastAction;
    /// permanent multiplier applied to an account, awarded for PERMANENT QuestTypes
    uint8 permMultiplier;
    /// multiplier that decays after each "season" (~9 months) by 75%, to avoid multipliers getting out of control
    uint8 seasonMultiplier;
}

/// @notice Quests can either give permanent rewards or only for the season
enum QuestType {
    PERMANENT,
    SEASONAL
}

/// @notice Quests can be turned off by the questMaster. All those who already completed remain
enum QuestStatus {
    ACTIVE,
    EXPIRED
}
struct Quest {
    /// Type of quest rewards
    QuestType model;
    /// Multiplier, from 1 == 1.01x to 100 == 2.00x
    uint8 multiplier;
    /// Is the current quest valid?
    QuestStatus status;
    /// Expiry date in seconds for the quest
    uint32 expiry;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUpgradeable {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute.
        return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2);
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a / b + (a % b == 0 ? 0 : 1);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSAUpgradeable {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return recover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return recover(hash, r, vs);
        } else {
            revert("ECDSA: invalid signature length");
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        bytes32 s;
        uint8 v;
        assembly {
            s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
            v := add(shr(255, vs), 27)
        }
        return recover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`, `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        require(
            uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
            "ECDSA: invalid signature 's' value"
        );
        require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { SafeCastExtended } from "../../shared/SafeCastExtended.sol";
import { ILockedERC20 } from "./interfaces/ILockedERC20.sol";
import { HeadlessStakingRewards } from "../../rewards/staking/HeadlessStakingRewards.sol";
import { QuestManager } from "./QuestManager.sol";
import "./deps/GamifiedTokenStructs.sol";

/**
 * @title GamifiedToken
 * @notice GamifiedToken is a non-transferrable ERC20 token that has both a raw balance and a scaled balance.
 * Scaled balance is determined by quests a user completes, and the length of time they keep the raw balance wrapped.
 * QuestMasters can add new quests for stakers to complete, for which they are rewarded with permanent or seasonal multipliers.
 * @author mStable
 * @dev Originally forked from openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
 * Changes:
 *   - Removed the transfer, transferFrom, approve fns to make non-transferrable
 *   - Removed `_allowances` storage
 *   - Removed `_beforeTokenTransfer` hook
 *   - Replaced standard uint256 balance with a single struct containing all data from which the scaledBalance can be derived
 *   - Quest system implemented that tracks a users quest status and applies multipliers for them
 **/
abstract contract GamifiedToken is
    ILockedERC20,
    Initializable,
    ContextUpgradeable,
    HeadlessStakingRewards
{
    /// @notice name of this token (ERC20)
    bytes32 private _name;
    /// @notice symbol of this token (ERC20)
    bytes32 private _symbol;
    /// @notice number of decimals of this token (ERC20)
    uint8 public constant override decimals = 18;

    /// @notice User balance structs containing all data needed to scale balance
    mapping(address => Balance) internal _balances;
    /// @notice Most recent price coefficients per user
    mapping(address => uint256) internal _userPriceCoeff;
    /// @notice Quest Manager
    QuestManager public immutable questManager;
    /// @notice Has variable price
    bool public immutable hasPriceCoeff;

    /***************************************
                    INIT
    ****************************************/

    /**
     * @param _nexus System nexus
     * @param _rewardsToken Token that is being distributed as a reward. eg MTA
     * @param _questManager Centralised manager of quests
     * @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
     */
    constructor(
        address _nexus,
        address _rewardsToken,
        address _questManager,
        bool _hasPriceCoeff
    ) HeadlessStakingRewards(_nexus, _rewardsToken) {
        questManager = QuestManager(_questManager);
        hasPriceCoeff = _hasPriceCoeff;
    }

    /**
     * @param _nameArg Token name
     * @param _symbolArg Token symbol
     * @param _rewardsDistributorArg mStable Rewards Distributor
     */
    function __GamifiedToken_init(
        bytes32 _nameArg,
        bytes32 _symbolArg,
        address _rewardsDistributorArg
    ) internal initializer {
        __Context_init_unchained();
        _name = _nameArg;
        _symbol = _symbolArg;
        HeadlessStakingRewards._initialize(_rewardsDistributorArg);
    }

    /**
     * @dev Checks that _msgSender is the quest Manager
     */
    modifier onlyQuestManager() {
        require(_msgSender() == address(questManager), "Not verified");
        _;
    }

    /***************************************
                    VIEWS
    ****************************************/

    function name() public view override returns (string memory) {
        return bytes32ToString(_name);
    }

    function symbol() public view override returns (string memory) {
        return bytes32ToString(_symbol);
    }

    /**
     * @dev Total sum of all scaled balances
     * In this instance, leave to the child token.
     */
    function totalSupply()
        public
        view
        virtual
        override(HeadlessStakingRewards, ILockedERC20)
        returns (uint256);

    /**
     * @dev Simply gets scaled balance
     * @return scaled balance for user
     */
    function balanceOf(address _account)
        public
        view
        virtual
        override(HeadlessStakingRewards, ILockedERC20)
        returns (uint256)
    {
        return _getBalance(_account, _balances[_account]);
    }

    /**
     * @dev Simply gets raw balance
     * @return raw balance for user
     */
    function rawBalanceOf(address _account) public view returns (uint256, uint256) {
        return (_balances[_account].raw, _balances[_account].cooldownUnits);
    }

    /**
     * @dev Scales the balance of a given user by applying multipliers
     */
    function _getBalance(address _account, Balance memory _balance)
        internal
        view
        returns (uint256 balance)
    {
        // e.g. raw = 1000, questMultiplier = 40, timeMultiplier = 30. Cooldown of 60%
        // e.g. 1000 * (100 + 40) / 100 = 1400
        balance = (_balance.raw * (100 + _balance.questMultiplier)) / 100;
        // e.g. 1400 * (100 + 30) / 100 = 1820
        balance = (balance * (100 + _balance.timeMultiplier)) / 100;

        if (hasPriceCoeff) {
            // e.g. 1820 * 16000 / 10000 = 2912
            balance = (balance * _userPriceCoeff[_account]) / 10000;
        }
    }

    /**
     * @notice Raw staked balance without any multipliers
     */
    function balanceData(address _account) external view returns (Balance memory) {
        return _balances[_account];
    }

    /**
     * @notice Raw staked balance without any multipliers
     */
    function userPriceCoeff(address _account) external view returns (uint256) {
        return _userPriceCoeff[_account];
    }

    /***************************************
                    QUESTS
    ****************************************/

    /**
     * @dev Called by anyone to poke the timestamp of a given account. This allows users to
     * effectively 'claim' any new timeMultiplier, but will revert if there is no change there.
     */
    function reviewTimestamp(address _account) external {
        _reviewWeightedTimestamp(_account);
    }

    /**
     * @dev Adds the multiplier awarded from quest completion to a users data, taking the opportunity
     * to check time multipliers etc.
     * @param _account Address of user that should be updated
     * @param _newMultiplier New Quest Multiplier
     */
    function applyQuestMultiplier(address _account, uint8 _newMultiplier)
        external
        onlyQuestManager
    {
        require(_account != address(0), "Invalid address");

        // 1. Get current balance & update questMultiplier, only if user has a balance
        Balance memory oldBalance = _balances[_account];
        uint256 oldScaledBalance = _getBalance(_account, oldBalance);
        if (oldScaledBalance > 0) {
            _applyQuestMultiplier(_account, oldBalance, oldScaledBalance, _newMultiplier);
        }
    }

    /**
     * @dev Gets the multiplier awarded for a given weightedTimestamp
     * @param _ts WeightedTimestamp of a user
     * @return timeMultiplier Ranging from 20 (0.2x) to 60 (0.6x)
     */
    function _timeMultiplier(uint32 _ts) internal view returns (uint8 timeMultiplier) {
        // If the user has no ts yet, they are not in the system
        if (_ts == 0) return 0;

        uint256 hodlLength = block.timestamp - _ts;
        if (hodlLength < 13 weeks) {
            // 0-3 months = 1x
            return 0;
        } else if (hodlLength < 26 weeks) {
            // 3 months = 1.2x
            return 20;
        } else if (hodlLength < 52 weeks) {
            // 6 months = 1.3x
            return 30;
        } else if (hodlLength < 78 weeks) {
            // 12 months = 1.4x
            return 40;
        } else if (hodlLength < 104 weeks) {
            // 18 months = 1.5x
            return 50;
        } else {
            // > 24 months = 1.6x
            return 60;
        }
    }

    function _getPriceCoeff() internal virtual returns (uint256) {
        return 10000;
    }

    /***************************************
                BALANCE CHANGES
    ****************************************/

    /**
     * @dev Adds the multiplier awarded from quest completion to a users data, taking the opportunity
     * to check time multiplier.
     * @param _account Address of user that should be updated
     * @param _newMultiplier New Quest Multiplier
     */
    function _applyQuestMultiplier(
        address _account,
        Balance memory _oldBalance,
        uint256 _oldScaledBalance,
        uint8 _newMultiplier
    ) private updateReward(_account) {
        // 1. Set the questMultiplier
        _balances[_account].questMultiplier = _newMultiplier;

        // 2. Take the opportunity to set weighted timestamp, if it changes
        _balances[_account].timeMultiplier = _timeMultiplier(_oldBalance.weightedTimestamp);

        // 3. Update scaled balance
        _settleScaledBalance(_account, _oldScaledBalance);
    }

    /**
     * @dev Entering a cooldown period means a user wishes to withdraw. With this in mind, their balance
     * should be reduced until they have shown more commitment to the system
     * @param _account Address of user that should be cooled
     * @param _units Units to cooldown for
     */
    function _enterCooldownPeriod(address _account, uint256 _units)
        internal
        updateReward(_account)
    {
        require(_account != address(0), "Invalid address");

        // 1. Get current balance
        (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
        uint88 totalUnits = oldBalance.raw + oldBalance.cooldownUnits;
        require(_units > 0 && _units <= totalUnits, "Must choose between 0 and 100%");

        // 2. Set weighted timestamp and enter cooldown
        _balances[_account].timeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
        // e.g. 1e18 / 1e16 = 100, 2e16 / 1e16 = 2, 1e15/1e16 = 0
        _balances[_account].raw = totalUnits - SafeCastExtended.toUint88(_units);

        // 3. Set cooldown data
        _balances[_account].cooldownTimestamp = SafeCastExtended.toUint32(block.timestamp);
        _balances[_account].cooldownUnits = SafeCastExtended.toUint88(_units);

        // 4. Update scaled balance
        _settleScaledBalance(_account, oldScaledBalance);
    }

    /**
     * @dev Exiting the cooldown period explicitly resets the users cooldown window and their balance
     * @param _account Address of user that should be exited
     */
    function _exitCooldownPeriod(address _account) internal updateReward(_account) {
        require(_account != address(0), "Invalid address");

        // 1. Get current balance
        (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);

        // 2. Set weighted timestamp and exit cooldown
        _balances[_account].timeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
        _balances[_account].raw += oldBalance.cooldownUnits;

        // 3. Set cooldown data
        _balances[_account].cooldownTimestamp = 0;
        _balances[_account].cooldownUnits = 0;

        // 4. Update scaled balance
        _settleScaledBalance(_account, oldScaledBalance);
    }

    /**
     * @dev Pokes the weightedTimestamp of a given user and checks if it entitles them
     * to a better timeMultiplier. If not, it simply reverts as there is nothing to update.
     * @param _account Address of user that should be updated
     */
    function _reviewWeightedTimestamp(address _account) internal updateReward(_account) {
        require(_account != address(0), "Invalid address");

        // 1. Get current balance
        (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);

        // 2. Set weighted timestamp, if it changes
        uint8 newTimeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
        require(newTimeMultiplier != oldBalance.timeMultiplier, "Nothing worth poking here");
        _balances[_account].timeMultiplier = newTimeMultiplier;

        // 3. Update scaled balance
        _settleScaledBalance(_account, oldScaledBalance);
    }

    /**
     * @dev Called to mint from raw tokens. Adds raw to a users balance, and then propagates the scaledBalance.
     * Importantly, when a user stakes more, their weightedTimestamp is reduced proportionate to their stake.
     * @param _account Address of user to credit
     * @param _rawAmount Raw amount of tokens staked
     * @param _exitCooldown Should we end any cooldown?
     */
    function _mintRaw(
        address _account,
        uint256 _rawAmount,
        bool _exitCooldown
    ) internal updateReward(_account) {
        require(_account != address(0), "ERC20: mint to the zero address");

        // 1. Get and update current balance
        (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
        uint88 totalRaw = oldBalance.raw + oldBalance.cooldownUnits;
        _balances[_account].raw = oldBalance.raw + SafeCastExtended.toUint88(_rawAmount);

        // 2. Exit cooldown if necessary
        if (_exitCooldown) {
            _balances[_account].raw += oldBalance.cooldownUnits;
            _balances[_account].cooldownTimestamp = 0;
            _balances[_account].cooldownUnits = 0;
        }

        // 3. Set weighted timestamp
        //  i) For new _account, set up weighted timestamp
        if (oldBalance.weightedTimestamp == 0) {
            _balances[_account].weightedTimestamp = SafeCastExtended.toUint32(block.timestamp);
            _mintScaled(_account, _getBalance(_account, _balances[_account]));
            return;
        }
        //  ii) For previous minters, recalculate time held
        //      Calc new weighted timestamp
        uint256 oldWeightedSecondsHeld = (block.timestamp - oldBalance.weightedTimestamp) *
            totalRaw;
        uint256 newSecondsHeld = oldWeightedSecondsHeld / (totalRaw + (_rawAmount / 2));
        uint32 newWeightedTs = SafeCastExtended.toUint32(block.timestamp - newSecondsHeld);
        _balances[_account].weightedTimestamp = newWeightedTs;

        uint8 timeMultiplier = _timeMultiplier(newWeightedTs);
        _balances[_account].timeMultiplier = timeMultiplier;

        // 3. Update scaled balance
        _settleScaledBalance(_account, oldScaledBalance);
    }

    /**
     * @dev Called to burn a given amount of raw tokens.
     * @param _account Address of user
     * @param _rawAmount Raw amount of tokens to remove
     * @param _exitCooldown Exit the cooldown?
     * @param _finalise Has recollateralisation happened? If so, everything is cooled down
     */
    function _burnRaw(
        address _account,
        uint256 _rawAmount,
        bool _exitCooldown,
        bool _finalise
    ) internal updateReward(_account) {
        require(_account != address(0), "ERC20: burn from zero address");

        // 1. Get and update current balance
        (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
        uint256 totalRaw = oldBalance.raw + oldBalance.cooldownUnits;
        // 1.1. If _finalise, move everything to cooldown
        if (_finalise) {
            _balances[_account].raw = 0;
            _balances[_account].cooldownUnits = SafeCastExtended.toUint88(totalRaw);
            oldBalance.cooldownUnits = SafeCastExtended.toUint88(totalRaw);
        }
        // 1.2. Update
        require(oldBalance.cooldownUnits >= _rawAmount, "ERC20: burn amount > balance");
        unchecked {
            _balances[_account].cooldownUnits -= SafeCastExtended.toUint88(_rawAmount);
        }

        // 2. If we are exiting cooldown, reset the balance
        if (_exitCooldown) {
            _balances[_account].raw += _balances[_account].cooldownUnits;
            _balances[_account].cooldownTimestamp = 0;
            _balances[_account].cooldownUnits = 0;
        }

        // 3. Set back scaled time
        // e.g. stake 10 for 100 seconds, withdraw 5.
        //      secondsHeld = (100 - 0) * (10 - 0.625) = 937.5
        uint256 secondsHeld = (block.timestamp - oldBalance.weightedTimestamp) *
            (totalRaw - (_rawAmount / 8));
        //      newWeightedTs = 937.5 / 100 = 93.75
        uint256 newSecondsHeld = secondsHeld / totalRaw;
        uint32 newWeightedTs = SafeCastExtended.toUint32(block.timestamp - newSecondsHeld);
        _balances[_account].weightedTimestamp = newWeightedTs;

        uint8 timeMultiplier = _timeMultiplier(newWeightedTs);
        _balances[_account].timeMultiplier = timeMultiplier;

        // 4. Update scaled balance
        _settleScaledBalance(_account, oldScaledBalance);
    }

    /***************************************
                    PRIVATE
    updateReward should already be called by now
    ****************************************/

    /**
     * @dev Fetches the balance of a given user, scales it, and also takes the opportunity
     * to check if the season has just finished between now and their last action.
     * @param _account Address of user to fetch
     * @return oldBalance struct containing all balance information
     * @return oldScaledBalance scaled balance after applying multipliers
     */
    function _prepareOldBalance(address _account)
        private
        returns (Balance memory oldBalance, uint256 oldScaledBalance)
    {
        // Get the old balance
        oldBalance = _balances[_account];
        oldScaledBalance = _getBalance(_account, oldBalance);
        // Take the opportunity to check for season finish
        _balances[_account].questMultiplier = questManager.checkForSeasonFinish(_account);
        if (hasPriceCoeff) {
            _userPriceCoeff[_account] = SafeCastExtended.toUint16(_getPriceCoeff());
        }
    }

    /**
     * @dev Settles the scaled balance of a given account. The reason this is done here, is because
     * in each of the write functions above, there is the chance that a users balance can go down,
     * requiring to burn sacled tokens. This could happen at the end of a season when multipliers are slashed.
     * This is called after updating all multipliers etc.
     * @param _account Address of user that should be updated
     * @param _oldScaledBalance Previous scaled balance of the user
     */
    function _settleScaledBalance(address _account, uint256 _oldScaledBalance) private {
        uint256 newScaledBalance = _getBalance(_account, _balances[_account]);
        if (newScaledBalance > _oldScaledBalance) {
            _mintScaled(_account, newScaledBalance - _oldScaledBalance);
        }
        // This can happen if the user moves back a time class, but is unlikely to result in a negative mint
        else {
            _burnScaled(_account, _oldScaledBalance - newScaledBalance);
        }
    }

    /**
     * @dev Propagates the minting of the tokens downwards.
     * @param _account Address of user that has minted
     * @param _amount Amount of scaled tokens minted
     */
    function _mintScaled(address _account, uint256 _amount) private {
        emit Transfer(address(0), _account, _amount);

        _afterTokenTransfer(address(0), _account, _amount);
    }

    /**
     * @dev Propagates the burning of the tokens downwards.
     * @param _account Address of user that has burned
     * @param _amount Amount of scaled tokens burned
     */
    function _burnScaled(address _account, uint256 _amount) private {
        emit Transfer(_account, address(0), _amount);

        _afterTokenTransfer(_account, address(0), _amount);
    }

    /***************************************
                    HOOKS
    ****************************************/

    /**
     * @dev Triggered after a user claims rewards from the HeadlessStakingRewards. Used
     * to check for season finish. If it has not, then do not spend gas updating the other vars.
     * @param _account Address of user that has burned
     */
    function _claimRewardHook(address _account) internal override {
        uint8 newMultiplier = questManager.checkForSeasonFinish(_account);
        bool priceCoeffChanged = hasPriceCoeff
            ? _getPriceCoeff() != _userPriceCoeff[_account]
            : false;
        if (newMultiplier != _balances[_account].questMultiplier || priceCoeffChanged) {
            // 1. Get current balance & trigger season finish
            uint256 oldScaledBalance = _getBalance(_account, _balances[_account]);
            _balances[_account].questMultiplier = newMultiplier;
            if (priceCoeffChanged) {
                _userPriceCoeff[_account] = SafeCastExtended.toUint16(_getPriceCoeff());
            }
            // 3. Update scaled balance
            _settleScaledBalance(_account, oldScaledBalance);
        }
    }

    /**
     * @dev Unchanged from OpenZeppelin. Used in child contracts to react to any balance changes.
     */
    function _afterTokenTransfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal virtual {}

    /***************************************
                    Utils
    ****************************************/

    function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
        uint256 i = 0;
        while (i < 32 && _bytes32[i] != 0) {
            i++;
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return string(bytesArray);
    }

    uint256[46] private __gap;
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

interface IGovernanceHook {
    function moveVotingPowerHook(
        address from,
        address to,
        uint256 amount
    ) external;
}

File 15 of 32 : Initializable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @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 a proxied contract can't have 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.
 *
 * 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.
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        require(_initializing || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "../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 initializer {
        __Context_init_unchained();
    }

    function __Context_init_unchained() internal initializer {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
    uint256[50] private __gap;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCastExtended {
    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(
            value >= type(int128).min && value <= type(int128).max,
            "SafeCast: value doesn't fit in 128 bits"
        );
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(
            value >= type(int64).min && value <= type(int64).max,
            "SafeCast: value doesn't fit in 64 bits"
        );
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(
            value >= type(int32).min && value <= type(int32).max,
            "SafeCast: value doesn't fit in 32 bits"
        );
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(
            value >= type(int16).min && value <= type(int16).max,
            "SafeCast: value doesn't fit in 16 bits"
        );
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(
            value >= type(int8).min && value <= type(int8).max,
            "SafeCast: value doesn't fit in 8 bits"
        );
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface ILockedERC20 {
    /**
     * @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);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev 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);
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

// Internal
import { InitializableRewardsDistributionRecipient } from "../InitializableRewardsDistributionRecipient.sol";
import { StableMath } from "../../shared/StableMath.sol";
import { PlatformTokenVendorFactory } from "./PlatformTokenVendorFactory.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

// Libs
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

/**
 * @title  HeadlessStakingRewards
 * @author mStable
 * @notice Rewards stakers of a given LP token with REWARDS_TOKEN, on a pro-rata basis
 * @dev Forked from `StakingRewards.sol`
 *      Changes:
 *          - `pendingAdditionalReward` added to support accumulation of any extra staking token
 *          - Removal of `StakingTokenWrapper`, instead, deposits and withdrawals are made in child contract,
 *            and balances are read from there through the abstract functions
 */
abstract contract HeadlessStakingRewards is
    ContextUpgradeable,
    InitializableRewardsDistributionRecipient
{
    using SafeERC20 for IERC20;
    using StableMath for uint256;

    /// @notice token the rewards are distributed in. eg MTA
    IERC20 public immutable REWARDS_TOKEN;

    /// @notice length of each staking period in seconds. 7 days = 604,800; 3 months = 7,862,400
    uint256 public constant DURATION = 1 weeks;

    /// @notice contract that holds the platform tokens
    address public rewardTokenVendor;

    struct Data {
        /// Timestamp for current period finish
        uint32 periodFinish;
        /// Last time any user took action
        uint32 lastUpdateTime;
        /// RewardRate for the rest of the period
        uint96 rewardRate;
        /// Ever increasing rewardPerToken rate, based on % of total supply
        uint96 rewardPerTokenStored;
    }

    struct UserData {
        uint128 rewardPerTokenPaid;
        uint128 rewards;
    }

    Data public globalData;
    mapping(address => UserData) public userData;
    uint256 public pendingAdditionalReward;

    event RewardAdded(uint256 reward);
    event RewardPaid(address indexed user, address indexed to, uint256 reward);

    /**
     * @param _nexus mStable system Nexus address
     * @param _rewardsToken first token that is being distributed as a reward. eg MTA
     */
    constructor(address _nexus, address _rewardsToken)
        InitializableRewardsDistributionRecipient(_nexus)
    {
        REWARDS_TOKEN = IERC20(_rewardsToken);
    }

    /**
     * @dev Initialization function for upgradable proxy contract.
     *      This function should be called via Proxy just after contract deployment.
     *      To avoid variable shadowing appended `Arg` after arguments name.
     * @param _rewardsDistributorArg mStable Reward Distributor contract address
     */
    function _initialize(address _rewardsDistributorArg) internal virtual override {
        InitializableRewardsDistributionRecipient._initialize(_rewardsDistributorArg);
        rewardTokenVendor = PlatformTokenVendorFactory.create(REWARDS_TOKEN);
    }

    /** @dev Updates the reward for a given address, before executing function */
    modifier updateReward(address _account) {
        _updateReward(_account);
        _;
    }

    function _updateReward(address _account) internal {
        // Setting of global vars
        (uint256 newRewardPerToken, uint256 lastApplicableTime) = _rewardPerToken();
        // If statement protects against loss in initialisation case
        if (newRewardPerToken > 0) {
            globalData.rewardPerTokenStored = SafeCast.toUint96(newRewardPerToken);
            globalData.lastUpdateTime = SafeCast.toUint32(lastApplicableTime);
            // Setting of personal vars based on new globals
            if (_account != address(0)) {
                userData[_account] = UserData({
                    rewardPerTokenPaid: SafeCast.toUint128(newRewardPerToken),
                    rewards: SafeCast.toUint128(_earned(_account, newRewardPerToken))
                });
            }
        }
    }

    /***************************************
                    ACTIONS
    ****************************************/

    /**
     * @dev Claims outstanding rewards for the sender.
     * First updates outstanding reward allocation and then transfers.
     */
    function claimReward(address _to) public {
        _claimReward(_to);
    }

    /**
     * @dev Claims outstanding rewards for the sender.
     * First updates outstanding reward allocation and then transfers.
     */
    function claimReward() public {
        _claimReward(_msgSender());
    }

    function _claimReward(address _to) internal updateReward(_msgSender()) {
        uint128 reward = userData[_msgSender()].rewards;
        if (reward > 0) {
            userData[_msgSender()].rewards = 0;
            REWARDS_TOKEN.safeTransferFrom(rewardTokenVendor, _to, reward);
            emit RewardPaid(_msgSender(), _to, reward);
        }
        _claimRewardHook(_msgSender());
    }

    /***************************************
                    GETTERS
    ****************************************/

    /**
     * @dev Gets the RewardsToken
     */
    function getRewardToken() external view override returns (IERC20) {
        return REWARDS_TOKEN;
    }

    /**
     * @dev Gets the last applicable timestamp for this reward period
     */
    function lastTimeRewardApplicable() public view returns (uint256) {
        return StableMath.min(block.timestamp, globalData.periodFinish);
    }

    /**
     * @dev Calculates the amount of unclaimed rewards per token since last update,
     * and sums with stored to give the new cumulative reward per token
     * @return 'Reward' per staked token
     */
    function rewardPerToken() public view returns (uint256) {
        (uint256 rewardPerToken_, ) = _rewardPerToken();
        return rewardPerToken_;
    }

    function _rewardPerToken()
        internal
        view
        returns (uint256 rewardPerToken_, uint256 lastTimeRewardApplicable_)
    {
        uint256 lastApplicableTime = lastTimeRewardApplicable(); // + 1 SLOAD
        Data memory data = globalData;
        uint256 timeDelta = lastApplicableTime - data.lastUpdateTime; // + 1 SLOAD
        // If this has been called twice in the same block, shortcircuit to reduce gas
        if (timeDelta == 0) {
            return (data.rewardPerTokenStored, lastApplicableTime);
        }
        // new reward units to distribute = rewardRate * timeSinceLastUpdate
        uint256 rewardUnitsToDistribute = data.rewardRate * timeDelta; // + 1 SLOAD
        uint256 supply = totalSupply(); // + 1 SLOAD
        // If there is no StakingToken liquidity, avoid div(0)
        // If there is nothing to distribute, short circuit
        if (supply == 0 || rewardUnitsToDistribute == 0) {
            return (data.rewardPerTokenStored, lastApplicableTime);
        }
        // new reward units per token = (rewardUnitsToDistribute * 1e18) / totalTokens
        uint256 unitsToDistributePerToken = rewardUnitsToDistribute.divPrecisely(supply);
        // return summed rate
        return (data.rewardPerTokenStored + unitsToDistributePerToken, lastApplicableTime); // + 1 SLOAD
    }

    /**
     * @dev Calculates the amount of unclaimed rewards a user has earned
     * @param _account User address
     * @return Total reward amount earned
     */
    function earned(address _account) public view returns (uint256) {
        return _earned(_account, rewardPerToken());
    }

    function _earned(address _account, uint256 _currentRewardPerToken)
        internal
        view
        returns (uint256)
    {
        // current rate per token - rate user previously received
        uint256 userRewardDelta = _currentRewardPerToken - userData[_account].rewardPerTokenPaid; // + 1 SLOAD
        // Short circuit if there is nothing new to distribute
        if (userRewardDelta == 0) {
            return userData[_account].rewards;
        }
        // new reward = staked tokens * difference in rate
        uint256 userNewReward = balanceOf(_account).mulTruncate(userRewardDelta); // + 1 SLOAD
        // add to previous rewards
        return userData[_account].rewards + userNewReward;
    }

    /***************************************
                    ABSTRACT
    ****************************************/

    function balanceOf(address account) public view virtual returns (uint256);

    function totalSupply() public view virtual returns (uint256);

    function _claimRewardHook(address account) internal virtual;

    /***************************************
                    ADMIN
    ****************************************/

    /**
     * @dev Notifies the contract that new rewards have been added.
     * Calculates an updated rewardRate based on the rewards in period.
     * @param _reward Units of RewardToken that have been added to the pool
     */
    function notifyRewardAmount(uint256 _reward)
        external
        override
        onlyRewardsDistributor
        updateReward(address(0))
    {
        require(_reward < 1e24, "Notify more than a million units");

        uint256 currentTime = block.timestamp;

        // Pay and reset the pendingAdditionalRewards
        if (pendingAdditionalReward > 1) {
            _reward += (pendingAdditionalReward - 1);
            pendingAdditionalReward = 1;
        }
        if (_reward > 0) {
            REWARDS_TOKEN.safeTransfer(rewardTokenVendor, _reward);
        }

        // If previous period over, reset rewardRate
        if (currentTime >= globalData.periodFinish) {
            globalData.rewardRate = SafeCast.toUint96(_reward / DURATION);
        }
        // If additional reward to existing period, calc sum
        else {
            uint256 remainingSeconds = globalData.periodFinish - currentTime;
            uint256 leftover = remainingSeconds * globalData.rewardRate;
            globalData.rewardRate = SafeCast.toUint96((_reward + leftover) / DURATION);
        }

        globalData.lastUpdateTime = SafeCast.toUint32(currentTime);
        globalData.periodFinish = SafeCast.toUint32(currentTime + DURATION);

        emit RewardAdded(_reward);
    }

    /**
     * @dev Called by the child contract to notify of any additional rewards that have accrued.
     *      Trusts that this is called honestly.
     * @param _additionalReward Units of additional RewardToken to add at the next notification
     */
    function _notifyAdditionalReward(uint256 _additionalReward) internal virtual {
        require(_additionalReward < 1e24, "Cannot notify with more than a million units");

        pendingAdditionalReward += _additionalReward;
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { SignatureVerifier } from "./deps/SignatureVerifier.sol";
import { ImmutableModule } from "../../shared/ImmutableModule.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IQuestManager } from "./interfaces/IQuestManager.sol";
import { IStakedToken } from "./interfaces/IStakedToken.sol";
import "./deps/GamifiedTokenStructs.sol";

/**
 * @title   QuestManager
 * @author  mStable
 * @notice  Centralised place to track quest management and completion status
 * @dev     VERSION: 1.0
 *          DATE:    2021-08-25
 */
contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, ImmutableModule {
    /// @notice Tracks the completion of each quest (user => questId => completion)
    mapping(address => mapping(uint256 => bool)) private _questCompletion;

    /// @notice User balance structs containing all data needed to scale balance
    mapping(address => QuestBalance) internal _balances;

    /// @notice List of quests, whose ID corresponds to their position in the array (from 0)
    Quest[] private _quests;
    /// @notice Timestamp at which the current season started
    uint32 public override seasonEpoch;
    /// @notice Timestamp at which the contract was created
    uint32 public startTime;

    /// @notice A whitelisted questMaster who can administer quests including signing user quests are completed.
    address public override questMaster;
    /// @notice account that can sign a user's quest as being completed.
    address internal _questSigner;

    /// @notice List of all staking tokens
    address[] internal _stakedTokens;

    /**
     * @param _nexus System nexus
     */
    constructor(address _nexus) ImmutableModule(_nexus) {}

    /**
     * @param _questMaster account that can sign user quests as completed
     * @param _questSignerArg account that can sign user quests as completed
     */
    function initialize(address _questMaster, address _questSignerArg) external initializer {
        startTime = SafeCast.toUint32(block.timestamp);
        questMaster = _questMaster;
        _questSigner = _questSignerArg;
    }

    /**
     * @dev Checks that _msgSender is either governor or the quest master
     */
    modifier questMasterOrGovernor() {
        _questMasterOrGovernor();
        _;
    }

    function _questMasterOrGovernor() internal view {
        require(_msgSender() == questMaster || _msgSender() == _governor(), "Not verified");
    }

    /***************************************
                    Getters
    ****************************************/

    /**
     * @notice Gets raw quest data
     */
    function getQuest(uint256 _id) external view override returns (Quest memory) {
        return _quests[_id];
    }

    /**
     * @dev Simply checks if a given user has already completed a given quest
     * @param _account User address
     * @param _id Position of quest in array
     * @return bool with completion status
     */
    function hasCompleted(address _account, uint256 _id) public view override returns (bool) {
        return _questCompletion[_account][_id];
    }

    /**
     * @notice Raw quest balance
     */
    function balanceData(address _account) external view override returns (QuestBalance memory) {
        return _balances[_account];
    }

    /***************************************
                    Admin
    ****************************************/

    /**
     * @dev Sets the quest master that can administoer quests. eg add, expire and start seasons.
     */
    function setQuestMaster(address _newQuestMaster) external override questMasterOrGovernor {
        emit QuestMaster(questMaster, _newQuestMaster);

        questMaster = _newQuestMaster;
    }

    /**
     * @dev Sets the quest signer that can sign user quests as being completed.
     */
    function setQuestSigner(address _newQuestSigner) external override onlyGovernor {
        emit QuestSigner(_questSigner, _newQuestSigner);

        _questSigner = _newQuestSigner;
    }

    /**
     * @dev Adds a new stakedToken
     */
    function addStakedToken(address _stakedToken) external override onlyGovernor {
        require(_stakedToken != address(0), "Invalid StakedToken");

        _stakedTokens.push(_stakedToken);

        emit StakedTokenAdded(_stakedToken);
    }

    /***************************************
                    QUESTS
    ****************************************/

    /**
     * @dev Called by questMasters to add a new quest to the system with default 'ACTIVE' status
     * @param _model Type of quest rewards multiplier (does it last forever or just for the season).
     * @param _multiplier Multiplier, from 1 == 1.01x to 100 == 2.00x
     * @param _expiry Timestamp at which quest expires. Note that permanent quests should still be given a timestamp.
     */
    function addQuest(
        QuestType _model,
        uint8 _multiplier,
        uint32 _expiry
    ) external override questMasterOrGovernor {
        require(_expiry > block.timestamp + 1 days, "Quest window too small");
        require(_multiplier > 0 && _multiplier <= 50, "Quest multiplier too large > 1.5x");

        _quests.push(
            Quest({
                model: _model,
                multiplier: _multiplier,
                status: QuestStatus.ACTIVE,
                expiry: _expiry
            })
        );

        emit QuestAdded(
            msg.sender,
            _quests.length - 1,
            _model,
            _multiplier,
            QuestStatus.ACTIVE,
            _expiry
        );
    }

    /**
     * @dev Called by questMasters to expire a quest, setting it's status as EXPIRED. After which it can
     * no longer be completed.
     * @param _id Quest ID (its position in the array)
     */
    function expireQuest(uint16 _id) external override questMasterOrGovernor {
        require(_id < _quests.length, "Quest does not exist");
        require(_quests[_id].status == QuestStatus.ACTIVE, "Quest already expired");

        _quests[_id].status = QuestStatus.EXPIRED;
        if (block.timestamp < _quests[_id].expiry) {
            _quests[_id].expiry = SafeCast.toUint32(block.timestamp);
        }

        emit QuestExpired(_id);
    }

    /**
     * @dev Called by questMasters to start a new quest season. After this, all current
     * seasonMultipliers will be reduced at the next user action (or triggered manually).
     * In order to reduce cost for any keepers, it is suggested to add quests at the start
     * of a new season to incentivise user actions.
     * A new season can only begin after 9 months has passed.
     */
    function startNewQuestSeason() external override questMasterOrGovernor {
        require(block.timestamp > (startTime + 39 weeks), "First season has not elapsed");
        require(block.timestamp > (seasonEpoch + 39 weeks), "Season has not elapsed");

        uint256 len = _quests.length;
        for (uint256 i = 0; i < len; i++) {
            Quest memory quest = _quests[i];
            if (quest.model == QuestType.SEASONAL) {
                require(
                    quest.status == QuestStatus.EXPIRED || block.timestamp > quest.expiry,
                    "All seasonal quests must have expired"
                );
            }
        }

        seasonEpoch = SafeCast.toUint32(block.timestamp);

        emit QuestSeasonEnded();
    }

    /***************************************
                    USER
    ****************************************/

    /**
     * @dev Called by anyone to complete one or more quests for a staker. The user must first collect a signed message
     * from the whitelisted _signer.
     * @param _account Account that has completed the quest
     * @param _ids Quest IDs (its position in the array)
     * @param _signature Signature from the verified _questSigner, containing keccak hash of account & ids
     */
    function completeUserQuests(
        address _account,
        uint256[] memory _ids,
        bytes calldata _signature
    ) external override {
        uint256 len = _ids.length;
        require(len > 0, "No quest IDs");

        uint8 questMultiplier = checkForSeasonFinish(_account);

        // For each quest
        for (uint256 i = 0; i < len; i++) {
            require(_validQuest(_ids[i]), "Invalid Quest ID");
            require(!hasCompleted(_account, _ids[i]), "Quest already completed");
            require(
                SignatureVerifier.verify(_questSigner, _account, _ids, _signature),
                "Invalid Quest Signer Signature"
            );

            // Store user quest has completed
            _questCompletion[_account][_ids[i]] = true;

            // Update multiplier
            Quest memory quest = _quests[_ids[i]];
            if (quest.model == QuestType.PERMANENT) {
                _balances[_account].permMultiplier += quest.multiplier;
            } else {
                _balances[_account].seasonMultiplier += quest.multiplier;
            }
            questMultiplier += quest.multiplier;
        }

        uint256 len2 = _stakedTokens.length;
        for (uint256 i = 0; i < len2; i++) {
            IStakedToken(_stakedTokens[i]).applyQuestMultiplier(_account, questMultiplier);
        }

        emit QuestCompleteQuests(_account, _ids);
    }

    /**
     * @dev Called by anyone to complete one or more accounts for a quest. The user must first collect a signed message
     * from the whitelisted _questMaster.
     * @param _questId Quest ID (its position in the array)
     * @param _accounts Accounts that has completed the quest
     * @param _signature Signature from the verified _questMaster, containing keccak hash of id and accounts
     */
    function completeQuestUsers(
        uint256 _questId,
        address[] memory _accounts,
        bytes calldata _signature
    ) external override {
        require(_validQuest(_questId), "Invalid Quest ID");
        uint256 len = _accounts.length;
        require(len > 0, "No accounts");
        require(
            SignatureVerifier.verify(_questSigner, _questId, _accounts, _signature),
            "Invalid Quest Signer Signature"
        );

        Quest memory quest = _quests[_questId];

        // For each user account
        for (uint256 i = 0; i < len; i++) {
            require(!hasCompleted(_accounts[i], _questId), "Quest already completed");

            // store user quest has completed
            _questCompletion[_accounts[i]][_questId] = true;

            // _applyQuestMultiplier(_accounts[i], quests);
            uint8 questMultiplier = checkForSeasonFinish(_accounts[i]);

            // Update multiplier
            if (quest.model == QuestType.PERMANENT) {
                _balances[_accounts[i]].permMultiplier += quest.multiplier;
            } else {
                _balances[_accounts[i]].seasonMultiplier += quest.multiplier;
            }
            questMultiplier += quest.multiplier;

            uint256 len2 = _stakedTokens.length;
            for (uint256 j = 0; j < len2; j++) {
                IStakedToken(_stakedTokens[j]).applyQuestMultiplier(_accounts[i], questMultiplier);
            }
        }

        emit QuestCompleteUsers(_questId, _accounts);
    }

    /**
     * @dev Simply checks if a quest is valid. Quests are valid if their id exists,
     * they have an ACTIVE status and they have not yet reached their expiry timestamp.
     * @param _id Position of quest in array
     * @return bool with validity status
     */
    function _validQuest(uint256 _id) internal view returns (bool) {
        return
            _id < _quests.length &&
            _quests[_id].status == QuestStatus.ACTIVE &&
            block.timestamp < _quests[_id].expiry;
    }

    /**
     * @dev Checks if the season has just finished between now and the users last action.
     * If it has, we reset the seasonMultiplier. Either way, we update the lastAction for the user.
     * NOTE - it is important that this is called as a hook before each state change operation
     * @param _account Address of user that should be updated
     */
    function checkForSeasonFinish(address _account)
        public
        override
        returns (uint8 newQuestMultiplier)
    {
        QuestBalance storage balance = _balances[_account];
        // If the last action was before current season, then reset the season timing
        if (_hasFinishedSeason(balance.lastAction)) {
            // Remove 85% of the multiplier gained in this season
            balance.seasonMultiplier = (balance.seasonMultiplier * 15) / 100;
            balance.lastAction = SafeCast.toUint32(block.timestamp);
        }
        return balance.seasonMultiplier + balance.permMultiplier;
    }

    /**
     * @dev Simple view fn to check if the users last action was before the starting of the current season
     */
    function _hasFinishedSeason(uint32 _lastAction) internal view returns (bool) {
        return _lastAction < seasonEpoch;
    }
}

File 21 of 32 : InitializableRewardsDistributionRecipient.sol
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { ImmutableModule } from "../shared/ImmutableModule.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IRewardsDistributionRecipient } from "../interfaces/IRewardsDistributionRecipient.sol";

/**
 * @title  RewardsDistributionRecipient
 * @author Originally: Synthetix (forked from /Synthetixio/synthetix/contracts/RewardsDistributionRecipient.sol)
 *         Changes by: mStable
 * @notice RewardsDistributionRecipient gets notified of additional rewards by the rewardsDistributor
 * @dev    Changes: Addition of Module and abstract `getRewardToken` func + cosmetic
 */
abstract contract InitializableRewardsDistributionRecipient is
    IRewardsDistributionRecipient,
    ImmutableModule
{
    // This address has the ability to distribute the rewards
    address public rewardsDistributor;

    constructor(address _nexus) ImmutableModule(_nexus) {}

    /** @dev Recipient is a module, governed by mStable governance */
    function _initialize(address _rewardsDistributor) internal virtual {
        rewardsDistributor = _rewardsDistributor;
    }

    /**
     * @dev Only the rewards distributor can notify about rewards
     */
    modifier onlyRewardsDistributor() {
        require(msg.sender == rewardsDistributor, "Caller is not reward distributor");
        _;
    }

    /**
     * @dev Change the rewardsDistributor - only called by mStable governor
     * @param _rewardsDistributor   Address of the new distributor
     */
    function setRewardsDistribution(address _rewardsDistributor) external onlyGovernor {
        rewardsDistributor = _rewardsDistributor;
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

/**
 * @title   StableMath
 * @author  mStable
 * @notice  A library providing safe mathematical operations to multiply and
 *          divide with standardised precision.
 * @dev     Derives from OpenZeppelin's SafeMath lib and uses generic system
 *          wide variables for managing precision.
 */
library StableMath {
    /**
     * @dev Scaling unit for use in specific calculations,
     * where 1 * 10**18, or 1e18 represents a unit '1'
     */
    uint256 private constant FULL_SCALE = 1e18;

    /**
     * @dev Token Ratios are used when converting between units of bAsset, mAsset and MTA
     * Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
     * bAsset ratio unit for use in exact calculations,
     * where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
     */
    uint256 private constant RATIO_SCALE = 1e8;

    /**
     * @dev Provides an interface to the scaling unit
     * @return Scaling unit (1e18 or 1 * 10**18)
     */
    function getFullScale() internal pure returns (uint256) {
        return FULL_SCALE;
    }

    /**
     * @dev Provides an interface to the ratio unit
     * @return Ratio scale unit (1e8 or 1 * 10**8)
     */
    function getRatioScale() internal pure returns (uint256) {
        return RATIO_SCALE;
    }

    /**
     * @dev Scales a given integer to the power of the full scale.
     * @param x   Simple uint256 to scale
     * @return    Scaled value a to an exact number
     */
    function scaleInteger(uint256 x) internal pure returns (uint256) {
        return x * FULL_SCALE;
    }

    /***************************************
              PRECISE ARITHMETIC
    ****************************************/

    /**
     * @dev Multiplies two precise units, and then truncates by the full scale
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit
     */
    function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulTruncateScale(x, y, FULL_SCALE);
    }

    /**
     * @dev Multiplies two precise units, and then truncates by the given scale. For example,
     * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @param scale Scale unit
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit
     */
    function mulTruncateScale(
        uint256 x,
        uint256 y,
        uint256 scale
    ) internal pure returns (uint256) {
        // e.g. assume scale = fullScale
        // z = 10e18 * 9e17 = 9e36
        // return 9e36 / 1e18 = 9e18
        return (x * y) / scale;
    }

    /**
     * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit, rounded up to the closest base unit.
     */
    function mulTruncateCeil(uint256 x, uint256 y) internal pure returns (uint256) {
        // e.g. 8e17 * 17268172638 = 138145381104e17
        uint256 scaled = x * y;
        // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
        uint256 ceil = scaled + FULL_SCALE - 1;
        // e.g. 13814538111.399...e18 / 1e18 = 13814538111
        return ceil / FULL_SCALE;
    }

    /**
     * @dev Precisely divides two units, by first scaling the left hand operand. Useful
     *      for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
     * @param x     Left hand input to division
     * @param y     Right hand input to division
     * @return      Result after multiplying the left operand by the scale, and
     *              executing the division on the right hand input.
     */
    function divPrecisely(uint256 x, uint256 y) internal pure returns (uint256) {
        // e.g. 8e18 * 1e18 = 8e36
        // e.g. 8e36 / 10e18 = 8e17
        return (x * FULL_SCALE) / y;
    }

    /***************************************
                  RATIO FUNCS
    ****************************************/

    /**
     * @dev Multiplies and truncates a token ratio, essentially flooring the result
     *      i.e. How much mAsset is this bAsset worth?
     * @param x     Left hand operand to multiplication (i.e Exact quantity)
     * @param ratio bAsset ratio
     * @return c    Result after multiplying the two inputs and then dividing by the ratio scale
     */
    function mulRatioTruncate(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
        return mulTruncateScale(x, ratio, RATIO_SCALE);
    }

    /**
     * @dev Multiplies and truncates a token ratio, rounding up the result
     *      i.e. How much mAsset is this bAsset worth?
     * @param x     Left hand input to multiplication (i.e Exact quantity)
     * @param ratio bAsset ratio
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              ratio scale, rounded up to the closest base unit.
     */
    function mulRatioTruncateCeil(uint256 x, uint256 ratio) internal pure returns (uint256) {
        // e.g. How much mAsset should I burn for this bAsset (x)?
        // 1e18 * 1e8 = 1e26
        uint256 scaled = x * ratio;
        // 1e26 + 9.99e7 = 100..00.999e8
        uint256 ceil = scaled + RATIO_SCALE - 1;
        // return 100..00.999e8 / 1e8 = 1e18
        return ceil / RATIO_SCALE;
    }

    /**
     * @dev Precisely divides two ratioed units, by first scaling the left hand operand
     *      i.e. How much bAsset is this mAsset worth?
     * @param x     Left hand operand in division
     * @param ratio bAsset ratio
     * @return c    Result after multiplying the left operand by the scale, and
     *              executing the division on the right hand input.
     */
    function divRatioPrecisely(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
        // e.g. 1e14 * 1e8 = 1e22
        // return 1e22 / 1e12 = 1e10
        return (x * RATIO_SCALE) / ratio;
    }

    /***************************************
                    HELPERS
    ****************************************/

    /**
     * @dev Calculates minimum of two numbers
     * @param x     Left hand input
     * @param y     Right hand input
     * @return      Minimum of the two inputs
     */
    function min(uint256 x, uint256 y) internal pure returns (uint256) {
        return x > y ? y : x;
    }

    /**
     * @dev Calculated maximum of two numbers
     * @param x     Left hand input
     * @param y     Right hand input
     * @return      Maximum of the two inputs
     */
    function max(uint256 x, uint256 y) internal pure returns (uint256) {
        return x > y ? x : y;
    }

    /**
     * @dev Clamps a value to an upper bound
     * @param x           Left hand input
     * @param upperBound  Maximum possible value to return
     * @return            Input x clamped to a maximum value, upperBound
     */
    function clamp(uint256 x, uint256 upperBound) internal pure returns (uint256) {
        return x > upperBound ? upperBound : x;
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { PlatformTokenVendor } from "./PlatformTokenVendor.sol";

/**
 * @title  PlatformTokenVendorFactory
 * @author mStable
 * @notice Library that deploys a PlatformTokenVendor contract which holds rewards tokens
 * @dev    Used to reduce the byte size of the contracts that need to deploy a PlatformTokenVendor contract
 */
library PlatformTokenVendorFactory {
    /// @dev for some reason Typechain will not generate the types if the library only has the create function
    function dummy() public pure returns (bool) {
        return true;
    }

    /**
     * @notice Deploys a new PlatformTokenVendor contract
     * @param _rewardsToken reward or platform rewards token. eg MTA or WMATIC
     * @return address of the deployed PlatformTokenVendor contract
     */
    function create(IERC20 _rewardsToken) public returns (address) {
        PlatformTokenVendor newPlatformTokenVendor = new PlatformTokenVendor(_rewardsToken);
        return address(newPlatformTokenVendor);
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { ModuleKeys } from "./ModuleKeys.sol";
import { INexus } from "../interfaces/INexus.sol";

/**
 * @title   ImmutableModule
 * @author  mStable
 * @dev     Subscribes to module updates from a given publisher and reads from its registry.
 *          Contract is used for upgradable proxy contracts.
 */
abstract contract ImmutableModule is ModuleKeys {
    INexus public immutable nexus;

    /**
     * @dev Initialization function for upgradable proxy contracts
     * @param _nexus Nexus contract address
     */
    constructor(address _nexus) {
        require(_nexus != address(0), "Nexus address is zero");
        nexus = INexus(_nexus);
    }

    /**
     * @dev Modifier to allow function calls only from the Governor.
     */
    modifier onlyGovernor() {
        _onlyGovernor();
        _;
    }

    function _onlyGovernor() internal view {
        require(msg.sender == _governor(), "Only governor can execute");
    }

    /**
     * @dev Modifier to allow function calls only from the Governance.
     *      Governance is either Governor address or Governance address.
     */
    modifier onlyGovernance() {
        require(
            msg.sender == _governor() || msg.sender == _governance(),
            "Only governance can execute"
        );
        _;
    }

    /**
     * @dev Returns Governor address from the Nexus
     * @return Address of Governor Contract
     */
    function _governor() internal view returns (address) {
        return nexus.governor();
    }

    /**
     * @dev Returns Governance Module address from the Nexus
     * @return Address of the Governance (Phase 2)
     */
    function _governance() internal view returns (address) {
        return nexus.getModule(KEY_GOVERNANCE);
    }

    /**
     * @dev Return SavingsManager Module address from the Nexus
     * @return Address of the SavingsManager Module contract
     */
    function _savingsManager() internal view returns (address) {
        return nexus.getModule(KEY_SAVINGS_MANAGER);
    }

    /**
     * @dev Return Recollateraliser Module address from the Nexus
     * @return  Address of the Recollateraliser Module contract (Phase 2)
     */
    function _recollateraliser() internal view returns (address) {
        return nexus.getModule(KEY_RECOLLATERALISER);
    }

    /**
     * @dev Return Liquidator Module address from the Nexus
     * @return  Address of the Liquidator Module contract
     */
    function _liquidator() internal view returns (address) {
        return nexus.getModule(KEY_LIQUIDATOR);
    }

    /**
     * @dev Return ProxyAdmin Module address from the Nexus
     * @return Address of the ProxyAdmin Module contract
     */
    function _proxyAdmin() internal view returns (address) {
        return nexus.getModule(KEY_PROXY_ADMIN);
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IRewardsDistributionRecipient {
    function notifyRewardAmount(uint256 reward) external;

    function getRewardToken() external view returns (IERC20);
}

interface IRewardsRecipientWithPlatformToken {
    function notifyRewardAmount(uint256 reward) external;

    function getRewardToken() external view returns (IERC20);

    function getPlatformToken() external view returns (IERC20);
}

File 26 of 32 : ModuleKeys.sol
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

/**
 * @title  ModuleKeys
 * @author mStable
 * @notice Provides system wide access to the byte32 represntations of system modules
 *         This allows each system module to be able to reference and update one another in a
 *         friendly way
 * @dev    keccak256() values are hardcoded to avoid re-evaluation of the constants at runtime.
 */
contract ModuleKeys {
    // Governance
    // ===========
    // keccak256("Governance");
    bytes32 internal constant KEY_GOVERNANCE =
        0x9409903de1e6fd852dfc61c9dacb48196c48535b60e25abf92acc92dd689078d;
    //keccak256("Staking");
    bytes32 internal constant KEY_STAKING =
        0x1df41cd916959d1163dc8f0671a666ea8a3e434c13e40faef527133b5d167034;
    //keccak256("ProxyAdmin");
    bytes32 internal constant KEY_PROXY_ADMIN =
        0x96ed0203eb7e975a4cbcaa23951943fa35c5d8288117d50c12b3d48b0fab48d1;

    // mStable
    // =======
    // keccak256("OracleHub");
    bytes32 internal constant KEY_ORACLE_HUB =
        0x8ae3a082c61a7379e2280f3356a5131507d9829d222d853bfa7c9fe1200dd040;
    // keccak256("Manager");
    bytes32 internal constant KEY_MANAGER =
        0x6d439300980e333f0256d64be2c9f67e86f4493ce25f82498d6db7f4be3d9e6f;
    //keccak256("Recollateraliser");
    bytes32 internal constant KEY_RECOLLATERALISER =
        0x39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f;
    //keccak256("MetaToken");
    bytes32 internal constant KEY_META_TOKEN =
        0xea7469b14936af748ee93c53b2fe510b9928edbdccac3963321efca7eb1a57a2;
    // keccak256("SavingsManager");
    bytes32 internal constant KEY_SAVINGS_MANAGER =
        0x12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf1;
    // keccak256("Liquidator");
    bytes32 internal constant KEY_LIQUIDATOR =
        0x1e9cb14d7560734a61fa5ff9273953e971ff3cd9283c03d8346e3264617933d4;
    // keccak256("InterestValidator");
    bytes32 internal constant KEY_INTEREST_VALIDATOR =
        0xc10a28f028c7f7282a03c90608e38a4a646e136e614e4b07d119280c5f7f839f;
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

/**
 * @title INexus
 * @dev Basic interface for interacting with the Nexus i.e. SystemKernel
 */
interface INexus {
    function governor() external view returns (address);

    function getModule(bytes32 key) external view returns (address);

    function proposeModule(bytes32 _key, address _addr) external;

    function cancelProposedModule(bytes32 _key) external;

    function acceptProposedModule(bytes32 _key) external;

    function acceptProposedModules(bytes32[] calldata _keys) external;

    function requestLockModule(bytes32 _key) external;

    function cancelLockModule(bytes32 _key) external;

    function lockModule(bytes32 _key) external;
}

File 28 of 32 : PlatformTokenVendor.sol
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MassetHelpers } from "../../shared/MassetHelpers.sol";

/**
 * @title  PlatformTokenVendor
 * @author mStable
 * @notice Stores platform tokens for distributing to StakingReward participants
 * @dev    Only deploy this during the constructor of a given StakingReward contract
 */
contract PlatformTokenVendor {
    IERC20 public immutable platformToken;
    address public immutable parentStakingContract;

    /** @dev Simple constructor that stores the parent address */
    constructor(IERC20 _platformToken) {
        parentStakingContract = msg.sender;
        platformToken = _platformToken;
        MassetHelpers.safeInfiniteApprove(address(_platformToken), msg.sender);
    }

    /**
     * @dev Re-approves the StakingReward contract to spend the platform token.
     * Just incase for some reason approval has been reset.
     */
    function reApproveOwner() external {
        MassetHelpers.safeInfiniteApprove(address(platformToken), parentStakingContract);
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title   MassetHelpers
 * @author  mStable
 * @notice  Helper functions to facilitate minting and redemption from off chain
 * @dev     VERSION: 1.0
 *          DATE:    2020-03-28
 */
library MassetHelpers {
    using SafeERC20 for IERC20;

    function transferReturnBalance(
        address _sender,
        address _recipient,
        address _bAsset,
        uint256 _qty
    ) internal returns (uint256 receivedQty, uint256 recipientBalance) {
        uint256 balBefore = IERC20(_bAsset).balanceOf(_recipient);
        IERC20(_bAsset).safeTransferFrom(_sender, _recipient, _qty);
        recipientBalance = IERC20(_bAsset).balanceOf(_recipient);
        receivedQty = recipientBalance - balBefore;
    }

    function safeInfiniteApprove(address _asset, address _spender) internal {
        IERC20(_asset).safeApprove(_spender, 0);
        IERC20(_asset).safeApprove(_spender, 2**256 - 1);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @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`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) private pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// ███████╗░█████╗░██████╗░██████╗░███████╗██████╗░░░░███████╗██╗
// ╚════██║██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗░░░██╔════╝██║
// ░░███╔═╝███████║██████╔╝██████╔╝█████╗░░██████╔╝░░░█████╗░░██║
// ██╔══╝░░██╔══██║██╔═══╝░██╔═══╝░██╔══╝░░██╔══██╗░░░██╔══╝░░██║
// ███████╗██║░░██║██║░░░░░██║░░░░░███████╗██║░░██║██╗██║░░░░░██║
// ╚══════╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░░░░╚══════╝╚═╝░░╚═╝╚═╝╚═╝░░░░░╚═╝
// Copyright (C) 2021 zapper
// Copyright (c) 2018 Tasuku Nakamura

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//

///@author Zapper
///@notice This contract checks if a message has been signed by a verified signer via personal_sign.
// SPDX-License-Identifier: GPLv2

pragma solidity ^0.8.0;

library SignatureVerifier {
    function verify(
        address signer,
        address account,
        uint256[] calldata ids,
        bytes calldata signature
    ) external pure returns (bool) {
        bytes32 messageHash = getMessageHash(account, ids);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == signer;
    }

    function verify(
        address signer,
        uint256 id,
        address[] calldata accounts,
        bytes calldata signature
    ) external pure returns (bool) {
        bytes32 messageHash = getMessageHash(id, accounts);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == signer;
    }

    function getMessageHash(address account, uint256[] memory ids) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(account, ids));
    }
    function getMessageHash(uint256 id, address[] memory accounts) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(id, accounts));
    }

    function getEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
    }

    function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
        internal
        pure
        returns (address)
    {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory signature)
        internal
        pure
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        )
    {
        require(signature.length == 65, "invalid signature length");

        //solium-disable-next-line
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }
    }
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;

import "../deps/GamifiedTokenStructs.sol";

interface IQuestManager {
    event QuestAdded(
        address questMaster,
        uint256 id,
        QuestType model,
        uint16 multiplier,
        QuestStatus status,
        uint32 expiry
    );
    event QuestCompleteQuests(address indexed user, uint256[] ids);
    event QuestCompleteUsers(uint256 indexed questId, address[] accounts);
    event QuestExpired(uint16 indexed id);
    event QuestMaster(address oldQuestMaster, address newQuestMaster);
    event QuestSeasonEnded();
    event QuestSigner(address oldQuestSigner, address newQuestSigner);
    event StakedTokenAdded(address stakedToken);

    // GETTERS
    function balanceData(address _account) external view returns (QuestBalance memory);

    function getQuest(uint256 _id) external view returns (Quest memory);

    function hasCompleted(address _account, uint256 _id) external view returns (bool);

    function questMaster() external view returns (address);

    function seasonEpoch() external view returns (uint32);

    // ADMIN
    function addQuest(
        QuestType _model,
        uint8 _multiplier,
        uint32 _expiry
    ) external;

    function addStakedToken(address _stakedToken) external;

    function expireQuest(uint16 _id) external;

    function setQuestMaster(address _newQuestMaster) external;

    function setQuestSigner(address _newQuestSigner) external;

    function startNewQuestSeason() external;

    // USER
    function completeUserQuests(
        address _account,
        uint256[] memory _ids,
        bytes calldata _signature
    ) external;

    function completeQuestUsers(
        uint256 _questId,
        address[] memory _accounts,
        bytes calldata _signature
    ) external;

    function checkForSeasonFinish(address _account) external returns (uint8 newQuestMultiplier);
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/rewards/staking/PlatformTokenVendorFactory.sol": {
      "PlatformTokenVendorFactory": "0xfb73476911c5e84556a5bf953644b7ef50f6cbc5"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_nexus","type":"address"},{"internalType":"address","name":"_rewardsToken","type":"address"},{"internalType":"address","name":"_questManager","type":"address"},{"internalType":"address","name":"_stakedToken","type":"address"},{"internalType":"uint256","name":"_cooldownSeconds","type":"uint256"},{"internalType":"uint256","name":"_unstakeWindow","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"percentage","type":"uint256"}],"name":"Cooldown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"CooldownExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"fromDelegate","type":"address"},{"indexed":true,"internalType":"address","name":"toDelegate","type":"address"}],"name":"DelegateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":false,"internalType":"uint256","name":"previousBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBalance","type":"uint256"}],"name":"DelegateVotesChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hook","type":"address"}],"name":"GovernanceHookChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"Recollateralised","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"SlashRateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"delegatee","type":"address"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WrapperBlacklisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WrapperWhitelisted","type":"event"},{"inputs":[],"name":"COOLDOWN_SECONDS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARDS_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKED_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSTAKE_WINDOW","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_nameArg","type":"bytes32"},{"internalType":"bytes32","name":"_symbolArg","type":"bytes32"},{"internalType":"address","name":"_rewardsDistributorArg","type":"address"}],"name":"__StakedToken_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint8","name":"_newMultiplier","type":"uint8"}],"name":"applyQuestMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceData","outputs":[{"components":[{"internalType":"uint88","name":"raw","type":"uint88"},{"internalType":"uint32","name":"weightedTimestamp","type":"uint32"},{"internalType":"uint8","name":"timeMultiplier","type":"uint8"},{"internalType":"uint8","name":"questMultiplier","type":"uint8"},{"internalType":"uint32","name":"cooldownTimestamp","type":"uint32"},{"internalType":"uint88","name":"cooldownUnits","type":"uint88"}],"internalType":"struct Balance","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wrapper","type":"address"}],"name":"blackListWrapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_weightedTimestamp","type":"uint32"}],"name":"calcRedemptionFeeRate","outputs":[{"internalType":"uint256","name":"_feeRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRate","type":"uint256"}],"name":"changeSlashingPercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint32","name":"pos","type":"uint32"}],"name":"checkpoints","outputs":[{"components":[{"internalType":"uint32","name":"fromBlock","type":"uint32"},{"internalType":"uint224","name":"votes","type":"uint224"}],"internalType":"struct GamifiedVotingToken.Checkpoint","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"compoundRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"createLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"delegates","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"earned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyRecollateralisation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPastTotalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPastVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalData","outputs":[{"internalType":"uint32","name":"periodFinish","type":"uint32"},{"internalType":"uint32","name":"lastUpdateTime","type":"uint32"},{"internalType":"uint96","name":"rewardRate","type":"uint96"},{"internalType":"uint96","name":"rewardPerTokenStored","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hasPriceCoeff","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"increaseLockAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_nameArg","type":"bytes32"},{"internalType":"bytes32","name":"_symbolArg","type":"bytes32"},{"internalType":"address","name":"_rewardsDistributorArg","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nexus","outputs":[{"internalType":"contract INexus","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_reward","type":"uint256"}],"name":"notifyRewardAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"numCheckpoints","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdditionalReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"questManager","outputs":[{"internalType":"contract QuestManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"rawBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"reviewTimestamp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardTokenVendor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsDistributor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"safetyData","outputs":[{"internalType":"uint128","name":"collateralisationRatio","type":"uint128"},{"internalType":"uint128","name":"slashingPercentage","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newHook","type":"address"}],"name":"setGovernanceHook","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewardsDistributor","type":"address"}],"name":"setRewardsDistribution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_exitCooldown","type":"bool"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_units","type":"uint256"}],"name":"startCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userData","outputs":[{"internalType":"uint128","name":"rewardPerTokenPaid","type":"uint128"},{"internalType":"uint128","name":"rewards","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"userPriceCoeff","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wrapper","type":"address"}],"name":"whitelistWrapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistedWrappers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"bool","name":"_amountIncludesFee","type":"bool"},{"internalType":"bool","name":"_exitCooldown","type":"bool"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6101606040523480156200001257600080fd5b506040516200533b3803806200533b83398101604081905262000035916200011b565b85858585858560008686868383838383838381806001600160a01b038116620000a45760405162461bcd60e51b815260206004820152601560248201527f4e657875732061646472657373206973207a65726f0000000000000000000000604482015260640160405180910390fd5b6001600160601b0319606091821b811660805292811b831660a05294851b821660c0525091151560f81b60e052509a901b9099166101005250505061012094909452505061014052506200018a9950505050505050505050565b80516001600160a01b03811681146200011657600080fd5b919050565b60008060008060008060c087890312156200013557600080fd5b6200014087620000fe565b95506200015060208801620000fe565b94506200016060408801620000fe565b93506200017060608801620000fe565b92506080870151915060a087015190509295509295509295565b60805160601c60a05160601c60c05160601c60e05160f81c6101005160601c610120516101405161508a620002b1600039600081816104e301528181611b71015281816127a40152612f750152600081816106ff01528181611b9501528181612737015281816127c50152612f9601526000818161048a015281816116ed015281816117970152818161183c01528181611a1201528181611adc015281816121df01526129d6015260008181610a3101528181612ab10152818161359301526140eb015260008181610a580152818161123c015281816134de015261406701526000818161069b0152818161085c01528181610ce80152818161171901528181612dee0152613f8e0152600081816107e4015281816130980152613241015261508a6000f3fe608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063abe50f191161010f578063c8910913116100ad578063d9a2ee571161007c578063d9a2ee5714610a2c578063dc1627eb14610a53578063e9fad8ee14610a7a578063f1127ed814610a8257600080fd5b8063c8910913146109b4578063cd3daf9d146109e8578063d09ad565146109f0578063d279c19114610a1957600080fd5b8063c00c9f7f116100e9578063c00c9f7f14610847578063c19fd2171461084f578063c560107214610857578063c822ddb91461087e57600080fd5b8063abe50f1914610819578063b52c05fe1461082c578063b88a802f1461083f57600080fd5b8063830698851161017c57806395d89b411161015657806395d89b41146107c45780639ab24eb0146107cc578063a3f5c1d2146107df578063a694fc3a1461080657600080fd5b806383069885146107755780638e539e8c1461077e578063943182141461079157600080fd5b806379172222116101b857806379172222146107345780637acb7757146107475780637c098a1b1461075a57806380faa57d1461076d57600080fd5b806370a08231146106e757806372b49d63146106fa5780637446e7ad1461072157600080fd5b8063478dc62f116102c35780635fcddddf1161026157806365c787751161023057806365c7877514610673578063672e1fd81461068657806369940d79146106995780636fcfff45146106bf57600080fd5b80635fcddddf146105cb578063612556bf146105de578063613ce410146105f15780636154913b1461066057600080fd5b8063587cde1e1161029d578063587cde1e1461057f57806358b235dd146105925780635c19a95c146105a55780635fa456dc146105b857600080fd5b8063478dc62f14610551578063519b9a3f1461056457806357c37b691461056c57600080fd5b8063312f6b83116103305780633a46b1a81161030a5780633a46b1a8146105055780633c6b16ab146105185780633f2a55401461052b578063403f44471461053e57600080fd5b8063312f6b8314610485578063313ce567146104c4578063359c4a96146104de57600080fd5b806312064c341161036c57806312064c341461040b57806318160ddd1461045e57806319762143146104665780631be052891461047b57600080fd5b80628cc262146103925780630552c61c146103b857806306fdde03146103f6575b600080fd5b6103a56103a0366004614b07565b610abf565b6040519081526020015b60405180910390f35b609d546103d6906001600160801b0380821691600160801b90041682565b604080516001600160801b039384168152929091166020830152016103af565b6103fe610ad8565b6040516103af9190614d76565b610449610419366004614b07565b6001600160a01b03166000908152603a60205260409020546001600160581b0380821692600160a81b9092041690565b604080519283526020830191909152016103af565b6103a5610aea565b610479610474366004614b07565b610b3c565b005b6103a562093a8081565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016103af565b6104cc601281565b60405160ff90911681526020016103af565b6103a57f000000000000000000000000000000000000000000000000000000000000000081565b6103a5610513366004614b41565b610b66565b610479610526366004614c31565b610be5565b6033546104ac906001600160a01b031681565b61047961054c366004614c31565b610e8a565b61047961055f366004614c31565b610ee4565b610479610fa3565b61047961057a366004614b07565b611030565b6104ac61058d366004614b07565b611082565b6103a56105a0366004614d22565b6110b2565b6104796105b3366004614b07565b611168565b6104796105c6366004614c88565b611172565b6104796105d9366004614b07565b611184565b6104796105ec366004614b07565b6111dd565b6035546106299063ffffffff80821691600160201b8104909116906001600160601b03600160401b8204811691600160a01b90041684565b6040805163ffffffff95861681529490931660208501526001600160601b03918216928401929092521660608201526080016103af565b61047961066e366004614ba2565b611239565b610479610681366004614b07565b61136a565b610479610694366004614c31565b611373565b7f00000000000000000000000000000000000000000000000000000000000000006104ac565b6106d26106cd366004614b07565b61137c565b60405163ffffffff90911681526020016103af565b6103a56106f5366004614b07565b61139e565b6103a57f000000000000000000000000000000000000000000000000000000000000000081565b61047961072f366004614bf8565b61142e565b610479610742366004614bf8565b6114dd565b610479610755366004614c63565b611556565b6034546104ac906001600160a01b031681565b6103a5611566565b6103a560375481565b6103a561078c366004614c31565b61157d565b6107b461079f366004614b07565b609e6020526000908152604090205460ff1681565b60405190151581526020016103af565b6103fe6115d9565b6103a56107da366004614b07565b6115e6565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b610479610814366004614c31565b610ed5565b610479610827366004614cdb565b61166c565b61047961083a366004614d00565b611678565b610479611684565b61047961168f565b61047961193c565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b61094961088c366004614b07565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b03166000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b81049092166080820152600160a81b90910490911660a082015290565b6040516103af9190600060c0820190506001600160581b03808451168352602084015163ffffffff808216602086015260ff604087015116604086015260ff606087015116606086015280608087015116608086015250508060a08501511660a08401525092915050565b6103d66109c2366004614b07565b6036602052600090815260409020546001600160801b0380821691600160801b90041682565b6103a5611b2f565b6103a56109fe366004614b07565b6001600160a01b03166000908152603b602052604090205490565b610479610a27366004614b07565b611b41565b6107b47f000000000000000000000000000000000000000000000000000000000000000081565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b610479611b4a565b610a95610a90366004614b6d565b611c2b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016103af565b6000610ad282610acd611b2f565b611cae565b92915050565b6060610ae5603854611d68565b905090565b606c5460009080610afd57600091505090565b606c610b0a600183614f38565b81548110610b1a57610b1a614ff6565b600091825260209091200154600160201b90046001600160e01b031692915050565b610b44611e83565b603380546001600160a01b0319166001600160a01b0392909216919091179055565b6000438210610bbc5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e65640060448201526064015b60405180910390fd5b6001600160a01b0383166000908152606b60205260409020610bde9083611eeb565b9392505050565b6033546001600160a01b03163314610c3f5760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f7420726577617264206469737472696275746f726044820152606401610bb3565b6000610c4a81611fa7565b69d3c21bcecceda10000008210610ca35760405162461bcd60e51b815260206004820181905260248201527f4e6f74696679206d6f7265207468616e2061206d696c6c696f6e20756e6974736044820152606401610bb3565b603754429060011015610cd1576001603754610cbf9190614f38565b610cc99084614e20565b600160375592505b8215610d1157603454610d11906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116856120a6565b60355463ffffffff168110610d6a57610d35610d3062093a8085614e88565b612109565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b19909216919091179055610df4565b603554600090610d8190839063ffffffff16614f38565b603554909150600090610da490600160401b90046001600160601b031683614ec2565b9050610dc162093a80610db78388614e20565b610d309190614e88565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b1990921691909117905550505b610dfd81612175565b6035805463ffffffff92909216600160201b0267ffffffff0000000019909216919091179055610e38610e3362093a8083614e20565b612175565b6035805463ffffffff191663ffffffff929092169190911790556040518381527fde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d9060200160405180910390a1505050565b610e933361139e565b610ed55760405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20696e63726561736560681b6044820152606401610bb3565b610ee1816000806121da565b50565b610eec611e83565b610ef461221a565b6706f05b59d3b20000811115610f405760405162461bcd60e51b815260206004820152601160248201527043616e6e6f74206578636565642035302560781b6044820152606401610bb3565b610f498161227c565b609d80546001600160801b03928316600160801b0292169190911790556040517f130d585ea14fd64e9d6a9e64231dba9b8692207d164bd5e38d66c02ae9929a4490610f989083815260200190565b60405180910390a150565b336000908152603a6020526040902054600160881b900463ffffffff16610ffa5760405162461bcd60e51b815260206004820152600b60248201526a27379031b7b7b63237bbb760a91b6044820152606401610bb3565b611003336122e5565b60405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a2565b611038611e83565b606d80546001600160a01b0319166001600160a01b0383169081179091556040517f70f5968ed58c0d81c7a8bca106614e2ea19375c430722f9f0ad68de8fd74f97890600090a250565b6001600160a01b038082166000908152606a602052604081205490911680156110ab5780610bde565b5090919050565b60008062093a806110c963ffffffff851642614f38565b6110db90670de0b6b3a7640000614ec2565b6110e59190614e88565b90506729a2241af62c000081111561115657611119611114826fe1b1e5f90f944d6e1c9e66c000000000614e88565b6123d8565b6111269062989680614ec2565b91506658d15e17628000821061114c576111476658d15e1762800083614f38565b61114f565b60005b9150611162565b67010a741a4627800091505b50919050565b610ee13382612552565b61117e848484846125d2565b50505050565b61118c611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916905590519182527f6843d4fd51341f35652c364bbf7be3666eacfcfc9ebac9db41bd6e05c432165f9101610f98565b6111e5611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916600117905590519182527f66a41827fb347195a5a3f3b9c5c921310e2879c6715d6f2dff54add1a2fc562e9101610f98565b337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316146112a05760405162461bcd60e51b815260206004820152600c60248201526b139bdd081d995c9a599a595960a21b6044820152606401610bb3565b6001600160a01b0382166112c65760405162461bcd60e51b8152600401610bb390614da9565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906113568483612a4a565b9050801561117e5761117e84838386612b06565b610ee181612b93565b610ee181612c76565b6001600160a01b0381166000908152606b6020526040812054610ad290612175565b6001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152610ad2908390612a4a565b600054610100900460ff1680611447575060005460ff16155b6114635760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611485576000805461ffff19166101011790555b611490848484612d0d565b6114a2609c805460ff19166001179055565b60408051808201909152670de0b6b3a76400008082526000602090920191909152609d55801561117e576000805461ff001916905550505050565b600054610100900460ff16806114f6575060005460ff16155b6115125760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611534576000805461ffff19166101011790555b61153f84848461142e565b801561117e576000805461ff001916905550505050565b611562828260006121da565b5050565b603554600090610ae590429063ffffffff16612d7f565b60004382106115ce5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e6564006044820152606401610bb3565b610ad2606c83611eeb565b6060610ae5603954611d68565b6001600160a01b0381166000908152606b60205260408120548015611659576001600160a01b0383166000908152606b60205260409020611628600183614f38565b8154811061163857611638614ff6565b600091825260209091200154600160201b90046001600160e01b031661165c565b60005b6001600160e01b03169392505050565b611562826000836121da565b611562826000806121da565b61168d33612d8e565b565b609c5460ff166116e15760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610bb3565b609c805460ff191690557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b039081167f0000000000000000000000000000000000000000000000000000000000000000919091161461177f5760405162461bcd60e51b81526020600482015260136024820152724f6e6c7920666f722073616d6520706169727360681b6044820152606401610bb3565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b1580156117e157600080fd5b505afa1580156117f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118199190614c4a565b905061182430612d8e565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b15801561188657600080fd5b505afa15801561189a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118be9190614c4a565b905060006118cc8383614f38565b90506000811161191e5760405162461bcd60e51b815260206004820152601760248201527f4d75737420636f6d706f756e6420736f6d657468696e670000000000000000006044820152606401610bb3565b61192a81600080612e6e565b5050609c805460ff1916600117905550565b611944613060565b6001600160a01b0316336001600160a01b0316146119a45760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79205265636f6c6c61746572616c69736174696f6e204d6f64756c65006044820152606401610bb3565b6119ac61221a565b609d546119d190600160801b90046001600160801b0316670de0b6b3a7640000614f10565b609d80546fffffffffffffffffffffffffffffffff19166001600160801b03929092169190911790556040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b158015611a5c57600080fd5b505afa158015611a70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a949190614c4a565b9050611b03611aa1613060565b609d54670de0b6b3a764000090611ac890600160801b90046001600160801b031685614ec2565b611ad29190614e88565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691906120a6565b6040517f8551ab4a9801f4813e5fff4a88eb374487886333f7c4e0aa1ca928a24916d53c90600090a150565b600080611b3a61311a565b5092915050565b610ee181612d8e565b336000908152603a6020526040902054600160881b900463ffffffff16801580611bd057507f0000000000000000000000000000000000000000000000000000000000000000611bc37f00000000000000000000000000000000000000000000000000000000000000006001600160801b038416614e20565b611bcd9190614e20565b42115b15611bfd57600080611be133610419565b9092509050611bf8611bf38284614e20565b612c76565b505050565b336000818152603a6020526040812054610ee192600160a81b9091046001600160581b0316916001906125d2565b60408051808201909152600080825260208201526001600160a01b0383166000908152606b60205260409020805463ffffffff8416908110611c6f57611c6f614ff6565b60009182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b6001600160a01b0382166000908152603660205260408120548190611cdc906001600160801b031684614f38565b905080611d135750506001600160a01b038216600090815260366020526040902054600160801b90046001600160801b0316610ad2565b6000611d2882611d228761139e565b90613228565b6001600160a01b038616600090815260366020526040902054909150611d5f908290600160801b90046001600160801b0316614e20565b95945050505050565b606060005b602081108015611d9b5750828160208110611d8a57611d8a614ff6565b1a60f81b6001600160f81b03191615155b15611db25780611daa81614f9b565b915050611d6d565b60008167ffffffffffffffff811115611dcd57611dcd61500c565b6040519080825280601f01601f191660200182016040528015611df7576020820181803683370190505b509050600091505b602082108015611e2d5750838260208110611e1c57611e1c614ff6565b1a60f81b6001600160f81b03191615155b15610bde57838260208110611e4457611e44614ff6565b1a60f81b818381518110611e5a57611e5a614ff6565b60200101906001600160f81b031916908160001a90535081611e7b81614f9b565b925050611dff565b611e8b61323d565b6001600160a01b0316336001600160a01b03161461168d5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c7920676f7665726e6f722063616e2065786563757465000000000000006044820152606401610bb3565b8154600090815b81811015611f4f576000611f068284613298565b905084868281548110611f1b57611f1b614ff6565b60009182526020909120015463ffffffff161115611f3b57809250611f49565b611f46816001614e20565b91505b50611ef2565b8115611f925784611f61600184614f38565b81548110611f7157611f71614ff6565b600091825260209091200154600160201b90046001600160e01b0316611f95565b60005b6001600160e01b031695945050505050565b600080611fb261311a565b90925090508115611bf857611fc682612109565b603580546001600160601b0392909216600160a01b026001600160a01b03909216919091179055611ff681612175565b6035805463ffffffff92909216600160201b0267ffffffff00000000199092169190911790556001600160a01b03831615611bf857604051806040016040528061203f8461227c565b6001600160801b0316815260200161205f61205a8686611cae565b61227c565b6001600160801b039081169091526001600160a01b0385166000908152603660209081526040909120835193909101518216600160801b0292909116919091179055505050565b6040516001600160a01b038316602482015260448101829052611bf890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526132ef565b60006001600160601b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b6064820152608401610bb3565b5090565b600063ffffffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b6064820152608401610bb3565b61220f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163330866133c1565b611bf8838383612e6e565b609d546001600160801b0316670de0b6b3a76400001461168d5760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79207768696c652066756c6c7920636f6c6c61746572616c69736564006044820152606401610bb3565b60006001600160801b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610bb3565b806122ef81611fa7565b6001600160a01b0382166123155760405162461bcd60e51b8152600401610bb390614da9565b600080612321846133f9565b9150915061233282602001516135e9565b6001600160a01b0385166000908152603a60205260408120805460ff93909316600160781b0260ff60781b1984168117825560a0860151939192916123869185916001600160581b03918216911617614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0384166000908152603a6020526040902080546001600160881b0316905561117e8482613682565b6000816123e757506000919050565b816001600160801b82106124005760809190911c9060401b5b600160401b82106124165760409190911c9060201b5b600160201b821061242c5760209190911c9060101b5b6201000082106124415760109190911c9060081b5b61010082106124555760089190911c9060041b5b601082106124685760049190911c9060021b5b600882106124745760011b5b60016124808286614e88565b61248a9083614e20565b901c9050600161249a8286614e88565b6124a49083614e20565b901c905060016124b48286614e88565b6124be9083614e20565b901c905060016124ce8286614e88565b6124d89083614e20565b901c905060016124e88286614e88565b6124f29083614e20565b901c905060016125028286614e88565b61250c9083614e20565b901c9050600161251c8286614e88565b6125269083614e20565b901c905060006125368286614e88565b90508082106125455780611d5f565b509392505050565b919050565b600061255d83611082565b9050600061256a8461139e565b6001600160a01b038581166000818152606a602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a461117e828483613742565b6125da6138fe565b8361261d5760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b609d546001600160801b0316670de0b6b3a7640000146126b057612645338560006001613964565b609d5461266b908490670de0b6b3a764000090611ac8906001600160801b031688614ec2565b6040518481526001600160a01b0384169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a361117e565b336000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b810490921660808201819052600160a81b90920490921660a083015261275c907f000000000000000000000000000000000000000000000000000000000000000090614e20565b42116127a25760405162461bcd60e51b815260206004820152601560248201527424a729aaa32324a1a4a2a72a2fa1a7a7a62227aba760591b6044820152606401610bb3565b7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000826080015163ffffffff166127f99190614e20565b6128039042614f38565b11156128515760405162461bcd60e51b815260206004820152601760248201527f554e5354414b455f57494e444f575f46494e49534845440000000000000000006044820152606401610bb3565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b8304811695840186905260ff600160781b8404811695850195909552600160801b83049094166060840152600160881b82049093166080830152600160a81b900490911660a082015291906128d8906110b2565b905060008561290d57670de0b6b3a76400006128f48382614e20565b6128fe908a614ec2565b6129089190614e88565b61290f565b875b9050600061292583670de0b6b3a7640000614e20565b61293783670de0b6b3a7640000614ec2565b6129419190614e88565b60a08601519091506001600160581b03168083111561299b5760405162461bcd60e51b8152602060048201526016602482015275115e18d959591cc81b585e081dda5d1a191c985dd85b60521b6044820152606401610bb3565b600087806129a857508184145b90506129b73385836000613964565b6129c96129c48486614f38565b613c93565b6129fd6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168b856120a6565b6040518b81526001600160a01b038b169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a35050505050505050505050565b6000606482606001516064612a5f9190614e38565b8351612a6e9160ff1690614ee1565b612a789190614e9c565b6001600160581b03169050606482604001516064612a969190614e38565b612aa39060ff1683614ec2565b612aad9190614e88565b90507f000000000000000000000000000000000000000000000000000000000000000015610ad2576001600160a01b0383166000908152603b602052604090205461271090612afc9083614ec2565b610bde9190614e88565b83612b1081611fa7565b6001600160a01b0385166000908152603a60209081526040909120805460ff60801b1916600160801b60ff861602179055840151612b4d906135e9565b6001600160a01b0386166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055612b8c8584613682565b5050505050565b80612b9d81611fa7565b6001600160a01b038216612bc35760405162461bcd60e51b8152600401610bb390614da9565b600080612bcf846133f9565b915091506000612be283602001516135e9565b9050826040015160ff168160ff161415612c3e5760405162461bcd60e51b815260206004820152601960248201527f4e6f7468696e6720776f72746820706f6b696e672068657265000000000000006044820152606401610bb3565b6001600160a01b0385166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055612b8c8583613682565b612c7f3361139e565b612ccb5760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f42414c414e43455f4f4e5f434f4f4c444f574e00000000006044820152606401610bb3565b612cd53382613d1b565b60405181815233907f8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d659060200160405180910390a250565b600054610100900460ff1680612d26575060005460ff16155b612d425760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015612d64576000805461ffff19166101011790555b612d6c613f03565b6038849055603983905561153f82613f6e565b60008183116111625782610bde565b33612d9881611fa7565b33600090815260366020526040902054600160801b90046001600160801b03168015612e655733600090815260366020526040902080546001600160801b03908116909155603454612e1b916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116921690869085166133c1565b604080516001600160801b038316815290516001600160a01b0385169133917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9181900360200190a35b611bf833614045565b612e7661221a565b612e7e6138fe565b82612ec15760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b6001600160a01b03821615612eda57612eda3383612552565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152908280612fd757506000826080015163ffffffff16118015612fd757507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000836080015163ffffffff16612fca9190614e20565b612fd49190614e20565b42115b9050801561300b5760405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a25b61301633868361426a565b604080518681526001600160a01b038616602082015233917f9f9e4044c5742cca66ca090b21552bac14645e68bad7a92364a9d9ff18111a1c910160405180910390a25050505050565b6040516385acd64160e01b81527f39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f60048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906385acd6419060240160206040518083038186803b1580156130e257600080fd5b505afa1580156130f6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190614b24565b6000806000613127611566565b6040805160808101825260355463ffffffff8082168352600160201b820416602083018190526001600160601b03600160401b8304811694840194909452600160a01b90910490921660608201529192506000906131859084614f38565b9050806131a25750606001516001600160601b0316939092509050565b60008183604001516001600160601b03166131bd9190614ec2565b905060006131c9610aea565b90508015806131d6575081155b156131f357505050606001516001600160601b0316939092509050565b60006131ff838361458c565b90508085606001516001600160601b031661321a9190614e20565b989597509495505050505050565b6000610bde8383670de0b6b3a76400006145a1565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b1580156130e257600080fd5b600060026132a68184614fb6565b6132b1600286614fb6565b6132bb9190614e20565b6132c59190614e88565b6132d0600284614e88565b6132db600286614e88565b6132e59190614e20565b610bde9190614e20565b6000613344826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166145c09092919063ffffffff16565b805190915015611bf857808060200190518101906133629190614bdb565b611bf85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bb3565b6040516001600160a01b038085166024830152831660448201526064810182905261117e9085906323b872dd60e01b906084016120d2565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906134bc8383612a4a565b604051639295ac5960e01b81526001600160a01b0385811660048301529192507f000000000000000000000000000000000000000000000000000000000000000090911690639295ac5990602401602060405180830381600087803b15801561352457600080fd5b505af1158015613538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061355c9190614d3d565b6001600160a01b0384166000908152603a60205260409020805460ff92909216600160801b0260ff60801b199092169190911790557f0000000000000000000000000000000000000000000000000000000000000000156135e4576135c26127106145cf565b6001600160a01b0384166000908152603b6020526040902061ffff9190911690555b915091565b600063ffffffff82166135fe57506000919050565b600061361063ffffffff841642614f38565b90506277f8808110156136265750600092915050565b62eff10081101561363a5750601492915050565b6301dfe20081101561364f5750601e92915050565b6302cfd3008110156136645750602892915050565b6303bfc4008110156136795750603292915050565b50603c92915050565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152613712908490612a4a565b90508181111561372f57611bf88361372a8484614f38565b614632565b611bf88361373d8385614f38565b61467f565b816001600160a01b0316836001600160a01b0316141580156137645750600081115b15611bf8576001600160a01b038316156137f2576001600160a01b0383166000908152606b60205260408120819061379f906146cc856146d8565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72483836040516137e7929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561387b576001600160a01b0382166000908152606b6020526040812081906138289061484f856146d8565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051613870929190918252602082015260400190565b60405180910390a250505b606d546001600160a01b031615611bf857606d546040516216c4a960ea1b81526001600160a01b03858116600483015284811660248301526044820184905290911690635b12a40090606401600060405180830381600087803b1580156138e157600080fd5b505af11580156138f5573d6000803e3d6000fd5b50505050505050565b33321461168d57336000908152609e602052604090205460ff1661168d5760405162461bcd60e51b815260206004820152601a60248201527f4e6f7420612077686974656c697374656420636f6e74726163740000000000006044820152606401610bb3565b8361396e81611fa7565b6001600160a01b0385166139c45760405162461bcd60e51b815260206004820152601d60248201527f45524332303a206275726e2066726f6d207a65726f20616464726573730000006044820152606401610bb3565b6000806139d0876133f9565b9150915060008260a0015183600001516139ea9190614e5d565b6001600160581b031690508415613a83576001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19169055613a2e8161485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613a748161485b565b6001600160581b031660a08401525b868360a001516001600160581b03161015613ae05760405162461bcd60e51b815260206004820152601c60248201527f45524332303a206275726e20616d6f756e74203e2062616c616e6365000000006044820152606401610bb3565b613ae98761485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b03600160a81b808304821694909403169092026001600160a81b039092169190911790558515613bb6576001600160a01b0388166000908152603a6020526040812080546001600160581b03600160a81b820481169391613b6d91859116614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0388166000908152603a6020526040902080546001600160881b031690555b6000613bc3600889614e88565b613bcd9083614f38565b6020850151613be29063ffffffff1642614f38565b613bec9190614ec2565b90506000613bfa8383614e88565b90506000613c0b610e338342614f38565b6001600160a01b038c166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150613c4b826135e9565b6001600160a01b038d166000908152603a60205260409020805460ff60781b1916600160781b60ff8416021790559050613c858c87613682565b505050505050505050505050565b69d3c21bcecceda10000008110613d015760405162461bcd60e51b815260206004820152602c60248201527f43616e6e6f74206e6f746966792077697468206d6f7265207468616e2061206d60448201526b696c6c696f6e20756e69747360a01b6064820152608401610bb3565b8060376000828254613d139190614e20565b909155505050565b81613d2581611fa7565b6001600160a01b038316613d4b5760405162461bcd60e51b8152600401610bb390614da9565b600080613d57856133f9565b9150915060008260a001518360000151613d719190614e5d565b9050600085118015613d8c5750806001600160581b03168511155b613dd85760405162461bcd60e51b815260206004820152601e60248201527f4d7573742063686f6f7365206265747765656e203020616e64203130302500006044820152606401610bb3565b613de583602001516135e9565b6001600160a01b0387166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055613e238561485b565b613e2d9082614f4f565b6001600160a01b0387166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b0392909216919091179055613e7042612175565b6001600160a01b0387166000908152603a60205260409020805463ffffffff92909216600160881b0263ffffffff60881b19909216919091179055613eb48561485b565b6001600160a01b0387166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613efb8683613682565b505050505050565b600054610100900460ff1680613f1c575060005460ff16155b613f385760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015613f5a576000805461ffff19166101011790555b8015610ee1576000805461ff001916905550565b613f7781610b44565b604051633e50523160e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482015273fb73476911c5e84556a5bf953644b7ef50f6cbc590633e5052319060240160206040518083038186803b158015613fea57600080fd5b505af4158015613ffe573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140229190614b24565b603480546001600160a01b0319166001600160a01b039290921691909117905550565b604051639295ac5960e01b81526001600160a01b0382811660048301526000917f000000000000000000000000000000000000000000000000000000000000000090911690639295ac5990602401602060405180830381600087803b1580156140ad57600080fd5b505af11580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614d3d565b905060007f0000000000000000000000000000000000000000000000000000000000000000614115576000614134565b6001600160a01b0383166000908152603b602052604090205461271014155b6001600160a01b0384166000908152603a602052604090205490915060ff838116600160801b909204161415806141685750805b15611bf8576001600160a01b0383166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a08201526141fd908590612a4a565b6001600160a01b0385166000908152603a60205260409020805460ff60801b1916600160801b60ff871602179055905081156142605761423e6127106145cf565b6001600160a01b0385166000908152603b6020526040902061ffff9190911690555b61117e8482613682565b8261427481611fa7565b6001600160a01b0384166142ca5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610bb3565b6000806142d6866133f9565b9150915060008260a0015183600001516142f09190614e5d565b90506142fb8661485b565b83516143079190614e5d565b6001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b039290921691909117905584156143c65760a08301516001600160a01b0388166000908152603a60205260408120805490919061437d9084906001600160581b0316614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0387166000908152603a6020526040902080546001600160881b031690555b602083015163ffffffff1661449c576143de42612175565b6001600160a01b0388166000908152603a6020908152604091829020805463ffffffff60581b198116600160581b63ffffffff968716810291821793849055855160c0810187526001600160581b039384169284169290921782528304861693810193909352600160781b820460ff90811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a082015261449490889061372a908290612a4a565b50505061117e565b6000816001600160581b0316846020015163ffffffff16426144be9190614f38565b6144c89190614ec2565b905060006144d7600289614e88565b6144ea906001600160581b038516614e20565b6144f49083614e88565b90506000614505610e338342614f38565b6001600160a01b038b166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150614545826135e9565b6001600160a01b038c166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055905061457f8b87613682565b5050505050505050505050565b600081612afc670de0b6b3a764000085614ec2565b6000816145ae8486614ec2565b6145b89190614e88565b949350505050565b60606145b884846000856148c3565b600061ffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608401610bb3565b6040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562600083836149eb565b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562826000836149eb565b6000610bde8284614f38565b82546000908190801561472257856146f1600183614f38565b8154811061470157614701614ff6565b600091825260209091200154600160201b90046001600160e01b0316614725565b60005b6001600160e01b0316925061473e83858763ffffffff16565b915060008111801561477c57504386614758600184614f38565b8154811061476857614768614ff6565b60009182526020909120015463ffffffff16145b156147dc5761478a82614a51565b86614796600184614f38565b815481106147a6576147a6614ff6565b9060005260206000200160000160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550614846565b8560405180604001604052806147f143612175565b63ffffffff16815260200161480585614a51565b6001600160e01b039081169091528254600181018455600093845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b6000610bde8284614e20565b60006001600160581b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203860448201526538206269747360d01b6064820152608401610bb3565b6060824710156149245760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bb3565b843b6149725760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bb3565b600080866001600160a01b0316858760405161498e9190614d5a565b60006040518083038185875af1925050503d80600081146149cb576040519150601f19603f3d011682016040523d82523d6000602084013e6149d0565b606091505b50915091506149e0828286614aba565b979650505050505050565b6001600160a01b0383161580614a0857506001600160a01b038216155b15614a3657614a33606c6001600160a01b03841615614a295761484f614a2d565b6146cc5b836146d8565b50505b611bf8614a4284611082565b614a4b84611082565b83613742565b60006001600160e01b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b6064820152608401610bb3565b60608315614ac9575081610bde565b825115614ad95782518084602001fd5b8160405162461bcd60e51b8152600401610bb39190614d76565b803563ffffffff8116811461254d57600080fd5b600060208284031215614b1957600080fd5b8135610bde81615022565b600060208284031215614b3657600080fd5b8151610bde81615022565b60008060408385031215614b5457600080fd5b8235614b5f81615022565b946020939093013593505050565b60008060408385031215614b8057600080fd5b8235614b8b81615022565b9150614b9960208401614af3565b90509250929050565b60008060408385031215614bb557600080fd5b8235614bc081615022565b91506020830135614bd081615045565b809150509250929050565b600060208284031215614bed57600080fd5b8151610bde81615037565b600080600060608486031215614c0d57600080fd5b83359250602084013591506040840135614c2681615022565b809150509250925092565b600060208284031215614c4357600080fd5b5035919050565b600060208284031215614c5c57600080fd5b5051919050565b60008060408385031215614c7657600080fd5b823591506020830135614bd081615022565b60008060008060808587031215614c9e57600080fd5b843593506020850135614cb081615022565b92506040850135614cc081615037565b91506060850135614cd081615037565b939692955090935050565b60008060408385031215614cee57600080fd5b823591506020830135614bd081615037565b60008060408385031215614d1357600080fd5b50508035926020909101359150565b600060208284031215614d3457600080fd5b610bde82614af3565b600060208284031215614d4f57600080fd5b8151610bde81615045565b60008251614d6c818460208701614f6f565b9190910192915050565b6020815260008251806020840152614d95816040850160208701614f6f565b601f01601f19169190910160400192915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008219821115614e3357614e33614fca565b500190565b600060ff821660ff84168060ff03821115614e5557614e55614fca565b019392505050565b60006001600160581b03808316818516808303821115614e7f57614e7f614fca565b01949350505050565b600082614e9757614e97614fe0565b500490565b60006001600160581b0380841680614eb657614eb6614fe0565b92169190910492915050565b6000816000190483118215151615614edc57614edc614fca565b500290565b60006001600160581b0380831681851681830481118215151615614f0757614f07614fca565b02949350505050565b60006001600160801b0383811690831681811015614f3057614f30614fca565b039392505050565b600082821015614f4a57614f4a614fca565b500390565b60006001600160581b0383811690831681811015614f3057614f30614fca565b60005b83811015614f8a578181015183820152602001614f72565b8381111561117e5750506000910152565b6000600019821415614faf57614faf614fca565b5060010190565b600082614fc557614fc5614fe0565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ee157600080fd5b8015158114610ee157600080fd5b60ff81168114610ee157600080fdfea2646970667358221220155d7c66c220678cc50928f92b1daeec0245e02d64d88f549ddb0b05148db48464736f6c63430008060033000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2000000000000000000000000861f12764780896fd783ea615dd55df0ff865752000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd200000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000127500

Deployed Bytecode

0x608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063abe50f191161010f578063c8910913116100ad578063d9a2ee571161007c578063d9a2ee5714610a2c578063dc1627eb14610a53578063e9fad8ee14610a7a578063f1127ed814610a8257600080fd5b8063c8910913146109b4578063cd3daf9d146109e8578063d09ad565146109f0578063d279c19114610a1957600080fd5b8063c00c9f7f116100e9578063c00c9f7f14610847578063c19fd2171461084f578063c560107214610857578063c822ddb91461087e57600080fd5b8063abe50f1914610819578063b52c05fe1461082c578063b88a802f1461083f57600080fd5b8063830698851161017c57806395d89b411161015657806395d89b41146107c45780639ab24eb0146107cc578063a3f5c1d2146107df578063a694fc3a1461080657600080fd5b806383069885146107755780638e539e8c1461077e578063943182141461079157600080fd5b806379172222116101b857806379172222146107345780637acb7757146107475780637c098a1b1461075a57806380faa57d1461076d57600080fd5b806370a08231146106e757806372b49d63146106fa5780637446e7ad1461072157600080fd5b8063478dc62f116102c35780635fcddddf1161026157806365c787751161023057806365c7877514610673578063672e1fd81461068657806369940d79146106995780636fcfff45146106bf57600080fd5b80635fcddddf146105cb578063612556bf146105de578063613ce410146105f15780636154913b1461066057600080fd5b8063587cde1e1161029d578063587cde1e1461057f57806358b235dd146105925780635c19a95c146105a55780635fa456dc146105b857600080fd5b8063478dc62f14610551578063519b9a3f1461056457806357c37b691461056c57600080fd5b8063312f6b83116103305780633a46b1a81161030a5780633a46b1a8146105055780633c6b16ab146105185780633f2a55401461052b578063403f44471461053e57600080fd5b8063312f6b8314610485578063313ce567146104c4578063359c4a96146104de57600080fd5b806312064c341161036c57806312064c341461040b57806318160ddd1461045e57806319762143146104665780631be052891461047b57600080fd5b80628cc262146103925780630552c61c146103b857806306fdde03146103f6575b600080fd5b6103a56103a0366004614b07565b610abf565b6040519081526020015b60405180910390f35b609d546103d6906001600160801b0380821691600160801b90041682565b604080516001600160801b039384168152929091166020830152016103af565b6103fe610ad8565b6040516103af9190614d76565b610449610419366004614b07565b6001600160a01b03166000908152603a60205260409020546001600160581b0380821692600160a81b9092041690565b604080519283526020830191909152016103af565b6103a5610aea565b610479610474366004614b07565b610b3c565b005b6103a562093a8081565b6104ac7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281565b6040516001600160a01b0390911681526020016103af565b6104cc601281565b60405160ff90911681526020016103af565b6103a57f000000000000000000000000000000000000000000000000000000000012750081565b6103a5610513366004614b41565b610b66565b610479610526366004614c31565b610be5565b6033546104ac906001600160a01b031681565b61047961054c366004614c31565b610e8a565b61047961055f366004614c31565b610ee4565b610479610fa3565b61047961057a366004614b07565b611030565b6104ac61058d366004614b07565b611082565b6103a56105a0366004614d22565b6110b2565b6104796105b3366004614b07565b611168565b6104796105c6366004614c88565b611172565b6104796105d9366004614b07565b611184565b6104796105ec366004614b07565b6111dd565b6035546106299063ffffffff80821691600160201b8104909116906001600160601b03600160401b8204811691600160a01b90041684565b6040805163ffffffff95861681529490931660208501526001600160601b03918216928401929092521660608201526080016103af565b61047961066e366004614ba2565b611239565b610479610681366004614b07565b61136a565b610479610694366004614c31565b611373565b7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26104ac565b6106d26106cd366004614b07565b61137c565b60405163ffffffff90911681526020016103af565b6103a56106f5366004614b07565b61139e565b6103a57f00000000000000000000000000000000000000000000000000000000001baf8081565b61047961072f366004614bf8565b61142e565b610479610742366004614bf8565b6114dd565b610479610755366004614c63565b611556565b6034546104ac906001600160a01b031681565b6103a5611566565b6103a560375481565b6103a561078c366004614c31565b61157d565b6107b461079f366004614b07565b609e6020526000908152604090205460ff1681565b60405190151581526020016103af565b6103fe6115d9565b6103a56107da366004614b07565b6115e6565b6104ac7f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb381565b610479610814366004614c31565b610ed5565b610479610827366004614cdb565b61166c565b61047961083a366004614d00565b611678565b610479611684565b61047961168f565b61047961193c565b6104ac7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281565b61094961088c366004614b07565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b03166000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b81049092166080820152600160a81b90910490911660a082015290565b6040516103af9190600060c0820190506001600160581b03808451168352602084015163ffffffff808216602086015260ff604087015116604086015260ff606087015116606086015280608087015116608086015250508060a08501511660a08401525092915050565b6103d66109c2366004614b07565b6036602052600090815260409020546001600160801b0380821691600160801b90041682565b6103a5611b2f565b6103a56109fe366004614b07565b6001600160a01b03166000908152603b602052604090205490565b610479610a27366004614b07565b611b41565b6107b47f000000000000000000000000000000000000000000000000000000000000000081565b6104ac7f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575281565b610479611b4a565b610a95610a90366004614b6d565b611c2b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016103af565b6000610ad282610acd611b2f565b611cae565b92915050565b6060610ae5603854611d68565b905090565b606c5460009080610afd57600091505090565b606c610b0a600183614f38565b81548110610b1a57610b1a614ff6565b600091825260209091200154600160201b90046001600160e01b031692915050565b610b44611e83565b603380546001600160a01b0319166001600160a01b0392909216919091179055565b6000438210610bbc5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e65640060448201526064015b60405180910390fd5b6001600160a01b0383166000908152606b60205260409020610bde9083611eeb565b9392505050565b6033546001600160a01b03163314610c3f5760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f7420726577617264206469737472696275746f726044820152606401610bb3565b6000610c4a81611fa7565b69d3c21bcecceda10000008210610ca35760405162461bcd60e51b815260206004820181905260248201527f4e6f74696679206d6f7265207468616e2061206d696c6c696f6e20756e6974736044820152606401610bb3565b603754429060011015610cd1576001603754610cbf9190614f38565b610cc99084614e20565b600160375592505b8215610d1157603454610d11906001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281169116856120a6565b60355463ffffffff168110610d6a57610d35610d3062093a8085614e88565b612109565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b19909216919091179055610df4565b603554600090610d8190839063ffffffff16614f38565b603554909150600090610da490600160401b90046001600160601b031683614ec2565b9050610dc162093a80610db78388614e20565b610d309190614e88565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b1990921691909117905550505b610dfd81612175565b6035805463ffffffff92909216600160201b0267ffffffff0000000019909216919091179055610e38610e3362093a8083614e20565b612175565b6035805463ffffffff191663ffffffff929092169190911790556040518381527fde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d9060200160405180910390a1505050565b610e933361139e565b610ed55760405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20696e63726561736560681b6044820152606401610bb3565b610ee1816000806121da565b50565b610eec611e83565b610ef461221a565b6706f05b59d3b20000811115610f405760405162461bcd60e51b815260206004820152601160248201527043616e6e6f74206578636565642035302560781b6044820152606401610bb3565b610f498161227c565b609d80546001600160801b03928316600160801b0292169190911790556040517f130d585ea14fd64e9d6a9e64231dba9b8692207d164bd5e38d66c02ae9929a4490610f989083815260200190565b60405180910390a150565b336000908152603a6020526040902054600160881b900463ffffffff16610ffa5760405162461bcd60e51b815260206004820152600b60248201526a27379031b7b7b63237bbb760a91b6044820152606401610bb3565b611003336122e5565b60405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a2565b611038611e83565b606d80546001600160a01b0319166001600160a01b0383169081179091556040517f70f5968ed58c0d81c7a8bca106614e2ea19375c430722f9f0ad68de8fd74f97890600090a250565b6001600160a01b038082166000908152606a602052604081205490911680156110ab5780610bde565b5090919050565b60008062093a806110c963ffffffff851642614f38565b6110db90670de0b6b3a7640000614ec2565b6110e59190614e88565b90506729a2241af62c000081111561115657611119611114826fe1b1e5f90f944d6e1c9e66c000000000614e88565b6123d8565b6111269062989680614ec2565b91506658d15e17628000821061114c576111476658d15e1762800083614f38565b61114f565b60005b9150611162565b67010a741a4627800091505b50919050565b610ee13382612552565b61117e848484846125d2565b50505050565b61118c611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916905590519182527f6843d4fd51341f35652c364bbf7be3666eacfcfc9ebac9db41bd6e05c432165f9101610f98565b6111e5611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916600117905590519182527f66a41827fb347195a5a3f3b9c5c921310e2879c6715d6f2dff54add1a2fc562e9101610f98565b337f000000000000000000000000861f12764780896fd783ea615dd55df0ff8657526001600160a01b0316146112a05760405162461bcd60e51b815260206004820152600c60248201526b139bdd081d995c9a599a595960a21b6044820152606401610bb3565b6001600160a01b0382166112c65760405162461bcd60e51b8152600401610bb390614da9565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906113568483612a4a565b9050801561117e5761117e84838386612b06565b610ee181612b93565b610ee181612c76565b6001600160a01b0381166000908152606b6020526040812054610ad290612175565b6001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152610ad2908390612a4a565b600054610100900460ff1680611447575060005460ff16155b6114635760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611485576000805461ffff19166101011790555b611490848484612d0d565b6114a2609c805460ff19166001179055565b60408051808201909152670de0b6b3a76400008082526000602090920191909152609d55801561117e576000805461ff001916905550505050565b600054610100900460ff16806114f6575060005460ff16155b6115125760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611534576000805461ffff19166101011790555b61153f84848461142e565b801561117e576000805461ff001916905550505050565b611562828260006121da565b5050565b603554600090610ae590429063ffffffff16612d7f565b60004382106115ce5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e6564006044820152606401610bb3565b610ad2606c83611eeb565b6060610ae5603954611d68565b6001600160a01b0381166000908152606b60205260408120548015611659576001600160a01b0383166000908152606b60205260409020611628600183614f38565b8154811061163857611638614ff6565b600091825260209091200154600160201b90046001600160e01b031661165c565b60005b6001600160e01b03169392505050565b611562826000836121da565b611562826000806121da565b61168d33612d8e565b565b609c5460ff166116e15760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610bb3565b609c805460ff191690557f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b039081167f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2919091161461177f5760405162461bcd60e51b81526020600482015260136024820152724f6e6c7920666f722073616d6520706169727360681b6044820152606401610bb3565b6040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b1580156117e157600080fd5b505afa1580156117f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118199190614c4a565b905061182430612d8e565b6040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b15801561188657600080fd5b505afa15801561189a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118be9190614c4a565b905060006118cc8383614f38565b90506000811161191e5760405162461bcd60e51b815260206004820152601760248201527f4d75737420636f6d706f756e6420736f6d657468696e670000000000000000006044820152606401610bb3565b61192a81600080612e6e565b5050609c805460ff1916600117905550565b611944613060565b6001600160a01b0316336001600160a01b0316146119a45760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79205265636f6c6c61746572616c69736174696f6e204d6f64756c65006044820152606401610bb3565b6119ac61221a565b609d546119d190600160801b90046001600160801b0316670de0b6b3a7640000614f10565b609d80546fffffffffffffffffffffffffffffffff19166001600160801b03929092169190911790556040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b158015611a5c57600080fd5b505afa158015611a70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a949190614c4a565b9050611b03611aa1613060565b609d54670de0b6b3a764000090611ac890600160801b90046001600160801b031685614ec2565b611ad29190614e88565b6001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd21691906120a6565b6040517f8551ab4a9801f4813e5fff4a88eb374487886333f7c4e0aa1ca928a24916d53c90600090a150565b600080611b3a61311a565b5092915050565b610ee181612d8e565b336000908152603a6020526040902054600160881b900463ffffffff16801580611bd057507f0000000000000000000000000000000000000000000000000000000000127500611bc37f00000000000000000000000000000000000000000000000000000000001baf806001600160801b038416614e20565b611bcd9190614e20565b42115b15611bfd57600080611be133610419565b9092509050611bf8611bf38284614e20565b612c76565b505050565b336000818152603a6020526040812054610ee192600160a81b9091046001600160581b0316916001906125d2565b60408051808201909152600080825260208201526001600160a01b0383166000908152606b60205260409020805463ffffffff8416908110611c6f57611c6f614ff6565b60009182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b6001600160a01b0382166000908152603660205260408120548190611cdc906001600160801b031684614f38565b905080611d135750506001600160a01b038216600090815260366020526040902054600160801b90046001600160801b0316610ad2565b6000611d2882611d228761139e565b90613228565b6001600160a01b038616600090815260366020526040902054909150611d5f908290600160801b90046001600160801b0316614e20565b95945050505050565b606060005b602081108015611d9b5750828160208110611d8a57611d8a614ff6565b1a60f81b6001600160f81b03191615155b15611db25780611daa81614f9b565b915050611d6d565b60008167ffffffffffffffff811115611dcd57611dcd61500c565b6040519080825280601f01601f191660200182016040528015611df7576020820181803683370190505b509050600091505b602082108015611e2d5750838260208110611e1c57611e1c614ff6565b1a60f81b6001600160f81b03191615155b15610bde57838260208110611e4457611e44614ff6565b1a60f81b818381518110611e5a57611e5a614ff6565b60200101906001600160f81b031916908160001a90535081611e7b81614f9b565b925050611dff565b611e8b61323d565b6001600160a01b0316336001600160a01b03161461168d5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c7920676f7665726e6f722063616e2065786563757465000000000000006044820152606401610bb3565b8154600090815b81811015611f4f576000611f068284613298565b905084868281548110611f1b57611f1b614ff6565b60009182526020909120015463ffffffff161115611f3b57809250611f49565b611f46816001614e20565b91505b50611ef2565b8115611f925784611f61600184614f38565b81548110611f7157611f71614ff6565b600091825260209091200154600160201b90046001600160e01b0316611f95565b60005b6001600160e01b031695945050505050565b600080611fb261311a565b90925090508115611bf857611fc682612109565b603580546001600160601b0392909216600160a01b026001600160a01b03909216919091179055611ff681612175565b6035805463ffffffff92909216600160201b0267ffffffff00000000199092169190911790556001600160a01b03831615611bf857604051806040016040528061203f8461227c565b6001600160801b0316815260200161205f61205a8686611cae565b61227c565b6001600160801b039081169091526001600160a01b0385166000908152603660209081526040909120835193909101518216600160801b0292909116919091179055505050565b6040516001600160a01b038316602482015260448101829052611bf890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526132ef565b60006001600160601b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b6064820152608401610bb3565b5090565b600063ffffffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b6064820152608401610bb3565b61220f7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b03163330866133c1565b611bf8838383612e6e565b609d546001600160801b0316670de0b6b3a76400001461168d5760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79207768696c652066756c6c7920636f6c6c61746572616c69736564006044820152606401610bb3565b60006001600160801b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610bb3565b806122ef81611fa7565b6001600160a01b0382166123155760405162461bcd60e51b8152600401610bb390614da9565b600080612321846133f9565b9150915061233282602001516135e9565b6001600160a01b0385166000908152603a60205260408120805460ff93909316600160781b0260ff60781b1984168117825560a0860151939192916123869185916001600160581b03918216911617614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0384166000908152603a6020526040902080546001600160881b0316905561117e8482613682565b6000816123e757506000919050565b816001600160801b82106124005760809190911c9060401b5b600160401b82106124165760409190911c9060201b5b600160201b821061242c5760209190911c9060101b5b6201000082106124415760109190911c9060081b5b61010082106124555760089190911c9060041b5b601082106124685760049190911c9060021b5b600882106124745760011b5b60016124808286614e88565b61248a9083614e20565b901c9050600161249a8286614e88565b6124a49083614e20565b901c905060016124b48286614e88565b6124be9083614e20565b901c905060016124ce8286614e88565b6124d89083614e20565b901c905060016124e88286614e88565b6124f29083614e20565b901c905060016125028286614e88565b61250c9083614e20565b901c9050600161251c8286614e88565b6125269083614e20565b901c905060006125368286614e88565b90508082106125455780611d5f565b509392505050565b919050565b600061255d83611082565b9050600061256a8461139e565b6001600160a01b038581166000818152606a602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a461117e828483613742565b6125da6138fe565b8361261d5760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b609d546001600160801b0316670de0b6b3a7640000146126b057612645338560006001613964565b609d5461266b908490670de0b6b3a764000090611ac8906001600160801b031688614ec2565b6040518481526001600160a01b0384169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a361117e565b336000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b810490921660808201819052600160a81b90920490921660a083015261275c907f00000000000000000000000000000000000000000000000000000000001baf8090614e20565b42116127a25760405162461bcd60e51b815260206004820152601560248201527424a729aaa32324a1a4a2a72a2fa1a7a7a62227aba760591b6044820152606401610bb3565b7f00000000000000000000000000000000000000000000000000000000001275007f00000000000000000000000000000000000000000000000000000000001baf80826080015163ffffffff166127f99190614e20565b6128039042614f38565b11156128515760405162461bcd60e51b815260206004820152601760248201527f554e5354414b455f57494e444f575f46494e49534845440000000000000000006044820152606401610bb3565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b8304811695840186905260ff600160781b8404811695850195909552600160801b83049094166060840152600160881b82049093166080830152600160a81b900490911660a082015291906128d8906110b2565b905060008561290d57670de0b6b3a76400006128f48382614e20565b6128fe908a614ec2565b6129089190614e88565b61290f565b875b9050600061292583670de0b6b3a7640000614e20565b61293783670de0b6b3a7640000614ec2565b6129419190614e88565b60a08601519091506001600160581b03168083111561299b5760405162461bcd60e51b8152602060048201526016602482015275115e18d959591cc81b585e081dda5d1a191c985dd85b60521b6044820152606401610bb3565b600087806129a857508184145b90506129b73385836000613964565b6129c96129c48486614f38565b613c93565b6129fd6001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2168b856120a6565b6040518b81526001600160a01b038b169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a35050505050505050505050565b6000606482606001516064612a5f9190614e38565b8351612a6e9160ff1690614ee1565b612a789190614e9c565b6001600160581b03169050606482604001516064612a969190614e38565b612aa39060ff1683614ec2565b612aad9190614e88565b90507f000000000000000000000000000000000000000000000000000000000000000015610ad2576001600160a01b0383166000908152603b602052604090205461271090612afc9083614ec2565b610bde9190614e88565b83612b1081611fa7565b6001600160a01b0385166000908152603a60209081526040909120805460ff60801b1916600160801b60ff861602179055840151612b4d906135e9565b6001600160a01b0386166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055612b8c8584613682565b5050505050565b80612b9d81611fa7565b6001600160a01b038216612bc35760405162461bcd60e51b8152600401610bb390614da9565b600080612bcf846133f9565b915091506000612be283602001516135e9565b9050826040015160ff168160ff161415612c3e5760405162461bcd60e51b815260206004820152601960248201527f4e6f7468696e6720776f72746820706f6b696e672068657265000000000000006044820152606401610bb3565b6001600160a01b0385166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055612b8c8583613682565b612c7f3361139e565b612ccb5760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f42414c414e43455f4f4e5f434f4f4c444f574e00000000006044820152606401610bb3565b612cd53382613d1b565b60405181815233907f8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d659060200160405180910390a250565b600054610100900460ff1680612d26575060005460ff16155b612d425760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015612d64576000805461ffff19166101011790555b612d6c613f03565b6038849055603983905561153f82613f6e565b60008183116111625782610bde565b33612d9881611fa7565b33600090815260366020526040902054600160801b90046001600160801b03168015612e655733600090815260366020526040902080546001600160801b03908116909155603454612e1b916001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd28116921690869085166133c1565b604080516001600160801b038316815290516001600160a01b0385169133917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9181900360200190a35b611bf833614045565b612e7661221a565b612e7e6138fe565b82612ec15760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b6001600160a01b03821615612eda57612eda3383612552565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152908280612fd757506000826080015163ffffffff16118015612fd757507f00000000000000000000000000000000000000000000000000000000001275007f00000000000000000000000000000000000000000000000000000000001baf80836080015163ffffffff16612fca9190614e20565b612fd49190614e20565b42115b9050801561300b5760405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a25b61301633868361426a565b604080518681526001600160a01b038616602082015233917f9f9e4044c5742cca66ca090b21552bac14645e68bad7a92364a9d9ff18111a1c910160405180910390a25050505050565b6040516385acd64160e01b81527f39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f60048201526000907f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316906385acd6419060240160206040518083038186803b1580156130e257600080fd5b505afa1580156130f6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190614b24565b6000806000613127611566565b6040805160808101825260355463ffffffff8082168352600160201b820416602083018190526001600160601b03600160401b8304811694840194909452600160a01b90910490921660608201529192506000906131859084614f38565b9050806131a25750606001516001600160601b0316939092509050565b60008183604001516001600160601b03166131bd9190614ec2565b905060006131c9610aea565b90508015806131d6575081155b156131f357505050606001516001600160601b0316939092509050565b60006131ff838361458c565b90508085606001516001600160601b031661321a9190614e20565b989597509495505050505050565b6000610bde8383670de0b6b3a76400006145a1565b60007f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b1580156130e257600080fd5b600060026132a68184614fb6565b6132b1600286614fb6565b6132bb9190614e20565b6132c59190614e88565b6132d0600284614e88565b6132db600286614e88565b6132e59190614e20565b610bde9190614e20565b6000613344826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166145c09092919063ffffffff16565b805190915015611bf857808060200190518101906133629190614bdb565b611bf85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bb3565b6040516001600160a01b038085166024830152831660448201526064810182905261117e9085906323b872dd60e01b906084016120d2565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906134bc8383612a4a565b604051639295ac5960e01b81526001600160a01b0385811660048301529192507f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575290911690639295ac5990602401602060405180830381600087803b15801561352457600080fd5b505af1158015613538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061355c9190614d3d565b6001600160a01b0384166000908152603a60205260409020805460ff92909216600160801b0260ff60801b199092169190911790557f0000000000000000000000000000000000000000000000000000000000000000156135e4576135c26127106145cf565b6001600160a01b0384166000908152603b6020526040902061ffff9190911690555b915091565b600063ffffffff82166135fe57506000919050565b600061361063ffffffff841642614f38565b90506277f8808110156136265750600092915050565b62eff10081101561363a5750601492915050565b6301dfe20081101561364f5750601e92915050565b6302cfd3008110156136645750602892915050565b6303bfc4008110156136795750603292915050565b50603c92915050565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152613712908490612a4a565b90508181111561372f57611bf88361372a8484614f38565b614632565b611bf88361373d8385614f38565b61467f565b816001600160a01b0316836001600160a01b0316141580156137645750600081115b15611bf8576001600160a01b038316156137f2576001600160a01b0383166000908152606b60205260408120819061379f906146cc856146d8565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72483836040516137e7929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561387b576001600160a01b0382166000908152606b6020526040812081906138289061484f856146d8565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051613870929190918252602082015260400190565b60405180910390a250505b606d546001600160a01b031615611bf857606d546040516216c4a960ea1b81526001600160a01b03858116600483015284811660248301526044820184905290911690635b12a40090606401600060405180830381600087803b1580156138e157600080fd5b505af11580156138f5573d6000803e3d6000fd5b50505050505050565b33321461168d57336000908152609e602052604090205460ff1661168d5760405162461bcd60e51b815260206004820152601a60248201527f4e6f7420612077686974656c697374656420636f6e74726163740000000000006044820152606401610bb3565b8361396e81611fa7565b6001600160a01b0385166139c45760405162461bcd60e51b815260206004820152601d60248201527f45524332303a206275726e2066726f6d207a65726f20616464726573730000006044820152606401610bb3565b6000806139d0876133f9565b9150915060008260a0015183600001516139ea9190614e5d565b6001600160581b031690508415613a83576001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19169055613a2e8161485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613a748161485b565b6001600160581b031660a08401525b868360a001516001600160581b03161015613ae05760405162461bcd60e51b815260206004820152601c60248201527f45524332303a206275726e20616d6f756e74203e2062616c616e6365000000006044820152606401610bb3565b613ae98761485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b03600160a81b808304821694909403169092026001600160a81b039092169190911790558515613bb6576001600160a01b0388166000908152603a6020526040812080546001600160581b03600160a81b820481169391613b6d91859116614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0388166000908152603a6020526040902080546001600160881b031690555b6000613bc3600889614e88565b613bcd9083614f38565b6020850151613be29063ffffffff1642614f38565b613bec9190614ec2565b90506000613bfa8383614e88565b90506000613c0b610e338342614f38565b6001600160a01b038c166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150613c4b826135e9565b6001600160a01b038d166000908152603a60205260409020805460ff60781b1916600160781b60ff8416021790559050613c858c87613682565b505050505050505050505050565b69d3c21bcecceda10000008110613d015760405162461bcd60e51b815260206004820152602c60248201527f43616e6e6f74206e6f746966792077697468206d6f7265207468616e2061206d60448201526b696c6c696f6e20756e69747360a01b6064820152608401610bb3565b8060376000828254613d139190614e20565b909155505050565b81613d2581611fa7565b6001600160a01b038316613d4b5760405162461bcd60e51b8152600401610bb390614da9565b600080613d57856133f9565b9150915060008260a001518360000151613d719190614e5d565b9050600085118015613d8c5750806001600160581b03168511155b613dd85760405162461bcd60e51b815260206004820152601e60248201527f4d7573742063686f6f7365206265747765656e203020616e64203130302500006044820152606401610bb3565b613de583602001516135e9565b6001600160a01b0387166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055613e238561485b565b613e2d9082614f4f565b6001600160a01b0387166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b0392909216919091179055613e7042612175565b6001600160a01b0387166000908152603a60205260409020805463ffffffff92909216600160881b0263ffffffff60881b19909216919091179055613eb48561485b565b6001600160a01b0387166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613efb8683613682565b505050505050565b600054610100900460ff1680613f1c575060005460ff16155b613f385760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015613f5a576000805461ffff19166101011790555b8015610ee1576000805461ff001916905550565b613f7781610b44565b604051633e50523160e01b81526001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd216600482015273fb73476911c5e84556a5bf953644b7ef50f6cbc590633e5052319060240160206040518083038186803b158015613fea57600080fd5b505af4158015613ffe573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140229190614b24565b603480546001600160a01b0319166001600160a01b039290921691909117905550565b604051639295ac5960e01b81526001600160a01b0382811660048301526000917f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575290911690639295ac5990602401602060405180830381600087803b1580156140ad57600080fd5b505af11580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614d3d565b905060007f0000000000000000000000000000000000000000000000000000000000000000614115576000614134565b6001600160a01b0383166000908152603b602052604090205461271014155b6001600160a01b0384166000908152603a602052604090205490915060ff838116600160801b909204161415806141685750805b15611bf8576001600160a01b0383166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a08201526141fd908590612a4a565b6001600160a01b0385166000908152603a60205260409020805460ff60801b1916600160801b60ff871602179055905081156142605761423e6127106145cf565b6001600160a01b0385166000908152603b6020526040902061ffff9190911690555b61117e8482613682565b8261427481611fa7565b6001600160a01b0384166142ca5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610bb3565b6000806142d6866133f9565b9150915060008260a0015183600001516142f09190614e5d565b90506142fb8661485b565b83516143079190614e5d565b6001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b039290921691909117905584156143c65760a08301516001600160a01b0388166000908152603a60205260408120805490919061437d9084906001600160581b0316614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0387166000908152603a6020526040902080546001600160881b031690555b602083015163ffffffff1661449c576143de42612175565b6001600160a01b0388166000908152603a6020908152604091829020805463ffffffff60581b198116600160581b63ffffffff968716810291821793849055855160c0810187526001600160581b039384169284169290921782528304861693810193909352600160781b820460ff90811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a082015261449490889061372a908290612a4a565b50505061117e565b6000816001600160581b0316846020015163ffffffff16426144be9190614f38565b6144c89190614ec2565b905060006144d7600289614e88565b6144ea906001600160581b038516614e20565b6144f49083614e88565b90506000614505610e338342614f38565b6001600160a01b038b166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150614545826135e9565b6001600160a01b038c166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055905061457f8b87613682565b5050505050505050505050565b600081612afc670de0b6b3a764000085614ec2565b6000816145ae8486614ec2565b6145b89190614e88565b949350505050565b60606145b884846000856148c3565b600061ffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608401610bb3565b6040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562600083836149eb565b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562826000836149eb565b6000610bde8284614f38565b82546000908190801561472257856146f1600183614f38565b8154811061470157614701614ff6565b600091825260209091200154600160201b90046001600160e01b0316614725565b60005b6001600160e01b0316925061473e83858763ffffffff16565b915060008111801561477c57504386614758600184614f38565b8154811061476857614768614ff6565b60009182526020909120015463ffffffff16145b156147dc5761478a82614a51565b86614796600184614f38565b815481106147a6576147a6614ff6565b9060005260206000200160000160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550614846565b8560405180604001604052806147f143612175565b63ffffffff16815260200161480585614a51565b6001600160e01b039081169091528254600181018455600093845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b6000610bde8284614e20565b60006001600160581b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203860448201526538206269747360d01b6064820152608401610bb3565b6060824710156149245760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bb3565b843b6149725760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bb3565b600080866001600160a01b0316858760405161498e9190614d5a565b60006040518083038185875af1925050503d80600081146149cb576040519150601f19603f3d011682016040523d82523d6000602084013e6149d0565b606091505b50915091506149e0828286614aba565b979650505050505050565b6001600160a01b0383161580614a0857506001600160a01b038216155b15614a3657614a33606c6001600160a01b03841615614a295761484f614a2d565b6146cc5b836146d8565b50505b611bf8614a4284611082565b614a4b84611082565b83613742565b60006001600160e01b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b6064820152608401610bb3565b60608315614ac9575081610bde565b825115614ad95782518084602001fd5b8160405162461bcd60e51b8152600401610bb39190614d76565b803563ffffffff8116811461254d57600080fd5b600060208284031215614b1957600080fd5b8135610bde81615022565b600060208284031215614b3657600080fd5b8151610bde81615022565b60008060408385031215614b5457600080fd5b8235614b5f81615022565b946020939093013593505050565b60008060408385031215614b8057600080fd5b8235614b8b81615022565b9150614b9960208401614af3565b90509250929050565b60008060408385031215614bb557600080fd5b8235614bc081615022565b91506020830135614bd081615045565b809150509250929050565b600060208284031215614bed57600080fd5b8151610bde81615037565b600080600060608486031215614c0d57600080fd5b83359250602084013591506040840135614c2681615022565b809150509250925092565b600060208284031215614c4357600080fd5b5035919050565b600060208284031215614c5c57600080fd5b5051919050565b60008060408385031215614c7657600080fd5b823591506020830135614bd081615022565b60008060008060808587031215614c9e57600080fd5b843593506020850135614cb081615022565b92506040850135614cc081615037565b91506060850135614cd081615037565b939692955090935050565b60008060408385031215614cee57600080fd5b823591506020830135614bd081615037565b60008060408385031215614d1357600080fd5b50508035926020909101359150565b600060208284031215614d3457600080fd5b610bde82614af3565b600060208284031215614d4f57600080fd5b8151610bde81615045565b60008251614d6c818460208701614f6f565b9190910192915050565b6020815260008251806020840152614d95816040850160208701614f6f565b601f01601f19169190910160400192915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008219821115614e3357614e33614fca565b500190565b600060ff821660ff84168060ff03821115614e5557614e55614fca565b019392505050565b60006001600160581b03808316818516808303821115614e7f57614e7f614fca565b01949350505050565b600082614e9757614e97614fe0565b500490565b60006001600160581b0380841680614eb657614eb6614fe0565b92169190910492915050565b6000816000190483118215151615614edc57614edc614fca565b500290565b60006001600160581b0380831681851681830481118215151615614f0757614f07614fca565b02949350505050565b60006001600160801b0383811690831681811015614f3057614f30614fca565b039392505050565b600082821015614f4a57614f4a614fca565b500390565b60006001600160581b0383811690831681811015614f3057614f30614fca565b60005b83811015614f8a578181015183820152602001614f72565b8381111561117e5750506000910152565b6000600019821415614faf57614faf614fca565b5060010190565b600082614fc557614fc5614fe0565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ee157600080fd5b8015158114610ee157600080fd5b60ff81168114610ee157600080fdfea2646970667358221220155d7c66c220678cc50928f92b1daeec0245e02d64d88f549ddb0b05148db48464736f6c63430008060033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2000000000000000000000000861f12764780896fd783ea615dd55df0ff865752000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd200000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000127500

-----Decoded View---------------
Arg [0] : _nexus (address): 0xAFcE80b19A8cE13DEc0739a1aaB7A028d6845Eb3
Arg [1] : _rewardsToken (address): 0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2
Arg [2] : _questManager (address): 0x861f12764780896FD783eA615Dd55Df0FF865752
Arg [3] : _stakedToken (address): 0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2
Arg [4] : _cooldownSeconds (uint256): 1814400
Arg [5] : _unstakeWindow (uint256): 1209600

-----Encoded View---------------
6 Constructor Arguments found :
Arg [0] : 000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3
Arg [1] : 000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2
Arg [2] : 000000000000000000000000861f12764780896fd783ea615dd55df0ff865752
Arg [3] : 000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2
Arg [4] : 00000000000000000000000000000000000000000000000000000000001baf80
Arg [5] : 0000000000000000000000000000000000000000000000000000000000127500


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

OVERVIEW

Logic contract for V2 vault that stakes MTA

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.