ETH Price: $2,819.22 (+7.41%)

Contract Diff Checker

Contract Name:
ExternalTokenStakeManager

Contract Source Code:

File 1 of 1 : ExternalTokenStakeManager

/*
    ___            _       ___  _                          
    | .\ ___  _ _ <_> ___ | __><_>._ _  ___ ._ _  ___  ___ 
    |  _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._>
    |_|  \___.|_|  |_|     |_|  |_||_|_|<___||_|_|\_|_.\___.
    
* 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);
        _;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):