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