ETH Price: $2,821.32 (+8.72%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Accept Ownership209615792024-10-14 4:59:1124 days ago1728881951IN
0x468BC180...9fe74f0De
0 ETH0.0002829510
Nominate New Own...209614362024-10-14 4:30:1124 days ago1728880211IN
0x468BC180...9fe74f0De
0 ETH0.0005652412
0x60806040197049402024-04-21 16:21:23199 days ago1713716483IN
 Create: ExternalTokenStakeManager
0 ETH0.0408953911

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ExternalTokenStakeManager

Compiler Version
v0.5.16+commit.9c3226ce

Optimization Enabled:
Yes with 1000 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2024-05-24
*/

/*
    ___            _       ___  _                          
    | .\ ___  _ _ <_> ___ | __><_>._ _  ___ ._ _  ___  ___ 
    |  _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._>
    |_|  \___.|_|  |_|     |_|  |_||_|_|<___||_|_|\_|_.\___.
    
* PeriFinance: ExternalTokenStakeManager.sol
*
* Latest source (may be newer): https://github.com/perifinance/peri-finance/blob/master/contracts/ExternalTokenStakeManager.sol
* Docs: Will be added in the future. 
* https://docs.peri.finance/contracts/source/contracts/ExternalTokenStakeManager
*
* Contract Dependencies: 
*	- IAddressResolver
*	- MixinResolver
*	- Owned
* Libraries: 
*	- SafeDecimalMath
*	- SafeMath
*
* MIT License
* ===========
*
* Copyright (c) 2024 PeriFinance
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/



pragma solidity 0.5.16;

// https://docs.peri.finance/contracts/source/contracts/owned
contract Owned {
    address public owner;
    address public nominatedOwner;

    constructor(address _owner) public {
        require(_owner != address(0), "Owner address cannot be 0");
        owner = _owner;
        emit OwnerChanged(address(0), _owner);
    }

    function nominateNewOwner(address _owner) external onlyOwner {
        nominatedOwner = _owner;
        emit OwnerNominated(_owner);
    }

    function acceptOwnership() external {
        require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
        emit OwnerChanged(owner, nominatedOwner);
        owner = nominatedOwner;
        nominatedOwner = address(0);
    }

    modifier onlyOwner {
        _onlyOwner();
        _;
    }

    function _onlyOwner() private view {
        require(msg.sender == owner, "Only the contract owner may perform this action");
    }

    event OwnerNominated(address newOwner);
    event OwnerChanged(address oldOwner, address newOwner);
}


// https://docs.peri.finance/contracts/source/interfaces/iaddressresolver
interface IAddressResolver {
    function getAddress(bytes32 name) external view returns (address);

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

    function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
}


// https://docs.peri.finance/contracts/source/interfaces/ipynth
interface IPynth {
    // Views
    function currencyKey() external view returns (bytes32);

    function transferablePynths(address account) external view returns (uint);

    // Mutative functions
    function transferAndSettle(address to, uint value) external returns (bool);

    function transferFromAndSettle(
        address from,
        address to,
        uint value
    ) external returns (bool);

    // Restricted: used internally to PeriFinance
    function burn(address account, uint amount) external;

    function issue(address account, uint amount) external;
}


// https://docs.peri.finance/contracts/source/interfaces/iissuer
interface IIssuer {
    // Views
    function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid);

    function availableCurrencyKeys() external view returns (bytes32[] memory);

    function availablePynthCount() external view returns (uint);

    function availablePynths(uint index) external view returns (IPynth);

    function canBurnPynths(address account) external view returns (bool);

    function collateral(address account) external view returns (uint);

    function collateralisationRatio(address issuer) external view returns (uint);

    function collateralisationRatioAndAnyRatesInvalid(address _issuer)
        external
        view
        returns (uint cratio, bool anyRateIsInvalid);

    function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance);

    function lastIssueEvent(address account) external view returns (uint);

    function maxIssuablePynths(address issuer)
        external
        view
        returns (
            uint,
            uint,
            uint
        );

    // function externalTokenQuota(
    //     address _account,
    //     uint _addtionalpUSD,
    //     uint _addtionalExToken,
    //     bool _isIssue
    // ) external view returns (uint);

    // function debtsCollateral(address _account, bool _rateCheck) external
    //     view
    //     returns (
    //         uint,
    //         uint,
    //         uint
    //     );
    function getRatios(address _account, bool _checkRate)
        external
        view
        returns (
            uint,
            uint,
            uint,
            uint,
            uint,
            uint
        );

    function getTargetRatio(address account) external view returns (uint);

    // function getTRatioCRatio(address _account)
    //     external
    //     view
    //     returns (
    //         uint,
    //         uint,
    //         uint,
    //         uint
    //     );

    function remainingIssuablePynths(address issuer)
        external
        view
        returns (
            uint maxIssuable,
            uint alreadyIssued,
            uint totalSystemDebt
        );

    function pynths(bytes32 currencyKey) external view returns (IPynth);

    function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory);

    function pynthsByAddress(address pynthAddress) external view returns (bytes32);

    function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint, bool);

    function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance)
        external
        view
        returns (uint transferable, bool anyRateIsInvalid);

    function amountsToFitClaimable(address _account) external view returns (uint burnAmount, uint exTokenAmountToUnstake);

    // Restricted: used internally to PeriFinance
    function issuePynths(
        address _issuer,
        bytes32 _currencyKey,
        uint _issueAmount
    ) external;

    function issueMaxPynths(address _issuer) external;

    function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external;

    function burnPynths(
        address _from,
        bytes32 _currencyKey,
        uint _burnAmount
    ) external;

    function fitToClaimable(address _from) external;

    function exit(address _from) external;

    function liquidateDelinquentAccount(
        address account,
        uint pusdAmount,
        address liquidator
    ) external returns (uint totalRedeemed, uint amountToLiquidate);
}


// Inheritance


// Internal references


// https://docs.peri.finance/contracts/source/contracts/addressresolver
contract AddressResolver is Owned, IAddressResolver {
    mapping(bytes32 => address) public repository;

    constructor(address _owner) public Owned(_owner) {}

    /* ========== RESTRICTED FUNCTIONS ========== */

    function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner {
        require(names.length == destinations.length, "Input lengths must match");

        for (uint i = 0; i < names.length; i++) {
            bytes32 name = names[i];
            address destination = destinations[i];
            repository[name] = destination;
            emit AddressImported(name, destination);
        }
    }

    /* ========= PUBLIC FUNCTIONS ========== */

    function rebuildCaches(MixinResolver[] calldata destinations) external {
        for (uint i = 0; i < destinations.length; i++) {
            destinations[i].rebuildCache();
        }
    }

    /* ========== VIEWS ========== */

    function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) {
        for (uint i = 0; i < names.length; i++) {
            if (repository[names[i]] != destinations[i]) {
                return false;
            }
        }
        return true;
    }

    function getAddress(bytes32 name) external view returns (address) {
        return repository[name];
    }

    function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) {
        address _foundAddress = repository[name];
        require(_foundAddress != address(0), reason);
        return _foundAddress;
    }

    function getPynth(bytes32 key) external view returns (address) {
        IIssuer issuer = IIssuer(repository["Issuer"]);
        require(address(issuer) != address(0), "Cannot find Issuer address");
        return address(issuer.pynths(key));
    }

    /* ========== EVENTS ========== */

    event AddressImported(bytes32 name, address destination);
}


// solhint-disable payable-fallback

// https://docs.peri.finance/contracts/source/contracts/readproxy
contract ReadProxy is Owned {
    address public target;

    constructor(address _owner) public Owned(_owner) {}

    function setTarget(address _target) external onlyOwner {
        target = _target;
        emit TargetUpdated(target);
    }

    function() external {
        // The basics of a proxy read call
        // Note that msg.sender in the underlying will always be the address of this contract.
        assembly {
            calldatacopy(0, 0, calldatasize)

            // Use of staticcall - this will revert if the underlying function mutates state
            let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0)
            returndatacopy(0, 0, returndatasize)

            if iszero(result) {
                revert(0, returndatasize)
            }
            return(0, returndatasize)
        }
    }

    event TargetUpdated(address newTarget);
}


// Inheritance


// Internal references


// https://docs.peri.finance/contracts/source/contracts/mixinresolver
contract MixinResolver {
    AddressResolver public resolver;

    mapping(bytes32 => address) private addressCache;

    constructor(address _resolver) internal {
        resolver = AddressResolver(_resolver);
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    function combineArrays(bytes32[] memory first, bytes32[] memory second)
        internal
        pure
        returns (bytes32[] memory combination)
    {
        combination = new bytes32[](first.length + second.length);

        for (uint i = 0; i < first.length; i++) {
            combination[i] = first[i];
        }

        for (uint j = 0; j < second.length; j++) {
            combination[first.length + j] = second[j];
        }
    }

    /* ========== PUBLIC FUNCTIONS ========== */

    // Note: this function is public not external in order for it to be overridden and invoked via super in subclasses
    function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {}

    function rebuildCache() public {
        bytes32[] memory requiredAddresses = resolverAddressesRequired();
        // The resolver must call this function whenver it updates its state
        for (uint i = 0; i < requiredAddresses.length; i++) {
            bytes32 name = requiredAddresses[i];
            // Note: can only be invoked once the resolver has all the targets needed added
            address destination =
                resolver.requireAndGetAddress(name, string(abi.encodePacked("Resolver missing target: ", name)));
            addressCache[name] = destination;
            emit CacheUpdated(name, destination);
        }
    }

    /* ========== VIEWS ========== */

    function isResolverCached() external view returns (bool) {
        bytes32[] memory requiredAddresses = resolverAddressesRequired();
        for (uint i = 0; i < requiredAddresses.length; i++) {
            bytes32 name = requiredAddresses[i];
            // false if our cache is invalid or if the resolver doesn't have the required address
            if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
                return false;
            }
        }

        return true;
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    function requireAndGetAddress(bytes32 name) internal view returns (address) {
        address _foundAddress = addressCache[name];
        require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name)));
        return _foundAddress;
    }

    /* ========== EVENTS ========== */

    event CacheUpdated(bytes32 name, address destination);
}


// https://docs.peri.finance/contracts/source/interfaces/iflexiblestorage
interface IFlexibleStorage {
    // Views
    function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint);

    function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory);

    function getIntValue(bytes32 contractName, bytes32 record) external view returns (int);

    function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory);

    function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address);

    function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory);

    function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool);

    function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory);

    function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32);

    function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory);

    // Mutative functions
    function deleteUIntValue(bytes32 contractName, bytes32 record) external;

    function deleteIntValue(bytes32 contractName, bytes32 record) external;

    function deleteAddressValue(bytes32 contractName, bytes32 record) external;

    function deleteBoolValue(bytes32 contractName, bytes32 record) external;

    function deleteBytes32Value(bytes32 contractName, bytes32 record) external;

    function setUIntValue(
        bytes32 contractName,
        bytes32 record,
        uint value
    ) external;

    function setUIntValues(
        bytes32 contractName,
        bytes32[] calldata records,
        uint[] calldata values
    ) external;

    function setIntValue(
        bytes32 contractName,
        bytes32 record,
        int value
    ) external;

    function setIntValues(
        bytes32 contractName,
        bytes32[] calldata records,
        int[] calldata values
    ) external;

    function setAddressValue(
        bytes32 contractName,
        bytes32 record,
        address value
    ) external;

    function setAddressValues(
        bytes32 contractName,
        bytes32[] calldata records,
        address[] calldata values
    ) external;

    function setBoolValue(
        bytes32 contractName,
        bytes32 record,
        bool value
    ) external;

    function setBoolValues(
        bytes32 contractName,
        bytes32[] calldata records,
        bool[] calldata values
    ) external;

    function setBytes32Value(
        bytes32 contractName,
        bytes32 record,
        bytes32 value
    ) external;

    function setBytes32Values(
        bytes32 contractName,
        bytes32[] calldata records,
        bytes32[] calldata values
    ) external;
}


// Internal references


// https://docs.peri.finance/contracts/source/contracts/mixinsystemsettings
contract MixinSystemSettings is MixinResolver {
    bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings";

    bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs";
    bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor";
    bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio";
    bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration";
    bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold";
    bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay";
    bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio";
    bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
    bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
    bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
    bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime";
    bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags";
    bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled";
    bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime";
    bytes32 internal constant SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT = "crossDomainDepositGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT = "crossDomainEscrowGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT = "crossDomainRewardGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT = "crossDomainWithdrawalGasLimit";
    bytes32 internal constant SETTING_EXTERNAL_TOKEN_QUOTA = "externalTokenQuota";
    bytes32 internal constant SETTING_BRIDGE_TRANSFER_GAS_COST = "bridgeTransferGasCost";
    bytes32 internal constant SETTING_BRIDGE_CLAIM_GAS_COST = "bridgeClaimGasCost";
    bytes32 internal constant SETTING_SYNC_STALE_THRESHOLD = "syncStalThreshold";
    bytes32 internal constant SETTING_EXTOKEN_ISSUANCE_RATIO = "exTokenIssuanceRatio";
    bytes32 internal constant SETTING_LIQUIDATION_RATIOS = "liquidationRatios";

    bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";

    enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal}

    constructor(address _resolver) internal MixinResolver(_resolver) {}

    function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
        addresses = new bytes32[](1);
        addresses[0] = CONTRACT_FLEXIBLESTORAGE;
    }

    function flexibleStorage() internal view returns (IFlexibleStorage) {
        return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE));
    }

    function _getGasLimitSetting(CrossDomainMessageGasLimits gasLimitType) internal pure returns (bytes32) {
        if (gasLimitType == CrossDomainMessageGasLimits.Deposit) {
            return SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Escrow) {
            return SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Reward) {
            return SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Withdrawal) {
            return SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT;
        } else {
            revert("Unknown gas limit type");
        }
    }

    function getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits gasLimitType) internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, _getGasLimitSetting(gasLimitType));
    }

    function getTradingRewardsEnabled() internal view returns (bool) {
        return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED);
    }

    function getWaitingPeriodSecs() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS);
    }

    function getPriceDeviationThresholdFactor() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR);
    }

    function getIssuanceRatio() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO);
    }

    function getFeePeriodDuration() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION);
    }

    function getTargetThreshold() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD);
    }

    function getLiquidationDelay() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY);
    }

    function getLiquidationRatio() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO);
    }

    function getLiquidationPenalty() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
    }

    function getRateStalePeriod() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
    }

    function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
            );
    }

    function getMinimumStakeTime() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME);
    }

    function getAggregatorWarningFlags() internal view returns (address) {
        return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS);
    }

    function getDebtSnapshotStaleTime() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME);
    }

    function getExternalTokenQuota() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXTERNAL_TOKEN_QUOTA);
    }

    function getBridgeTransferGasCost() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_BRIDGE_TRANSFER_GAS_COST);
    }

    function getBridgeClaimGasCost() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_BRIDGE_CLAIM_GAS_COST);
    }

    function getSyncStaleThreshold() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_SYNC_STALE_THRESHOLD);
    }

    function getExTokenIssuanceRatio(bytes32 tokenKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_EXTOKEN_ISSUANCE_RATIO, tokenKey))
            );
    }

    function getLiquidationRatios(bytes32 types) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_LIQUIDATION_RATIOS, types))
            );
    }
}


// https://docs.peri.finance/contracts/source/contracts/limitedsetup
contract LimitedSetup {
    uint public setupExpiryTime;

    /**
     * @dev LimitedSetup Constructor.
     * @param setupDuration The time the setup period will last for.
     */
    constructor(uint setupDuration) internal {
        setupExpiryTime = now + setupDuration;
    }

    modifier onlyDuringSetup {
        require(now < setupExpiryTime, "Can only perform this action during setup");
        _;
    }
}


/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

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

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

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


// Libraries


// https://docs.peri.finance/contracts/source/libraries/safedecimalmath
library SafeDecimalMath {
    using SafeMath for uint;

    /* Number of decimal places in the representations. */
    uint8 public constant decimals = 18;
    uint8 public constant highPrecisionDecimals = 27;

    /* The number representing 1.0. */
    uint public constant UNIT = 10**uint(decimals);

    /* The number representing 1.0 for higher fidelity numbers. */
    uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals);
    uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals);

    /**
     * @return Provides an interface to UNIT.
     */
    function unit() external pure returns (uint) {
        return UNIT;
    }

    /**
     * @return Provides an interface to PRECISE_UNIT.
     */
    function preciseUnit() external pure returns (uint) {
        return PRECISE_UNIT;
    }

    /**
     * @return The result of multiplying x and y, interpreting the operands as fixed-point
     * decimals.
     *
     * @dev A unit factor is divided out after the product of x and y is evaluated,
     * so that product must be less than 2**256. As this is an integer division,
     * the internal division always rounds down. This helps save on gas. Rounding
     * is more expensive on gas.
     */
    function multiplyDecimal(uint x, uint y) internal pure returns (uint) {
        /* Divide by UNIT to remove the extra factor introduced by the product. */
        return x.mul(y) / UNIT;
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of the specified precision unit.
     *
     * @dev The operands should be in the form of a the specified unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function _multiplyDecimalRound(
        uint x,
        uint y,
        uint precisionUnit
    ) private pure returns (uint) {
        /* Divide by UNIT to remove the extra factor introduced by the product. */
        uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of a precise unit.
     *
     * @dev The operands should be in the precise unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
        return _multiplyDecimalRound(x, y, PRECISE_UNIT);
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of a standard unit.
     *
     * @dev The operands should be in the standard unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) {
        return _multiplyDecimalRound(x, y, UNIT);
    }

    /**
     * @return The result of safely dividing x and y. The return value is a high
     * precision decimal.
     *
     * @dev y is divided after the product of x and the standard precision unit
     * is evaluated, so the product of x and UNIT must be less than 2**256. As
     * this is an integer division, the result is always rounded down.
     * This helps save on gas. Rounding is more expensive on gas.
     */
    function divideDecimal(uint x, uint y) internal pure returns (uint) {
        /* Reintroduce the UNIT factor that will be divided out by y. */
        return x.mul(UNIT).div(y);
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * decimal in the precision unit specified in the parameter.
     *
     * @dev y is divided after the product of x and the specified precision unit
     * is evaluated, so the product of x and the specified precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function _divideDecimalRound(
        uint x,
        uint y,
        uint precisionUnit
    ) private pure returns (uint) {
        uint resultTimesTen = x.mul(precisionUnit * 10).div(y);

        if (resultTimesTen % 10 >= 5) {
            resultTimesTen += 10;
        }

        return resultTimesTen / 10;
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * standard precision decimal.
     *
     * @dev y is divided after the product of x and the standard precision unit
     * is evaluated, so the product of x and the standard precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function divideDecimalRound(uint x, uint y) internal pure returns (uint) {
        return _divideDecimalRound(x, y, UNIT);
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * high precision decimal.
     *
     * @dev y is divided after the product of x and the high precision unit
     * is evaluated, so the product of x and the high precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
        return _divideDecimalRound(x, y, PRECISE_UNIT);
    }

    /**
     * @dev Convert a standard decimal representation to a high precision one.
     */
    function decimalToPreciseDecimal(uint i) internal pure returns (uint) {
        return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
    }

    /**
     * @dev Convert a high precision decimal to a standard decimal representation.
     */
    function preciseDecimalToDecimal(uint i) internal pure returns (uint) {
        uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }

    /**
     * @dev Round down the value with given number
     */
    function roundDownDecimal(uint x, uint d) internal pure returns (uint) {
        return x.div(10**d).mul(10**d);
    }

    /**
     * @dev Round up the value with given number
     */
    function roundUpDecimal(uint x, uint d) internal pure returns (uint) {
        uint _decimal = 10**d;

        if (x % _decimal > 0) {
            x = x.add(10**d);
        }

        return x.div(_decimal).mul(_decimal);
    }
}


// https://docs.peri.finance/contracts/source/interfaces/ierc20
interface IERC20 {
    // ERC20 Optional Views
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    // Views
    function totalSupply() external view returns (uint);

    function balanceOf(address owner) external view returns (uint);

    function allowance(address owner, address spender) external view returns (uint);

    // Mutative functions
    function transfer(address to, uint value) external returns (bool);

    function approve(address spender, uint value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint value
    ) external returns (bool);

    // Events
    event Transfer(address indexed from, address indexed to, uint value);

    event Approval(address indexed owner, address indexed spender, uint value);
}


interface IStakingState {
    // Mutative
    function stake(
        bytes32 _currencyKey,
        address _account,
        uint _amount
    ) external;

    function unstake(
        bytes32 _currencyKey,
        address _account,
        uint _amount
    ) external;

    function refund(
        bytes32 _currencyKey,
        address _account,
        uint _amount
    ) external returns (bool);

    /* 
    function setTargetRatio(address _account, uint _targetRatio) external;

    function setExTargetRatio(address _account, uint _exTRatio) external;
 */
    // View
    function targetTokens(bytes32 _currencyKey)
        external
        view
        returns (
            address tokenAddress,
            uint8 decimals,
            bool activated
        );

    function stakedAmountOf(bytes32 _currencyKey, address _account) external view returns (uint);

    function totalStakedAmount(bytes32 _currencyKey) external view returns (uint);

    function totalStakerCount(bytes32 _currencyKey) external view returns (uint);

    function tokenList(uint _index) external view returns (bytes32);

    function tokenAddress(bytes32 _currencyKey) external view returns (address);

    function tokenDecimals(bytes32 _currencyKey) external view returns (uint8);

    function tokenActivated(bytes32 _currencyKey) external view returns (bool);

    function getTokenCurrencyKeys() external view returns (bytes32[] memory);
    /* 
    function getTargetRatio(address _account) external view returns (uint);

    function getExTargetRatio(address _account) external view returns (uint);
 */
}


// https://docs.peri.finance/contracts/source/interfaces/iexchangerates
interface IExchangeRates {
    // Structs
    struct RateAndUpdatedTime {
        uint216 rate;
        uint40 time;
    }

    struct InversePricing {
        uint entryPoint;
        uint upperLimit;
        uint lowerLimit;
        bool frozenAtUpperLimit;
        bool frozenAtLowerLimit;
    }

    // Views
    function aggregators(bytes32 currencyKey) external view returns (address);

    function aggregatorWarningFlags() external view returns (address);

    function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);

    function canFreezeRate(bytes32 currencyKey) external view returns (bool);

    function currentRoundForRate(bytes32 currencyKey) external view returns (uint);

    function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);

    function effectiveValue(
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey
    ) external view returns (uint value);

    function effectiveValueAndRates(
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey
    )
        external
        view
        returns (
            uint value,
            uint sourceRate,
            uint destinationRate
        );

    function effectiveValueAtRound(
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey,
        uint roundIdForSrc,
        uint roundIdForDest
    ) external view returns (uint value);

    function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);

    function getLastRoundIdBeforeElapsedSecs(
        bytes32 currencyKey,
        uint startingRoundId,
        uint startingTimestamp,
        uint timediff
    ) external view returns (uint);

    function inversePricing(bytes32 currencyKey)
        external
        view
        returns (
            uint entryPoint,
            uint upperLimit,
            uint lowerLimit,
            bool frozenAtUpperLimit,
            bool frozenAtLowerLimit
        );

    function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);

    function oracle() external view returns (address);

    function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);

    function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);

    function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid);

    function rateForCurrency(bytes32 currencyKey) external view returns (uint);

    function rateIsFlagged(bytes32 currencyKey) external view returns (bool);

    function rateIsFrozen(bytes32 currencyKey) external view returns (bool);

    function rateIsInvalid(bytes32 currencyKey) external view returns (bool);

    function rateIsStale(bytes32 currencyKey) external view returns (bool);

    function rateStalePeriod() external view returns (uint);

    function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
        external
        view
        returns (uint[] memory rates, uint[] memory times);

    function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
        external
        view
        returns (uint[] memory rates, bool anyRateInvalid);

    function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);

    // Mutative functions
    function freezeRate(bytes32 currencyKey) external;
}


// https://docs.peri.finance/contracts/source/interfaces/iliquidations
interface ILiquidations {
    // Views
    function isOpenForLiquidation(address account) external view returns (bool);

    function getLiquidationDeadlineForAccount(address account) external view returns (uint);

    function isLiquidationDeadlinePassed(address account) external view returns (bool);

    function liquidationDelay() external view returns (uint);

    function liquidationRatio(address account) external view returns (uint);

    function liquidationPenalty() external view returns (uint);

    // function calcAmtToFixCollateral(address account, uint debtBalance, uint collateral) external view returns (uint);

    // Mutative Functions
    function flagAccountForLiquidation(address account) external;

    // Restricted: used internally to Issuer
    function removeAccountInLiquidation(address account) external;

    function liquidateAccount(
        address account,
        address liquidator,
        uint pusdAmount,
        uint debtBalance
    ) external returns (uint totalRedeemed, uint amountToLiquidate);

    function checkAndRemoveAccountInLiquidation(address account) external;
}


contract ExternalTokenStakeManager is Owned, MixinResolver, MixinSystemSettings, LimitedSetup(8 weeks) {
    using SafeMath for uint;
    using SafeDecimalMath for uint;

    IStakingState public stakingState;

    bytes32 internal constant pUSD = "pUSD";
    bytes32 internal constant PERI = "PERI";
    bytes32 internal constant USDC = "USDC";

    bytes32 public constant CONTRACT_NAME = "ExternalTokenStakeManager";

    bytes32 private constant CONTRACT_ISSUER = "Issuer";
    bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
    bytes32 private constant CONTRACT_LIQUIDATIONS = "Liquidations";

    // This key order is used from unstaking multiple coins
    // bytes32[] public currencyKeyOrder;

    constructor(
        address _owner,
        address _stakingState,
        address _resolver
    ) public Owned(_owner) MixinSystemSettings(_resolver) {
        stakingState = IStakingState(_stakingState);
    }

    function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
        bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
        bytes32[] memory newAddresses = new bytes32[](3);

        newAddresses[0] = CONTRACT_ISSUER;
        newAddresses[1] = CONTRACT_EXRATES;
        newAddresses[2] = CONTRACT_LIQUIDATIONS;

        return combineArrays(existingAddresses, newAddresses);
    }

    function tokenInstance(bytes32 _currencyKey) internal view tokenRegistered(_currencyKey) returns (IERC20) {
        return IERC20(stakingState.tokenAddress(_currencyKey));
    }

    function issuer() internal view returns (IIssuer) {
        return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
    }

    function exchangeRates() internal view returns (IExchangeRates) {
        return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
    }

    function liquidations() internal view returns (ILiquidations) {
        return ILiquidations(requireAndGetAddress(CONTRACT_LIQUIDATIONS));
    }

    function getTokenList() external view returns (bytes32[] memory) {
        return stakingState.getTokenCurrencyKeys();
    }

    function getTokenAddress(bytes32 _currencyKey) external view returns (address) {
        return stakingState.tokenAddress(_currencyKey);
    }

    function getTokenDecimals(bytes32 _currencyKey) external view returns (uint8) {
        return stakingState.tokenDecimals(_currencyKey);
    }

    function getTokenActivation(bytes32 _currencyKey) external view returns (bool) {
        return stakingState.tokenActivated(_currencyKey);
    }

    // function getCurrencyKeyOrder() external view returns (bytes32[] memory) {
    //     return currencyKeyOrder;
    // }

    function combinedStakedAmountOf(address _user, bytes32 _unitCurrency)
        external
        view
        returns (
            uint combinedSA /* , uint minDecimals */
        )
    {
        return _combinedStakedAmountOf(_user, _unitCurrency);
    }

    /* 
    function compiledStakableAmountOf(address _user, bytes32 _unitCurrency) external view returns (uint) {
        return _compiledStakableAmountOf(_user, _unitCurrency);
    }
 */
    function getExEADebt(address _account)
        external
        view
        returns (
            uint exDebt,
            uint exEA,
            uint exTRatio
        )
    {
        (exDebt, exEA, exTRatio) = _calcExEADebt(_account);
    }

    function getExDebt(address _account) external view returns (uint exDebt) {
        return _calcExDebt(_account);
    }

    function getTargetRatio(address _account, uint _existDebt)
        external
        view
        returns (
            uint tRatio,
            uint exTRatio,
            uint exEA
        )
    {
        (tRatio, exTRatio, exEA, , ) = _getTRatio(_account, _existDebt);
    }

    function getRatios(
        address _account,
        uint existDebt,
        uint periCol
    )
        external
        view
        returns (
            uint tRatio,
            uint cRatio,
            uint exTRatio,
            uint exEA,
            uint exSR,
            uint maxSR
        )
    {
        return _tRatioCRatio(_account, existDebt, periCol);
    }

    function exStakingRatio(address _account, uint _existDebt) external view returns (uint exSR, uint maxSR) {
        (uint exDebt, uint exEA, uint exTRatio) = _calcExEADebt(_account);
        return _getExSR(_existDebt, exDebt, exEA, exTRatio);
    }

    function maxStakableAmountOf(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    )
        external
        view
        returns (
            uint maxAmount /* , uint tRatio */
        )
    {
        // return _maxStakableAmountOf(_account, _existDebt, _targetKey, _unitKey);
        // (maxAmount, exTRatio, tRatio) = _maxExStakableAmt(_account, _existDebt, _periCol, _targetKey);
        (, , maxAmount) = _getTRAddDebtOrAmt(_account, _existDebt, 0, _periCol, _targetKey);
        // uint stakingAmt = _toCurrency(_unitKey, _targetKey, maxAmount);

        // uint targetDecimals = stakingState.tokenDecimals(_targetKey);

        // stakingAmt = stakingAmt.roundDownDecimal(uint(18).sub(targetDecimals));

        // maxAmount = stakingAmt.div(10**(uint(18).sub(targetDecimals)));
    }

    /* 
    function maxExAmtToTRatio(
        address _account,
        uint _existDebt,
        bytes32 _unitKey
    ) external view returns (uint) {
        return _maxExAmtToTRatio(_account, _existDebt, _unitKey);
    }
 */
    function burnAmtToFitTR(
        address _account,
        uint _existDebt,
        uint _periCol
    )
        external
        view
        returns (
            uint burnAmount,
            uint exRefundAmt,
            uint exEA
        )
    {
        return _burnAmtToFitTR(_account, _existDebt, _periCol);
    }

    /* 
    function calcTRatio(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    )
        external
        view
        returns (
            uint tRatio,
            uint exTRatio,
            uint eaSaGap
        )
    {
        return _calcTRatio(_account, _existDebt, _periCol, _targetKey);
    } */
    /* 
    function calcTokenSR(
        uint exTR,
        uint tokenIR,
        uint otherIR
    ) external pure returns (uint) {
        return _tokenSR(exTR, tokenIR, otherIR);
    }
 */
    /**
     * @notice calculate the pUSD value of the external tokens in user's wallet.
     * @dev external view function
     * @param _user user's wallet address
     * @param _currencyKey target currency key to be calculated
     */
    function getTokenPUSDValueOf(address _user, bytes32 _currencyKey) external view returns (uint) {
        return _tokenPUSDValueOf(_user, _currencyKey);
    }

    /**
     *
     * @param _account user's wallet address
     * @param _amount the amount of debt to be removed
     * @param _unitKey the unit currency key for the amount
     */
    function proRataUnstake(
        address _account,
        uint _amount,
        bytes32 _unitKey
    ) external returns (uint remainAmt) {
        // get ex-staked amount in unit currency
        (uint exDebt, uint exEA, uint exTRatio) = _calcExEADebt(_account);
        require(exDebt >= _amount, "Not enough external staked amount");

        _amount = _preciseDivToDecimal(_amount, exTRatio);

        // remainAmt = _amount > exEA ?  _amount.sub(exEA) : 0;

        // remainAmt = remainAmt.add(_proRataUnstake(_account, _account, _amount, exEA, _unitKey));

        remainAmt = _proRataUnstake(_account, _account, _amount, exEA, _unitKey);
    }

    /*     function proRataRefundAmt(address _account, uint _amount, bytes32 _unitKey) external view returns (uint overAmt, uint remainAmt) {
        (uint exDebt, uint exEA, uint exTRatio) = _calcExEADebt(_account);
        if(exDebt < _amount) return (_amount.sub(exDebt), 0);

        _amount = _preciseDivToDecimal(_amount, exTRatio);

        (_amount, overAmt) = _amount > exEA ? (exEA, _amount.sub(exEA)) : (_amount, 0);

        remainAmt = _proRataRefundAmt(_account, _amount, exEA, _unitKey);
    }
 */
    /**
     * @notice calculate the pUSD value of the external tokens in user's wallet.
     * @dev internal view function
     * @param _user user's wallet address
     * @param _currencyKey target currency key to be calculated
     */
    function _tokenPUSDValueOf(address _user, bytes32 _currencyKey) internal view returns (uint) {
        // (uint tokenRate, bool rateIsInvalid) = exchangeRates().rateAndInvalid(_currencyKey);

        // _requireRatesNotInvalid(rateIsInvalid);
        uint tokenRate = _rateCheck(_currencyKey);

        IERC20 exToken = tokenInstance(_currencyKey);

        uint balance = exToken.balanceOf(_user).mul(10**(uint(18).sub(exToken.decimals())));

        return balance.multiplyDecimal(tokenRate);
    }

    /* 
    /**
     * @notice calculate the max pUSD value of the external tokens in user's wallet.
     *
     * @param _user user's wallet address
     * @param _currencyKey target currency key to be calculated
     */
    /*    function maxSAPulsTokensOf(address _user, bytes32 _currencyKey) external view returns (uint maxStakableAmt) {
        require(_currencyKey != pUSD && _currencyKey != PERI, "PERI and pUSD not allowed");

        maxStakableAmt = _combinedStakedAmountOf(_user, pUSD);
        return maxStakableAmt.add(_tokenPUSDValueOf(_user, _currencyKey));
    }
 */
    /* 
    function expectedTargetRatios(
        address _account,
        uint _existDebt,
        uint _amount,
        bytes32 _targetKey,
        bool _stake
    )
        external
        view
        returns (
            uint exTargetRatio,
            uint targetRatio,
            uint changedAmt
        )
    {
        (exTargetRatio, targetRatio, changedAmt) = _expectedTargetRatios(_account, _existDebt, _amount, _targetKey, _stake);
    }
 */
    function stakedAmountOf(
        address _user,
        bytes32 _currencyKey,
        bytes32 _unitCurrency
    ) external view returns (uint) {
        return _stakedAmountOf(_user, _currencyKey, _unitCurrency);
    }

    function _stakedAmountOf(
        address _user,
        bytes32 _currencyKey,
        bytes32 _unitCurrency
    ) internal view returns (uint amountOf) {
        amountOf = stakingState.stakedAmountOf(_currencyKey, _user);

        if (amountOf == 0) {
            return 0;
        }

        if (_currencyKey == _unitCurrency) {
            return amountOf;
        }

        amountOf = _toCurrency(_currencyKey, _unitCurrency, amountOf);
        // amountOf = amountOf.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_currencyKey)));
    }

    // function requireNotExceedsQuotaLimit(
    //     address _account,
    //     uint _debtBalance,
    //     uint _additionalpUSD,
    //     uint _additionalExToken,
    //     bool _isIssue
    // ) external view {
    //     uint estimatedExternalTokenQuota =
    //         externalTokenQuota(_account, _debtBalance, _additionalpUSD, _isIssue);

    //     bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();
    //     uint minDecimals = 18;
    //     for (uint i; i < tokenList.length; i++) {
    //         uint decimals = stakingState.tokenDecimals(tokenList[i]);

    //         minDecimals = decimals < minDecimals ? decimals : minDecimals;
    //     }

    //     require(
    //         // due to the error caused by decimal difference, round down it upto minimum decimals among staking token list.
    //         estimatedExternalTokenQuota.roundDownDecimal(uint(18).sub(minDecimals)) <= getExternalTokenQuota(),
    //         "External token staking amount exceeds quota limit"
    //     );
    // }

    // /**
    //  * @notice It calculates the quota of user's staked amount to the debt.
    //  *         If parameters are not 0, it estimates the quota assuming those value is applied to current status.
    //  *
    //  * @param _account account
    //  * @param _debtBalance Debt balance to estimate [USD]
    //  * @param _addAmt amount to ex-staked amount [USD]
    //  * @param _isIssue If true, it is staking. Otherwise, it is unstaking.
    //  */
    /* function externalTokenQuota(
        address _account,
        uint _debtBalance,
        uint _addAmt,
        bool _isIssue
    ) external view returns (uint exTargetRatio) {
        // (exTargetRatio, , ) = _expectedTargetRatios(_account, _debtBalance, _addAmt, USDC, _isIssue);
    } */

    function _getExSR(
        uint _existDebt,
        /* uint _periCol,  */
        uint _exDebt,
        uint _exEA,
        uint _exTRatio
    ) internal view returns (uint exSR, uint maxSR) {
        if (_exEA == 0) return (0, 0);

        uint periIR = getIssuanceRatio();
        uint periSA = _existDebt > _exDebt ? _existDebt.sub(_exDebt) : 0;
        periSA = _preciseDivToDecimal(periSA, periIR);
        // totalSA = periSA + _exEA
        // periSA = periSA > _periCol ? _periCol.add(_exEA) : periSA.add(_exEA);
        periSA = periSA.add(_exEA);
        exSR = _exEA.divideDecimal(periSA);

        // Se = (T - Tp) / (Te - Tp)
        maxSR = _preciseDivToDecimal(getExternalTokenQuota().sub(periIR), _exTRatio.sub(periIR));
    }

    function _tRatioCRatio(
        address _account,
        uint _existDebt,
        uint _periCol
    )
        internal
        view
        returns (
            uint tRatio,
            uint cRatio,
            uint exTRatio,
            uint exEA,
            uint exSR,
            uint maxSR
        )
    {
        (tRatio, exTRatio, exEA, exSR, maxSR) = _getTRatio(_account, _existDebt);

        uint totalSA = _periCol.add(exEA);

        cRatio = totalSA > 0 ? _existDebt.divideDecimal(totalSA) : 0;
    }

    function _getTRatio(address _account, uint _existDebt)
        internal
        view
        returns (
            uint tRatio,
            uint exTRatio,
            uint exEA,
            uint exSR,
            uint maxSR
        )
    {
        if (_existDebt == 0) {
            return (getIssuanceRatio(), SafeDecimalMath.unit(), 0, 0, 0);
        }

        // get tokenEA, otherEA, tokenIR, otherIR
        // uint tokenIR; uint otherIR;
        // uint otherEA;
        // uint tokenEA;
        // uint exDebt;
        // (otherIR, otherEA, tokenIR, tokenEA, exDebt) = _otherTokenIREA(_account, USDC);

        // // get exEA
        // exEA = tokenEA.add(otherEA);
        uint exDebt;
        (exDebt, exEA, exTRatio) = _calcExEADebt(_account);

        uint periIR = getIssuanceRatio();
        // uint maxTR = getExternalTokenQuota();

        if (exEA == 0) {
            return (periIR, SafeDecimalMath.unit(), 0, 0, 0);
        }

        // // get peri SA = periDebt / peri issuance ratio
        // otherEA = _existDebt > exDebt ? _preciseDivToDecimal(_existDebt.sub(exDebt), periIR) : 0;

        // // get exTRatio (Te = To - (To - Tt) * St)
        // exTRatio = _toTRatio(otherIR, tokenIR, tokenEA.divideDecimal(exEA));

        // // Se-max = (Tmax - Tp) / (Te - Tp)
        // otherIR = _preciseDivToDecimal(maxTR.sub(periIR), exTRatio.sub(periIR));

        // // get external Target Staking Ratio and save it to otherIR
        // uint exSR = exEA.divideDecimal(exEA.add(otherEA));
        // exSR = exSR > otherIR ? otherIR : exSR;

        (exSR, maxSR) = _getExSR(_existDebt, exDebt, exEA, exTRatio);

        exDebt = exSR > maxSR ? maxSR : exSR;

        // get TRatio (Tp + ( Te - Tp) * Se)
        tRatio = _toTRatio(periIR, exTRatio, exDebt);

        // tRatio = tRatio > maxTR ? maxTR : tRatio < periIR ? periIR : tRatio;
    }

    // function _targetRatio(address _account) internal view returns (uint tRatio) {
    //     tRatio = stakingState.getTargetRatio(_account);
    //     tRatio = tRatio == 0 ? getIssuanceRatio() : tRatio;
    // }

    function _rateCheck(bytes32 _currencyKey) internal view returns (uint rate) {
        bool isInvalid;
        (rate, isInvalid) = exchangeRates().rateAndInvalid(_currencyKey);
        _requireRatesNotInvalid(isInvalid);
    }

    function _preciseMul(uint x, uint y) internal pure returns (uint) {
        return (y == 0 || x == 0) ? 0 : x.decimalToPreciseDecimal().multiplyDecimalRoundPrecise(y.decimalToPreciseDecimal());
    }

    function _preciseDiv(uint x, uint y) internal pure returns (uint) {
        return (y == 0 || x == 0) ? 0 : x.decimalToPreciseDecimal().divideDecimalRoundPrecise(y.decimalToPreciseDecimal());
    }

    function _preciseMulToDecimal(uint x, uint y) internal pure returns (uint) {
        return (y == 0 || x == 0) ? 0 : _preciseMul(x, y).preciseDecimalToDecimal();
    }

    function _preciseDivToDecimal(uint x, uint y) internal pure returns (uint) {
        return (y == 0 || x == 0) ? 0 : _preciseDiv(x, y).preciseDecimalToDecimal();
    }

    /**
     * @notice calculate the total staked value of the external tokens of the staker in given currency unit.
     *
     * @param _user staker address
     * @param _unitCurrency The currency unit to be applied for estimation [USD]
     */
    function _combinedStakedAmountOf(address _user, bytes32 _unitCurrency)
        internal
        view
        returns (
            uint combinedStakedAmount /* , uint minDecimals */
        )
    {
        bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();
        // minDecimals = 18;
        for (uint i; i < tokenList.length; i++) {
            uint stakedAmount = _stakedAmountOf(_user, tokenList[i], _unitCurrency);

            if (stakedAmount == 0) {
                continue;
            }

            combinedStakedAmount = combinedStakedAmount.add(stakedAmount);

            // uint decimals = stakingState.tokenDecimals(tokenList[i]);
            // minDecimals = decimals < minDecimals ? decimals : minDecimals;
        }
    }

    /* 
    /**
     * @notice calculate stakable amount of the external tokens in the staker's wallet.
     *
     * @return stakable amount of the external tokens in the staker's wallet.
     * @param _user staker address
     * @param _unitCurrency The currency unit to be applied for estimation [USD]
     */
    /*    function _compiledStakableAmountOf(address _user, bytes32 _unitCurrency)
        internal
        view
        returns (uint compiledStakableAmount)
    {
        bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();

        for (uint i; i < tokenList.length; i++) {
            uint _stakedAmount = stakingState.stakedAmountOf(tokenList[i], _user);

            if (_stakedAmount == 0) {
                continue;
            }

            _stakedAmount =
                tokenInstance(tokenList[i]).balanceOf(_user).mul(10**(18 - (uint)(tokenInstance(tokenList[i]).decimals()))) -
                _stakedAmount;

            compiledStakableAmount = compiledStakableAmount.add(_toCurrency(tokenList[i], _unitCurrency, _stakedAmount));
        }
    }
 */
    /**
     * @notice calculate the value in given currency unit.
     *
     * @return convAmt the value in given currency unit.
     * @param _fromKey The currency key of the external token
     * @param _toKey The currency key to be converted
     * @param _amount The amount of the external token
     */
    function _toCurrency(
        bytes32 _fromKey,
        bytes32 _toKey,
        uint _amount
    ) internal view returns (uint convAmt) {
        if (_fromKey == _toKey) {
            return _amount;
        }

        convAmt = _amount;
        // uint amountToUSD;
        uint rate;
        // bool rateIsInvalid;
        if (_fromKey != pUSD) {
            // if (_fromKey == pUSD) {
            //     amountToUSD = _amount;
            // } else {
            // (rate, rateIsInvalid) = exchangeRates().rateAndInvalid(_fromKey);

            // _requireRatesNotInvalid(rateIsInvalid);
            rate = _rateCheck(_fromKey);

            // amountToUSD = _amount.multiplyDecimalRound(rate);
            // convAmt = _amount.multiplyDecimalRound(rate);
            convAmt = _preciseMulToDecimal(_amount, rate);
        }

        if (_toKey != pUSD) {
            // if (_toKey == pUSD) {
            //     return amountToUSD;
            // } else {
            // (rate, rateIsInvalid) = exchangeRates().rateAndInvalid(_toKey);

            // _requireRatesNotInvalid(rateIsInvalid);
            rate = _rateCheck(_toKey);

            // return convAmt.divideDecimalRound(rate);
            // convAmt = convAmt.divideDecimalRound(rate);
            convAmt = _preciseDivToDecimal(convAmt, rate);
        }
    }

    /**
     * @notice Utils checking given two key arrays' value are matching each other(its order will not be considered).
     */
    function _keyChecker(bytes32[] memory _keysA, bytes32[] memory _keysB) internal pure returns (bool) {
        if (_keysA.length != _keysB.length) {
            return false;
        }

        for (uint i; i < _keysA.length; i++) {
            bool exist;
            for (uint j; j < _keysA.length; j++) {
                if (_keysA[i] == _keysB[j]) {
                    exist = true;

                    break;
                }
            }

            // given currency key is not matched
            if (!exist) {
                return false;
            }
        }

        return true;
    }

    function _calcExDebt(address _account) internal view returns (uint exDebt) {
        // get exEA
        (, , , , exDebt) = _otherTokenIREA(_account, USDC);

        // get exDebt = tokenEA * tokenIR + otherEA * otherIR
        // exDebt = _preciseMulToDecimal(tokenEA, tokenIR).add(
        //     _preciseMulToDecimal(otherEA, otherIR)
        // );
    }

    // function _calcTotalSA(address _account, uint _existDebt)
    //     internal view returns (uint totalSA, uint exEA, uint exDebt) {
    //     (exDebt, exEA,) = _calcExDebt(_account);

    //     // get peri debt amount
    //     totalSA = _preciseDivToDecimal(_existDebt.sub(exDebt), getIssuanceRatio());
    // }

    function _calcExEADebt(address _account)
        internal
        view
        returns (
            uint exDebt,
            uint exEA,
            uint exTRatio /* , uint minDecimals */
        )
    {
        // get exEA
        uint otherIR;
        uint otherEA;
        uint tokenIR;
        uint tokenEA;
        (otherIR, otherEA, tokenIR, tokenEA, exDebt) = _otherTokenIREA(_account, USDC);

        // // get exDebt = tokenEA * tokenIR + otherEA * otherIR
        // exDebt = _preciseMulToDecimal(tokenEA, tokenIR).add(
        //     _preciseMulToDecimal(otherEA, otherIR)
        // );

        // get exEA
        exEA = tokenEA.add(otherEA);

        // get exTRatio
        exTRatio = exEA > 0 ? _toTRatio(otherIR, tokenIR, tokenEA.divideDecimal(exEA)) : SafeDecimalMath.unit();
    }

    /* 
    /**
     * @notice calculate unstake amount of the external tokens with the given amount and debt
     *
     * @param _account staker address
     * @param _amount the amount of debt to be removed
     * @param _existDebt existing debt amount
     * @param _periCol PERI collateral amount
     * @param _targetKey external token key
     *
     * @return exRefundAmt unstaking ex-token amount in pUSD
     */
    /*    function _calcUnstakeAmt(
        address _account,
        uint _amount,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    ) internal view returns (uint exRefundAmt) {
        exRefundAmt = _calcExDebt(_account);

        // calc peri debt amount
        uint periDebt = _existDebt > exRefundAmt ? _existDebt.sub(exRefundAmt) : 0;

        // get peri estimated debt from the collateral value
        uint periCol2Debt = _preciseMulToDecimal(_periCol, getIssuanceRatio());

        // calc the gap between periDebt and periCol2Debt and check if it is short or long
        exRefundAmt = periDebt > periCol2Debt ? periDebt.sub(periCol2Debt) : 0;

        // in case short, check if the amount is more than the gap. if amount > gap, unstake amount = amount - gap else amount itself
        // in case long, unstake amount is amount + gap
        exRefundAmt = exRefundAmt > 0
            ? _amount > exRefundAmt ? _preciseMulToDecimal(_amount.sub(exRefundAmt), getExTokenIssuanceRatio(_targetKey)) : 0
            : _preciseMulToDecimal(_amount, getExTokenIssuanceRatio(_targetKey));
    }
 */
    /**
     * @notice get the other token's Issuance Ratio and Staked Amount
     * (if the target token is stable, this is gold token such as PAXG or vice versa)
     *
     * @param _account staker address
     * @param _targetKey external token key
     *
     * @return otherIR other token's Issuance Ratio
     * @return otherEA other token's Staked Amount
     * @return tokenIR target token's Issuance Ratio
     */
    function _otherTokenIREA(address _account, bytes32 _targetKey)
        internal
        view
        returns (
            uint otherIR,
            uint otherEA,
            uint tokenIR,
            uint tokenEA,
            uint exDebt /* , uint minDecimals */
        )
    {
        tokenIR = getExTokenIssuanceRatio(_targetKey);
        uint minDecimals = 18;
        uint oMinDecimals = 18;
        bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();
        for (uint i; i < tokenList.length; i++) {
            exDebt = _stakedAmountOf(_account, tokenList[i], pUSD);
            if (tokenIR != getExTokenIssuanceRatio(tokenList[i])) {
                otherIR = getExTokenIssuanceRatio(tokenList[i]);
                if (exDebt > 0) {
                    otherEA = otherEA.add(exDebt);
                    exDebt = stakingState.tokenDecimals(tokenList[i]);
                    oMinDecimals = oMinDecimals < exDebt ? oMinDecimals : exDebt;
                }
            } else if (exDebt > 0) {
                tokenEA = tokenEA.add(exDebt);
                exDebt = stakingState.tokenDecimals(tokenList[i]);
                minDecimals = minDecimals < exDebt ? minDecimals : exDebt;
            }
        }

        exDebt = _preciseMulToDecimal(tokenEA, tokenIR).roundDownDecimal(uint(18).sub(minDecimals)).add(
            _preciseMulToDecimal(otherEA, otherIR).roundDownDecimal(uint(18).sub(oMinDecimals))
        );

        // minDecimals = minDecimals < oMinDecimals ? minDecimals : oMinDecimals;
    }

    function otherTokenIREA(address _account, bytes32 _targetKey)
        external
        view
        returns (
            uint otherIR,
            uint otherEA,
            uint tokenIR,
            uint tokenEA /* , uint minDecimals */
        )
    {
        (otherIR, otherEA, tokenIR, tokenEA, ) = _otherTokenIREA(_account, _targetKey);
    }

    function tokenStakeStatus(address _account)
        external
        view
        returns (
            bytes32[] memory tokenList,
            uint[] memory stakedAmts,
            uint[] memory decimals,
            uint[] memory balances
        )
    {
        tokenList = stakingState.getTokenCurrencyKeys();
        stakedAmts = new uint[](tokenList.length);
        decimals = new uint[](tokenList.length);
        balances = new uint[](tokenList.length);
        /* tokenIRs = new uint[](tokenList.length); */
        for (uint i; i < tokenList.length; i++) {
            stakedAmts[i] = stakingState.stakedAmountOf(tokenList[i], _account);
            decimals[i] = stakingState.tokenDecimals(tokenList[i]);
            balances[i] = tokenInstance(tokenList[i]).balanceOf(_account);
            // balances[i] = decimals[i] < uint(18)
            //     ? balances[i].mul(10**(uint(18).sub(decimals[i])))
            //     : balances[i];
        }
    }

    /* 
    /**
     * @notice get total external tokens' staking ratio
     *
     * @param _account staker address
     * @param targetRatio external tokens' target ratio
     */
    /*    function _exStakingRatio(address _account, uint targetRatio) internal view returns (uint exSR) {
        // if target ratio is 0, return exSR 0
        // if (targetRatio == getIssuanceRatio()) {
        //     return 0;
        // }

        // get external tokens' target ratio
        exSR = stakingState.getExTargetRatio(_account);

        // get total external tokens' target ratio
        // Ex-Staking Ratio = (Target Ratio - Peri Issuance Ratio) / (Sum of Ex-Token Target Ratio - Peri Issuance Ratio)
        exSR = _preciseDivToDecimal(targetRatio.sub(getIssuanceRatio()), exSR.sub(getIssuanceRatio()));
    }
 */
    /* 
    /**
     * @notice get target token's staking ratio
     * @dev St = (To-Te) / (To-Tt)
     * @param exTR external token's target ratio
     * @param tokenIR target token's issuance ratio
     * @param otherIR the other token's issuance ratio
     */
    /*    function _tokenSR(
        uint exTR,
        uint tokenIR,
        uint otherIR
    ) internal pure returns (uint tokenSR) {
        // get target token's staking ratio
        // Token Staking Ratio = +/-( Ex-Target Ratio - Token Issuance Ratio ) / +/-( Token Issuance Ratio - Other Issuance Ratio )
        tokenSR = tokenIR > otherIR
            ? _preciseDivToDecimal(exTR.sub(otherIR), tokenIR.sub(otherIR))
            : _preciseDivToDecimal(otherIR.sub(exTR), otherIR.sub(tokenIR));
    }
 */
    /* 
    /**
     * @notice get target token's staking ratio and the other token's issuance ratio(if the target token is stable, this is Gold token such as PAXG)
     * @dev only keeps 2 types of external tokens' staking ratio. one is
     * , the other is non-stables such as PAXG.
     * @param _account staker address
     * @param _targetKey external token key
     *
     * @return tokenER target token's staking ratio
     * @return otherIR the other token's issuance ratio
     * @return tokenIR target token's issuance ratio
     */
    /*    function _tokenER(address _account, bytes32 _targetKey)
        internal
        view
        returns (
            uint tokenER,
            uint otherIR,
            uint tokenIR
        )
    {
        // get the other token's issuance ratio if the target token is stable, it is non-stable token such as PAXG
        // tokenER(Estimated Value Ratio) = otherEA(other type's Estimated Value Amount) at the moment
        uint tokenEA;
        (otherIR, tokenER, tokenIR, tokenEA, ) = _otherTokenIREA(_account, _targetKey);

        // if there is no other token, return tokenER 0, otherIR, tokenIR
        if (tokenEA == 0) {
            return (0, otherIR, tokenIR);
        }

        // get target token's estimated value ratio tokenER = tokenEA / ( tokenEA + otherEA )
        tokenER = _preciseDivToDecimal(tokenEA, tokenEA.add(tokenER));
    }
 */
    /*
    /**
     * @notice get max ex-tokens' stakable amount in _unitKey for the current target ratio
     *
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _unitKey external token key
     *
     * @return maxAmount max ex-tokens' stakable amount in _unitKey for the current target ratio
     */
    /*    function _maxExAmtToTRatio(
        address _account,
        uint _existDebt,
        bytes32 _unitKey
    ) internal view returns (uint maxAmount) {
        uint tRatio = _targetRatio(_account);
        // get max stakable amount : maxAmt = Debt Balance * Staking Ratio / Target Ratio
        maxAmount = _preciseMulToDecimal(_existDebt, _exStakingRatio(_account, tRatio));
        maxAmount = _preciseDivToDecimal(maxAmount, tRatio);

        // convert maxAmount to _unitKey
        if (_unitKey != pUSD) {
            maxAmount = _preciseDivToDecimal(maxAmount, _rateCheck(_unitKey));
        }
    }
*/

    /**
     * @notice get burn amount to meet the current target ratio
     *
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _periCol PERI collateral amount
     *
     * @return burnAmount burn amount
     * @return exRefundAmt ex-refund amount
     */
    function _burnAmtToFitTR(
        address _account,
        uint _existDebt,
        uint _periCol
    )
        internal
        view
        returns (
            uint burnAmount,
            uint exRefundAmt,
            uint exEA
        )
    {
        // get ex-debt and ex-staked amount
        uint exDebt;
        uint exTRatio;
        (exDebt, exEA, exTRatio) = _calcExEADebt(_account);

        // require(exEA > 0, "No external token staked");

        if (exEA == 0) {
            exDebt = _preciseMulToDecimal(_periCol, getIssuanceRatio());
            burnAmount = _existDebt > exDebt ? _existDebt.sub(exDebt) : 0;
            return (burnAmount, 0, 0);
        }

        /* // get total SA(D) 
        uint tmpEA = _existDebt > exDebt
            ? _preciseDivToDecimal(_existDebt.sub(exDebt), getIssuanceRatio()).add(exEA)
            : exEA; //_preciseDivToDecimal(_existDebt, exTRatio);

        // calc max ex-token value upto max target ratio(0.5)
        // maxAmt = { (Tmax - Tp) * V + (Te - Tp) * Ve } / (Te - Tmax)
        tmpEA = _preciseMulToDecimal(tmpEA, getExternalTokenQuota().sub(getIssuanceRatio()));
        uint tmpExEA = _preciseMulToDecimal(tmpEA, exTRatio.sub(getIssuanceRatio()));
        exRefundAmt = tmpExEA > tmpEA 
            ? _preciseDivToDecimal(tmpExEA.sub( exEA ), exTRatio.sub(getExternalTokenQuota()))
            : 0;

        exDebt = exRefundAmt > 0 
            ? _preciseMulToDecimal(exEA.sub(exRefundAmt), exTRatio)
            : exDebt;

        // get peri estimated debt from the collateral value
        uint periCol2Debt = _preciseMulToDecimal(_periCol, getIssuanceRatio());

        // get periDebt
        uint periDebt = _existDebt.sub(exDebt);
        burnAmount = periDebt > periCol2Debt ? periDebt.sub(periCol2Debt) : 0; */
        uint periIR = getIssuanceRatio();
        uint maxSR = getExternalTokenQuota();

        // get ex-staking ratio for max target ratio(0.5)
        // Se-max = (Tmax - Tp) / (Te - Tp)
        uint tmpExSR = _preciseDivToDecimal(maxSR.sub(periIR), exTRatio.sub(periIR));

        // get exSA for max target ratio(0.5) and save it to tmpExEA
        uint tmpExEA = _preciseDivToDecimal(_existDebt, maxSR);
        tmpExEA = _preciseMulToDecimal(tmpExEA, tmpExSR);

        // get peri estimated debt from the collateral value
        uint periCol2Debt = _preciseMulToDecimal(_periCol, periIR);

        // in case exDebt > maxExDebt, periDebt = existDebt - maxExDebt,
        // otherwiase periDebt = existDebt - exDebt
        uint periDebt = exEA > tmpExEA ? _existDebt.sub(_preciseMulToDecimal(tmpExEA, exTRatio)) : _existDebt.sub(exDebt);

        // when periDebt is bigger than peri collateral-converted debt
        if (periDebt > periCol2Debt) {
            // fix max EX-token Staking Amount with peri's collateral and max exSR (exSA = periCol * exSR / (1 - exSR)
            tmpExEA = _preciseDivToDecimal(_preciseMulToDecimal(_periCol, tmpExSR), SafeDecimalMath.unit().sub(tmpExSR));

            // calc max debt amount by adding periCol2Debt and max exDebt
            exDebt = periCol2Debt.add(exEA > tmpExEA ? _preciseMulToDecimal(tmpExEA, exTRatio) : exDebt);
            // calc burn amount by substracting sum of max exDebt and periCol2Debt from existDebt
            burnAmount = _existDebt > exDebt ? _existDebt.sub(exDebt) : 0;

            // // calc exRefundAmt by substracting max ex-tokens' SA from ex-tokens' SA
            // exRefundAmt = tmpExEA >= exEA ? exEA.sub(tmpExEA) : 0;
        }

        // calc exRefundAmt by substracting max ex-tokens' SA from ex-tokens' SA
        exRefundAmt = exEA > tmpExEA ? exEA.sub(tmpExEA) : 0;

        /* uint periDebt;
        // if exDebt is bigger than max exDebt
        if (exEA > tmpExEA) {
            // convert the exSA to exDebt for max target ratio(0.5) and get periDebt
            periDebt = _existDebt.sub(tmpExEA.multiplyDecimal(exTRatio));

            // when periDebt is bigger than peri collateral-converted debt
            if (periDebt > periCol2Debt) {

                // calc max EX-token Staking Amount with peri's collateral and max exSR
                tmpExEA = _periCol.multiplyDecimal(tmpExSR).divideDecimal(SafeDecimalMath.unit().sub(tmpExSR));

                // calc burn amount by substracting sum of max exDebt and periCol2Debt from existDebt
                burnAmount = _existDebt.sub(periCol2Debt.add(tmpExEA.multiplyDecimal(exTRatio)));
            }

            // calc exRefundAmt by substracting max ex-tokens' SA from ex-tokens' SA
            exRefundAmt = exEA.sub(tmpExEA);

        // when exDebt is smaller than max exDebt
        } else {
            // get periDebt
            periDebt = _existDebt.sub(exDebt);
            // burnAmount = (periDebt > periCol2Debt) ? periDebt.sub(periCol2Debt) : 0;
            if (periDebt > periCol2Debt) {

                // calc max EX-token Staking Amount with peri's collateral and max exSR
                tmpExEA = _periCol.multiplyDecimal(tmpExSR).divideDecimal(SafeDecimalMath.unit().sub(tmpExSR));

                // calc burn amount by substracting sum of max exDebt and periCol2Debt from existDebt
                burnAmount = _existDebt.sub(periCol2Debt.add(tmpExEA.multiplyDecimal(exTRatio)));

                // calc exRefundAmt by substracting max ex-tokens' SA from ex-tokens' SA
                exRefundAmt = exEA.sub(tmpExEA);
            }
            
        } */
    }

    /* 
    /**
     * @notice get needed ex-tokens amount in _unitKey to meet max target ratio (ex. 0.5)
     * @dev needed amount(to max target ratio)
     *      X = { ( Tt - Tp ) * Vt + ( To - Tp ) * Do - ( Tmax - Tp ) * (D - Dt + Vt) } / ( Tmax - Tt )
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _unitKey external token key
     *
     * @return exTRatio ex-tokens' staking ratio
     * @return addableAmt needed ex-tokens amount in _unitKey to meet max target ratio (ex. 0.5)
     */
    /*    function _maxStakableAmountOf(
        address _account,
        uint _existDebt,
        bytes32 _targetKey,
        bytes32 _unitKey
    ) internal view returns (uint exTRatio, uint addableAmt) {
        // get tokenEA(Vt), tokenIR(Tt), otherEA, otherIR(To) and decimals
        (uint otherIR, uint otherEA, uint tokenIR, uint tokenEA, ) = _otherTokenIREA(_account, _targetKey);

        exTRatio = _targetRatio(_account);

        // get total SA(D) : totalSA = _existDebt / Target Ratio
        uint debt2ToSA = _existDebt.divideDecimal(exTRatio);

        // get ex-staking ratio(exSR) and ex-staked amount(exSA)
        uint exSA = debt2ToSA.multiplyDecimal(_exStakingRatio(_account, exTRatio));

        // if exSA < exEA return (0, 0)
        if (exSA < tokenEA.add(otherEA)) {
            return (0, 0);
        }

        // calc otherSA(Do) : otherSA = exSA * otherSR
        otherEA = exSA != 0
            ? _preciseMulToDecimal(_tokenSR(stakingState.getExTargetRatio(_account), otherIR, tokenIR), exSA)
            : 0;

        // get target token's stakable amount  getExternalTokenQuota() = Tmax
        // X = [ ( Tmax - Tp ) * (D + Vt - Dt) - { ( Tt - Tp ) * Vt + ( To - Tp ) * Do } ] / ( Tt - Tmax )
        // addableAmt = ( Tt - Tp ) * Vt : always Tt > Tp
        addableAmt = _preciseMulToDecimal(tokenIR.sub(getIssuanceRatio()), tokenEA);

        // addableAmt = addableAmt + ( To - Tp ) * Do : always To > Tp
        addableAmt = addableAmt.add(_preciseMulToDecimal(otherIR.sub(getIssuanceRatio()), otherEA));

        // calc tokenSA(Dt) : tokenSA = tokenSR * exSA
        exSA = exSA != 0
            ? _preciseMulToDecimal(_tokenSR(stakingState.getExTargetRatio(_account), tokenIR, otherIR), exSA)
            : 0;

        // debt2ToSA = ( Tmax - Tp ) * (D + Vt - Dt)
        debt2ToSA = _preciseMulToDecimal(getExternalTokenQuota().sub(getIssuanceRatio()), debt2ToSA.add(tokenEA).sub(exSA));

        // if target token's EA > target token's SA, return (token's SA, 0)
        if (debt2ToSA < addableAmt) {
            return (exSA, 0);
        }

        // addableAmt = ( Tmax - Tp ) * (D + Vt - Dt) - addableAmt : always Tmax > Tp
        addableAmt = debt2ToSA.sub(addableAmt);

        // addableAmt = addableAmt / ( Tt - Tmax )
        addableAmt = _preciseDivToDecimal(addableAmt, tokenIR.sub(getExternalTokenQuota()));

        // round down it upto minimum decimals among staking token list.
        addableAmt = addableAmt.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_targetKey)));

        // we need to consider the decimals of the ex-token.
        // get all of _currencyKey token's amount of from the user wallet
        uint tokenPUSDValue = _tokenPUSDValueOf(_account, _targetKey);

        // cap the staking amount within the user's wallet amount
        addableAmt = tokenPUSDValue < addableAmt ? tokenPUSDValue : addableAmt;

        if (_unitKey != pUSD) {
            // addableAmt = _preciseDivToDecimal(addableAmt, _rateCheck(_unitKey));
            addableAmt = addableAmt.divideDecimal(_rateCheck(_unitKey));
        }

        // get token's staking ratio and save it to exTRatio
        exTRatio = tokenEA.add(addableAmt).divideDecimal(tokenEA.add(addableAmt).add(otherEA));

        // get exTRatio
        exTRatio = _toTRatio(otherIR, tokenIR, exTRatio);
    }
 */
    /*
    /**
     * @notice get new target ratio of the staker
     * @dev _debt2TotSA should not be exeeded the staked tokens' total amount in USD, which means _debt2TotSA needs to be caluculated before calling this function.
     *      _debt2TotSA can be calculated by combinedStakedAmountOf() function or stakedAmountOf() function with token key.
     * @param _amount external token amount in USD
     * @param _debt2TotSA existing total staked amount based on the existing debt
     * @param _debt2ExSA external token's staking amount based on the existing debt
     * @param _periIR Peri Issuance Ratio
     * @param _exTargetRatio external token's target ratio
     * @param _stake if true, it is staking, otherwise unstaking
     */
    /*    function _calcTargetRatio(
        uint _amount,
        uint _debt2TotSA,
        uint _debt2ExSA,
        uint _periIR,
        uint _exTargetRatio,
        bool _stake
    ) internal pure returns (uint estTargetRatio) {
        // if _ex-Staking Amount is 0, it means there is no external token staked yet, so _stake must be true
        if (!_stake && _debt2ExSA == 0) {
            return 0;
        }

        // get new staking ratio(var: estTargetRatio) :
        // Ex-Staking Ratio = (Staked Amount(Ex) +/- Ex-Staking Amount) / ( Staked Amount(Total) +/- Ex-Staking Amount)
        estTargetRatio = !_stake
            ? _debt2ExSA > _amount ? _debt2ExSA.sub(_amount).divideDecimal(_debt2TotSA.sub(_amount)) : 0 // : _preciseDivToDecimal(_debt2ExSA.add(_amount), _debt2TotSA.add(_amount));
            : _debt2ExSA.add(_amount).divideDecimal(_debt2TotSA.add(_amount));

        // get new target ratio(var: estTargetRatio) :
        // Target Ratio =  Peri Issuance Ratio - (Peri Issuance Ratio - Ex-Target Ratio) * Ex-Staking Ratio
        estTargetRatio = _toTRatio(_periIR, _exTargetRatio, estTargetRatio);
        // estTargetRatio = targetRatio.decimalToPreciseDecimal().multiplyDecimalRoundPrecise(SafeDecimalMath.unit().sub(estTargetRatio))
        //     .add(exTargetRatio.decimalToPreciseDecimal().multiplyDecimalRoundPrecise(estTargetRatio)).preciseDecimalToDecimal();
    }
*/
    /*
    /**
     * @notice calculate new target ratios of the staker
     *
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _amount adding/subtracting token value amount
     * @param _targetKey external token key
     * @param _stake if true, it is staking, otherwise unstaking
     *
     * @return exTargetRatio external token's target ratio
     * @return targetRatio total external tokens' target ratio
     * @return changedAmt if staking, CHANGED DEBT amount, if not, UNSTAKING amount in USD
     */
    /*    function _expectedTargetRatios(
        address _account,
        uint _existDebt,
        uint _amount,
        bytes32 _targetKey,
        bool _stake
    )
        internal
        view
        returns (
            uint exTargetRatio,
            uint targetRatio,
            uint changedAmt
        )
    {
        // get the staker's Target Ratio
        targetRatio = _targetRatio(_account);

        // get the staker's existing staked amount in USD based on existing debt (debt / target ratio)
        uint debt2totSA = _preciseDivToDecimal(_existDebt, targetRatio);

        // get the staker's old external staked amount in USD based on the debt and the external staking ratio
        uint debt2ExSA = _preciseMulToDecimal(debt2totSA, _exStakingRatio(_account, targetRatio));

        // get the staker's External token Target Ratio
        (exTargetRatio, changedAmt) = _expectedExTargetRatio(_account, debt2ExSA, _amount, _targetKey, _stake);

        // get the staker's Target Ratio
        targetRatio = _calcTargetRatio(changedAmt, debt2totSA, debt2ExSA, getIssuanceRatio(), exTargetRatio, _stake);
    }
*/
    /*
    /**
     * @notice calculate new ex-target ratio of the staker
     *
     * @param _account staker address
     * @param _debt2ExSA existing debt amount
     * @param _amount external token amount
     * @param _targetKey external token key
     * @param _stake if true, it is staking, otherwise unstaking
     *
     * @return exTRatio external token's target ratio
     * @return changedAmt if staking, CHANGED DEBT amount, if not, UNSTAKING amount in USD
     */
    /*    function _expectedExTargetRatio(
        address _account,
        uint _debt2ExSA,
        uint _amount,
        bytes32 _targetKey,
        bool _stake
    ) internal view returns (uint exTRatio, uint changedAmt) {
        // get ex-target ratio
        exTRatio = stakingState.getExTargetRatio(_account);
        if (exTRatio == SafeDecimalMath.unit()) {
            return (getExTokenIssuanceRatio(_targetKey), _amount);
        }

        // get the other token's Issuance Ratio, target token's Issuance Ratio and target token's Staked Amount
        (uint otherIR, , uint tokenIR, uint tokenEA, ) = _otherTokenIREA(_account, _targetKey);

        // get target SR : St = (To-Te) / (To-Tt)
        uint tokenSR = _tokenSR(exTRatio, tokenIR, otherIR);

        // get target token's staked amount in USD based on the debt
        uint tokenSA = tokenSR != 0 ? _preciseMulToDecimal(_debt2ExSA, tokenSR) : 0;

        // applying the decimals of the target token.
        tokenSA = tokenSA.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_targetKey)));

        // get SA change in order for debt change in USD
        changedAmt = tokenEA.add(_amount);
        _amount = _stake ? changedAmt > tokenSA ? changedAmt.sub(tokenSA) : 0 : tokenEA > tokenSA
            ? _amount > tokenEA.sub(tokenSA) ? _amount.sub(tokenEA.sub(tokenSA)) : _amount
            : _amount;

        // if _amount is 0, return exTRatio, 0
        if (_amount == 0) {
            return (exTRatio, 0);
        }

        // calc staking/ unstaking amount in USD
        changedAmt = _stake ? _amount : tokenSA >= tokenEA
            ? _amount >= tokenSA.sub(tokenEA) ? _amount.sub(tokenSA.sub(tokenEA)) : 0
            : _amount.add(tokenEA.sub(tokenSA));

        // calc new target token's Staking Ratio
        tokenSR = _stake ? tokenSA.add(_amount).divideDecimal(_debt2ExSA.add(_amount)) : tokenSA > _amount
            ? tokenSA.sub(_amount).divideDecimal(_debt2ExSA.sub(_amount))
            : 0;

        // get new ex-target ratio(var: exTRatio) :
        // Ex-Target Ratio = other Issuance Ratio - (other Issuance Ratio - token Issuance Ratio) * token Staking Ratio
        exTRatio = _toTRatio(otherIR, tokenIR, tokenSR);
    }
*/

    function _toTRatio(
        uint _Tp,
        uint _Te,
        uint _Se
    ) internal pure returns (uint) {
        // Target Ratio =  Peri Issuance Ratio - (Peri Issuance Ratio - Ex-Staking Ratio) * Ex-Staking Ratio
        (uint temp, bool sub) = _Tp > _Te ? (_Tp.sub(_Te), true) : (_Te.sub(_Tp), false);

        return sub ? _Tp.sub(_preciseMulToDecimal(temp, _Se)) : _Tp.add(_preciseMulToDecimal(temp, _Se));
    }

    /* 
    function calcInitTargetRatios(
        address _account,
        uint _periCol
    )
        external
        view
        onlyIssuer
        returns (
            uint exTRatio,
            uint tRatio,
            uint maxIDebt
        )
    {
        return
            _calcInitTargetRatios(
                _account,
                _periCol
            );
    }
*/
    /*
    /**
     * @notice calulate re-initializable the staker's Target Ratios
     *
     * @param _account staker address
     * @param _periCol Peri Collateral amount
     */
    /*    function _calcInitTargetRatios(
        address _account,
        uint _periCol
    )
        internal
        view
        returns (
            uint tRatio,
            uint exTRatio,
            uint maxIDebt
        )
    {
        // get the other token's issuance ratio that is non-stable token such as PAXG
        (uint otherIR, uint otherEA, uint tokenIR, uint tokenEA, ) = _otherTokenIREA(_account, USDC);

        // if no exEA, return tRatio 0.25
        if (otherEA == 0 && tokenEA == 0) {
            return (getIssuanceRatio(), SafeDecimalMath.unit(), _preciseMulToDecimal(getIssuanceRatio(), _periCol));
        }

        // get old target ratio
        tRatio = _targetRatio(_account);

        // get old total SA(D) : totalSA = _existDebt / Target Ratio
        // uint debt2totSA = _preciseDivToDecimal(_existDebt, tRatio);

        // calc ex-tokens's Estimated Value(exEA)
        tokenEA = otherEA.add(tokenEA);

        // calc the other token's staking ratio(ex: PAXG)
        uint otherSR = tokenEA > 0 ? _preciseDivToDecimal(otherEA, tokenEA) : 0;

        // Ex-Target Ratio = Stable Issuance Ratio - (Stable Issuance Ratio - Other(ex PAXG) Issuance Ratio) * Other(ex:PAXG) Staking Ratio
        exTRatio = otherSR > 0 ? _toTRatio(tokenIR, otherIR, otherSR) : SafeDecimalMath.unit();

        // sum ex-tokens' estimated value and peri collateral to get max stakable value { peri(all in the wallet) + ex-tokens(staked) }
        maxIDebt = _periCol.add(tokenEA);

        // calc new ex-token's staking ratio
        otherSR = tokenEA.divideDecimal(maxIDebt);

        // calc new target ratio
        // Target Ratio =  Peri Issuance Ratio - (Peri Issuance Ratio - Ex-Target Ratio) * Ex-Staking Ratio
        tRatio = _toTRatio(getIssuanceRatio(), exTRatio, otherSR);

        _requireOverIssuanceRatio(tRatio);

        // get max issuable debt : max issuable debt = (max issuable value - staked value) * tRatio
        maxIDebt = _preciseMulToDecimal(tRatio, maxIDebt);
    }
*/
    /*
    /**
     * @notice get needed ex-tokens amount in _unitKey to meet max target ratio (ex. 0.5)
     * @dev needed amount(to max target ratio)
     *      X = { ( Tt - Tp ) * Vt + ( To - Tp ) * Vo - ( Tmax - Tp ) * V } / ( Tmax - Tt )
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _periCol PERI collateral amount
     * @param _targetKey external token key
     *
     * @return exTRatio ex-tokens' staking ratio
     * @return addableAmt needed ex-tokens amount in _unitKey to meet max target ratio (ex. 0.5)
     */
    /*    function _maxExStakableAmt(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    )
        internal
        view
        returns (
            uint addableAmt,
            uint exTRatio,
            uint tRatio
        )
    {
        // get tokenEA(Vt), tokenIR(Tt), otherEA(Vo), otherIR(To) and decimals
        (uint otherIR, uint otherEA, uint tokenIR, uint tokenEA, ) = _otherTokenIREA(_account, _targetKey);

        tRatio = _targetRatio(_account);

        // get total SA(D) : totalSA = _existDebt / Target Ratio
        uint totalSA = _existDebt.divideDecimal(tRatio);

        // get taltal EA(V) : totalSA = totalSA + exEA - exSA
        totalSA = totalSA.add(tokenEA.add(otherEA)).sub(totalSA.multiplyDecimal(_exStakingRatio(_account, tRatio)));

        // get target token's stakable amount  getExternalTokenQuota() = Tmax
        // X = [ ( Tmax - Tp ) * V - { ( Tt - Tp ) * Vt + ( To - Tp ) * Vo } ] / ( Tt - Tmax )
        // addableAmt = ( Tt - Tp ) * Vt : always Tt > Tp
        addableAmt = _preciseMulToDecimal(tokenIR.sub(getIssuanceRatio()), tokenEA);

        // addableAmt = addableAmt + ( To - Tp ) * Vo : always To > Tp
        addableAmt = addableAmt.add(_preciseMulToDecimal(otherIR.sub(getIssuanceRatio()), otherEA));

        // tempAmt = ( Tmax - Tp ) * V
        uint tempAmt = _preciseMulToDecimal(getExternalTokenQuota().sub(getIssuanceRatio()), totalSA);

        // if target token's EA > target token's SA, return (old exTRatio, 0)
        if (tempAmt < addableAmt) {
            return (0, stakingState.getExTargetRatio(_account), tRatio);
        }

        // addableAmt = ( Tmax - Tp ) * V - addableAmt : always Tmax > Tp
        addableAmt = tempAmt.sub(addableAmt);

        // addableAmt = addableAmt / ( Tt - Tmax )
        addableAmt = _preciseDivToDecimal(addableAmt, tokenIR.sub(getExternalTokenQuota()));

        // round down it upto minimum decimals among staking token list.
        addableAmt = addableAmt.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_targetKey)));

        // we need to consider the decimals of the ex-token.
        // get all of _currencyKey token's amount of from the user wallet
        uint tokenPUSDValue = _tokenPUSDValueOf(_account, _targetKey);

        // cap the staking amount within the user's wallet amount
        addableAmt = tokenPUSDValue < addableAmt ? tokenPUSDValue : addableAmt;

        // calc new exSA and save it to tempAmt: exSA = exEA + addableAmt
        tempAmt = tokenEA.add(addableAmt).add(otherEA);

        // get max SA
        uint maxSA = _periCol.add(tempAmt);

        // get total SA
        totalSA = totalSA.add(addableAmt);

        // adjust the changed amount and totalSA if totalSA is over maxSA
        (addableAmt, totalSA) = totalSA > maxSA
            ? (addableAmt > totalSA.sub(maxSA) ? addableAmt.sub(totalSA.sub(maxSA)) : 0, maxSA)
            : (addableAmt, totalSA);

        // get token's staking ratio and save it to exTRatio
        exTRatio = tokenEA.add(addableAmt).divideDecimal(tempAmt);

        // get new exTRatio
        exTRatio = _toTRatio(otherIR, tokenIR, exTRatio);

        // calc new exSR and save it to tRatio
        tRatio = tempAmt.divideDecimal(totalSA);

        // calc new tRatio : tRatio = Tp + ( Te - Tp) * Se
        tRatio = _toTRatio(getIssuanceRatio(), exTRatio, tRatio);
    }
*/
    function _calcMaxStakableAmt(
        uint _tokenIR,
        uint _otherIR,
        uint _tokenEA,
        uint _otherEA,
        uint _totalEA
    ) internal view returns (uint addableAmt) {
        uint periIR = getIssuanceRatio();
        uint maxSR = getExternalTokenQuota();
        // get target token's stakable amount  getExternalTokenQuota() = Tmax
        // X = [ ( Tmax - Tp ) * V - { ( Tt - Tp ) * Vt + ( To - Tp ) * Vo } ] / ( Tt - Tmax )
        // addableAmt = ( Tt - Tp ) * Vt : always Tt > Tp
        uint temp = _tokenIR.sub(periIR);
        addableAmt = _preciseMulToDecimal(temp, _tokenEA);

        // addableAmt = addableAmt + ( To - Tp ) * Vo : always To > Tp
        temp = _preciseMulToDecimal(_otherIR > 0 ? _otherIR.sub(periIR) : 0, _otherEA);
        addableAmt = addableAmt.add(temp);

        // temp = ( Tmax - Tp ) * V : always Tmax > Tp
        temp = maxSR.sub(periIR);
        temp = _preciseMulToDecimal(temp, _totalEA);

        // if target token's EA > target token's SA, return (old exTRatio, 0)
        if (temp < addableAmt) {
            return 0;
        }

        // addableAmt = ( Tmax - Tp ) * V - addableAmt
        addableAmt = temp.sub(addableAmt);

        // addableAmt = addableAmt / ( Tt - Tmax )
        temp = _tokenIR.sub(maxSR);
        addableAmt = _preciseDivToDecimal(addableAmt, temp);
    }

    /**
     * @notice get target token's staking ratio if _addAmt is 0, it retruns current target ratio. if not it returns new target ratio with _addAmt
     *      ( periCol * Tp + stableEA * Ts + paxg * To ) < _existDebt --> lower c-Ratio : burning debt is first thing to do.
     *      both ex-token staking and peri staking need to remove debt first and if there is any extra staking amount, it can be converted to newly added debt.
     *      X = [ ( Tmax - Tp ) * V - { ( Tt - Tp ) * Vt + ( To - Tp ) * Vo } ] / ( Tt - Tmax )
     *      if ex-token staking amount reaches to max, no more ex-token staking is allowed.
     *      if periSA > periCol, any ex-token staking can't compensate peri's over-collateral debt. ex-token staking is only possible to compensate its max addable amount of debt.
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _addAmt adding token value amount
     * @param _targetKey external token key
     *
     * @return tRatio target ratio
     * @return addableAmt max stakable amount in USD(exMaxStakableAmt call)
     * @return addDebt adding debt amount in USD
     */
    function _getTRAddDebtOrAmt(
        address _account,
        uint _existDebt,
        uint _addAmt,
        uint _periCol,
        bytes32 _targetKey
    )
        internal
        view
        returns (
            uint tRatio,
            uint addDebt,
            uint addableAmt
        )
    {
        if (_existDebt == 0) {
            return (getIssuanceRatio(), 0, 0);
        }
        // get tokenEA, otherEA, tokenIR, otherIR, exDebt
        (uint otherIR, uint otherEA, uint tokenIR, uint tokenEA, uint exDebt) =
            _otherTokenIREA(_account, _targetKey == bytes32(0) ? USDC : _targetKey);

        // get exEA
        uint exEA = tokenEA.add(otherEA);

        // // get exDebt = tokenEA * tokenIR + otherEA * otherIR
        // uint exDebt = _preciseMulToDecimal(tokenEA, tokenIR).add(
        //     _preciseMulToDecimal(otherEA, otherIR)
        // );

        // get peri debt amount
        uint periDebt = _existDebt > exDebt ? _existDebt.sub(exDebt) : 0;

        // if _addAmt is not 0, the function call while staking ex-token.
        // if _targetKey is not 0, the function call is for getting max ex-stakable amount.
        if (_addAmt != 0 || _targetKey != bytes32(0)) {
            // if max ex-stakable amount call
            if (_addAmt == 0) {
                // calc total SA = exEA + (periDebt / PERI Issuance Ratio)
                addDebt = exEA.add(
                    periDebt > _preciseMulToDecimal(_periCol, getIssuanceRatio())
                        ? _periCol
                        : _preciseDivToDecimal(periDebt, getIssuanceRatio())
                );

                // get addable amount within max ex-issuance ratio
                addableAmt = _calcMaxStakableAmt(tokenIR, otherIR, tokenEA, otherEA, addDebt);

                // get all of _currencyKey token's amount of from the user wallet
                addDebt = _tokenPUSDValueOf(_account, _targetKey);

                // cap the staking amount within the user's wallet amount
                addableAmt = addDebt < addableAmt ? addDebt : addableAmt;

                addDebt = _preciseMulToDecimal(addableAmt, getExTokenIssuanceRatio(_targetKey));

                // if staking ex-token call
            } else {
                // get adding debt(addDebt) and staking amount(_addAmt) in USD
                (addDebt, addableAmt) = (_addAmt, _preciseDivToDecimal(_addAmt, getExTokenIssuanceRatio(_targetKey)));
                addableAmt = addableAmt.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_targetKey)));
            }

            // ** check if ex-token staking is able to compensate over-collateral debt
            // if exist periDebt is less than periCol2Debt, which means there is enough PERI in the wallet, set adding debt = addDebt
            // if not and addDebt is more than periDebt - periCol2Debt,
            //  which means PERI value in the wallen doss not cover addDebt + existing debt but covers existing debt,
            //  set adding debt = addDebt - (periDebt - periCol2Debt)
            // otherwise, adding debt = 0
            // get peri-collateral-converted debt : periCol2Debt = periCol * Tp
            _periCol = _preciseMulToDecimal(_periCol, getIssuanceRatio());
            addDebt = periDebt > _periCol
                ? addDebt > periDebt.sub(_periCol) ? addDebt.sub(periDebt.sub(_periCol)) : 0
                : addDebt;

            // ** get updated exEA and ex-debt
            // update tokenEA = tokenEA + addableAmt
            tokenEA = tokenEA.add(addableAmt);
            // update exEA = updated tokenEA + otherEA
            exEA = tokenEA.add(otherEA);

            // update periDebt = tokenEA * tokenIR + otherEA * otherIR
            exDebt = _preciseMulToDecimal(tokenEA, tokenIR).add(_preciseMulToDecimal(otherEA, otherIR));

            // update periDebt = _existDebt + addDebt - exDebt
            _existDebt = _existDebt.add(addDebt);
            periDebt = _existDebt > exDebt ? _existDebt.sub(exDebt) : 0;
        }

        // if no exEA, return getIssuanceRatio(), 0, _preciseDivToDecimal(_existDebt, getIssuanceRatio())
        if (exEA == 0) {
            return (getIssuanceRatio(), addDebt, addableAmt);
        }

        // get peri SA = periDebt / peri issuance ratio
        periDebt = _preciseDivToDecimal(periDebt, getIssuanceRatio());

        // get exTRatio and save it to tRatio (Te = To - (To - Tt) * St)
        tRatio = _toTRatio(otherIR, tokenIR, tokenEA.divideDecimal(exEA));

        // get ex-Staking Ratio and calc TRatio (Tp + ( Te - Tp) * Se)
        tRatio = _toTRatio(getIssuanceRatio(), tRatio, exEA.divideDecimal(exEA.add(periDebt)));
    }

    /*
    /**
     * @notice calulate changed external token's staking amount, new target ratios and ex-target ratio
     *
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _periCol Peri Collateral amount
     * @param _targetKey external token key
     *
     * @return tRatio target ratio
     * @return exTRatio external token's target ratio
     * @return changedAmt External Changed Staked Aamount(exCSA) in USD
     */
    /*    function _calcTRatio(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    )
        internal
        view
        returns (
            uint tRatio,
            uint exTRatio,
            uint exChangeSA
        )
    {
        // get the staker's Target Ratio
        tRatio = _targetRatio(_account);

        // get the staker's existing staked amount in USD based on existing debt (debt / target ratio)
        uint totalSA = _preciseDivToDecimal(_existDebt, tRatio);

        // get the staker's old external staked amount in USD based on the debt and the external staking ratio
        uint exSA = _preciseMulToDecimal(totalSA, _exStakingRatio(_account, tRatio));

        // get the other token's Issuance Ratio, target token's Issuance Ratio and target token's Staked Amount
        (uint otherIR, uint otherEA, uint tokenIR, uint tokenEA, ) = _otherTokenIREA(_account, _targetKey);

        // get ex-target ratio
        exTRatio = stakingState.getExTargetRatio(_account);

        // get target SR : St = (To-Te) / (To-Tt)
        uint tokenSA = _tokenSR(exTRatio, tokenIR, otherIR);

        // get target token's staked amount in USD based on the debt
        tokenSA = _preciseMulToDecimal(exSA, tokenSA);

        // applying the decimals of the ex-token.
        tokenSA = tokenSA > 0 ? tokenSA.roundDownDecimal(uint(18).sub(stakingState.tokenDecimals(_targetKey))) : 0;

        // calc external staked amount(exEA) and save it to otherEA
        uint exEA = tokenEA.add(otherEA);

        // if there is no newly added debt, return exTRatio
        if (tokenEA <= tokenSA) {
            return (tRatio, exTRatio, 0);
        }

        // get Max SA and save it to tokenSA
        uint maxSA = _periCol.add(exEA);

        // get changed amount
        exChangeSA = tokenEA.sub(tokenSA);

        totalSA = totalSA.add(exEA).sub(exSA);

        // (exTRatio, tRatio, exChangeSA) = _calcSAChange(exChangeSA, totalSA, maxSA, tokenEA, exEA, tokenIR, otherIR);

        (exChangeSA, totalSA) = totalSA > maxSA
            ? (exChangeSA > totalSA.sub(maxSA) ? exChangeSA.sub(totalSA.sub(maxSA)) : 0, maxSA)
            : (exChangeSA, totalSA);

        // get token's staking ratio and save it to exTRatio
        exTRatio = tokenEA.divideDecimal(exEA);

        // get new exTRatio
        exTRatio = _toTRatio(otherIR, tokenIR, exTRatio);

        // calc new exSR and save it to tRatio
        tRatio = exEA.divideDecimal(totalSA);

        // calc new tRatio : tRatio = Tp + ( Te - Tp) * Se
        tRatio = _toTRatio(getIssuanceRatio(), exTRatio, tRatio);
    }
*/
    /* ========== MUTATIVE FUNCTIONS ========== */
    /* 
    function setTargetRatios(
        address _account,
        uint _tRatio,
        uint _exTRatio
    ) external onlyIssuer {
        stakingState.setExTargetRatio(_account, _exTRatio);
        stakingState.setTargetRatio(_account, _tRatio);
    }
 */
    /* 
    function _saveTRatios(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    ) internal returns (uint changedAmt) {
        // get the staker's expected External token Target Ratio and Target Ratio
        uint tRatio;
        uint exTRatio;
        (tRatio, exTRatio, changedAmt) = _calcTRatio(_account, _existDebt, _periCol, _targetKey);

        // only if it is staking, check the new target ratio is over the max external issuance ratio
        // _requireOverIssuanceRatio(tRatio);

        // set the staker's External token Target Ratio
        stakingState.setExTargetRatio(_account, exTRatio);

        // set the staker's Target Ratio
        stakingState.setTargetRatio(_account, tRatio);
    }
 */
    /*
    /**
     * @notice It sets 2 target ratios of the staker.
     *
     * @param _account staker address
     * @param _existDebt existing debt amount
     * @param _amount newly adding debt amount
     * @param _targetKey the external key to be staked
     * @param _stake if true, it is staking, otherwise unstaking
     */
    /*    function _setTargetRatios(
        address _account,
        uint _existDebt,
        uint _amount,
        bytes32 _targetKey,
        bool _stake
    ) internal returns (uint changedAmt) {
        // get the staker's expected External token Target Ratio and Target Ratio
        uint exTRatio;
        uint tRatio;
        (exTRatio, tRatio, changedAmt) = _expectedTargetRatios(_account, _existDebt, _amount, _targetKey, _stake);

        // only if it is staking, check the new target ratio is over the max issuance ratio
        require(!_stake || tRatio <= getExternalTokenQuota(), "over max issuance ratio");

        // set the staker's External token Target Ratio
        stakingState.setExTargetRatio(_account, exTRatio);

        // set the staker's Target Ratio
        stakingState.setTargetRatio(_account, tRatio);
    }
*/
    function stakeToMaxExQuota(
        address _account,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey
    ) external onlyIssuer returns (uint debtChange) {
        // this function is not to inculde the other token's staked value decrease.
        // (uint exTRatio, uint maxAddableAmt) = _maxStakableAmountOf(_account, _existDebt, _targetKey, _unitKey);
        // this fuction is to include the other token's staked value decrease in order to get the max stakable value.
        // (uint maxAddableAmt, uint exTRatio, uint tRatio) = _maxExStakableAmt(_account, _existDebt, _periCol, _targetKey);
        uint maxAddableAmt;
        uint tRatio;
        (tRatio, debtChange, maxAddableAmt) = _getTRAddDebtOrAmt(_account, _existDebt, 0, _periCol, _targetKey);
        require(maxAddableAmt > 0, "No available ex-tokens to stake");

        // check if the new target ratio is out of allowed external issuance ratio
        _requireOverIssuanceRatio(tRatio);

        // stake the external token
        _stakeTokens(_account, maxAddableAmt, _targetKey, pUSD);

        // debtChange : issuing debt amount
        // debtChange = _preciseMulToDecimal(getExTokenIssuanceRatio(_targetKey), maxAddableAmt);

        // set ex-target ratio
        // stakingState.setExTargetRatio(_account, exTRatio);

        // set target ratio
        // stakingState.setTargetRatio(_account, tRatio);
    }

    function stake(
        address _account,
        uint _amount,
        uint _existDebt,
        uint _periCol,
        bytes32 _targetKey,
        bytes32 _unitKey
    ) external onlyIssuer returns (uint debtChange) {
        uint tRatio;
        (tRatio, debtChange, _amount) = _getTRAddDebtOrAmt(_account, _existDebt, _amount, _periCol, _targetKey);
        _requireOverIssuanceRatio(tRatio);
        // applying target ratio ( debt to SA ) = (debt / target ratio)
        // changing _amount from requested issuing debt to requested staking amount
        // _amount = _preciseDivToDecimal(_amount, getExTokenIssuanceRatio(_targetKey));

        // set the staker's External token Target Ratio and Target Ratio
        // _amount : requested staking amount
        // debtChange = _setTargetRatios(_account, _existDebt, _amount, _targetKey, true);

        _stakeTokens(_account, _amount, _targetKey, _unitKey);

        // save the staker's Target Ratios
        // debtChange = _saveTRatios(_account, _existDebt, _periCol, _targetKey);

        // debtChange : issuing debt amount
        // debtChange = _preciseMulToDecimal(getExTokenIssuanceRatio(_targetKey), debtChange);
    }

    /**
     * @notice It stakes the external token to the staking contract
     *
     * @param _staker staker address
     * @param _amount adding staking value amount in unit currency
     * @param _targetKey the external key to be staked
     * @param _unitKey the unit currency key
     */
    function _stakeTokens(
        address _staker,
        uint _amount,
        bytes32 _targetKey,
        bytes32 _unitKey
    ) internal {
        // get the staking amount in target currency
        uint stakingAmt = _toCurrency(_unitKey, _targetKey, _amount);

        uint decimals = stakingState.tokenDecimals(_targetKey);

        require(decimals <= 18, "Invalid decimal number");

        decimals = uint(18).sub(decimals);
        stakingAmt = stakingAmt.roundDownDecimal(decimals);

        // uint balance = exToken.balanceOf(_staker).mul(10**(decimals));

        require(
            tokenInstance(_targetKey).transferFrom(_staker, address(stakingState), stakingAmt.div(10**decimals)),
            "Transferring staking token has been failed"
        );

        stakingState.stake(_targetKey, _staker, stakingAmt);
    }

    /**
     * @notice unstakes and moves the external token to the staker's wallet.
     *
     * @param _staker the staker address
     * @param _amount unit currency amount
     * @param _targetKey the external key to be unstaked
     * @param _unitKey the unit currency key
     */
    function unstake(
        address _staker,
        uint _amount,
        /* uint _curDebt,
        uint _periCol, */
        bytes32 _targetKey,
        bytes32 _unitKey
    ) external onlyIssuer {
        // changing _amount from requested issuing debt to requested staking amount
        _amount = _preciseDivToDecimal(_amount, getExTokenIssuanceRatio(_targetKey));
        // _amount = _calcUnstakeAmt(_staker, _amount, _curDebt, _periCol, _targetKey);

        // set the staker's External token Target Ratio and Target Ratio
        // _setTargetRatios(_staker, _existDebt, _amount, _targetKey, false);

        // convert the un-staking amount to one in target currency
        // and unstake the external token
        _unstakeAndRefund(_staker, _staker, _toCurrency(_unitKey, _targetKey, _amount), _targetKey);
    }

    // /**
    //  *
    //  * @param _staker target staker address getting liquidated
    //  * @param _liquidator taker address
    //  * @param _amount unit currency amount getting liquidated
    //  * @param _targetKey the external key to be unstaked
    //  * @param _unitKey the unit currency key
    //  */
    // function unstakeAndLiquidate(
    //     address _staker,
    //     address _liquidator,
    //     uint _amount,
    //     bytes32 _targetKey,
    //     bytes32 _unitKey
    // ) external onlyIssuer {
    //     uint outUnitAmount = _toCurrency(_unitKey, _targetKey, _amount);

    //     _unstakeAndRefund(_staker, _liquidator, outUnitAmount, _targetKey);
    // }

    /**
     * @notice It redeems the external token to move off the debt.
     *
     * @param _account the account address to redeem
     * @param _amount total amount to move off the debt
     *
     * @param _liquidator the liquidator address
     */
    function redeem(
        address _account,
        uint _amount,
        address _liquidator
    ) external onlyLiquidations returns (uint remainAmt) {
        // get ex-staked amount in unit currency
        uint exEA = _combinedStakedAmountOf(_account, pUSD);

        (_amount, remainAmt) = _amount > exEA ? (exEA, _amount.sub(exEA)) : (_amount, 0);

        exEA = _proRataUnstake(_account, _liquidator, _amount, exEA, pUSD);

        remainAmt = remainAmt.add(exEA);
        // _initTargetRatios(_account, _existDebt.sub(_amount));
        /* remainAmount = amount;
        bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();

        for (uint i; i < tokenList.length; i++) {
            if (tokenList[i] == PERI) {
                continue;
            }

            if (remainAmount == 0) {
                break;
            }

            // staked token amount
            uint stakedAmt = _stakedAmountOf(account, tokenList[i], tokenList[i]);

            // if there is staked amount left
            if (stakedAmt > 0) {
                // convert th token amount to pUSD amount
                uint usdAmount = _toCurrency(tokenList[i], pUSD, stakedAmt);

                // staked pUSD value is bgger than remainAmount(getting liquidated pUSD value)
                if (remainAmount < usdAmount) {
                    // replace usdAmount to remainAmount
                    usdAmount = remainAmount;
                    // convert the pUSD amount to the token amount
                    stakedAmt = _toCurrency(pUSD, tokenList[i], usdAmount);
                }

                //uint unstakingAmountConverted = _toCurrency(tokenList[i], pUSD, redeemed);
                //_unstakeAndRefund(account, liquidator, unstakingAmountConverted, tokenList[i]);

                // unstake staked tokens and reward the liquidator
                _unstakeAndRefund(account, liquidator, stakedAmt, tokenList[i]);
                // subtract to-be-moved-off-debt amount by already moved off debt amount
                remainAmount = remainAmount.sub(usdAmount);
            }
        } */
    }

    /* 
    function _proRataRefundAmt(address _staker, uint _amount, uint exEA, bytes32 _unitKey) 
        internal view returns(uint remainAmount) {

        // uint totUnstake = _amount > exEA ? exEA : _amount;

        uint stakedAmt; uint unstakAmt; uint decimals; uint tokenSR;
        // set the currency key order if currencyKeyOrder is not set
        bytes32[] memory keys = stakingState.getTokenCurrencyKeys();
        for (uint i; i < keys.length; i++) {
            // get the staked amount of the token
            stakedAmt = _stakedAmountOf(_staker, keys[i], _unitKey);

            // if the amount to be unstaked is 0, move to the next token
            if (stakedAmt == 0) {
                continue;
            }

            // get the token's staking ratio against ex-staked amount
            tokenSR = stakedAmt.divideDecimal(exEA);
            // get unstake amount
            unstakAmt = _preciseMulToDecimal(_amount, tokenSR).add(remainAmount);
            // get remain amount and cap unstakAmt within stakedAmt
            (remainAmount, unstakAmt) = unstakAmt > stakedAmt 
                ? (unstakAmt.sub(stakedAmt), stakedAmt) 
                : (0, unstakAmt);
            // convert the unstake amount to the token amount
            unstakAmt = _toCurrency(_unitKey, keys[i], unstakAmt);
            // get the token's decimals
            decimals = stakingState.tokenDecimals(keys[i]);
            if (uint(18) > decimals) {
                tokenSR = unstakAmt;
                unstakAmt = unstakAmt.roundDownDecimal(uint(18).sub(decimals));

                // update remainAmount
                tokenSR = tokenSR.sub(unstakAmt);
                tokenSR = _toCurrency(keys[i], _unitKey, tokenSR);
                remainAmount = remainAmount.add(tokenSR);

            }
        }
    }
 */
    /**
     * @notice It unstakes multiple tokens by pre-defined order.
     * @dev internal function
     * @param _staker staker address
     * @param _taker taker address
     * @param _amount amount to get unstaked in unit currency
     * @param _unitKey the currency unit of _amount
     *
     */
    function _proRataUnstake(
        address _staker,
        address _taker,
        uint _amount,
        uint exEA,
        bytes32 _unitKey
    ) internal returns (uint remainAmount) {
        // uint totUnstake = _amount > exEA ? exEA : _amount;

        uint stakedAmt;
        uint unstakAmt;
        uint decimals;
        uint tokenSR;
        uint minDecimals = 16;
        // set the currency key order if currencyKeyOrder is not set
        bytes32[] memory keys = stakingState.getTokenCurrencyKeys();
        for (uint i; i < keys.length; i++) {
            // get the staked amount of the token
            stakedAmt = _stakedAmountOf(_staker, keys[i], _unitKey);

            // if the amount to be unstaked is 0, move to the next token
            if (stakedAmt == 0) {
                continue;
            }

            // get the token's staking ratio against ex-staked amount
            tokenSR = stakedAmt.divideDecimal(exEA);
            // get unstake amount
            unstakAmt = _preciseMulToDecimal(_amount, tokenSR);
            unstakAmt = unstakAmt.add(remainAmount);
            // get remain amount and cap unstakAmt within stakedAmt
            (remainAmount, unstakAmt) = unstakAmt > stakedAmt ? (unstakAmt.sub(stakedAmt), stakedAmt) : (0, unstakAmt);
            // convert the unstake amount to the token amount
            unstakAmt = _toCurrency(_unitKey, keys[i], unstakAmt);
            // get the token's decimals
            decimals = stakingState.tokenDecimals(keys[i]);
            if (uint(18) > decimals) {
                // save unstakAmt to tokenSR
                tokenSR = unstakAmt;
                // round down the unstake amount
                unstakAmt = unstakAmt.roundDownDecimal(uint(18).sub(decimals));

                // update remainAmount
                tokenSR = tokenSR.sub(unstakAmt);
                tokenSR = _toCurrency(keys[i], _unitKey, tokenSR);
                remainAmount = remainAmount.add(tokenSR);
                minDecimals = minDecimals > decimals ? decimals : minDecimals;
            }

            // unstake the token and refund it to the staker/liquidator
            _unstakeAndRefund(_staker, _taker, unstakAmt, keys[i]);
        }

        remainAmount = minDecimals < 18 && remainAmount > 10**(18 - minDecimals) ? remainAmount : 0;
    }

    /**
     * @notice It unstakes multiple tokens by pre-defined order.
     *
     * @param _staker staker address
     * @param _existDebt existing debt amount
     * @param _periCol Peri Collateral amount
     */
    function unstakeToFitTR(
        address _staker,
        uint _existDebt,
        uint _periCol
    ) external onlyIssuer returns (uint burnAmt) {
        // bytes32[] memory currencyKeys = stakingState.getTokenCurrencyKeys();

        // get the order of unstaking
        // bytes32[] memory order;
        // if (!_keyChecker(currencyKeys, currencyKeyOrder)) {
        //     order = currencyKeys;
        // } else {
        //     order = currencyKeyOrder;
        // }

        /*  
        // set the currency key order if currencyKeyOrder is not set
        bytes32[] memory order = stakingState.getTokenCurrencyKeys();
        if (_keyChecker(order, currencyKeyOrder)) {
            order = currencyKeyOrder;
        }

        // get the staker's total staked amount in unit currency
        uint combinedAmount = _combinedStakedAmountOf(_staker, _unitKey);
        require(_combinedStakedAmountOf(_staker, _unitKey) >= _amount, "Combined staked amount is not enough");

        uint[] memory unsakingAmts = new uint[](order.length);
        for (uint i = 0; i < order.length; i++) {
            // get the staked amount of the token
            uint stakedAmt = stakingState.stakedAmountOf(order[i], _staker);

            // Becacuse of exchange rate calculation error,
            // remained unstaking debt amount is converted into each currency rather than converting staked amount.
            uint unstakingAmt = _toCurrency(_unitKey, order[i], _amount);
            // If the token amount is smaller than amount to be unstaked, 
            if (stakedAmt < unstakingAmt) {
                // set the token amount to the amount to be unstaked.
                unsakingAmts[i] = stakedAmt;

                // subtract the amount to be unstaked by the token amount
                // convert remained unstaking amount into unit currency and set it to _amount
                _amount = _toCurrency(order[i], _unitKey, unstakingAmt.sub(stakedAmt));
            } else {
                unsakingAmts[i] = unstakingAmt;

                _amount = 0;
            }

            // unstake the token and refund it to the staker
            _unstakeAndRefund(_staker, _staker, (_amount > 0 ? stakedAmt : unstakingAmt), order[i]);

            // if the amount to be unstaked is 0, break the loop
            if (_amount == 0) {
                break;
            }
        }*/

        uint exRefundAmt;
        uint exEA;
        (burnAmt, exRefundAmt, exEA) = _burnAmtToFitTR(_staker, _existDebt, _periCol);

        require(burnAmt != 0 || exRefundAmt != 0, "Account is already claimable");

        if (exRefundAmt != 0) {
            _proRataUnstake(_staker, _staker, exRefundAmt, exEA, pUSD);
        }

        // _initTargetRatios(_staker, _existDebt.sub(_amount).add(remainAmt));

        // for (uint i = 0; i < order.length; i++) {
        //     if (unsakingAmts[i] == 0) {
        //         continue;
        //     }

        //     _unstakeAndRefund(_staker, _staker, unsakingAmts[i], order[i]);
        // }
    }

    /**
     * @notice unstakes tokens and refund it to the staker or liquidator.
     *
     * @param _unstaker staker address
     * @param _liquidator liquidator or staker address
     * @param _amount amount to get unstaked in unit currency
     * @param _targetKey the currency unit of _amount
     *
     */
    function _unstakeAndRefund(
        address _unstaker,
        address _liquidator,
        uint _amount,
        bytes32 _targetKey
    ) internal tokenRegistered(_targetKey) {
        uint targetDecimals = stakingState.tokenDecimals(_targetKey);
        require(targetDecimals <= 18, "Invalid decimal number");

        // We don't have to round up for staking or unstaking amount.
        // uint unstakingAmountConvertedRoundedUp = _amount.roundUpDecimal(uint(18).sub(targetDecimals));
        uint floorUnstakingAmount = _amount.roundDownDecimal(uint(18).sub(targetDecimals));

        // stakingState.unstake(_targetKey, _unstaker, unstakingAmountConvertedRoundedUp);
        stakingState.unstake(_targetKey, _unstaker, floorUnstakingAmount);

        require(
            // stakingState.refund(_targetKey, _liquidator, unstakingAmountConvertedRoundedUp),
            stakingState.refund(_targetKey, _liquidator, floorUnstakingAmount),
            "Refund has been failed"
        );
    }

    /**
     * @notice Sets stakingState contract address
     *
     * @param _stakingState stakingState contract address
     */
    function setStakingState(address _stakingState) external onlyOwner {
        stakingState = IStakingState(_stakingState);
    }

    /**
     * @notice unstakes all tokens and refund it to the staker.
     *
     * @param _from address of staker
     */
    function exit(address _from) external onlyIssuer {
        bytes32[] memory tokenList = stakingState.getTokenCurrencyKeys();
        for (uint i; i < tokenList.length; i++) {
            uint stakedAmount = _stakedAmountOf(_from, tokenList[i], tokenList[i]);

            if (stakedAmount == 0) {
                continue;
            }

            _unstakeAndRefund(_from, _from, stakedAmount, tokenList[i]);
        }
        // stakingState.setTargetRatio(_from, getIssuanceRatio());
        // stakingState.setExTargetRatio(_from, 0);
    }

    function _requireOverIssuanceRatio(uint _tRatio) internal view {
        require(_tRatio.roundDownDecimal(uint(12)) <= getExternalTokenQuota(), "Over max external quota");
    }

    function _requireRatesNotInvalid(bool anyRateIsInvalid) internal pure {
        require(!anyRateIsInvalid, "A pynth or a external token rate is invalid");
    }

    function _onlyIssuer() internal view {
        require(msg.sender == address(issuer()), "Sender is not Issuer");
    }

    function _onlyLiquidations() internal view {
        require(msg.sender == address(liquidations()), "Sender is not Liquidations");
    }

    function _tokenRegistered(bytes32 _currencyKey) internal view {
        require(stakingState.tokenAddress(_currencyKey) != address(0), "Target token is not registered");
    }

    modifier onlyIssuer() {
        _onlyIssuer();
        _;
    }

    modifier onlyLiquidations() {
        _onlyLiquidations();
        _;
    }

    modifier tokenRegistered(bytes32 _currencyKey) {
        _tokenRegistered(_currencyKey);
        _;
    }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_stakingState","type":"address"},{"internalType":"address","name":"_resolver","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"name","type":"bytes32"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"CacheUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"constant":true,"inputs":[],"name":"CONTRACT_NAME","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"},{"internalType":"uint256","name":"_periCol","type":"uint256"}],"name":"burnAmtToFitTR","outputs":[{"internalType":"uint256","name":"burnAmount","type":"uint256"},{"internalType":"uint256","name":"exRefundAmt","type":"uint256"},{"internalType":"uint256","name":"exEA","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"bytes32","name":"_unitCurrency","type":"bytes32"}],"name":"combinedStakedAmountOf","outputs":[{"internalType":"uint256","name":"combinedSA","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"}],"name":"exStakingRatio","outputs":[{"internalType":"uint256","name":"exSR","type":"uint256"},{"internalType":"uint256","name":"maxSR","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_from","type":"address"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"getExDebt","outputs":[{"internalType":"uint256","name":"exDebt","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"getExEADebt","outputs":[{"internalType":"uint256","name":"exDebt","type":"uint256"},{"internalType":"uint256","name":"exEA","type":"uint256"},{"internalType":"uint256","name":"exTRatio","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"existDebt","type":"uint256"},{"internalType":"uint256","name":"periCol","type":"uint256"}],"name":"getRatios","outputs":[{"internalType":"uint256","name":"tRatio","type":"uint256"},{"internalType":"uint256","name":"cRatio","type":"uint256"},{"internalType":"uint256","name":"exTRatio","type":"uint256"},{"internalType":"uint256","name":"exEA","type":"uint256"},{"internalType":"uint256","name":"exSR","type":"uint256"},{"internalType":"uint256","name":"maxSR","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"}],"name":"getTargetRatio","outputs":[{"internalType":"uint256","name":"tRatio","type":"uint256"},{"internalType":"uint256","name":"exTRatio","type":"uint256"},{"internalType":"uint256","name":"exEA","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"_currencyKey","type":"bytes32"}],"name":"getTokenActivation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"_currencyKey","type":"bytes32"}],"name":"getTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"_currencyKey","type":"bytes32"}],"name":"getTokenDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTokenList","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"bytes32","name":"_currencyKey","type":"bytes32"}],"name":"getTokenPUSDValueOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isResolverCached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"},{"internalType":"uint256","name":"_periCol","type":"uint256"},{"internalType":"bytes32","name":"_targetKey","type":"bytes32"}],"name":"maxStakableAmountOf","outputs":[{"internalType":"uint256","name":"maxAmount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nominateNewOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"bytes32","name":"_targetKey","type":"bytes32"}],"name":"otherTokenIREA","outputs":[{"internalType":"uint256","name":"otherIR","type":"uint256"},{"internalType":"uint256","name":"otherEA","type":"uint256"},{"internalType":"uint256","name":"tokenIR","type":"uint256"},{"internalType":"uint256","name":"tokenEA","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes32","name":"_unitKey","type":"bytes32"}],"name":"proRataUnstake","outputs":[{"internalType":"uint256","name":"remainAmt","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"rebuildCache","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_liquidator","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"remainAmt","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"resolver","outputs":[{"internalType":"contract AddressResolver","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"resolverAddressesRequired","outputs":[{"internalType":"bytes32[]","name":"addresses","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_stakingState","type":"address"}],"name":"setStakingState","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"setupExpiryTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_existDebt","type":"uint256"},{"internalType":"uint256","name":"_periCol","type":"uint256"},{"internalType":"bytes32","name":"_targetKey","type":"bytes32"},{"internalType":"bytes32","name":"_unitKey","type":"bytes32"}],"name":"stake","outputs":[{"internalType":"uint256","name":"debtChange","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"},{"internalType":"uint256","name":"_periCol","type":"uint256"},{"internalType":"bytes32","name":"_targetKey","type":"bytes32"}],"name":"stakeToMaxExQuota","outputs":[{"internalType":"uint256","name":"debtChange","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"bytes32","name":"_currencyKey","type":"bytes32"},{"internalType":"bytes32","name":"_unitCurrency","type":"bytes32"}],"name":"stakedAmountOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stakingState","outputs":[{"internalType":"contract IStakingState","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"tokenStakeStatus","outputs":[{"internalType":"bytes32[]","name":"tokenList","type":"bytes32[]"},{"internalType":"uint256[]","name":"stakedAmts","type":"uint256[]"},{"internalType":"uint256[]","name":"decimals","type":"uint256[]"},{"internalType":"uint256[]","name":"balances","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_staker","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes32","name":"_targetKey","type":"bytes32"},{"internalType":"bytes32","name":"_unitKey","type":"bytes32"}],"name":"unstake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_staker","type":"address"},{"internalType":"uint256","name":"_existDebt","type":"uint256"},{"internalType":"uint256","name":"_periCol","type":"uint256"}],"name":"unstakeToFitTR","outputs":[{"internalType":"uint256","name":"burnAmt","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

608060405234801561001057600080fd5b50604051620041ed380380620041ed8339818101604052606081101561003557600080fd5b50805160208201516040909201519091906249d4008180856001600160a01b0381166100a8576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a150600280546001600160a01b039283166001600160a01b031991821617909155429093016004556005805495909116949092169390931790555050506140a1806200014c6000396000f3fe608060405234801561001057600080fd5b50600436106102415760003560e01c80637a84b61711610145578063b42652e9116100bd578063e082f1d41161008c578063f5faf9bd11610071578063f5faf9bd14610865578063ff18cf46146108b7578063ffb0a081146108ea57610241565b8063e082f1d4146107f8578063ede459561461080057610241565b8063b42652e914610631578063c23d798114610657578063c70cc960146107a0578063dac6e576146107c657610241565b80639c1b38f711610114578063ad47ffc5116100f9578063ad47ffc5146105aa578063b12e4410146105dc578063b23e5053146105f957610241565b80639c1b38f714610558578063a4678bd21461057e57610241565b80637a84b617146104e45780637d52c4ec14610510578063899ffef4146105485780638da5cb5b1461055057610241565b806353a47bb7116101d8578063614d08f8116101a75780636446406c1161018c5780636446406c1461049c57806374185360146104d457806379ba5097146104dc57610241565b8063614d08f81461046257806362197d9a1461046a57610241565b806353a47bb7146103d55780635abebd07146103dd5780635b6f39791461040f5780635c833bfd1461042c57610241565b8063273cbaa011610214578063273cbaa0146102fd5780632af64bd3146103555780633b1cd52b1461037157806346ba2d90146103bb57610241565b806304f3bcec146102465780630fc3fc8c1461026a5780631627540c146102925780631982de22146102b8575b600080fd5b61024e61092e565b604080516001600160a01b039092168252519081900360200190f35b6102906004803603602081101561028057600080fd5b50356001600160a01b031661093d565b005b610290600480360360208110156102a857600080fd5b50356001600160a01b0316610974565b6102e4600480360360408110156102ce57600080fd5b506001600160a01b0381351690602001356109dd565b6040805192835260208301919091528051918290030190f35b610305610a0e565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610341578181015183820152602001610329565b505050509050019250505060405180910390f35b61035d610b22565b604080519115158252519081900360200190f35b61039d6004803603604081101561038757600080fd5b506001600160a01b038135169060200135610c45565b60408051938452602084019290925282820152519081900360600190f35b6103c3610c63565b60408051918252519081900360200190f35b61024e610c69565b61039d600480360360608110156103f357600080fd5b506001600160a01b038135169060208101359060400135610c78565b61035d6004803603602081101561042557600080fd5b5035610c98565b6103c36004803603606081101561044257600080fd5b506001600160a01b03813581169160208101359160409091013516610d2f565b6103c3610da2565b6103c36004803603606081101561048057600080fd5b506001600160a01b038135169060208101359060400135610dc6565b6103c3600480360360808110156104b257600080fd5b506001600160a01b038135169060208101359060408101359060600135610e68565b610290610f02565b6102906110f0565b6103c3600480360360408110156104fa57600080fd5b506001600160a01b0381351690602001356111b9565b6103c36004803603608081101561052657600080fd5b506001600160a01b0381351690602081013590604081013590606001356111ce565b6103056111e9565b61024e6112a1565b6103c36004803603602081101561056e57600080fd5b50356001600160a01b03166112b0565b6103c36004803603604081101561059457600080fd5b506001600160a01b0381351690602001356112bb565b6103c3600480360360608110156105c057600080fd5b506001600160a01b0381351690602081013590604001356112c7565b61024e600480360360208110156105f257600080fd5b50356112de565b6102906004803603608081101561060f57600080fd5b506001600160a01b03813516906020810135906040810135906060013561132a565b6102906004803603602081101561064757600080fd5b50356001600160a01b0316611362565b61067d6004803603602081101561066d57600080fd5b50356001600160a01b03166114f4565b6040518080602001806020018060200180602001858103855289818151815260200191508051906020019060200280838360005b838110156106c95781810151838201526020016106b1565b50505050905001858103845288818151815260200191508051906020019060200280838360005b838110156107085781810151838201526020016106f0565b50505050905001858103835287818151815260200191508051906020019060200280838360005b8381101561074757818101518382015260200161072f565b50505050905001858103825286818151815260200191508051906020019060200280838360005b8381101561078657818101518382015260200161076e565b505050509050019850505050505050505060405180910390f35b61039d600480360360208110156107b657600080fd5b50356001600160a01b03166118d8565b6103c3600480360360608110156107dc57600080fd5b506001600160a01b0381351690602081013590604001356118f3565b61024e611960565b6108326004803603606081101561081657600080fd5b506001600160a01b03813516906020810135906040013561196f565b604080519687526020870195909552858501939093526060850191909152608084015260a0830152519081900360c00190f35b6108916004803603604081101561087b57600080fd5b506001600160a01b038135169060200135611999565b604080519485526020850193909352838301919091526060830152519081900360800190f35b6108d4600480360360208110156108cd57600080fd5b50356119ba565b6040805160ff9092168252519081900360200190f35b6103c3600480360360c081101561090057600080fd5b506001600160a01b038135169060208101359060408101359060608101359060808101359060a00135611a06565b6002546001600160a01b031681565b610945611a45565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b61097c611a45565b600180546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b60008060008060006109ee87611a90565b925092509250610a0086848484611b6d565b945094505050509250929050565b60055460408051635b08123760e01b815290516060926001600160a01b031691635b081237916004808301926000929190829003018186803b158015610a5357600080fd5b505afa158015610a67573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610a9057600080fd5b8101908080516040519392919084640100000000821115610ab057600080fd5b908301906020820185811115610ac557600080fd5b8251866020820283011164010000000082111715610ae257600080fd5b82525081516020918201928201910280838360005b83811015610b0f578181015183820152602001610af7565b5050505090500160405250505090505b90565b60006060610b2e6111e9565b905060005b8151811015610c3c576000828281518110610b4a57fe5b6020908102919091018101516000818152600383526040908190205460025482517f21f8a7210000000000000000000000000000000000000000000000000000000081526004810185905292519395506001600160a01b03918216949116926321f8a721926024808201939291829003018186803b158015610bcb57600080fd5b505afa158015610bdf573d6000803e3d6000fd5b505050506040513d6020811015610bf557600080fd5b50516001600160a01b0316141580610c2257506000818152600360205260409020546001600160a01b0316155b15610c335760009350505050610b1f565b50600101610b33565b50600191505090565b6000806000610c548585611c17565b50929891975095509350505050565b60045481565b6001546001600160a01b031681565b6000806000610c88868686611d94565b9250925092505b93509350939050565b600554604080517f8547dbd90000000000000000000000000000000000000000000000000000000081526004810184905290516000926001600160a01b031691638547dbd9916024808301926020929190829003018186803b158015610cfd57600080fd5b505afa158015610d11573d6000803e3d6000fd5b505050506040513d6020811015610d2757600080fd5b505192915050565b6000610d39611fa7565b6000610d4c85631c1554d160e21b612014565b9050808411610d5d57836000610d6e565b80610d6e858263ffffffff61218e16565b9094509150610d8785848684631c1554d160e21b6121eb565b9050610d99828263ffffffff61252316565b95945050505050565b7f45787465726e616c546f6b656e5374616b654d616e616765720000000000000081565b6000610dd061257d565b600080610dde868686611d94565b9194509250905082151580610df257508115155b610e43576040805162461bcd60e51b815260206004820152601c60248201527f4163636f756e7420697320616c726561647920636c61696d61626c6500000000604482015290519081900360640190fd5b8115610e5f57610e5d86878484631c1554d160e21b6121eb565b505b50509392505050565b6000610e7261257d565b600080610e838787600088886125ea565b9094509250905081610edc576040805162461bcd60e51b815260206004820152601f60248201527f4e6f20617661696c61626c652065782d746f6b656e7320746f207374616b6500604482015290519081900360640190fd5b610ee58161290f565b610ef8878386631c1554d160e21b61297e565b5050949350505050565b6060610f0c6111e9565b905060005b81518110156110ec576000828281518110610f2857fe5b602090810291909101810151600254604080517f5265736f6c766572206d697373696e67207461726765743a200000000000000081860152603980820185905282518083039091018152605982018084527fdacb2d01000000000000000000000000000000000000000000000000000000009052605d8201858152607d83019384528151609d84015281519597506000966001600160a01b039095169563dacb2d01958995939492939260bd0191908501908083838c5b83811015610ff7578181015183820152602001610fdf565b50505050905090810190601f1680156110245780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b15801561104257600080fd5b505afa158015611056573d6000803e3d6000fd5b505050506040513d602081101561106c57600080fd5b5051600083815260036020908152604091829020805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03851690811790915582518681529182015281519293507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa68929081900390910190a15050600101610f11565b5050565b6001546001600160a01b031633146111395760405162461bcd60e51b8152600401808060200182810382526035815260200180613f726035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1600180546000805473ffffffffffffffffffffffffffffffffffffffff199081166001600160a01b03841617909155169055565b60006111c58383612014565b90505b92915050565b60006111de8585600086866125ea565b979650505050505050565b6060806111f4612c22565b60408051600380825260808201909252919250606091906020820183803883390190505090506524b9b9bab2b960d11b8160008151811061123157fe5b6020026020010181815250506c45786368616e6765526174657360981b8160018151811061125b57fe5b6020026020010181815250506b4c69717569646174696f6e7360a01b8160028151811061128457fe5b60200260200101818152505061129a8282612c81565b9250505090565b6000546001600160a01b031681565b60006111c882612d3d565b60006111c58383612d5a565b60006112d4848484612e59565b90505b9392505050565b600554604080516397bb3ce960e01b81526004810184905290516000926001600160a01b0316916397bb3ce9916024808301926020929190829003018186803b158015610cfd57600080fd5b61133261257d565b6113448361133f84612f1d565b612fd1565b925061135c8485611356848688613002565b85613068565b50505050565b61136a61257d565b60055460408051635b08123760e01b815290516060926001600160a01b031691635b081237916004808301926000929190829003018186803b1580156113af57600080fd5b505afa1580156113c3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156113ec57600080fd5b810190808051604051939291908464010000000082111561140c57600080fd5b90830190602082018581111561142157600080fd5b825186602082028301116401000000008211171561143e57600080fd5b82525081516020918201928201910280838360005b8381101561146b578181015183820152602001611453565b50505050905001604052505050905060005b81518110156114ef5760006114b98484848151811061149857fe5b60200260200101518585815181106114ac57fe5b6020026020010151612e59565b9050806114c657506114e7565b6114e58485838686815181106114d857fe5b6020026020010151613068565b505b60010161147d565b505050565b606080606080600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561154857600080fd5b505afa15801561155c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561158557600080fd5b81019080805160405193929190846401000000008211156115a557600080fd5b9083019060208201858111156115ba57600080fd5b82518660208202830111640100000000821117156115d757600080fd5b82525081516020918201928201910280838360005b838110156116045781810151838201526020016115ec565b505050509050016040525050509350835160405190808252806020026020018201604052801561163e578160200160208202803883390190505b509250835160405190808252806020026020018201604052801561166c578160200160208202803883390190505b509150835160405190808252806020026020018201604052801561169a578160200160208202803883390190505b50905060005b84518110156118d05760055485516001600160a01b039091169063480310bb908790849081106116cc57fe5b6020026020010151886040518363ffffffff1660e01b815260040180838152602001826001600160a01b03166001600160a01b031681526020019250505060206040518083038186803b15801561172257600080fd5b505afa158015611736573d6000803e3d6000fd5b505050506040513d602081101561174c57600080fd5b5051845185908390811061175c57fe5b602090810291909101015260055485516001600160a01b03909116906344aedc5f9087908490811061178a57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d60208110156117f057600080fd5b5051835160ff9091169084908390811061180657fe5b60200260200101818152505061182e85828151811061182157fe5b60200260200101516132f3565b6001600160a01b03166370a08231876040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561188357600080fd5b505afa158015611897573d6000803e3d6000fd5b505050506040513d60208110156118ad57600080fd5b505182518390839081106118bd57fe5b60209081029190910101526001016116a0565b509193509193565b60008060006118e684611a90565b9196909550909350915050565b60008060008061190287611a90565b925092509250858310156119475760405162461bcd60e51b81526004018080602001828103825260218152602001806140226021913960400191505060405180910390fd5b6119518682612fd1565b95506111de87888885896121eb565b6005546001600160a01b031681565b600080600080600080611983898989613380565b949e939d50919b50995097509095509350505050565b6000806000806119a986866133e1565b509299919850965090945092505050565b600554604080516344aedc5f60e01b81526004810184905290516000926001600160a01b0316916344aedc5f916024808301926020929190829003018186803b158015610cfd57600080fd5b6000611a1061257d565b6000611a1f88878988886125ea565b985092509050611a2e8161290f565b611a3a8888868661297e565b509695505050505050565b6000546001600160a01b03163314611a8e5760405162461bcd60e51b815260040180806020018281038252602f815260200180613fd2602f913960400191505060405180910390fd5b565b6000806000806000806000611aac88635553444360e01b6133e1565b9a5092965090945092509050611ac8818463ffffffff61252316565b955060008611611b4757736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611b1657600080fd5b505af4158015611b2a573d6000803e3d6000fd5b505050506040513d6020811015611b4057600080fd5b5051611b61565b611b618483611b5c848a63ffffffff61374d16565b613777565b96989597505050505050565b60008083611b8057506000905080611c0e565b6000611b8a6137ef565b90506000868811611b9c576000611bac565b611bac888863ffffffff61218e16565b9050611bb88183612fd1565b9050611bca818763ffffffff61252316565b9050611bdc868263ffffffff61374d16565b9350611c09611bf983611bed6138a6565b9063ffffffff61218e16565b61133f878563ffffffff61218e16565b925050505b94509492505050565b60008080808085611cae57611c2a6137ef565b736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611c6e57600080fd5b505af4158015611c82573d6000803e3d6000fd5b505050506040513d6020811015611c9857600080fd5b5051909550935060009250829150819050611d8a565b6000611cb988611a90565b9650945090506000611cc96137ef565b905084611d585780736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611d1557600080fd5b505af4158015611d29573d6000803e3d6000fd5b505050506040513d6020811015611d3f57600080fd5b5051909750955060009450849350839250611d8a915050565b611d6488838789611b6d565b9094509250828411611d765783611d78565b825b9150611d85818784613777565b965050505b9295509295909350565b6000806000806000611da588611a90565b909450909250905082611df457611dc386611dbe6137ef565b61392c565b9150818711611dd3576000611de3565b611de3878363ffffffff61218e16565b945060009350839250610c8f915050565b6000611dfe6137ef565b90506000611e0a6138a6565b90506000611e31611e21838563ffffffff61218e16565b61133f868663ffffffff61218e16565b90506000611e3f8b84612fd1565b9050611e4b818361392c565b90506000611e598b8661392c565b90506000828911611e7957611e748d8963ffffffff61218e16565b611e93565b611e93611e86848961392c565b8e9063ffffffff61218e16565b905081811115611f7657611f2a611eaa8d8661392c565b61133f86736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611ef257600080fd5b505af4158015611f06573d6000803e3d6000fd5b505050506040513d6020811015611f1c57600080fd5b50519063ffffffff61218e16565b9250611f53838a11611f3c5788611f46565b611f46848961392c565b839063ffffffff61252316565b9750878d11611f63576000611f73565b611f738d8963ffffffff61218e16565b9a505b828911611f84576000611f94565b611f94898463ffffffff61218e16565b9950505050505050505093509350939050565b611faf61394a565b6001600160a01b0316336001600160a01b031614611a8e576040805162461bcd60e51b815260206004820152601a60248201527f53656e646572206973206e6f74204c69717569646174696f6e73000000000000604482015290519081900360640190fd5b60006060600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561206657600080fd5b505afa15801561207a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156120a357600080fd5b81019080805160405193929190846401000000008211156120c357600080fd5b9083019060208201858111156120d857600080fd5b82518660208202830111640100000000821117156120f557600080fd5b82525081516020918201928201910280838360005b8381101561212257818101518382015260200161210a565b50505050905001604052505050905060005b815181101561218657600061215d8684848151811061214f57fe5b602002602001015187612e59565b90508061216a575061217e565b61217a848263ffffffff61252316565b9350505b600101612134565b505092915050565b6000828211156121e5576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600080600080600080601090506060600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561224857600080fd5b505afa15801561225c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561228557600080fd5b81019080805160405193929190846401000000008211156122a557600080fd5b9083019060208201858111156122ba57600080fd5b82518660208202830111640100000000821117156122d757600080fd5b82525081516020918201928201910280838360005b838110156123045781810151838201526020016122ec565b50505050905001604052505050905060005b81518110156124f05761233d8d83838151811061232f57fe5b60200260200101518b612e59565b965086612349576124e8565b612359878b63ffffffff61374d16565b93506123658b8561392c565b9550612377868963ffffffff61252316565b95508686116123885760008661239a565b612398868863ffffffff61218e16565b875b80975081995050506123c0898383815181106123b257fe5b602002602001015188613002565b60055483519197506001600160a01b0316906344aedc5f908490849081106123e457fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561242057600080fd5b505afa158015612434573d6000803e3d6000fd5b505050506040513d602081101561244a57600080fd5b505160ff16945060128510156124d65785935061247e61247160128763ffffffff61218e16565b879063ffffffff61396916565b9550612490848763ffffffff61218e16565b93506124b08282815181106124a157fe5b60200260200101518a86613002565b93506124c2888563ffffffff61252316565b97508483116124d157826124d3565b845b92505b6124e88d8d888585815181106114d857fe5b600101612316565b50601282108015612506575081601203600a0a87115b612511576000612513565b865b9c9b505050505050505050505050565b6000828201838110156111c5576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b61258561398f565b6001600160a01b0316336001600160a01b031614611a8e576040805162461bcd60e51b815260206004820152601460248201527f53656e646572206973206e6f7420497373756572000000000000000000000000604482015290519081900360640190fd5b6000808086612609576125fb6137ef565b925060009150819050612904565b60008080808061262d8d8a1561261f578a612628565b635553444360e01b5b6133e1565b939850919650945092509050600061264b838663ffffffff61252316565b90506000828e1161265d57600061266d565b61266d8e8463ffffffff61218e16565b90508c15158061267c57508a15155b1561288d578c6126f5576126b36126958d611dbe6137ef565b82116126ac576126a78261133f6137ef565b611f46565b828d612523565b98506126c2858886898d6139a3565b97506126ce8f8c612d5a565b98508789106126dd57876126df565b885b97506126ee88611dbe8d612f1d565b98506127b7565b8c6127038e61133f8e612f1d565b809950819a5050506127b46127a7600560009054906101000a90046001600160a01b03166001600160a01b03166344aedc5f8e6040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561276a57600080fd5b505afa15801561277e573d6000803e3d6000fd5b505050506040513d602081101561279457600080fd5b505160129060ff1663ffffffff61218e16565b899063ffffffff61396916565b97505b6127c38c611dbe6137ef565b9b508b81116127d2578861280f565b6127e2818d63ffffffff61218e16565b89116127ef57600061280f565b61280f612802828e63ffffffff61218e16565b8a9063ffffffff61218e16565b9850612821848963ffffffff61252316565b9350612833848763ffffffff61252316565b9150612858612842878961392c565b61284c868861392c565b9063ffffffff61252316565b925061286a8e8a63ffffffff61252316565b9d50828e1161287a57600061288a565b61288a8e8463ffffffff61218e16565b90505b816128a95761289a6137ef565b99506129049650505050505050565b6128b58161133f6137ef565b90506128cc8786611b5c878663ffffffff61374d16565b99506128fa6128d96137ef565b8b611b5c6128ed868663ffffffff61252316565b869063ffffffff61374d16565b9950505050505050505b955095509592505050565b6129176138a6565b61292882600c63ffffffff61396916565b111561297b576040805162461bcd60e51b815260206004820152601760248201527f4f766572206d61782065787465726e616c2071756f7461000000000000000000604482015290519081900360640190fd5b50565b600061298b828486613002565b600554604080516344aedc5f60e01b81526004810187905290519293506000926001600160a01b03909216916344aedc5f91602480820192602092909190829003018186803b1580156129dd57600080fd5b505afa1580156129f1573d6000803e3d6000fd5b505050506040513d6020811015612a0757600080fd5b505160ff1690506012811115612a64576040805162461bcd60e51b815260206004820152601660248201527f496e76616c696420646563696d616c206e756d62657200000000000000000000604482015290519081900360640190fd5b612a7560128263ffffffff61218e16565b9050612a87828263ffffffff61396916565b9150612a92846132f3565b6005546001600160a01b03918216916323b872dd91899116612abe86600a87900a63ffffffff613a8516565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050602060405180830381600087803b158015612b2657600080fd5b505af1158015612b3a573d6000803e3d6000fd5b505050506040513d6020811015612b5057600080fd5b5051612b8d5760405162461bcd60e51b815260040180806020018281038252602a815260200180614043602a913960400191505060405180910390fd5b600554604080517f8c610c8d000000000000000000000000000000000000000000000000000000008152600481018790526001600160a01b0389811660248301526044820186905291519190921691638c610c8d91606480830192600092919082900301818387803b158015612c0257600080fd5b505af1158015612c16573d6000803e3d6000fd5b50505050505050505050565b604080516001808252818301909252606091602080830190803883390190505090507f466c657869626c6553746f72616765000000000000000000000000000000000081600081518110612c7257fe5b60200260200101818152505090565b60608151835101604051908082528060200260200182016040528015612cb1578160200160208202803883390190505b50905060005b8351811015612cf357838181518110612ccc57fe5b6020026020010151828281518110612ce057fe5b6020908102919091010152600101612cb7565b5060005b8251811015612d3657828181518110612d0c57fe5b6020026020010151828286510181518110612d2357fe5b6020908102919091010152600101612cf7565b5092915050565b6000612d5082635553444360e01b6133e1565b9695505050505050565b600080612d6683613aef565b90506000612d73846132f3565b90506000612e47612db6836001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561276a57600080fd5b600a0a836001600160a01b03166370a08231896040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015612e0f57600080fd5b505afa158015612e23573d6000803e3d6000fd5b505050506040513d6020811015612e3957600080fd5b50519063ffffffff613b7d16565b9050612d50818463ffffffff613bd616565b600554604080517f480310bb000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b0386811660248301529151600093929092169163480310bb91604480820192602092909190829003018186803b158015612eca57600080fd5b505afa158015612ede573d6000803e3d6000fd5b505050506040513d6020811015612ef457600080fd5b5051905080612f05575060006112d7565b81831415612f12576112d7565b6112d4838383613002565b6000612f27613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f6578546f6b656e49737375616e6365526174696f000000000000000000000000856040516020018083815260200182815260200192505050604051602081830303815290604052805190602001206040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b158015610cfd57600080fd5b6000811580612fde575082155b612ff957612ff4612fef8484613c2b565b613c5d565b6111c5565b50600092915050565b6000828414156130135750806112d7565b50806000631c1554d160e21b851461303d5761302e85613aef565b905061303a838261392c565b91505b631c1554d160e21b84146130605761305484613aef565b9050610d998282612fd1565b509392505050565b8061307281613c7f565b600554604080516344aedc5f60e01b81526004810185905290516000926001600160a01b0316916344aedc5f916024808301926020929190829003018186803b1580156130be57600080fd5b505afa1580156130d2573d6000803e3d6000fd5b505050506040513d60208110156130e857600080fd5b505160ff1690506012811115613145576040805162461bcd60e51b815260206004820152601660248201527f496e76616c696420646563696d616c206e756d62657200000000000000000000604482015290519081900360640190fd5b600061316861315b60128463ffffffff61218e16565b869063ffffffff61396916565b600554604080517fa98b9ff4000000000000000000000000000000000000000000000000000000008152600481018890526001600160a01b038b8116602483015260448201859052915193945091169163a98b9ff49160648082019260009290919082900301818387803b1580156131df57600080fd5b505af11580156131f3573d6000803e3d6000fd5b5050600554604080517f4020ed14000000000000000000000000000000000000000000000000000000008152600481018990526001600160a01b038b81166024830152604482018790529151919092169350634020ed14925060648083019260209291908290030181600087803b15801561326d57600080fd5b505af1158015613281573d6000803e3d6000fd5b505050506040513d602081101561329757600080fd5b50516132ea576040805162461bcd60e51b815260206004820152601660248201527f526566756e6420686173206265656e206661696c656400000000000000000000604482015290519081900360640190fd5b50505050505050565b6000816132ff81613c7f565b600554604080516397bb3ce960e01b81526004810186905290516001600160a01b03909216916397bb3ce991602480820192602092909190829003018186803b15801561334b57600080fd5b505afa15801561335f573d6000803e3d6000fd5b505050506040513d602081101561337557600080fd5b505191505b50919050565b6000806000806000806133938989611c17565b93995091965094509250905060006133b1888563ffffffff61252316565b9050600081116133c25760006133d2565b6133d2898263ffffffff61374d16565b95505093975093979195509350565b60008060008060006133f286612f1d565b60055460408051635b08123760e01b8152905192955060129283926060926001600160a01b0390911691635b08123791600480820192600092909190829003018186803b15801561344257600080fd5b505afa158015613456573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561347f57600080fd5b810190808051604051939291908464010000000082111561349f57600080fd5b9083019060208201858111156134b457600080fd5b82518660208202830111640100000000821117156134d157600080fd5b82525081516020918201928201910280838360005b838110156134fe5781810151838201526020016134e6565b50505050905001604052505050905060005b81518110156136f25761353e8b83838151811061352957fe5b6020026020010151631c1554d160e21b612e59565b945061355c82828151811061354f57fe5b6020026020010151612f1d565b87146136315761357182828151811061354f57fe5b9850841561362c57613589888663ffffffff61252316565b60055483519199506001600160a01b0316906344aedc5f908490849081106135ad57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156135e957600080fd5b505afa1580156135fd573d6000803e3d6000fd5b505050506040513d602081101561361357600080fd5b505160ff1694508483106136275784613629565b825b92505b6136ea565b84156136ea57613647868663ffffffff61252316565b60055483519197506001600160a01b0316906344aedc5f9084908490811061366b57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156136a757600080fd5b505afa1580156136bb573d6000803e3d6000fd5b505050506040513d60208110156136d157600080fd5b505160ff1694508484106136e557846136e7565b835b93505b600101613510565b5061373e61372061370a60128563ffffffff61218e16565b6137148a8c61392c565b9063ffffffff61396916565b61284c61373460128763ffffffff61218e16565b613714898b61392c565b93505050509295509295909350565b60006111c58261376b85670de0b6b3a764000063ffffffff613b7d16565b9063ffffffff613a8516565b600080600084861161379a57613793858763ffffffff61218e16565b60006137ad565b6137aa868663ffffffff61218e16565b60015b91509150806137d5576137d06137c3838661392c565b879063ffffffff61252316565b612d50565b612d506137e2838661392c565b879063ffffffff61218e16565b60006137f9613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f69737375616e6365526174696f000000000000000000000000000000000000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561387557600080fd5b505afa158015613889573d6000803e3d6000fd5b505050506040513d602081101561389f57600080fd5b5051905090565b60006138b0613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f65787465726e616c546f6b656e51756f746100000000000000000000000000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561387557600080fd5b6000811580613939575082155b612ff957612ff4612fef8484613d53565b60006139646b4c69717569646174696f6e7360a01b613d85565b905090565b60006111c5600a83900a613983858263ffffffff613a8516565b9063ffffffff613b7d16565b60006139646524b9b9bab2b960d11b613d85565b6000806139ae6137ef565b905060006139ba6138a6565b905060006139ce898463ffffffff61218e16565b90506139da818861392c565b9350613a04600089116139ee5760006139fe565b6139fe898563ffffffff61218e16565b8761392c565b9050613a16848263ffffffff61252316565b9350613a28828463ffffffff61218e16565b9050613a34818661392c565b905083811015613a4a5760009350505050610d99565b613a5a818563ffffffff61218e16565b9350613a6c898363ffffffff61218e16565b9050613a788482612fd1565b9998505050505050505050565b6000808211613adb576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b6000828481613ae657fe5b04949350505050565b600080613afa613e6e565b6001600160a01b0316630c71cd23846040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b158015613b3c57600080fd5b505afa158015613b50573d6000803e3d6000fd5b505050506040513d6040811015613b6657600080fd5b508051602090910151909250905061337a81613e89565b600082613b8c575060006111c8565b82820282848281613b9957fe5b04146111c55760405162461bcd60e51b81526004018080602001828103825260218152602001806140016021913960400191505060405180910390fd5b6000670de0b6b3a7640000613bf1848463ffffffff613b7d16565b81613bf857fe5b049392505050565b60006139647f466c657869626c6553746f726167650000000000000000000000000000000000613d85565b6000811580613c38575082155b612ff957612ff4613c4883613ec6565b613c5185613ec6565b9063ffffffff613edc16565b60006305f5e10082046005600a820610613c7557600a015b600a900492915050565b600554604080516397bb3ce960e01b81526004810184905290516000926001600160a01b0316916397bb3ce9916024808301926020929190829003018186803b158015613ccb57600080fd5b505afa158015613cdf573d6000803e3d6000fd5b505050506040513d6020811015613cf557600080fd5b50516001600160a01b0316141561297b576040805162461bcd60e51b815260206004820152601e60248201527f54617267657420746f6b656e206973206e6f7420726567697374657265640000604482015290519081900360640190fd5b6000811580613d60575082155b612ff957612ff4613d7083613ec6565b613d7985613ec6565b9063ffffffff613ef516565b60008181526003602090815260408083205481517f4d697373696e6720616464726573733a200000000000000000000000000000009381019390935260318084018690528251808503909101815260519093019091526001600160a01b03169081612d365760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613e33578181015183820152602001613e1b565b50505050905090810190601f168015613e605780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60006139646c45786368616e6765526174657360981b613d85565b801561297b5760405162461bcd60e51b815260040180806020018281038252602b815260200180613fa7602b913960400191505060405180910390fd5b60006111c882633b9aca0063ffffffff613b7d16565b60006111c583836b033b2e3c9fd0803ce8000000613f0e565b60006111c583836b033b2e3c9fd0803ce8000000613f46565b600080613f288461376b87600a870263ffffffff613b7d16565b90506005600a825b0610613f3a57600a015b600a9004949350505050565b600080600a8304613f5d868663ffffffff613b7d16565b81613f6457fe5b0490506005600a82613f3056fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e657273686970412070796e7468206f7220612065787465726e616c20746f6b656e207261746520697320696e76616c69644f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774e6f7420656e6f7567682065787465726e616c207374616b656420616d6f756e745472616e7366657272696e67207374616b696e6720746f6b656e20686173206265656e206661696c6564a265627a7a72315820cfbd39bfcc87230b5e9aef41175923b35ce6712d42e1c3176bc6b48a8a8ba80164736f6c634300051000320000000000000000000000001c19d81a6f11958d58130ddbc505933426c80369000000000000000000000000263b671648675bda5263525eca554e4d5697e2100000000000000000000000007015cd1e78ba1428d103b0c2513077b2826b64fc

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102415760003560e01c80637a84b61711610145578063b42652e9116100bd578063e082f1d41161008c578063f5faf9bd11610071578063f5faf9bd14610865578063ff18cf46146108b7578063ffb0a081146108ea57610241565b8063e082f1d4146107f8578063ede459561461080057610241565b8063b42652e914610631578063c23d798114610657578063c70cc960146107a0578063dac6e576146107c657610241565b80639c1b38f711610114578063ad47ffc5116100f9578063ad47ffc5146105aa578063b12e4410146105dc578063b23e5053146105f957610241565b80639c1b38f714610558578063a4678bd21461057e57610241565b80637a84b617146104e45780637d52c4ec14610510578063899ffef4146105485780638da5cb5b1461055057610241565b806353a47bb7116101d8578063614d08f8116101a75780636446406c1161018c5780636446406c1461049c57806374185360146104d457806379ba5097146104dc57610241565b8063614d08f81461046257806362197d9a1461046a57610241565b806353a47bb7146103d55780635abebd07146103dd5780635b6f39791461040f5780635c833bfd1461042c57610241565b8063273cbaa011610214578063273cbaa0146102fd5780632af64bd3146103555780633b1cd52b1461037157806346ba2d90146103bb57610241565b806304f3bcec146102465780630fc3fc8c1461026a5780631627540c146102925780631982de22146102b8575b600080fd5b61024e61092e565b604080516001600160a01b039092168252519081900360200190f35b6102906004803603602081101561028057600080fd5b50356001600160a01b031661093d565b005b610290600480360360208110156102a857600080fd5b50356001600160a01b0316610974565b6102e4600480360360408110156102ce57600080fd5b506001600160a01b0381351690602001356109dd565b6040805192835260208301919091528051918290030190f35b610305610a0e565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610341578181015183820152602001610329565b505050509050019250505060405180910390f35b61035d610b22565b604080519115158252519081900360200190f35b61039d6004803603604081101561038757600080fd5b506001600160a01b038135169060200135610c45565b60408051938452602084019290925282820152519081900360600190f35b6103c3610c63565b60408051918252519081900360200190f35b61024e610c69565b61039d600480360360608110156103f357600080fd5b506001600160a01b038135169060208101359060400135610c78565b61035d6004803603602081101561042557600080fd5b5035610c98565b6103c36004803603606081101561044257600080fd5b506001600160a01b03813581169160208101359160409091013516610d2f565b6103c3610da2565b6103c36004803603606081101561048057600080fd5b506001600160a01b038135169060208101359060400135610dc6565b6103c3600480360360808110156104b257600080fd5b506001600160a01b038135169060208101359060408101359060600135610e68565b610290610f02565b6102906110f0565b6103c3600480360360408110156104fa57600080fd5b506001600160a01b0381351690602001356111b9565b6103c36004803603608081101561052657600080fd5b506001600160a01b0381351690602081013590604081013590606001356111ce565b6103056111e9565b61024e6112a1565b6103c36004803603602081101561056e57600080fd5b50356001600160a01b03166112b0565b6103c36004803603604081101561059457600080fd5b506001600160a01b0381351690602001356112bb565b6103c3600480360360608110156105c057600080fd5b506001600160a01b0381351690602081013590604001356112c7565b61024e600480360360208110156105f257600080fd5b50356112de565b6102906004803603608081101561060f57600080fd5b506001600160a01b03813516906020810135906040810135906060013561132a565b6102906004803603602081101561064757600080fd5b50356001600160a01b0316611362565b61067d6004803603602081101561066d57600080fd5b50356001600160a01b03166114f4565b6040518080602001806020018060200180602001858103855289818151815260200191508051906020019060200280838360005b838110156106c95781810151838201526020016106b1565b50505050905001858103845288818151815260200191508051906020019060200280838360005b838110156107085781810151838201526020016106f0565b50505050905001858103835287818151815260200191508051906020019060200280838360005b8381101561074757818101518382015260200161072f565b50505050905001858103825286818151815260200191508051906020019060200280838360005b8381101561078657818101518382015260200161076e565b505050509050019850505050505050505060405180910390f35b61039d600480360360208110156107b657600080fd5b50356001600160a01b03166118d8565b6103c3600480360360608110156107dc57600080fd5b506001600160a01b0381351690602081013590604001356118f3565b61024e611960565b6108326004803603606081101561081657600080fd5b506001600160a01b03813516906020810135906040013561196f565b604080519687526020870195909552858501939093526060850191909152608084015260a0830152519081900360c00190f35b6108916004803603604081101561087b57600080fd5b506001600160a01b038135169060200135611999565b604080519485526020850193909352838301919091526060830152519081900360800190f35b6108d4600480360360208110156108cd57600080fd5b50356119ba565b6040805160ff9092168252519081900360200190f35b6103c3600480360360c081101561090057600080fd5b506001600160a01b038135169060208101359060408101359060608101359060808101359060a00135611a06565b6002546001600160a01b031681565b610945611a45565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b61097c611a45565b600180546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b60008060008060006109ee87611a90565b925092509250610a0086848484611b6d565b945094505050509250929050565b60055460408051635b08123760e01b815290516060926001600160a01b031691635b081237916004808301926000929190829003018186803b158015610a5357600080fd5b505afa158015610a67573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610a9057600080fd5b8101908080516040519392919084640100000000821115610ab057600080fd5b908301906020820185811115610ac557600080fd5b8251866020820283011164010000000082111715610ae257600080fd5b82525081516020918201928201910280838360005b83811015610b0f578181015183820152602001610af7565b5050505090500160405250505090505b90565b60006060610b2e6111e9565b905060005b8151811015610c3c576000828281518110610b4a57fe5b6020908102919091018101516000818152600383526040908190205460025482517f21f8a7210000000000000000000000000000000000000000000000000000000081526004810185905292519395506001600160a01b03918216949116926321f8a721926024808201939291829003018186803b158015610bcb57600080fd5b505afa158015610bdf573d6000803e3d6000fd5b505050506040513d6020811015610bf557600080fd5b50516001600160a01b0316141580610c2257506000818152600360205260409020546001600160a01b0316155b15610c335760009350505050610b1f565b50600101610b33565b50600191505090565b6000806000610c548585611c17565b50929891975095509350505050565b60045481565b6001546001600160a01b031681565b6000806000610c88868686611d94565b9250925092505b93509350939050565b600554604080517f8547dbd90000000000000000000000000000000000000000000000000000000081526004810184905290516000926001600160a01b031691638547dbd9916024808301926020929190829003018186803b158015610cfd57600080fd5b505afa158015610d11573d6000803e3d6000fd5b505050506040513d6020811015610d2757600080fd5b505192915050565b6000610d39611fa7565b6000610d4c85631c1554d160e21b612014565b9050808411610d5d57836000610d6e565b80610d6e858263ffffffff61218e16565b9094509150610d8785848684631c1554d160e21b6121eb565b9050610d99828263ffffffff61252316565b95945050505050565b7f45787465726e616c546f6b656e5374616b654d616e616765720000000000000081565b6000610dd061257d565b600080610dde868686611d94565b9194509250905082151580610df257508115155b610e43576040805162461bcd60e51b815260206004820152601c60248201527f4163636f756e7420697320616c726561647920636c61696d61626c6500000000604482015290519081900360640190fd5b8115610e5f57610e5d86878484631c1554d160e21b6121eb565b505b50509392505050565b6000610e7261257d565b600080610e838787600088886125ea565b9094509250905081610edc576040805162461bcd60e51b815260206004820152601f60248201527f4e6f20617661696c61626c652065782d746f6b656e7320746f207374616b6500604482015290519081900360640190fd5b610ee58161290f565b610ef8878386631c1554d160e21b61297e565b5050949350505050565b6060610f0c6111e9565b905060005b81518110156110ec576000828281518110610f2857fe5b602090810291909101810151600254604080517f5265736f6c766572206d697373696e67207461726765743a200000000000000081860152603980820185905282518083039091018152605982018084527fdacb2d01000000000000000000000000000000000000000000000000000000009052605d8201858152607d83019384528151609d84015281519597506000966001600160a01b039095169563dacb2d01958995939492939260bd0191908501908083838c5b83811015610ff7578181015183820152602001610fdf565b50505050905090810190601f1680156110245780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b15801561104257600080fd5b505afa158015611056573d6000803e3d6000fd5b505050506040513d602081101561106c57600080fd5b5051600083815260036020908152604091829020805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03851690811790915582518681529182015281519293507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa68929081900390910190a15050600101610f11565b5050565b6001546001600160a01b031633146111395760405162461bcd60e51b8152600401808060200182810382526035815260200180613f726035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a1600180546000805473ffffffffffffffffffffffffffffffffffffffff199081166001600160a01b03841617909155169055565b60006111c58383612014565b90505b92915050565b60006111de8585600086866125ea565b979650505050505050565b6060806111f4612c22565b60408051600380825260808201909252919250606091906020820183803883390190505090506524b9b9bab2b960d11b8160008151811061123157fe5b6020026020010181815250506c45786368616e6765526174657360981b8160018151811061125b57fe5b6020026020010181815250506b4c69717569646174696f6e7360a01b8160028151811061128457fe5b60200260200101818152505061129a8282612c81565b9250505090565b6000546001600160a01b031681565b60006111c882612d3d565b60006111c58383612d5a565b60006112d4848484612e59565b90505b9392505050565b600554604080516397bb3ce960e01b81526004810184905290516000926001600160a01b0316916397bb3ce9916024808301926020929190829003018186803b158015610cfd57600080fd5b61133261257d565b6113448361133f84612f1d565b612fd1565b925061135c8485611356848688613002565b85613068565b50505050565b61136a61257d565b60055460408051635b08123760e01b815290516060926001600160a01b031691635b081237916004808301926000929190829003018186803b1580156113af57600080fd5b505afa1580156113c3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156113ec57600080fd5b810190808051604051939291908464010000000082111561140c57600080fd5b90830190602082018581111561142157600080fd5b825186602082028301116401000000008211171561143e57600080fd5b82525081516020918201928201910280838360005b8381101561146b578181015183820152602001611453565b50505050905001604052505050905060005b81518110156114ef5760006114b98484848151811061149857fe5b60200260200101518585815181106114ac57fe5b6020026020010151612e59565b9050806114c657506114e7565b6114e58485838686815181106114d857fe5b6020026020010151613068565b505b60010161147d565b505050565b606080606080600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561154857600080fd5b505afa15801561155c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561158557600080fd5b81019080805160405193929190846401000000008211156115a557600080fd5b9083019060208201858111156115ba57600080fd5b82518660208202830111640100000000821117156115d757600080fd5b82525081516020918201928201910280838360005b838110156116045781810151838201526020016115ec565b505050509050016040525050509350835160405190808252806020026020018201604052801561163e578160200160208202803883390190505b509250835160405190808252806020026020018201604052801561166c578160200160208202803883390190505b509150835160405190808252806020026020018201604052801561169a578160200160208202803883390190505b50905060005b84518110156118d05760055485516001600160a01b039091169063480310bb908790849081106116cc57fe5b6020026020010151886040518363ffffffff1660e01b815260040180838152602001826001600160a01b03166001600160a01b031681526020019250505060206040518083038186803b15801561172257600080fd5b505afa158015611736573d6000803e3d6000fd5b505050506040513d602081101561174c57600080fd5b5051845185908390811061175c57fe5b602090810291909101015260055485516001600160a01b03909116906344aedc5f9087908490811061178a57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d60208110156117f057600080fd5b5051835160ff9091169084908390811061180657fe5b60200260200101818152505061182e85828151811061182157fe5b60200260200101516132f3565b6001600160a01b03166370a08231876040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561188357600080fd5b505afa158015611897573d6000803e3d6000fd5b505050506040513d60208110156118ad57600080fd5b505182518390839081106118bd57fe5b60209081029190910101526001016116a0565b509193509193565b60008060006118e684611a90565b9196909550909350915050565b60008060008061190287611a90565b925092509250858310156119475760405162461bcd60e51b81526004018080602001828103825260218152602001806140226021913960400191505060405180910390fd5b6119518682612fd1565b95506111de87888885896121eb565b6005546001600160a01b031681565b600080600080600080611983898989613380565b949e939d50919b50995097509095509350505050565b6000806000806119a986866133e1565b509299919850965090945092505050565b600554604080516344aedc5f60e01b81526004810184905290516000926001600160a01b0316916344aedc5f916024808301926020929190829003018186803b158015610cfd57600080fd5b6000611a1061257d565b6000611a1f88878988886125ea565b985092509050611a2e8161290f565b611a3a8888868661297e565b509695505050505050565b6000546001600160a01b03163314611a8e5760405162461bcd60e51b815260040180806020018281038252602f815260200180613fd2602f913960400191505060405180910390fd5b565b6000806000806000806000611aac88635553444360e01b6133e1565b9a5092965090945092509050611ac8818463ffffffff61252316565b955060008611611b4757736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611b1657600080fd5b505af4158015611b2a573d6000803e3d6000fd5b505050506040513d6020811015611b4057600080fd5b5051611b61565b611b618483611b5c848a63ffffffff61374d16565b613777565b96989597505050505050565b60008083611b8057506000905080611c0e565b6000611b8a6137ef565b90506000868811611b9c576000611bac565b611bac888863ffffffff61218e16565b9050611bb88183612fd1565b9050611bca818763ffffffff61252316565b9050611bdc868263ffffffff61374d16565b9350611c09611bf983611bed6138a6565b9063ffffffff61218e16565b61133f878563ffffffff61218e16565b925050505b94509492505050565b60008080808085611cae57611c2a6137ef565b736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611c6e57600080fd5b505af4158015611c82573d6000803e3d6000fd5b505050506040513d6020811015611c9857600080fd5b5051909550935060009250829150819050611d8a565b6000611cb988611a90565b9650945090506000611cc96137ef565b905084611d585780736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611d1557600080fd5b505af4158015611d29573d6000803e3d6000fd5b505050506040513d6020811015611d3f57600080fd5b5051909750955060009450849350839250611d8a915050565b611d6488838789611b6d565b9094509250828411611d765783611d78565b825b9150611d85818784613777565b965050505b9295509295909350565b6000806000806000611da588611a90565b909450909250905082611df457611dc386611dbe6137ef565b61392c565b9150818711611dd3576000611de3565b611de3878363ffffffff61218e16565b945060009350839250610c8f915050565b6000611dfe6137ef565b90506000611e0a6138a6565b90506000611e31611e21838563ffffffff61218e16565b61133f868663ffffffff61218e16565b90506000611e3f8b84612fd1565b9050611e4b818361392c565b90506000611e598b8661392c565b90506000828911611e7957611e748d8963ffffffff61218e16565b611e93565b611e93611e86848961392c565b8e9063ffffffff61218e16565b905081811115611f7657611f2a611eaa8d8661392c565b61133f86736e0bff12512a94f3b1ecebe08203fe7f8f8fae6463907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b158015611ef257600080fd5b505af4158015611f06573d6000803e3d6000fd5b505050506040513d6020811015611f1c57600080fd5b50519063ffffffff61218e16565b9250611f53838a11611f3c5788611f46565b611f46848961392c565b839063ffffffff61252316565b9750878d11611f63576000611f73565b611f738d8963ffffffff61218e16565b9a505b828911611f84576000611f94565b611f94898463ffffffff61218e16565b9950505050505050505093509350939050565b611faf61394a565b6001600160a01b0316336001600160a01b031614611a8e576040805162461bcd60e51b815260206004820152601a60248201527f53656e646572206973206e6f74204c69717569646174696f6e73000000000000604482015290519081900360640190fd5b60006060600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561206657600080fd5b505afa15801561207a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156120a357600080fd5b81019080805160405193929190846401000000008211156120c357600080fd5b9083019060208201858111156120d857600080fd5b82518660208202830111640100000000821117156120f557600080fd5b82525081516020918201928201910280838360005b8381101561212257818101518382015260200161210a565b50505050905001604052505050905060005b815181101561218657600061215d8684848151811061214f57fe5b602002602001015187612e59565b90508061216a575061217e565b61217a848263ffffffff61252316565b9350505b600101612134565b505092915050565b6000828211156121e5576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600080600080600080601090506060600560009054906101000a90046001600160a01b03166001600160a01b0316635b0812376040518163ffffffff1660e01b815260040160006040518083038186803b15801561224857600080fd5b505afa15801561225c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561228557600080fd5b81019080805160405193929190846401000000008211156122a557600080fd5b9083019060208201858111156122ba57600080fd5b82518660208202830111640100000000821117156122d757600080fd5b82525081516020918201928201910280838360005b838110156123045781810151838201526020016122ec565b50505050905001604052505050905060005b81518110156124f05761233d8d83838151811061232f57fe5b60200260200101518b612e59565b965086612349576124e8565b612359878b63ffffffff61374d16565b93506123658b8561392c565b9550612377868963ffffffff61252316565b95508686116123885760008661239a565b612398868863ffffffff61218e16565b875b80975081995050506123c0898383815181106123b257fe5b602002602001015188613002565b60055483519197506001600160a01b0316906344aedc5f908490849081106123e457fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561242057600080fd5b505afa158015612434573d6000803e3d6000fd5b505050506040513d602081101561244a57600080fd5b505160ff16945060128510156124d65785935061247e61247160128763ffffffff61218e16565b879063ffffffff61396916565b9550612490848763ffffffff61218e16565b93506124b08282815181106124a157fe5b60200260200101518a86613002565b93506124c2888563ffffffff61252316565b97508483116124d157826124d3565b845b92505b6124e88d8d888585815181106114d857fe5b600101612316565b50601282108015612506575081601203600a0a87115b612511576000612513565b865b9c9b505050505050505050505050565b6000828201838110156111c5576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b61258561398f565b6001600160a01b0316336001600160a01b031614611a8e576040805162461bcd60e51b815260206004820152601460248201527f53656e646572206973206e6f7420497373756572000000000000000000000000604482015290519081900360640190fd5b6000808086612609576125fb6137ef565b925060009150819050612904565b60008080808061262d8d8a1561261f578a612628565b635553444360e01b5b6133e1565b939850919650945092509050600061264b838663ffffffff61252316565b90506000828e1161265d57600061266d565b61266d8e8463ffffffff61218e16565b90508c15158061267c57508a15155b1561288d578c6126f5576126b36126958d611dbe6137ef565b82116126ac576126a78261133f6137ef565b611f46565b828d612523565b98506126c2858886898d6139a3565b97506126ce8f8c612d5a565b98508789106126dd57876126df565b885b97506126ee88611dbe8d612f1d565b98506127b7565b8c6127038e61133f8e612f1d565b809950819a5050506127b46127a7600560009054906101000a90046001600160a01b03166001600160a01b03166344aedc5f8e6040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561276a57600080fd5b505afa15801561277e573d6000803e3d6000fd5b505050506040513d602081101561279457600080fd5b505160129060ff1663ffffffff61218e16565b899063ffffffff61396916565b97505b6127c38c611dbe6137ef565b9b508b81116127d2578861280f565b6127e2818d63ffffffff61218e16565b89116127ef57600061280f565b61280f612802828e63ffffffff61218e16565b8a9063ffffffff61218e16565b9850612821848963ffffffff61252316565b9350612833848763ffffffff61252316565b9150612858612842878961392c565b61284c868861392c565b9063ffffffff61252316565b925061286a8e8a63ffffffff61252316565b9d50828e1161287a57600061288a565b61288a8e8463ffffffff61218e16565b90505b816128a95761289a6137ef565b99506129049650505050505050565b6128b58161133f6137ef565b90506128cc8786611b5c878663ffffffff61374d16565b99506128fa6128d96137ef565b8b611b5c6128ed868663ffffffff61252316565b869063ffffffff61374d16565b9950505050505050505b955095509592505050565b6129176138a6565b61292882600c63ffffffff61396916565b111561297b576040805162461bcd60e51b815260206004820152601760248201527f4f766572206d61782065787465726e616c2071756f7461000000000000000000604482015290519081900360640190fd5b50565b600061298b828486613002565b600554604080516344aedc5f60e01b81526004810187905290519293506000926001600160a01b03909216916344aedc5f91602480820192602092909190829003018186803b1580156129dd57600080fd5b505afa1580156129f1573d6000803e3d6000fd5b505050506040513d6020811015612a0757600080fd5b505160ff1690506012811115612a64576040805162461bcd60e51b815260206004820152601660248201527f496e76616c696420646563696d616c206e756d62657200000000000000000000604482015290519081900360640190fd5b612a7560128263ffffffff61218e16565b9050612a87828263ffffffff61396916565b9150612a92846132f3565b6005546001600160a01b03918216916323b872dd91899116612abe86600a87900a63ffffffff613a8516565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050602060405180830381600087803b158015612b2657600080fd5b505af1158015612b3a573d6000803e3d6000fd5b505050506040513d6020811015612b5057600080fd5b5051612b8d5760405162461bcd60e51b815260040180806020018281038252602a815260200180614043602a913960400191505060405180910390fd5b600554604080517f8c610c8d000000000000000000000000000000000000000000000000000000008152600481018790526001600160a01b0389811660248301526044820186905291519190921691638c610c8d91606480830192600092919082900301818387803b158015612c0257600080fd5b505af1158015612c16573d6000803e3d6000fd5b50505050505050505050565b604080516001808252818301909252606091602080830190803883390190505090507f466c657869626c6553746f72616765000000000000000000000000000000000081600081518110612c7257fe5b60200260200101818152505090565b60608151835101604051908082528060200260200182016040528015612cb1578160200160208202803883390190505b50905060005b8351811015612cf357838181518110612ccc57fe5b6020026020010151828281518110612ce057fe5b6020908102919091010152600101612cb7565b5060005b8251811015612d3657828181518110612d0c57fe5b6020026020010151828286510181518110612d2357fe5b6020908102919091010152600101612cf7565b5092915050565b6000612d5082635553444360e01b6133e1565b9695505050505050565b600080612d6683613aef565b90506000612d73846132f3565b90506000612e47612db6836001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561276a57600080fd5b600a0a836001600160a01b03166370a08231896040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015612e0f57600080fd5b505afa158015612e23573d6000803e3d6000fd5b505050506040513d6020811015612e3957600080fd5b50519063ffffffff613b7d16565b9050612d50818463ffffffff613bd616565b600554604080517f480310bb000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b0386811660248301529151600093929092169163480310bb91604480820192602092909190829003018186803b158015612eca57600080fd5b505afa158015612ede573d6000803e3d6000fd5b505050506040513d6020811015612ef457600080fd5b5051905080612f05575060006112d7565b81831415612f12576112d7565b6112d4838383613002565b6000612f27613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f6578546f6b656e49737375616e6365526174696f000000000000000000000000856040516020018083815260200182815260200192505050604051602081830303815290604052805190602001206040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b158015610cfd57600080fd5b6000811580612fde575082155b612ff957612ff4612fef8484613c2b565b613c5d565b6111c5565b50600092915050565b6000828414156130135750806112d7565b50806000631c1554d160e21b851461303d5761302e85613aef565b905061303a838261392c565b91505b631c1554d160e21b84146130605761305484613aef565b9050610d998282612fd1565b509392505050565b8061307281613c7f565b600554604080516344aedc5f60e01b81526004810185905290516000926001600160a01b0316916344aedc5f916024808301926020929190829003018186803b1580156130be57600080fd5b505afa1580156130d2573d6000803e3d6000fd5b505050506040513d60208110156130e857600080fd5b505160ff1690506012811115613145576040805162461bcd60e51b815260206004820152601660248201527f496e76616c696420646563696d616c206e756d62657200000000000000000000604482015290519081900360640190fd5b600061316861315b60128463ffffffff61218e16565b869063ffffffff61396916565b600554604080517fa98b9ff4000000000000000000000000000000000000000000000000000000008152600481018890526001600160a01b038b8116602483015260448201859052915193945091169163a98b9ff49160648082019260009290919082900301818387803b1580156131df57600080fd5b505af11580156131f3573d6000803e3d6000fd5b5050600554604080517f4020ed14000000000000000000000000000000000000000000000000000000008152600481018990526001600160a01b038b81166024830152604482018790529151919092169350634020ed14925060648083019260209291908290030181600087803b15801561326d57600080fd5b505af1158015613281573d6000803e3d6000fd5b505050506040513d602081101561329757600080fd5b50516132ea576040805162461bcd60e51b815260206004820152601660248201527f526566756e6420686173206265656e206661696c656400000000000000000000604482015290519081900360640190fd5b50505050505050565b6000816132ff81613c7f565b600554604080516397bb3ce960e01b81526004810186905290516001600160a01b03909216916397bb3ce991602480820192602092909190829003018186803b15801561334b57600080fd5b505afa15801561335f573d6000803e3d6000fd5b505050506040513d602081101561337557600080fd5b505191505b50919050565b6000806000806000806133938989611c17565b93995091965094509250905060006133b1888563ffffffff61252316565b9050600081116133c25760006133d2565b6133d2898263ffffffff61374d16565b95505093975093979195509350565b60008060008060006133f286612f1d565b60055460408051635b08123760e01b8152905192955060129283926060926001600160a01b0390911691635b08123791600480820192600092909190829003018186803b15801561344257600080fd5b505afa158015613456573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561347f57600080fd5b810190808051604051939291908464010000000082111561349f57600080fd5b9083019060208201858111156134b457600080fd5b82518660208202830111640100000000821117156134d157600080fd5b82525081516020918201928201910280838360005b838110156134fe5781810151838201526020016134e6565b50505050905001604052505050905060005b81518110156136f25761353e8b83838151811061352957fe5b6020026020010151631c1554d160e21b612e59565b945061355c82828151811061354f57fe5b6020026020010151612f1d565b87146136315761357182828151811061354f57fe5b9850841561362c57613589888663ffffffff61252316565b60055483519199506001600160a01b0316906344aedc5f908490849081106135ad57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156135e957600080fd5b505afa1580156135fd573d6000803e3d6000fd5b505050506040513d602081101561361357600080fd5b505160ff1694508483106136275784613629565b825b92505b6136ea565b84156136ea57613647868663ffffffff61252316565b60055483519197506001600160a01b0316906344aedc5f9084908490811061366b57fe5b60200260200101516040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156136a757600080fd5b505afa1580156136bb573d6000803e3d6000fd5b505050506040513d60208110156136d157600080fd5b505160ff1694508484106136e557846136e7565b835b93505b600101613510565b5061373e61372061370a60128563ffffffff61218e16565b6137148a8c61392c565b9063ffffffff61396916565b61284c61373460128763ffffffff61218e16565b613714898b61392c565b93505050509295509295909350565b60006111c58261376b85670de0b6b3a764000063ffffffff613b7d16565b9063ffffffff613a8516565b600080600084861161379a57613793858763ffffffff61218e16565b60006137ad565b6137aa868663ffffffff61218e16565b60015b91509150806137d5576137d06137c3838661392c565b879063ffffffff61252316565b612d50565b612d506137e2838661392c565b879063ffffffff61218e16565b60006137f9613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f69737375616e6365526174696f000000000000000000000000000000000000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561387557600080fd5b505afa158015613889573d6000803e3d6000fd5b505050506040513d602081101561389f57600080fd5b5051905090565b60006138b0613c00565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f65787465726e616c546f6b656e51756f746100000000000000000000000000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561387557600080fd5b6000811580613939575082155b612ff957612ff4612fef8484613d53565b60006139646b4c69717569646174696f6e7360a01b613d85565b905090565b60006111c5600a83900a613983858263ffffffff613a8516565b9063ffffffff613b7d16565b60006139646524b9b9bab2b960d11b613d85565b6000806139ae6137ef565b905060006139ba6138a6565b905060006139ce898463ffffffff61218e16565b90506139da818861392c565b9350613a04600089116139ee5760006139fe565b6139fe898563ffffffff61218e16565b8761392c565b9050613a16848263ffffffff61252316565b9350613a28828463ffffffff61218e16565b9050613a34818661392c565b905083811015613a4a5760009350505050610d99565b613a5a818563ffffffff61218e16565b9350613a6c898363ffffffff61218e16565b9050613a788482612fd1565b9998505050505050505050565b6000808211613adb576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b6000828481613ae657fe5b04949350505050565b600080613afa613e6e565b6001600160a01b0316630c71cd23846040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b158015613b3c57600080fd5b505afa158015613b50573d6000803e3d6000fd5b505050506040513d6040811015613b6657600080fd5b508051602090910151909250905061337a81613e89565b600082613b8c575060006111c8565b82820282848281613b9957fe5b04146111c55760405162461bcd60e51b81526004018080602001828103825260218152602001806140016021913960400191505060405180910390fd5b6000670de0b6b3a7640000613bf1848463ffffffff613b7d16565b81613bf857fe5b049392505050565b60006139647f466c657869626c6553746f726167650000000000000000000000000000000000613d85565b6000811580613c38575082155b612ff957612ff4613c4883613ec6565b613c5185613ec6565b9063ffffffff613edc16565b60006305f5e10082046005600a820610613c7557600a015b600a900492915050565b600554604080516397bb3ce960e01b81526004810184905290516000926001600160a01b0316916397bb3ce9916024808301926020929190829003018186803b158015613ccb57600080fd5b505afa158015613cdf573d6000803e3d6000fd5b505050506040513d6020811015613cf557600080fd5b50516001600160a01b0316141561297b576040805162461bcd60e51b815260206004820152601e60248201527f54617267657420746f6b656e206973206e6f7420726567697374657265640000604482015290519081900360640190fd5b6000811580613d60575082155b612ff957612ff4613d7083613ec6565b613d7985613ec6565b9063ffffffff613ef516565b60008181526003602090815260408083205481517f4d697373696e6720616464726573733a200000000000000000000000000000009381019390935260318084018690528251808503909101815260519093019091526001600160a01b03169081612d365760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613e33578181015183820152602001613e1b565b50505050905090810190601f168015613e605780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60006139646c45786368616e6765526174657360981b613d85565b801561297b5760405162461bcd60e51b815260040180806020018281038252602b815260200180613fa7602b913960400191505060405180910390fd5b60006111c882633b9aca0063ffffffff613b7d16565b60006111c583836b033b2e3c9fd0803ce8000000613f0e565b60006111c583836b033b2e3c9fd0803ce8000000613f46565b600080613f288461376b87600a870263ffffffff613b7d16565b90506005600a825b0610613f3a57600a015b600a9004949350505050565b600080600a8304613f5d868663ffffffff613b7d16565b81613f6457fe5b0490506005600a82613f3056fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e657273686970412070796e7468206f7220612065787465726e616c20746f6b656e207261746520697320696e76616c69644f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774e6f7420656e6f7567682065787465726e616c207374616b656420616d6f756e745472616e7366657272696e67207374616b696e6720746f6b656e20686173206265656e206661696c6564a265627a7a72315820cfbd39bfcc87230b5e9aef41175923b35ce6712d42e1c3176bc6b48a8a8ba80164736f6c63430005100032

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

0000000000000000000000001c19d81a6f11958d58130ddbc505933426c80369000000000000000000000000263b671648675bda5263525eca554e4d5697e2100000000000000000000000007015cd1e78ba1428d103b0c2513077b2826b64fc

-----Decoded View---------------
Arg [0] : _owner (address): 0x1c19D81a6F11958D58130DdbC505933426C80369
Arg [1] : _stakingState (address): 0x263b671648675Bda5263525ecA554e4d5697e210
Arg [2] : _resolver (address): 0x7015cd1E78bA1428d103B0C2513077b2826b64fc

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000001c19d81a6f11958d58130ddbc505933426c80369
Arg [1] : 000000000000000000000000263b671648675bda5263525eca554e4d5697e210
Arg [2] : 0000000000000000000000007015cd1e78ba1428d103b0c2513077b2826b64fc


Libraries Used


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

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

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