creation_sourcecode: /*
  
  ⚠⚠⚠ WARNING WARNING WARNING ⚠⚠⚠
  
  This is a TARGET contract - DO NOT CONNECT TO IT DIRECTLY IN YOUR CONTRACTS or DAPPS!
  
  This contract has an associated PROXY that MUST be used for all integrations - this TARGET will be REPLACED in an upcoming Peri Finance release!
  The proxy for this contract can be found here:
  
  https://contracts.peri.finance/ProxyFeePool
  
  *//*
      ___            _       ___  _                          
      | .\ ___  _ _ <_> ___ | __><_>._ _  ___ ._ _  ___  ___ 
      |  _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._>
      |_|  \___.|_|  |_|     |_|  |_||_|_|<___||_|_|\_|_.\___.
      
  * PeriFinance: FeePool.sol
  *
  * Latest source (may be newer): https://github.com/perifinance/peri-finance/blob/master/contracts/FeePool.sol
  * Docs: Will be added in the future. 
  * https://docs.peri.finance/contracts/source/contracts/FeePool
  *
  * Contract Dependencies: 
  *	- EternalStorage
  *	- IAddressResolver
  *	- IFeePool
  *	- LimitedSetup
  *	- MixinResolver
  *	- MixinSystemSettings
  *	- Owned
  *	- Proxyable
  *	- State
  * Libraries: 
  *	- SafeDecimalMath
  *	- SafeMath
  *	- VestingEntries
  *
  * 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);
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/proxy
  contract Proxy is Owned {
      Proxyable public target;
  
      constructor(address _owner) public Owned(_owner) {}
  
      function setTarget(Proxyable _target) external onlyOwner {
          target = _target;
          emit TargetUpdated(_target);
      }
  
      function _emit(
          bytes calldata callData,
          uint numTopics,
          bytes32 topic1,
          bytes32 topic2,
          bytes32 topic3,
          bytes32 topic4
      ) external onlyTarget {
          uint size = callData.length;
          bytes memory _callData = callData;
  
          assembly {
              /* The first 32 bytes of callData contain its length (as specified by the abi).
               * Length is assumed to be a uint256 and therefore maximum of 32 bytes
               * in length. It is also leftpadded to be a multiple of 32 bytes.
               * This means moving call_data across 32 bytes guarantees we correctly access
               * the data itself. */
              switch numTopics
                  case 0 {
                      log0(add(_callData, 32), size)
                  }
                  case 1 {
                      log1(add(_callData, 32), size, topic1)
                  }
                  case 2 {
                      log2(add(_callData, 32), size, topic1, topic2)
                  }
                  case 3 {
                      log3(add(_callData, 32), size, topic1, topic2, topic3)
                  }
                  case 4 {
                      log4(add(_callData, 32), size, topic1, topic2, topic3, topic4)
                  }
          }
      }
  
      // solhint-disable no-complex-fallback
      function() external payable {
          // Mutable call setting Proxyable.messageSender as this is using call not delegatecall
          target.setMessageSender(msg.sender);
  
          assembly {
              let free_ptr := mload(0x40)
              calldatacopy(free_ptr, 0, calldatasize)
  
              /* We must explicitly forward ether to the underlying contract as well. */
              let result := call(gas, sload(target_slot), callvalue, free_ptr, calldatasize, 0, 0)
              returndatacopy(free_ptr, 0, returndatasize)
  
              if iszero(result) {
                  revert(free_ptr, returndatasize)
              }
              return(free_ptr, returndatasize)
          }
      }
  
      modifier onlyTarget {
          require(Proxyable(msg.sender) == target, "Must be proxy target");
          _;
      }
  
      event TargetUpdated(Proxyable newTarget);
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/proxyable
  contract Proxyable is Owned {
      // This contract should be treated like an abstract contract
  
      /* The proxy this contract exists behind. */
      Proxy public proxy;
      Proxy public integrationProxy;
  
      /* The caller of the proxy, passed through to this contract.
       * Note that every function using this member must apply the onlyProxy or
       * optionalProxy modifiers, otherwise their invocations can use stale values. */
      address public messageSender;
  
      constructor(address payable _proxy) internal {
          // This contract is abstract, and thus cannot be instantiated directly
          require(owner != address(0), "Owner must be set");
  
          proxy = Proxy(_proxy);
          emit ProxyUpdated(_proxy);
      }
  
      function setProxy(address payable _proxy) external onlyOwner {
          proxy = Proxy(_proxy);
          emit ProxyUpdated(_proxy);
      }
  
      function setIntegrationProxy(address payable _integrationProxy) external onlyOwner {
          integrationProxy = Proxy(_integrationProxy);
      }
  
      function setMessageSender(address sender) external onlyProxy {
          messageSender = sender;
      }
  
      modifier onlyProxy {
          _onlyProxy();
          _;
      }
  
      function _onlyProxy() private view {
          require(Proxy(msg.sender) == proxy || Proxy(msg.sender) == integrationProxy, "Only the proxy can call");
      }
  
      modifier optionalProxy {
          _optionalProxy();
          _;
      }
  
      function _optionalProxy() private {
          if (Proxy(msg.sender) != proxy && Proxy(msg.sender) != integrationProxy && messageSender != msg.sender) {
              messageSender = msg.sender;
          }
      }
  
      modifier optionalProxy_onlyOwner {
          _optionalProxy_onlyOwner();
          _;
      }
  
      // solhint-disable-next-line func-name-mixedcase
      function _optionalProxy_onlyOwner() private {
          if (Proxy(msg.sender) != proxy && Proxy(msg.sender) != integrationProxy && messageSender != msg.sender) {
              messageSender = msg.sender;
          }
          require(messageSender == owner, "Owner only function");
      }
  
      event ProxyUpdated(address proxyAddress);
  }
  
  
  // 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");
          _;
      }
  }
  
  
  // 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/interfaces/ifeepool
  interface IFeePool {
      // Views
  
      // solhint-disable-next-line func-name-mixedcase
      function FEE_ADDRESS() external view returns (address);
  
      function feesAvailable(address account) external view returns (uint, uint);
  
      function feePeriodDuration() external view returns (uint);
  
      function isFeesClaimable(address account) external view returns (bool);
  
      function targetThreshold() external view returns (uint);
  
      function totalFeesAvailable() external view returns (uint);
  
      function totalRewardsAvailable() external view returns (uint);
  
      function everDistributedFeeRewards() external view returns (bool);
  
      // function everAllocatedFeeRewards() external view returns (bool);
  
      // Mutative Functions
      function claimFees() external returns (bool);
  
      function claimOnBehalf(address claimingForAddress) external returns (bool);
  
      function closeCurrentFeePeriod() external;
  
      // Restricted: used internally to PeriFinance
      function appendAccountIssuanceRecord(
          address account,
          uint lockedAmount,
          uint debtEntryIndex
      ) external;
  
      function recordFeePaid(uint pUSDAmount) external;
  
      function setRewardsToDistribute(uint amount) external;
  
      function distributeFeeRewards(uint[] calldata feeRewards) external;
  
      function recentFeePeriods(uint index) external view returns (uint64, uint64, uint64, uint, uint, uint, uint);
  }
  
  
  /**
   * @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);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/isystemstatus
  interface ISystemStatus {
      struct Status {
          bool canSuspend;
          bool canResume;
      }
  
      struct Suspension {
          bool suspended;
          // reason is an integer code,
          // 0 => no reason, 1 => upgrading, 2+ => defined by system usage
          uint248 reason;
      }
  
      // Views
      function accessControl(bytes32 section, address account) external view returns (bool canSuspend, bool canResume);
  
      function requireSystemActive() external view;
  
      function requireIssuanceActive() external view;
  
      function requireExchangeActive() external view;
  
      function requireExchangeBetweenPynthsAllowed(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
  
      function requirePynthActive(bytes32 currencyKey) external view;
  
      function requirePynthsActive(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
  
      function systemSuspension() external view returns (bool suspended, uint248 reason);
  
      function issuanceSuspension() external view returns (bool suspended, uint248 reason);
  
      function exchangeSuspension() external view returns (bool suspended, uint248 reason);
  
      function pynthExchangeSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
  
      function pynthSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
  
      function getPynthExchangeSuspensions(bytes32[] calldata pynths)
          external
          view
          returns (bool[] memory exchangeSuspensions, uint256[] memory reasons);
  
      function getPynthSuspensions(bytes32[] calldata pynths)
          external
          view
          returns (bool[] memory suspensions, uint256[] memory reasons);
  
      // Restricted functions
      function suspendPynth(bytes32 currencyKey, uint256 reason) external;
  
      function updateAccessControl(
          bytes32 section,
          address account,
          bool canSuspend,
          bool canResume
      ) external;
  }
  
  
  // Inheritance
  
  
  // Libraries
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/feepoolstate
  contract FeePoolState is Owned, LimitedSetup {
      using SafeMath for uint;
      using SafeDecimalMath for uint;
  
      /* ========== STATE VARIABLES ========== */
  
      uint8 public constant FEE_PERIOD_LENGTH = 6;
  
      address public feePool;
  
      // The IssuanceData activity that's happened in a fee period.
      struct IssuanceData {
          uint debtPercentage;
          uint debtEntryIndex;
      }
  
      // The IssuanceData activity that's happened in a fee period.
      mapping(address => IssuanceData[FEE_PERIOD_LENGTH]) public accountIssuanceLedger;
  
      constructor(address _owner, IFeePool _feePool) public Owned(_owner) LimitedSetup(6 weeks) {
          feePool = address(_feePool);
      }
  
      /* ========== SETTERS ========== */
  
      /**
       * @notice set the FeePool contract as it is the only authority to be able to call
       * appendAccountIssuanceRecord with the onlyFeePool modifer
       * @dev Must be set by owner when FeePool logic is upgraded
       */
      function setFeePool(IFeePool _feePool) external onlyOwner {
          feePool = address(_feePool);
      }
  
      /* ========== VIEWS ========== */
  
      /**
       * @notice Get an accounts issuanceData for
       * @param account users account
       * @param index Index in the array to retrieve. Upto FEE_PERIOD_LENGTH
       */
      function getAccountsDebtEntry(address account, uint index)
          public
          view
          returns (uint debtPercentage, uint debtEntryIndex)
      {
          require(index < FEE_PERIOD_LENGTH, "index exceeds the FEE_PERIOD_LENGTH");
  
          debtPercentage = accountIssuanceLedger[account][index].debtPercentage;
          debtEntryIndex = accountIssuanceLedger[account][index].debtEntryIndex;
      }
  
      /**
       * @notice Find the oldest debtEntryIndex for the corresponding closingDebtIndex
       * @param account users account
       * @param closingDebtIndex the last periods debt index on close
       */
      function applicableIssuanceData(address account, uint closingDebtIndex) external view returns (uint, uint) {
          IssuanceData[FEE_PERIOD_LENGTH] memory issuanceData = accountIssuanceLedger[account];
  
          // We want to use the user's debtEntryIndex at when the period closed
          // Find the oldest debtEntryIndex for the corresponding closingDebtIndex
          for (uint i = 0; i < FEE_PERIOD_LENGTH; i++) {
              if (closingDebtIndex >= issuanceData[i].debtEntryIndex) {
                  return (issuanceData[i].debtPercentage, issuanceData[i].debtEntryIndex);
              }
          }
      }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      /**
       * @notice Logs an accounts issuance data in the current fee period which is then stored historically
       * @param account Message.Senders account address
       * @param debtRatio Debt of this account as a percentage of the global debt.
       * @param debtEntryIndex The index in the global debt ledger. periFinance.periFinanceState().issuanceData(account)
       * @param currentPeriodStartDebtIndex The startingDebtIndex of the current fee period
       * @dev onlyFeePool to call me on periFinance.issue() & periFinance.burn() calls to store the locked PERI
       * per fee period so we know to allocate the correct proportions of fees and rewards per period
        accountIssuanceLedger[account][0] has the latest locked amount for the current period. This can be update as many time
        accountIssuanceLedger[account][1-2] has the last locked amount for a previous period they minted or burned
       */
      function appendAccountIssuanceRecord(
          address account,
          uint debtRatio,
          uint debtEntryIndex,
          uint currentPeriodStartDebtIndex
      ) external onlyFeePool {
          // Is the current debtEntryIndex within this fee period
          if (accountIssuanceLedger[account][0].debtEntryIndex < currentPeriodStartDebtIndex) {
              // If its older then shift the previous IssuanceData entries periods down to make room for the new one.
              issuanceDataIndexOrder(account);
          }
  
          // Always store the latest IssuanceData entry at [0]
          accountIssuanceLedger[account][0].debtPercentage = debtRatio;
          accountIssuanceLedger[account][0].debtEntryIndex = debtEntryIndex;
      }
  
      /**
       * @notice Pushes down the entire array of debt ratios per fee period
       */
      function issuanceDataIndexOrder(address account) private {
          for (uint i = FEE_PERIOD_LENGTH - 2; i < FEE_PERIOD_LENGTH; i--) {
              uint next = i + 1;
              accountIssuanceLedger[account][next].debtPercentage = accountIssuanceLedger[account][i].debtPercentage;
              accountIssuanceLedger[account][next].debtEntryIndex = accountIssuanceLedger[account][i].debtEntryIndex;
          }
      }
  
      /**
       * @notice Import issuer data from periFinanceState.issuerData on FeePeriodClose() block #
       * @dev Only callable by the contract owner, and only for 6 weeks after deployment.
       * @param accounts Array of issuing addresses
       * @param ratios Array of debt ratios
       * @param periodToInsert The Fee Period to insert the historical records into
       * @param feePeriodCloseIndex An accounts debtEntryIndex is valid when within the fee peroid,
       * since the input ratio will be an average of the pervious periods it just needs to be
       * > recentFeePeriods[periodToInsert].startingDebtIndex
       * < recentFeePeriods[periodToInsert - 1].startingDebtIndex
       */
      function importIssuerData(
          address[] calldata accounts,
          uint[] calldata ratios,
          uint periodToInsert,
          uint feePeriodCloseIndex
      ) external onlyOwner onlyDuringSetup {
          require(accounts.length == ratios.length, "Length mismatch");
  
          for (uint i = 0; i < accounts.length; i++) {
              accountIssuanceLedger[accounts[i]][periodToInsert].debtPercentage = ratios[i];
              accountIssuanceLedger[accounts[i]][periodToInsert].debtEntryIndex = feePeriodCloseIndex;
              emit IssuanceDebtRatioEntry(accounts[i], ratios[i], feePeriodCloseIndex);
          }
      }
  
      /* ========== MODIFIERS ========== */
  
      modifier onlyFeePool {
          require(msg.sender == address(feePool), "Only the FeePool contract can perform this action");
          _;
      }
  
      /* ========== Events ========== */
      event IssuanceDebtRatioEntry(address indexed account, uint debtRatio, uint feePeriodCloseIndex);
  }
  
  
  // Inheritance
  
  
  // https://docs.peri.finance/contracts/source/contracts/state
  contract State is Owned {
      // the address of the contract that can modify variables
      // this can only be changed by the owner of this contract
      address public associatedContract;
  
      constructor(address _associatedContract) internal {
          // This contract is abstract, and thus cannot be instantiated directly
          require(owner != address(0), "Owner must be set");
  
          associatedContract = _associatedContract;
          emit AssociatedContractUpdated(_associatedContract);
      }
  
      /* ========== SETTERS ========== */
  
      // Change the associated contract to a new address
      function setAssociatedContract(address _associatedContract) external onlyOwner {
          associatedContract = _associatedContract;
          emit AssociatedContractUpdated(_associatedContract);
      }
  
      /* ========== MODIFIERS ========== */
  
      modifier onlyAssociatedContract {
          require(msg.sender == associatedContract, "Only the associated contract can perform this action");
          _;
      }
  
      /* ========== EVENTS ========== */
  
      event AssociatedContractUpdated(address associatedContract);
  }
  
  
  // Inheritance
  
  
  // https://docs.peri.finance/contracts/source/contracts/eternalstorage
  /**
   * @notice  This contract is based on the code available from this blog
   * https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88/
   * Implements support for storing a keccak256 key and value pairs. It is the more flexible
   * and extensible option. This ensures data schema changes can be implemented without
   * requiring upgrades to the storage contract.
   */
  contract EternalStorage is Owned, State {
      constructor(address _owner, address _associatedContract) public Owned(_owner) State(_associatedContract) {}
  
      /* ========== DATA TYPES ========== */
      mapping(bytes32 => uint) internal UIntStorage;
      mapping(bytes32 => string) internal StringStorage;
      mapping(bytes32 => address) internal AddressStorage;
      mapping(bytes32 => bytes) internal BytesStorage;
      mapping(bytes32 => bytes32) internal Bytes32Storage;
      mapping(bytes32 => bool) internal BooleanStorage;
      mapping(bytes32 => int) internal IntStorage;
  
      // UIntStorage;
      function getUIntValue(bytes32 record) external view returns (uint) {
          return UIntStorage[record];
      }
  
      function setUIntValue(bytes32 record, uint value) external onlyAssociatedContract {
          UIntStorage[record] = value;
      }
  
      function deleteUIntValue(bytes32 record) external onlyAssociatedContract {
          delete UIntStorage[record];
      }
  
      // StringStorage
      function getStringValue(bytes32 record) external view returns (string memory) {
          return StringStorage[record];
      }
  
      function setStringValue(bytes32 record, string calldata value) external onlyAssociatedContract {
          StringStorage[record] = value;
      }
  
      function deleteStringValue(bytes32 record) external onlyAssociatedContract {
          delete StringStorage[record];
      }
  
      // AddressStorage
      function getAddressValue(bytes32 record) external view returns (address) {
          return AddressStorage[record];
      }
  
      function setAddressValue(bytes32 record, address value) external onlyAssociatedContract {
          AddressStorage[record] = value;
      }
  
      function deleteAddressValue(bytes32 record) external onlyAssociatedContract {
          delete AddressStorage[record];
      }
  
      // BytesStorage
      function getBytesValue(bytes32 record) external view returns (bytes memory) {
          return BytesStorage[record];
      }
  
      function setBytesValue(bytes32 record, bytes calldata value) external onlyAssociatedContract {
          BytesStorage[record] = value;
      }
  
      function deleteBytesValue(bytes32 record) external onlyAssociatedContract {
          delete BytesStorage[record];
      }
  
      // Bytes32Storage
      function getBytes32Value(bytes32 record) external view returns (bytes32) {
          return Bytes32Storage[record];
      }
  
      function setBytes32Value(bytes32 record, bytes32 value) external onlyAssociatedContract {
          Bytes32Storage[record] = value;
      }
  
      function deleteBytes32Value(bytes32 record) external onlyAssociatedContract {
          delete Bytes32Storage[record];
      }
  
      // BooleanStorage
      function getBooleanValue(bytes32 record) external view returns (bool) {
          return BooleanStorage[record];
      }
  
      function setBooleanValue(bytes32 record, bool value) external onlyAssociatedContract {
          BooleanStorage[record] = value;
      }
  
      function deleteBooleanValue(bytes32 record) external onlyAssociatedContract {
          delete BooleanStorage[record];
      }
  
      // IntStorage
      function getIntValue(bytes32 record) external view returns (int) {
          return IntStorage[record];
      }
  
      function setIntValue(bytes32 record, int value) external onlyAssociatedContract {
          IntStorage[record] = value;
      }
  
      function deleteIntValue(bytes32 record) external onlyAssociatedContract {
          delete IntStorage[record];
      }
  }
  
  
  // Inheritance
  
  
  // https://docs.peri.finance/contracts/source/contracts/feepooleternalstorage
  contract FeePoolEternalStorage is EternalStorage, LimitedSetup {
      bytes32 internal constant LAST_FEE_WITHDRAWAL = "last_fee_withdrawal";
  
      constructor(address _owner, address _feePool) public EternalStorage(_owner, _feePool) LimitedSetup(6 weeks) {}
  
      function importFeeWithdrawalData(address[] calldata accounts, uint[] calldata feePeriodIDs)
          external
          onlyOwner
          onlyDuringSetup
      {
          require(accounts.length == feePeriodIDs.length, "Length mismatch");
  
          for (uint8 i = 0; i < accounts.length; i++) {
              this.setUIntValue(keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, accounts[i])), feePeriodIDs[i]);
          }
      }
  }
  
  
  interface IVirtualPynth {
      // Views
      function balanceOfUnderlying(address account) external view returns (uint);
  
      function rate() external view returns (uint);
  
      function readyToSettle() external view returns (bool);
  
      function secsLeftInWaitingPeriod() external view returns (uint);
  
      function settled() external view returns (bool);
  
      function pynth() external view returns (IPynth);
  
      // Mutative functions
      function settle(address account) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iexchanger
  interface IExchanger {
      // Views
      function calculateAmountAfterSettlement(
          address from,
          bytes32 currencyKey,
          uint amount,
          uint refunded
      ) external view returns (uint amountAfterSettlement);
  
      function isPynthRateInvalid(bytes32 currencyKey) external view returns (bool);
  
      function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) external view returns (uint);
  
      function settlementOwing(address account, bytes32 currencyKey)
          external
          view
          returns (
              uint reclaimAmount,
              uint rebateAmount,
              uint numEntries
          );
  
      function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool);
  
      function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
          external
          view
          returns (uint exchangeFeeRate);
  
      function getAmountsForExchange(
          uint sourceAmount,
          bytes32 sourceCurrencyKey,
          bytes32 destinationCurrencyKey
      )
          external
          view
          returns (
              uint amountReceived,
              uint fee,
              uint exchangeFeeRate
          );
  
      function priceDeviationThresholdFactor() external view returns (uint);
  
      function waitingPeriodSecs() external view returns (uint);
  
      // Mutative functions
      function exchange(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress
      ) external returns (uint amountReceived);
  
      function exchangeOnBehalf(
          address exchangeForAddress,
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) external returns (uint amountReceived);
  
      function exchangeWithTracking(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          address originator,
          bytes32 trackingCode
      ) external returns (uint amountReceived);
  
      function exchangeOnBehalfWithTracking(
          address exchangeForAddress,
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address originator,
          bytes32 trackingCode
      ) external returns (uint amountReceived);
  
      function exchangeWithVirtual(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          bytes32 trackingCode
      ) external returns (uint amountReceived, IVirtualPynth vPynth);
  
      function settle(address from, bytes32 currencyKey)
          external
          returns (
              uint reclaimed,
              uint refunded,
              uint numEntries
          );
  
      function setLastExchangeRateForPynth(bytes32 currencyKey, uint rate) external;
  
      function suspendPynthWithInvalidRate(bytes32 currencyKey) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iperiFinancestate
  interface IPeriFinanceState {
      // Views
      function debtLedger(uint index) external view returns (uint);
  
      function issuanceData(address account) external view returns (uint initialDebtOwnership, uint debtEntryIndex);
  
      function debtLedgerLength() external view returns (uint);
  
      function hasIssued(address account) external view returns (bool);
  
      function lastDebtLedgerEntry() external view returns (uint);
  
      // Mutative functions
      function incrementTotalIssuerCount() external;
  
      function decrementTotalIssuerCount() external;
  
      function setCurrentIssuanceData(address account, uint initialDebtOwnership) external;
  
      function appendDebtLedgerValue(uint value) external;
  
      function clearIssuanceData(address account) external;
  }
  
  
  pragma experimental ABIEncoderV2;
  
  library VestingEntries {
      struct VestingEntry {
          uint64 endTime;
          uint256 escrowAmount;
      }
      struct VestingEntryWithID {
          uint64 endTime;
          uint256 escrowAmount;
          uint256 entryID;
      }
  }
  
  interface IRewardEscrowV2 {
      // Views
      function balanceOf(address account) external view returns (uint);
  
      function numVestingEntries(address account) external view returns (uint);
  
      function totalEscrowedAccountBalance(address account) external view returns (uint);
  
      function totalVestedAccountBalance(address account) external view returns (uint);
  
      function totalEscrowedBalance() external view returns (uint);
  
      function getVestingQuantity(address account, uint256[] calldata entryIDs) external view returns (uint);
  
      function getVestingSchedules(
          address account,
          uint256 index,
          uint256 pageSize
      ) external view returns (VestingEntries.VestingEntryWithID[] memory);
  
      function getAccountVestingEntryIDs(
          address account,
          uint256 index,
          uint256 pageSize
      ) external view returns (uint256[] memory);
  
      function getVestingEntryClaimable(address account, uint256 entryID) external view returns (uint);
  
      function getVestingEntry(address account, uint256 entryID) external view returns (uint64, uint256);
  
      // Mutative functions
      function vest(uint256[] calldata entryIDs) external;
  
      function createEscrowEntry(
          address beneficiary,
          uint256 deposit,
          uint256 duration
      ) external;
  
      function appendVestingEntry(
          address account,
          uint256 quantity,
          uint256 duration
      ) external;
  
      function migrateVestingSchedule(address _addressToMigrate) external;
  
      function migrateAccountEscrowBalances(
          address[] calldata accounts,
          uint256[] calldata escrowBalances,
          uint256[] calldata vestedBalances
      ) external;
  
      // Account Merging
      function startMergingWindow() external;
  
      function mergeAccount(address accountToMerge, uint256[] calldata entryIDs) external;
  
      function nominateAccountToMerge(address account) external;
  
      function accountMergingIsOpen() external view returns (bool);
  
      // L2 Migration
      function importVestingEntries(
          address account,
          uint256 escrowedAmount,
          VestingEntries.VestingEntry[] calldata vestingEntries
      ) external;
  
      // Return amount of PERI transfered to PeriFinanceBridgeToOptimism deposit contract
      function burnForMigration(address account, uint256[] calldata entryIDs)
          external
          returns (uint256 escrowedAccountBalance, VestingEntries.VestingEntry[] memory vestingEntries);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/idelegateapprovals
  interface IDelegateApprovals {
      // Views
      function canBurnFor(address authoriser, address delegate) external view returns (bool);
  
      function canIssueFor(address authoriser, address delegate) external view returns (bool);
  
      function canClaimFor(address authoriser, address delegate) external view returns (bool);
  
      function canExchangeFor(address authoriser, address delegate) external view returns (bool);
  
      // Mutative
      function approveAllDelegatePowers(address delegate) external;
  
      function removeAllDelegatePowers(address delegate) external;
  
      function approveBurnOnBehalf(address delegate) external;
  
      function removeBurnOnBehalf(address delegate) external;
  
      function approveIssueOnBehalf(address delegate) external;
  
      function removeIssueOnBehalf(address delegate) external;
  
      function approveClaimOnBehalf(address delegate) external;
  
      function removeClaimOnBehalf(address delegate) external;
  
      function approveExchangeOnBehalf(address delegate) external;
  
      function removeExchangeOnBehalf(address delegate) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/irewardsdistribution
  interface IRewardsDistribution {
      // Structs
      struct DistributionData {
          address destination;
          uint amount;
      }
  
      // Views
      function authority() external view returns (address);
  
      function distributions(uint index) external view returns (address destination, uint amount); // DistributionData
  
      function distributionsLength() external view returns (uint);
  
      // Mutative Functions
      function distributeRewards(uint amount) external returns (bool);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iethercollateralsusd
  interface IEtherCollateralpUSD {
      // Views
      function totalIssuedPynths() external view returns (uint256);
  
      function totalLoansCreated() external view returns (uint256);
  
      function totalOpenLoanCount() external view returns (uint256);
  
      // Mutative functions
      function openLoan(uint256 _loanAmount) external payable returns (uint256 loanID);
  
      function closeLoan(uint256 loanID) external;
  
      function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external;
  
      function depositCollateral(address account, uint256 loanID) external payable;
  
      function withdrawCollateral(uint256 loanID, uint256 withdrawAmount) external;
  
      function repayLoan(
          address _loanCreatorsAddress,
          uint256 _loanID,
          uint256 _repayAmount
      ) external;
  }
  
  
  interface ICollateralManager {
      // Manager information
      function hasCollateral(address collateral) external view returns (bool);
  
      function isPynthManaged(bytes32 currencyKey) external view returns (bool);
  
      // State information
      function long(bytes32 pynth) external view returns (uint amount);
  
      function short(bytes32 pynth) external view returns (uint amount);
  
      function totalLong() external view returns (uint pusdValue, bool anyRateIsInvalid);
  
      function totalShort() external view returns (uint pusdValue, bool anyRateIsInvalid);
  
      function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid);
  
      function getShortRate(bytes32 pynth) external view returns (uint shortRate, bool rateIsInvalid);
  
      function getRatesAndTime(uint index)
          external
          view
          returns (
              uint entryRate,
              uint lastRate,
              uint lastUpdated,
              uint newIndex
          );
  
      function getShortRatesAndTime(bytes32 currency, uint index)
          external
          view
          returns (
              uint entryRate,
              uint lastRate,
              uint lastUpdated,
              uint newIndex
          );
  
      function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid);
  
      function arePynthsAndCurrenciesSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys)
          external
          view
          returns (bool);
  
      function areShortablePynthsSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys)
          external
          view
          returns (bool);
  
      // Loans
      function getNewLoanId() external returns (uint id);
  
      // Manager mutative
      function addCollaterals(address[] calldata collaterals) external;
  
      function removeCollaterals(address[] calldata collaterals) external;
  
      function addPynths(bytes32[] calldata pynthNamesInResolver, bytes32[] calldata pynthKeys) external;
  
      function removePynths(bytes32[] calldata pynths, bytes32[] calldata pynthKeys) external;
  
      function addShortablePynths(bytes32[2][] calldata requiredPynthAndInverseNamesInResolver, bytes32[] calldata pynthKeys)
          external;
  
      function removeShortablePynths(bytes32[] calldata pynths) external;
  
      // State mutative
      function updateBorrowRates(uint rate) external;
  
      function updateShortRates(bytes32 currency, uint rate) external;
  
      function incrementLongs(bytes32 pynth, uint amount) external;
  
      function decrementLongs(bytes32 pynth, uint amount) external;
  
      function incrementShorts(bytes32 pynth, uint amount) external;
  
      function decrementShorts(bytes32 pynth, uint amount) external;
  }
  
  
  interface ICrossChainManager {
      // View Functions
      function crossChainState() external view returns (address);
  
      function debtManager() external view returns (address);
  
      // supply schedule wrapper functions
      // function mintableSupply() external view returns (uint);
      // // supply schedule wrapper functions
      // function isMintable() external view returns (bool);
      // // supply schedule wrapper functions
      // function minterReward() external view returns (uint);
  
      // function getCrossChainIds() external view returns (bytes32[] memory);
  
      // function getNetworkId(bytes32 _chainID) external view returns (uint);
  
      function currentNetworkIssuedDebtOf(bytes32 currencyKey) external view returns (uint, bool);
  
      function currentNetworkActiveDebtOf(bytes32 currencyKey) external view returns (uint, bool);
  
      function currentNetworkIssuedDebt() external view returns (uint);
  
      function currentNetworkActiveDebt() external view returns (uint);
  
      // function getCrossNetworkIssuedDebt(bytes32 _chainID) external view returns (uint);
  
      // function getCrossNetworkActiveDebt(bytes32 _chainID) external view returns (uint);
  
      function crossNetworkIssuedDebtAll() external view returns (uint);
  
      function crossNetworkActiveDebtAll() external view returns (uint);
  
      function currentNetworkDebtPercentage() external view returns (uint);
  
      function movedAmount(uint _inboundOutbound, uint targetNetworkId) external view returns (uint);
  
      function outboundSumToCurrentNetwork() external view returns (uint);
  
      function syncTimestamp() external view returns (uint);
  
      function syncStale() external view returns (bool);
  
      // Mutative functions
      function setCrossChainState(address) external;
  
      function setDebtManager(address) external;
  
      // supply schedule wrapper functions
      // function recordMintEvent(uint supplyMinted) external returns (bool);
  
      // function setCrosschain(bytes32 _chainID) external;
  
      // function addCrosschain(bytes32 _chainID) external;
  
      // function addNetworkId(bytes32 _chainID, uint _networkId) external;
  
      // function addNetworkIds(uint[] calldata _networkIds) external;
  
      function addCurrentNetworkIssuedDebt(uint _amount) external;
  
      function subtractCurrentNetworkIssuedDebt(uint _amount) external;
  
      function setCrossNetworkIssuedDebtAll(uint[] calldata _chainIDs, uint[] calldata _amounts) external;
  
      function setCrossNetworkActiveDebtAll(uint[] calldata _chainIDs, uint[] calldata _amounts) external;
  
      function setOutboundSumToCurrentNetwork(uint _amount) external;
  
      // deprecated functions --> to be removed. they do nothing inside the contract
      // for backwards compatibility with Exchanger
      function addTotalNetworkDebt(uint amount) external;
  
      // for backwards compatibility with Exchanger
      function subtractTotalNetworkDebt(uint amount) external;
  
      // for backwards compatibility with Issuer
      function setCrossNetworkUserDebt(address account, uint userStateDebtLedgerIndex) external;
  
      // for backwards compatibility with Issuer
      function clearCrossNetworkUserDebt(address account) external;
  }
  
  
  // Inheritance
  
  
  // Libraries
  
  
  // Internal references
  
  
  // import "./interfaces/IPeriFinance.sol";
  
  
  // https://docs.peri.finance/contracts/source/contracts/feepool
  contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePool {
      using SafeMath for uint;
      using SafeDecimalMath for uint;
  
      // Where fees are pooled in pUSD.
      address public constant FEE_ADDRESS = 0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF;
  
      // pUSD currencyKey. Fees stored and paid in pUSD
      bytes32 private pUSD = "pUSD";
  
      // This struct represents the issuance activity that's happened in a fee period.
      struct FeePeriod {
          uint64 feePeriodId;
          uint64 startingDebtIndex;
          uint64 startTime;
          uint feesToDistribute;
          uint feesClaimed;
          uint rewardsToDistribute;
          uint rewardsClaimed;
      }
  
      // A staker(mintr) can claim from the previous fee period (7 days) only.
      // Fee Periods stored and managed from [0], such that [0] is always
      // the current active fee period which is not claimable until the
      // public function closeCurrentFeePeriod() is called closing the
      // current weeks collected fees. [1] is last weeks feeperiod
      uint8 public constant FEE_PERIOD_LENGTH = 2;
  
      FeePeriod[FEE_PERIOD_LENGTH] private _recentFeePeriods;
      uint256 private _currentFeePeriod;
      bool private _everDistributedFeeRewards;
      // bool private _everAllocatedFeeRewards;
      uint256 private _feeRewardsToBeAllocated;
  
      uint256 public quotaTolerance;
  
      /* ========== ADDRESS RESOLVER CONFIGURATION ========== */
  
      bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
      // bytes32 private constant CONTRACT_PERIFINANCE = "PeriFinance";
      bytes32 private constant CONTRACT_FEEPOOLSTATE = "FeePoolState";
      bytes32 private constant CONTRACT_FEEPOOLETERNALSTORAGE = "FeePoolEternalStorage";
      bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
      bytes32 private constant CONTRACT_ISSUER = "Issuer";
      bytes32 private constant CONTRACT_PERIFINANCESTATE = "PeriFinanceState";
      bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
      bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
      bytes32 private constant CONTRACT_ETH_COLLATERAL_PUSD = "EtherCollateralpUSD";
      bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
      bytes32 private constant CONTRACT_REWARDSDISTRIBUTION = "RewardsDistribution";
      bytes32 private constant CONTRACT_CROSSCHAINMANAGER = "CrossChainManager";
  
      /* ========== ETERNAL STORAGE CONSTANTS ========== */
  
      bytes32 private constant LAST_FEE_WITHDRAWAL = "last_fee_withdrawal";
  
      constructor(
          address payable _proxy,
          address _owner,
          address _resolver
      ) public Owned(_owner) Proxyable(_proxy) LimitedSetup(3 weeks) MixinSystemSettings(_resolver) {
          // Set our initial fee period
          _recentFeePeriodsStorage(0).feePeriodId = 1;
          _recentFeePeriodsStorage(0).startTime = uint64(now);
      }
  
      /* ========== VIEWS ========== */
      function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
          bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
          bytes32[] memory newAddresses = new bytes32[](12);
          newAddresses[0] = CONTRACT_SYSTEMSTATUS;
          // newAddresses[1] = CONTRACT_PERIFINANCE;
          newAddresses[1] = CONTRACT_FEEPOOLSTATE;
          newAddresses[2] = CONTRACT_FEEPOOLETERNALSTORAGE;
          newAddresses[3] = CONTRACT_EXCHANGER;
          newAddresses[4] = CONTRACT_ISSUER;
          newAddresses[5] = CONTRACT_PERIFINANCESTATE;
          newAddresses[6] = CONTRACT_REWARDESCROW_V2;
          newAddresses[7] = CONTRACT_DELEGATEAPPROVALS;
          newAddresses[8] = CONTRACT_ETH_COLLATERAL_PUSD;
          newAddresses[9] = CONTRACT_REWARDSDISTRIBUTION;
          newAddresses[10] = CONTRACT_COLLATERALMANAGER;
          newAddresses[11] = CONTRACT_CROSSCHAINMANAGER;
          addresses = combineArrays(existingAddresses, newAddresses);
      }
  
      function systemStatus() internal view returns (ISystemStatus) {
          return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
      }
  
      // function periFinance() internal view returns (IPeriFinance) {
      //     return IPeriFinance(requireAndGetAddress(CONTRACT_PERIFINANCE));
      // }
  
      function feePoolState() internal view returns (FeePoolState) {
          return FeePoolState(requireAndGetAddress(CONTRACT_FEEPOOLSTATE));
      }
  
      function feePoolEternalStorage() internal view returns (FeePoolEternalStorage) {
          return FeePoolEternalStorage(requireAndGetAddress(CONTRACT_FEEPOOLETERNALSTORAGE));
      }
  
      function exchanger() internal view returns (IExchanger) {
          return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
      }
  
      function etherCollateralpUSD() internal view returns (IEtherCollateralpUSD) {
          return IEtherCollateralpUSD(requireAndGetAddress(CONTRACT_ETH_COLLATERAL_PUSD));
      }
  
      function collateralManager() internal view returns (ICollateralManager) {
          return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER));
      }
  
      function crossChainManager() internal view returns (ICrossChainManager) {
          return ICrossChainManager(requireAndGetAddress(CONTRACT_CROSSCHAINMANAGER));
      }
  
      function issuer() internal view returns (IIssuer) {
          return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
      }
  
      function periFinanceState() internal view returns (IPeriFinanceState) {
          return IPeriFinanceState(requireAndGetAddress(CONTRACT_PERIFINANCESTATE));
      }
  
      function rewardEscrowV2() internal view returns (IRewardEscrowV2) {
          return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
      }
  
      function delegateApprovals() internal view returns (IDelegateApprovals) {
          return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
      }
  
      function rewardsDistribution() internal view returns (IRewardsDistribution) {
          return IRewardsDistribution(requireAndGetAddress(CONTRACT_REWARDSDISTRIBUTION));
      }
  
      function issuanceRatio() external view returns (uint) {
          return getIssuanceRatio();
      }
  
      function feePeriodDuration() external view returns (uint) {
          return getFeePeriodDuration();
      }
  
      function targetThreshold() external view returns (uint) {
          return getTargetThreshold();
      }
  
      function recentFeePeriods(uint index)
          external
          view
          returns (
              uint64 feePeriodId,
              uint64 startingDebtIndex,
              uint64 startTime,
              uint feesToDistribute,
              uint feesClaimed,
              uint rewardsToDistribute,
              uint rewardsClaimed
          )
      {
          FeePeriod memory feePeriod = _recentFeePeriodsStorage(index);
          return (
              feePeriod.feePeriodId,
              feePeriod.startingDebtIndex,
              feePeriod.startTime,
              feePeriod.feesToDistribute,
              feePeriod.feesClaimed,
              feePeriod.rewardsToDistribute,
              feePeriod.rewardsClaimed
          );
      }
  
      function everDistributedFeeRewards() external view returns (bool) {
          return _everDistributedFeeRewards;
      }
  
      // function everAllocatedFeeRewards() external view returns (bool) {
      //     return _everAllocatedFeeRewards;
      // }
  
      function feeRewardsToBeAllocated() external view returns (uint) {
          // when _everDistributedFeeRewards is true, _feeRewardsToBeAllocated has the right value
          // since _recentFeePeriodsStorage(0).feesToDistribute has the fee for the current network at the moment.
          return
              _everDistributedFeeRewards || (_recentFeePeriodsStorage(0).startTime > (now - getFeePeriodDuration()))
                  ? _feeRewardsToBeAllocated
                  : _recentFeePeriodsStorage(0).feesToDistribute;
      }
  
      function _recentFeePeriodsStorage(uint index) internal view returns (FeePeriod storage) {
          return _recentFeePeriods[(_currentFeePeriod + index) % FEE_PERIOD_LENGTH];
      }
  
      // function _allocatedOtherNetworkFeeRewards() internal view returns (uint allocatedFeesForOtherNetworks) {
      //     uint otherNetworksShare = SafeDecimalMath.preciseUnit().sub(crossChainManager().currentNetworkDebtPercentage());
  
      //     allocatedFeesForOtherNetworks = _recentFeePeriodsStorage(0)
      //         .feesToDistribute
      //         .decimalToPreciseDecimal()
      //         .multiplyDecimalRoundPrecise(otherNetworksShare)
      //         .preciseDecimalToDecimal();
      // }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      function distributeFeeRewards(uint[] calldata feeRewards) external optionalProxy onlyDebtManager {
          require(getFeePeriodDuration() > 0, "Fee Period Duration not set");
          require(
              _recentFeePeriodsStorage(0).startTime <= (now - getFeePeriodDuration()),
              "distributing fee reward not yet available"
          );
  
          require(_everDistributedFeeRewards == false, "Distributing fee rewards is possible only once in a period");
          _everDistributedFeeRewards = true;
  
          // backup the fees from self network from last period.
          _feeRewardsToBeAllocated = _recentFeePeriodsStorage(0).feesToDistribute;
  
          uint totalFeeRewards;
          // Add up the fees from other networks
          for (uint i; i < feeRewards.length; i++) {
              totalFeeRewards = totalFeeRewards.add(feeRewards[i]);
          }
          // Add up the fees from self networks
          totalFeeRewards = totalFeeRewards.add(_feeRewardsToBeAllocated);
  
          // Set the proportionate rewards for self network
          _recentFeePeriodsStorage(0).feesToDistribute = totalFeeRewards
              .decimalToPreciseDecimal()
              .multiplyDecimalRoundPrecise(crossChainManager().currentNetworkDebtPercentage())
              .preciseDecimalToDecimal();
  
          if (_feeRewardsToBeAllocated > _recentFeePeriodsStorage(0).feesToDistribute) {
              // Burn the distributed rewards to other networks
              issuer().pynths(pUSD).burn(
                  FEE_ADDRESS,
                  _feeRewardsToBeAllocated.sub(_recentFeePeriodsStorage(0).feesToDistribute)
              );
          } else if (_feeRewardsToBeAllocated < _recentFeePeriodsStorage(0).feesToDistribute) {
              // Mint the extra rewards from other networks
              issuer().pynths(pUSD).issue(
                  FEE_ADDRESS,
                  _recentFeePeriodsStorage(0).feesToDistribute.sub(_feeRewardsToBeAllocated)
              );
          }
  
          // // If there are fees to be allocated to other networks, we need to burn them and subtract from feesToDistribute
          // if (_feeRewardsToBeAllocated > 0) {
          //     issuer().pynths(pUSD).burn(FEE_ADDRESS, _feeRewardsToBeAllocated);
  
          //     _feeRewardsToBeAllocated = _feeRewardsToBeAllocated;
          //     _recentFeePeriodsStorage(0).feesToDistribute = _recentFeePeriodsStorage(0).feesToDistribute.sub(
          //         _feeRewardsToBeAllocated
          //     );
          // }
      }
  
      // function allocateFeeRewards(uint amount) external optionalProxy onlyDebtManager {
      //     require(getFeePeriodDuration() > 0, "Fee Period Duration not set");
      //     require(
      //         _recentFeePeriodsStorage(0).startTime <= (now - getFeePeriodDuration()),
      //         "allocating fee reward not yet available"
      //     );
  
      //     require(_everAllocatedFeeRewards == false, "Allocating fee rewards is possible only once in a period");
      //     _everAllocatedFeeRewards = true;
  
      //     if (amount == 0) return;
  
      //     uint currentNetworkAllocatedFeeRewards =
      //         amount
      //             .decimalToPreciseDecimal()
      //             .multiplyDecimalRoundPrecise(crossChainManager().currentNetworkDebtPercentage())
      //             .preciseDecimalToDecimal();
  
      //     issuer().pynths(pUSD).issue(FEE_ADDRESS, currentNetworkAllocatedFeeRewards);
  
      //     _recentFeePeriodsStorage(0).feesToDistribute = _recentFeePeriodsStorage(0).feesToDistribute.add(
      //         currentNetworkAllocatedFeeRewards
      //     );
      // }
  
      /**
       * @notice Logs an accounts issuance data per fee period
       * @param account Message.Senders account address
       * @param debtRatio Debt percentage this account has locked after minting or burning their pynth
       * @param debtEntryIndex The index in the global debt ledger. periFinanceState.issuanceData(account)
       * @dev onlyIssuer to call me on periFinance.issue() & periFinance.burn() calls to store the locked PERI
       * per fee period so we know to allocate the correct proportions of fees and rewards per period
       */
      function appendAccountIssuanceRecord(
          address account,
          uint debtRatio,
          uint debtEntryIndex
      ) external onlyIssuerAndPeriFinanceState {
          feePoolState().appendAccountIssuanceRecord(
              account,
              debtRatio,
              debtEntryIndex,
              _recentFeePeriodsStorage(0).startingDebtIndex
          );
  
          emitIssuanceDebtRatioEntry(account, debtRatio, debtEntryIndex, _recentFeePeriodsStorage(0).startingDebtIndex);
      }
  
      /**
       * @notice The Exchanger contract informs us when fees are paid.
       * @param amount susd amount in fees being paid.
       */
      function recordFeePaid(uint amount) external onlyInternalContracts {
          // Keep track off fees in pUSD in the open fee pool period.
          _recentFeePeriodsStorage(0).feesToDistribute = _recentFeePeriodsStorage(0).feesToDistribute.add(amount);
      }
  
      /**
       * @notice The RewardsDistribution contract informs us how many PERI rewards are sent to RewardEscrow to be claimed.
       */
      function setRewardsToDistribute(uint amount) external {
          address rewardsAuthority = address(rewardsDistribution());
          require(messageSender == rewardsAuthority || msg.sender == rewardsAuthority, "Caller is not rewardsAuthority");
          // Add the amount of PERI rewards to distribute on top of any rolling unclaimed amount
          _recentFeePeriodsStorage(0).rewardsToDistribute = _recentFeePeriodsStorage(0).rewardsToDistribute.add(amount);
      }
  
      function setQuotaTolerance(uint _val) external onlyOwner {
          require(_val < SafeDecimalMath.unit(), "Tolerance value cannot exceeds 1");
  
          quotaTolerance = _val;
      }
  
      /**
       * @notice Fix the recent period start time.
       */
      function setRecentPeriodStartTime(uint64 _startTime) external optionalProxy onlyDebtManager {
          require(now >= _startTime, "Cannot be more than the current time");
          _recentFeePeriodsStorage(0).startTime = _startTime;
      }
  
      /**
       * @notice Close the current fee period and start a new one.
       */
      function closeCurrentFeePeriod() external issuanceActive {
          require(getFeePeriodDuration() > 0, "Fee Period Duration not set");
          require(_recentFeePeriodsStorage(0).startTime <= (now - getFeePeriodDuration()), "Too early to close fee period");
          require(
              _everDistributedFeeRewards ||
                  crossChainManager().currentNetworkDebtPercentage() == SafeDecimalMath.preciseUnit(),
              "fee rewards should be distributed before closing period"
          );
  
          // Note:  when FEE_PERIOD_LENGTH = 2, periodClosing is the current period & periodToRollover is the last open claimable period
          FeePeriod storage periodClosing = _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 2);
          FeePeriod storage periodToRollover = _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 1);
          // IERC20 _perifinance = IERC20(requireAndGetAddress(CONTRACT_PERIFINANCE));
  
          // Any unclaimed fees from the last period in the array roll back one period.
          // Because of the subtraction here, they're effectively proportionally redistributed to those who
          // have already claimed from the old period, available in the new period.
          // The subtraction is important so we don't create a ticking time bomb of an ever growing
          // number of fees that can never decrease and will eventually overflow at the end of the fee pool.
          _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 2).feesToDistribute = periodToRollover
              .feesToDistribute
              .add(periodClosing.feesToDistribute)
              .sub(periodToRollover.feesClaimed);
          _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 2).rewardsToDistribute = periodToRollover
              .rewardsToDistribute
              .add(periodClosing.rewardsToDistribute)
              .sub(periodToRollover.rewardsClaimed);
  
          // _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 2).rewardsToDistribute = _perifinance
          //     .balanceOf(requireAndGetAddress(CONTRACT_REWARDESCROW_V2))
          //     .sub(rewardEscrowV2().totalEscrowedBalance());
  
          // Shift the previous fee periods across to make room for the new one.
          _currentFeePeriod = _currentFeePeriod.add(FEE_PERIOD_LENGTH).sub(1).mod(FEE_PERIOD_LENGTH);
  
          // Clear the first element of the array to make sure we don't have any stale values.
          delete _recentFeePeriods[_currentFeePeriod];
  
          // Open up the new fee period.
          // Increment periodId from the recent closed period feePeriodId
          _recentFeePeriodsStorage(0).feePeriodId = uint64(uint256(_recentFeePeriodsStorage(1).feePeriodId).add(1));
          _recentFeePeriodsStorage(0).startingDebtIndex = uint64(periFinanceState().debtLedgerLength());
          _recentFeePeriodsStorage(0).startTime = uint64(now);
  
          // allow fee rewards to be distributed when the period is closed
          _everDistributedFeeRewards = false;
          // _everAllocatedFeeRewards = false;
          emitFeePeriodClosed(_recentFeePeriodsStorage(1).feePeriodId);
      }
  
      /**
       * @notice Claim fees for last period when available or not already withdrawn.
       */
      function claimFees() external issuanceActive optionalProxy returns (bool) {
          return _claimFees(messageSender);
      }
  
      /**
       * @notice Delegated claimFees(). Call from the deletegated address
       * and the fees will be sent to the claimingForAddress.
       * approveClaimOnBehalf() must be called first to approve the deletage address
       * @param claimingForAddress The account you are claiming fees for
       */
      function claimOnBehalf(address claimingForAddress) external issuanceActive optionalProxy returns (bool) {
          require(delegateApprovals().canClaimFor(claimingForAddress, messageSender), "Not approved to claim on behalf");
  
          return _claimFees(claimingForAddress);
      }
  
      function _claimFees(address claimingAddress) internal returns (bool) {
          uint rewardsPaid;
          uint feesPaid;
          uint availableFees;
          uint availableRewards;
  
          // Address won't be able to claim fees if it is too far below the target c-ratio.
          // It will need to burn pynths then try claiming again.
          bool feesClaimable = _isFeesClaimableAndAnyRatesInvalid(claimingAddress, true);
  
          require(feesClaimable, "C-Ratio below penalty threshold");
  
          // require(!anyRateIsInvalid, "A pynth or PERI rate is invalid");
  
          // Get the claimingAddress available fees and rewards
          (availableFees, availableRewards) = feesAvailable(claimingAddress);
  
          require(
              availableFees > 0 || availableRewards > 0,
              "No fees or rewards available for period, or fees already claimed"
          );
  
          // Record the address has claimed for this period
          _setLastFeeWithdrawal(claimingAddress, _recentFeePeriodsStorage(1).feePeriodId);
  
          if (availableFees > 0) {
              // Record the fee payment in our recentFeePeriods
              feesPaid = _recordFeePayment(availableFees);
  
              // Send them their fees
              _payFees(claimingAddress, feesPaid);
          }
  
          if (availableRewards > 0) {
              // Record the reward payment in our recentFeePeriods
              rewardsPaid = _recordRewardPayment(availableRewards);
  
              // Send them their rewards
              _payRewards(claimingAddress, rewardsPaid);
          }
  
          emitFeesClaimed(claimingAddress, feesPaid, rewardsPaid);
  
          return true;
      }
  
      /**
       * @notice Admin function to import the FeePeriod data from the previous contract
       */
      /* function importFeePeriod(
          uint feePeriodIndex,
          uint feePeriodId,
          uint startingDebtIndex,
          uint startTime,
          uint feesToDistribute,
          uint feesClaimed,
          uint rewardsToDistribute,
          uint rewardsClaimed
      ) public optionalProxy_onlyOwner onlyDuringSetup {
          require(startingDebtIndex <= periFinanceState().debtLedgerLength(), "Cannot import bad data");
  
          _recentFeePeriods[_currentFeePeriod.add(feePeriodIndex).mod(FEE_PERIOD_LENGTH)] = FeePeriod({
              feePeriodId: uint64(feePeriodId),
              startingDebtIndex: uint64(startingDebtIndex),
              startTime: uint64(startTime),
              feesToDistribute: feesToDistribute,
              feesClaimed: feesClaimed,
              rewardsToDistribute: rewardsToDistribute,
              rewardsClaimed: rewardsClaimed
          });
      } */
  
      function setInitialFeePeriods(address prevFeePool) external optionalProxy_onlyOwner onlyDuringSetup {
          require(prevFeePool != address(0), "Previous FeePool address must be set");
  
          for (uint i; i < FEE_PERIOD_LENGTH; i++) {
              (
                  uint64 feePeriodId,
                  uint64 startingDebtIndex,
                  uint64 startTime,
                  uint feesToDistribute,
                  uint feesClaimed,
                  uint rewardsToDistribute,
                  uint rewardsClaimed
              ) = IFeePool(prevFeePool).recentFeePeriods(i);
  
              _recentFeePeriodsStorage(i).feePeriodId = feePeriodId;
              _recentFeePeriodsStorage(i).startingDebtIndex = startingDebtIndex;
              _recentFeePeriodsStorage(i).startTime = startTime;
              _recentFeePeriodsStorage(i).feesToDistribute = feesToDistribute;
              _recentFeePeriodsStorage(i).feesClaimed = feesClaimed;
              _recentFeePeriodsStorage(i).rewardsToDistribute = rewardsToDistribute;
              _recentFeePeriodsStorage(i).rewardsClaimed = rewardsClaimed;
          }
      }
  
      /**
       * @notice Record the fee payment in our recentFeePeriods.
       * @param pUSDAmount The amount of fees priced in pUSD.
       */
      function _recordFeePayment(uint pUSDAmount) internal returns (uint feesPaid) {
          // Don't assign to the parameter
          uint remainingToAllocate = pUSDAmount;
  
          // Start at the oldest period and record the amount, moving to newer periods
          // until we've exhausted the amount.
          // The condition checks for overflow because we're going to 0 with an unsigned int.
          for (uint i = FEE_PERIOD_LENGTH - 1; i < FEE_PERIOD_LENGTH; i--) {
              uint feesAlreadyClaimed = _recentFeePeriodsStorage(i).feesClaimed;
              uint delta = _recentFeePeriodsStorage(i).feesToDistribute.sub(feesAlreadyClaimed);
  
              if (delta > 0) {
                  // Take the smaller of the amount left to claim in the period and the amount we need to allocate
                  uint amountInPeriod = delta < remainingToAllocate ? delta : remainingToAllocate;
  
                  _recentFeePeriodsStorage(i).feesClaimed = feesAlreadyClaimed.add(amountInPeriod);
                  remainingToAllocate = remainingToAllocate.sub(amountInPeriod);
                  feesPaid = feesPaid.add(amountInPeriod);
  
                  // No need to continue iterating if we've recorded the whole amount;
                  if (remainingToAllocate == 0) return feesPaid;
  
                  // We've exhausted feePeriods to distribute and no fees remain in last period
                  // User last to claim would in this scenario have their remainder slashed
                  if (i == 0 && remainingToAllocate > 0) {
                      remainingToAllocate = 0;
                  }
              }
          }
  
          // return feesPaid;
      }
  
      /**
       * @notice Record the reward payment in our recentFeePeriods.
       * @param periAmount The amount of PERI tokens.
       */
      function _recordRewardPayment(uint periAmount) internal returns (uint rewardPaid) {
          // Don't assign to the parameter
          uint remainingToAllocate = periAmount;
  
          // uint rewardPaid;
  
          // Start at the oldest period and record the amount, moving to newer periods
          // until we've exhausted the amount.
          // The condition checks for overflow because we're going to 0 with an unsigned int.
          for (uint i = FEE_PERIOD_LENGTH - 1; i < FEE_PERIOD_LENGTH; i--) {
              uint toDistribute =
                  _recentFeePeriodsStorage(i).rewardsToDistribute.sub(_recentFeePeriodsStorage(i).rewardsClaimed);
  
              if (toDistribute > 0) {
                  // Take the smaller of the amount left to claim in the period and the amount we need to allocate
                  uint amountInPeriod = toDistribute < remainingToAllocate ? toDistribute : remainingToAllocate;
  
                  _recentFeePeriodsStorage(i).rewardsClaimed = _recentFeePeriodsStorage(i).rewardsClaimed.add(amountInPeriod);
                  remainingToAllocate = remainingToAllocate.sub(amountInPeriod);
                  rewardPaid = rewardPaid.add(amountInPeriod);
  
                  // No need to continue iterating if we've recorded the whole amount;
                  if (remainingToAllocate == 0) return rewardPaid;
  
                  // We've exhausted feePeriods to distribute and no rewards remain in last period
                  // User last to claim would in this scenario have their remainder slashed
                  // due to rounding up of PreciseDecimal
                  if (i == 0 && remainingToAllocate > 0) {
                      remainingToAllocate = 0;
                  }
              }
          }
          // return rewardPaid;
      }
  
      /**
       * @notice Send the fees to claiming address.
       * @param account The address to send the fees to.
       * @param pUSDAmount The amount of fees priced in pUSD.
       */
      function _payFees(address account, uint pUSDAmount) internal notFeeAddress(account) {
          // Grab the pUSD Pynth
          IPynth pUSDPynth = issuer().pynths(pUSD);
  
          // NOTE: we do not control the FEE_ADDRESS so it is not possible to do an
          // ERC20.approve() transaction to allow this feePool to call ERC20.transferFrom
          // to the accounts address
  
          // Burn the source amount
          pUSDPynth.burn(FEE_ADDRESS, pUSDAmount);
  
          // Mint their new pynths
          pUSDPynth.issue(account, pUSDAmount);
      }
  
      /**
       * @notice Send the rewards to claiming address - will be locked in rewardEscrow.
       * @param account The address to send the fees to.
       * @param periAmount The amount of PERI.
       */
      function _payRewards(address account, uint periAmount) internal notFeeAddress(account) {
          /* Escrow the tokens for 1 year. */
          uint escrowDuration = 52 weeks;
  
          // Record vesting entry for claiming address and amount
          // PERI already minted to rewardEscrow balance
          rewardEscrowV2().appendVestingEntry(account, periAmount, escrowDuration);
      }
  
      /**
       * @notice The total fees available in the system to be withdrawnn in pUSD
       */
      function totalFeesAvailable() external view returns (uint) {
          uint totalFees = 0;
  
          // Fees in fee period [0] are not yet available for withdrawal
          for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
              totalFees = totalFees.add(_recentFeePeriodsStorage(i).feesToDistribute);
              totalFees = totalFees.sub(_recentFeePeriodsStorage(i).feesClaimed);
          }
  
          return totalFees;
      }
  
      /**
       * @notice The total PERI rewards available in the system to be withdrawn
       */
      function totalRewardsAvailable() external view returns (uint totalRewards) {
          // Rewards in fee period [0] are not yet available for withdrawal
          for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
              totalRewards = totalRewards.add(_recentFeePeriodsStorage(i).rewardsToDistribute);
              totalRewards = totalRewards.sub(_recentFeePeriodsStorage(i).rewardsClaimed);
          }
      }
  
      /**
       * @notice The fees available to be withdrawn by a specific account, priced in pUSD
       * @dev Returns two amounts, one for fees and one for PERI rewards
       */
      function feesAvailable(address account) public view returns (uint totalFees, uint totalRewards) {
          // Add up the fees
          uint[2][FEE_PERIOD_LENGTH] memory userFees = feesByPeriod(account);
  
          // Fees & Rewards in fee period [0] are not yet available for withdrawal
          for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
              totalFees = totalFees.add(userFees[i][0]);
              totalRewards = totalRewards.add(userFees[i][1]);
          }
  
          // And convert totalFees to pUSD
          // Return totalRewards as is in PERI amount
          // return (totalFees, totalRewards);
      }
  
      function _isFeesClaimableAndAnyRatesInvalid(address account, bool _checkRate)
          internal
          view
          returns (bool feesClaimable)
      {
          // complete: Check if this is still needed
          // External token staked amount should not over the quota limit.
          /* uint accountExternalTokenQuota = issuer().externalTokenQuota(account, 0, 0, true);
          if (
              accountExternalTokenQuota > getExternalTokenQuota().multiplyDecimal(SafeDecimalMath.unit().add(quotaTolerance))
          ) {
              return (false, false);
          } */
  
          (uint tRatio, uint ratio, , , uint exSR, uint maxSR) = issuer().getRatios(account, _checkRate);
  
          // uint ratio;
          // Threshold is calculated from ratio % above the target ratio (issuanceRatio).
          // 10 decimals round-up is used to ensure that the threshold is reached when the ratio is above the target ratio.
  
          feesClaimable = true;
          // Claimable if collateral ratio below target ratio
          if (ratio <= tRatio && exSR <= maxSR) {
              return feesClaimable;
          }
  
          // Calculate the threshold for collateral ratio before fees can't be claimed.
          uint ratio_threshold = tRatio.roundUpDecimal(uint(10));
          uint exSR_threshold = maxSR.multiplyDecimal(SafeDecimalMath.unit().add(getTargetThreshold()));
  
          // Not claimable if collateral ratio above threshold
          if (ratio > ratio_threshold || exSR > exSR_threshold) {
              feesClaimable = false;
          }
      }
  
      function isFeesClaimable(address account) external view returns (bool feesClaimable) {
          return _isFeesClaimableAndAnyRatesInvalid(account, false);
      }
  
      /**
       * @notice Calculates fees by period for an account, priced in pUSD
       * @param account The address you want to query the fees for
       */
      function feesByPeriod(address account) public view returns (uint[2][FEE_PERIOD_LENGTH] memory results) {
          // What's the user's debt entry index and the debt they owe to the system at current feePeriod
          uint userOwnershipPercentage;
          uint debtEntryIndex;
          FeePoolState _feePoolState = feePoolState();
  
          (userOwnershipPercentage, debtEntryIndex) = _feePoolState.getAccountsDebtEntry(account, 0);
  
          // If they don't have any debt ownership and they never minted, they don't have any fees.
          // User ownership can reduce to 0 if user burns all pynths,
          // however they could have fees applicable for periods they had minted in before so we check debtEntryIndex.
          if (debtEntryIndex == 0 && userOwnershipPercentage == 0) {
              uint[2][FEE_PERIOD_LENGTH] memory nullResults;
              return nullResults;
          }
  
          // The [0] fee period is not yet ready to claim, but it is a fee period that they can have
          // fees owing for, so we need to report on it anyway.
          uint feesFromPeriod;
          uint rewardsFromPeriod;
          (feesFromPeriod, rewardsFromPeriod) = _feesAndRewardsFromPeriod(0, userOwnershipPercentage, debtEntryIndex);
  
          results[0][0] = feesFromPeriod;
          results[0][1] = rewardsFromPeriod;
  
          // Retrieve user's last fee claim by periodId
          uint lastFeeWithdrawal = getLastFeeWithdrawal(account);
  
          // Go through our fee periods from the oldest feePeriod[FEE_PERIOD_LENGTH - 1] and figure out what we owe them.
          // Condition checks for periods > 0
          for (uint i = FEE_PERIOD_LENGTH - 1; i > 0; i--) {
              uint next = i - 1;
              uint nextPeriodStartingDebtIndex = _recentFeePeriodsStorage(next).startingDebtIndex;
  
              // We can skip the period, as no debt minted during period (next period's startingDebtIndex is still 0)
              if (nextPeriodStartingDebtIndex > 0 && lastFeeWithdrawal < _recentFeePeriodsStorage(i).feePeriodId) {
                  // We calculate a feePeriod's closingDebtIndex by looking at the next feePeriod's startingDebtIndex
                  // we can use the most recent issuanceData[0] for the current feePeriod
                  // else find the applicableIssuanceData for the feePeriod based on the StartingDebtIndex of the period
                  uint closingDebtIndex = uint256(nextPeriodStartingDebtIndex).sub(1);
  
                  // Gas optimisation - to reuse debtEntryIndex if found new applicable one
                  // if applicable is 0,0 (none found) we keep most recent one from issuanceData[0]
                  // return if userOwnershipPercentage = 0)
                  (userOwnershipPercentage, debtEntryIndex) = _feePoolState.applicableIssuanceData(account, closingDebtIndex);
  
                  (feesFromPeriod, rewardsFromPeriod) = _feesAndRewardsFromPeriod(i, userOwnershipPercentage, debtEntryIndex);
  
                  results[i][0] = feesFromPeriod;
                  results[i][1] = rewardsFromPeriod;
              }
          }
      }
  
      /**
       * @notice ownershipPercentage is a high precision decimals uint based on
       * wallet's debtPercentage. Gives a precise amount of the feesToDistribute
       * for fees in the period. Precision factor is removed before results are
       * returned.
       * @dev The reported fees owing for the current period [0] are just a
       * running balance until the fee period closes
       */
      function _feesAndRewardsFromPeriod(
          uint period,
          uint ownershipPercentage,
          uint debtEntryIndex
      ) internal view returns (uint, uint) {
          // If it's zero, they haven't issued, and they have no fees OR rewards.
          if (ownershipPercentage == 0) return (0, 0);
  
          uint debtOwnershipForPeriod = ownershipPercentage;
  
          // If period has closed we want to calculate debtPercentage for the period
          if (period > 0) {
              uint closingDebtIndex = uint256(_recentFeePeriodsStorage(period - 1).startingDebtIndex).sub(1);
              debtOwnershipForPeriod = _effectiveDebtRatioForPeriod(closingDebtIndex, ownershipPercentage, debtEntryIndex);
          }
  
          // Calculate their percentage of the fees / rewards in this period
          // This is a high precision integer.
          uint feesFromPeriod = _recentFeePeriodsStorage(period).feesToDistribute.multiplyDecimal(debtOwnershipForPeriod);
  
          uint rewardsFromPeriod =
              _recentFeePeriodsStorage(period).rewardsToDistribute.multiplyDecimal(debtOwnershipForPeriod);
  
          return (feesFromPeriod.preciseDecimalToDecimal(), rewardsFromPeriod.preciseDecimalToDecimal());
      }
  
      function _effectiveDebtRatioForPeriod(
          uint closingDebtIndex,
          uint ownershipPercentage,
          uint debtEntryIndex
      ) internal view returns (uint) {
          // Figure out their global debt percentage delta at end of fee Period.
          // This is a high precision integer.
          IPeriFinanceState _periFinanceState = periFinanceState();
          uint feePeriodDebtOwnership =
              _periFinanceState
                  .debtLedger(closingDebtIndex)
                  .divideDecimalRoundPrecise(_periFinanceState.debtLedger(debtEntryIndex))
                  .multiplyDecimalRoundPrecise(ownershipPercentage);
  
          return feePeriodDebtOwnership;
      }
  
      function effectiveDebtRatioForPeriod(address account, uint period) external view returns (uint) {
          require(period != 0, "Current period is not closed yet");
          require(period < FEE_PERIOD_LENGTH, "Exceeds the FEE_PERIOD_LENGTH");
  
          // If the period being checked is uninitialised then return 0. This is only at the start of the system.
          if (_recentFeePeriodsStorage(period - 1).startingDebtIndex == 0) return 0;
  
          uint closingDebtIndex = uint256(_recentFeePeriodsStorage(period - 1).startingDebtIndex).sub(1);
  
          uint ownershipPercentage;
          uint debtEntryIndex;
          (ownershipPercentage, debtEntryIndex) = feePoolState().applicableIssuanceData(account, closingDebtIndex);
  
          // internal function will check closingDebtIndex has corresponding debtLedger entry
          return _effectiveDebtRatioForPeriod(closingDebtIndex, ownershipPercentage, debtEntryIndex);
      }
  
      /**
       * @notice Get the feePeriodID of the last claim this account made
       * @param _claimingAddress account to check the last fee period ID claim for
       * @return uint of the feePeriodID this account last claimed
       */
      function getLastFeeWithdrawal(address _claimingAddress) public view returns (uint) {
          return feePoolEternalStorage().getUIntValue(keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, _claimingAddress)));
      }
  
      /**
       * @notice Calculate the collateral ratio before user is blocked from claiming.
       */
      function getPenaltyThresholdRatio() public view returns (uint) {
          return getIssuanceRatio().multiplyDecimal(SafeDecimalMath.unit().add(getTargetThreshold()));
      }
  
      /**
       * @notice Set the feePeriodID of the last claim this account made
       * @param _claimingAddress account to set the last feePeriodID claim for
       * @param _feePeriodID the feePeriodID this account claimed fees for
       */
      function _setLastFeeWithdrawal(address _claimingAddress, uint _feePeriodID) internal {
          feePoolEternalStorage().setUIntValue(
              keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, _claimingAddress)),
              _feePeriodID
          );
      }
  
      function _debtManager() internal view returns (address) {
          return crossChainManager().debtManager();
      }
  
      /* ========== Modifiers ========== */
      modifier onlyInternalContracts {
          bool isExchanger = msg.sender == address(exchanger());
          bool isPynth = issuer().pynthsByAddress(msg.sender) != bytes32(0);
          bool isEtherCollateralpUSD = msg.sender == address(etherCollateralpUSD());
          bool isCollateral = collateralManager().hasCollateral(msg.sender);
  
          require(isExchanger || isPynth || isEtherCollateralpUSD || isCollateral, "Only Internal Contracts");
          _;
      }
  
      modifier onlyIssuerAndPeriFinanceState {
          bool isIssuer = msg.sender == address(issuer());
          bool isPeriFinanceState = msg.sender == address(periFinanceState());
          require(isIssuer || isPeriFinanceState, "Issuer and PeriFinanceState only");
          _;
      }
  
      modifier onlyDebtManager {
          bool isDebtManager = messageSender == _debtManager();
          require(isDebtManager, "debt manager only");
          _;
      }
  
      modifier notFeeAddress(address account) {
          require(account != FEE_ADDRESS, "Fee address not allowed");
          _;
      }
  
      modifier issuanceActive() {
          systemStatus().requireIssuanceActive();
          _;
      }
  
      /* ========== Proxy Events ========== */
  
      event IssuanceDebtRatioEntry(
          address indexed account,
          uint debtRatio,
          uint debtEntryIndex,
          uint feePeriodStartingDebtIndex
      );
      bytes32 private constant ISSUANCEDEBTRATIOENTRY_SIG =
          keccak256("IssuanceDebtRatioEntry(address,uint256,uint256,uint256)");
  
      function emitIssuanceDebtRatioEntry(
          address account,
          uint debtRatio,
          uint debtEntryIndex,
          uint feePeriodStartingDebtIndex
      ) internal {
          proxy._emit(
              abi.encode(debtRatio, debtEntryIndex, feePeriodStartingDebtIndex),
              2,
              ISSUANCEDEBTRATIOENTRY_SIG,
              bytes32(uint256(uint160(account))),
              0,
              0
          );
      }
  
      event FeePeriodClosed(uint feePeriodId);
      bytes32 private constant FEEPERIODCLOSED_SIG = keccak256("FeePeriodClosed(uint256)");
  
      function emitFeePeriodClosed(uint feePeriodId) internal {
          proxy._emit(abi.encode(feePeriodId), 1, FEEPERIODCLOSED_SIG, 0, 0, 0);
      }
  
      event FeesClaimed(address account, uint pUSDAmount, uint periRewards);
      bytes32 private constant FEESCLAIMED_SIG = keccak256("FeesClaimed(address,uint256,uint256)");
  
      function emitFeesClaimed(
          address account,
          uint pUSDAmount,
          uint periRewards
      ) internal {
          proxy._emit(abi.encode(account, pUSDAmount, periRewards), 1, FEESCLAIMED_SIG, 0, 0, 0);
      }
  }
  
      