creation_sourcecode: /*
      ___            _       ___  _                          
      | .\ ___  _ _ <_> ___ | __><_>._ _  ___ ._ _  ___  ___ 
      |  _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._>
      |_|  \___.|_|  |_|     |_|  |_||_|_|<___||_|_|\_|_.\___.
      
  * PeriFinance: Exchanger.sol
  *
  * Latest source (may be newer): https://github.com/perifinance/peri-finance/blob/master/contracts/Exchanger.sol
  * Docs: Will be added in the future. 
  * https://docs.peri.finance/contracts/source/contracts/Exchanger
  *
  * Contract Dependencies: 
  *	- IAddressResolver
  *	- IExchanger
  *	- MixinResolver
  *	- MixinSystemSettings
  *	- Owned
  * Libraries: 
  *	- SafeDecimalMath
  *	- SafeMath
  *
  * MIT License
  * ===========
  *
  * Copyright (c) 2024 PeriFinance
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in all
  * copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  */
  
  
  
  pragma solidity 0.5.16;
  
  // https://docs.peri.finance/contracts/source/contracts/owned
  contract Owned {
      address public owner;
      address public nominatedOwner;
  
      constructor(address _owner) public {
          require(_owner != address(0), "Owner address cannot be 0");
          owner = _owner;
          emit OwnerChanged(address(0), _owner);
      }
  
      function nominateNewOwner(address _owner) external onlyOwner {
          nominatedOwner = _owner;
          emit OwnerNominated(_owner);
      }
  
      function acceptOwnership() external {
          require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
          emit OwnerChanged(owner, nominatedOwner);
          owner = nominatedOwner;
          nominatedOwner = address(0);
      }
  
      modifier onlyOwner {
          _onlyOwner();
          _;
      }
  
      function _onlyOwner() private view {
          require(msg.sender == owner, "Only the contract owner may perform this action");
      }
  
      event OwnerNominated(address newOwner);
      event OwnerChanged(address oldOwner, address newOwner);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iaddressresolver
  interface IAddressResolver {
      function getAddress(bytes32 name) external view returns (address);
  
      function getPynth(bytes32 key) external view returns (address);
  
      function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/ipynth
  interface IPynth {
      // Views
      function currencyKey() external view returns (bytes32);
  
      function transferablePynths(address account) external view returns (uint);
  
      // Mutative functions
      function transferAndSettle(address to, uint value) external returns (bool);
  
      function transferFromAndSettle(
          address from,
          address to,
          uint value
      ) external returns (bool);
  
      // Restricted: used internally to PeriFinance
      function burn(address account, uint amount) external;
  
      function issue(address account, uint amount) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iissuer
  interface IIssuer {
      // Views
      function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid);
  
      function availableCurrencyKeys() external view returns (bytes32[] memory);
  
      function availablePynthCount() external view returns (uint);
  
      function availablePynths(uint index) external view returns (IPynth);
  
      function canBurnPynths(address account) external view returns (bool);
  
      function collateral(address account) external view returns (uint);
  
      function collateralisationRatio(address issuer) external view returns (uint);
  
      function collateralisationRatioAndAnyRatesInvalid(address _issuer)
          external
          view
          returns (uint cratio, bool anyRateIsInvalid);
  
      function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance);
  
      function lastIssueEvent(address account) external view returns (uint);
  
      function maxIssuablePynths(address issuer)
          external
          view
          returns (
              uint,
              uint,
              uint
          );
  
      // function externalTokenQuota(
      //     address _account,
      //     uint _addtionalpUSD,
      //     uint _addtionalExToken,
      //     bool _isIssue
      // ) external view returns (uint);
  
      // function debtsCollateral(address _account, bool _rateCheck) external
      //     view
      //     returns (
      //         uint,
      //         uint,
      //         uint
      //     );
      function getRatios(address _account, bool _checkRate)
          external
          view
          returns (
              uint,
              uint,
              uint,
              uint,
              uint,
              uint
          );
  
      function getTargetRatio(address account) external view returns (uint);
  
      // function getTRatioCRatio(address _account)
      //     external
      //     view
      //     returns (
      //         uint,
      //         uint,
      //         uint,
      //         uint
      //     );
  
      function remainingIssuablePynths(address issuer)
          external
          view
          returns (
              uint maxIssuable,
              uint alreadyIssued,
              uint totalSystemDebt
          );
  
      function pynths(bytes32 currencyKey) external view returns (IPynth);
  
      function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory);
  
      function pynthsByAddress(address pynthAddress) external view returns (bytes32);
  
      function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint, bool);
  
      function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance)
          external
          view
          returns (uint transferable, bool anyRateIsInvalid);
  
      function amountsToFitClaimable(address _account) external view returns (uint burnAmount, uint exTokenAmountToUnstake);
  
      // Restricted: used internally to PeriFinance
      function issuePynths(
          address _issuer,
          bytes32 _currencyKey,
          uint _issueAmount
      ) external;
  
      function issueMaxPynths(address _issuer) external;
  
      function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external;
  
      function burnPynths(
          address _from,
          bytes32 _currencyKey,
          uint _burnAmount
      ) external;
  
      function fitToClaimable(address _from) external;
  
      function exit(address _from) external;
  
      function liquidateDelinquentAccount(
          address account,
          uint pusdAmount,
          address liquidator
      ) external returns (uint totalRedeemed, uint amountToLiquidate);
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/addressresolver
  contract AddressResolver is Owned, IAddressResolver {
      mapping(bytes32 => address) public repository;
  
      constructor(address _owner) public Owned(_owner) {}
  
      /* ========== RESTRICTED FUNCTIONS ========== */
  
      function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner {
          require(names.length == destinations.length, "Input lengths must match");
  
          for (uint i = 0; i < names.length; i++) {
              bytes32 name = names[i];
              address destination = destinations[i];
              repository[name] = destination;
              emit AddressImported(name, destination);
          }
      }
  
      /* ========= PUBLIC FUNCTIONS ========== */
  
      function rebuildCaches(MixinResolver[] calldata destinations) external {
          for (uint i = 0; i < destinations.length; i++) {
              destinations[i].rebuildCache();
          }
      }
  
      /* ========== VIEWS ========== */
  
      function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) {
          for (uint i = 0; i < names.length; i++) {
              if (repository[names[i]] != destinations[i]) {
                  return false;
              }
          }
          return true;
      }
  
      function getAddress(bytes32 name) external view returns (address) {
          return repository[name];
      }
  
      function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) {
          address _foundAddress = repository[name];
          require(_foundAddress != address(0), reason);
          return _foundAddress;
      }
  
      function getPynth(bytes32 key) external view returns (address) {
          IIssuer issuer = IIssuer(repository["Issuer"]);
          require(address(issuer) != address(0), "Cannot find Issuer address");
          return address(issuer.pynths(key));
      }
  
      /* ========== EVENTS ========== */
  
      event AddressImported(bytes32 name, address destination);
  }
  
  
  // solhint-disable payable-fallback
  
  // https://docs.peri.finance/contracts/source/contracts/readproxy
  contract ReadProxy is Owned {
      address public target;
  
      constructor(address _owner) public Owned(_owner) {}
  
      function setTarget(address _target) external onlyOwner {
          target = _target;
          emit TargetUpdated(target);
      }
  
      function() external {
          // The basics of a proxy read call
          // Note that msg.sender in the underlying will always be the address of this contract.
          assembly {
              calldatacopy(0, 0, calldatasize)
  
              // Use of staticcall - this will revert if the underlying function mutates state
              let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0)
              returndatacopy(0, 0, returndatasize)
  
              if iszero(result) {
                  revert(0, returndatasize)
              }
              return(0, returndatasize)
          }
      }
  
      event TargetUpdated(address newTarget);
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/mixinresolver
  contract MixinResolver {
      AddressResolver public resolver;
  
      mapping(bytes32 => address) private addressCache;
  
      constructor(address _resolver) internal {
          resolver = AddressResolver(_resolver);
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function combineArrays(bytes32[] memory first, bytes32[] memory second)
          internal
          pure
          returns (bytes32[] memory combination)
      {
          combination = new bytes32[](first.length + second.length);
  
          for (uint i = 0; i < first.length; i++) {
              combination[i] = first[i];
          }
  
          for (uint j = 0; j < second.length; j++) {
              combination[first.length + j] = second[j];
          }
      }
  
      /* ========== PUBLIC FUNCTIONS ========== */
  
      // Note: this function is public not external in order for it to be overridden and invoked via super in subclasses
      function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {}
  
      function rebuildCache() public {
          bytes32[] memory requiredAddresses = resolverAddressesRequired();
          // The resolver must call this function whenver it updates its state
          for (uint i = 0; i < requiredAddresses.length; i++) {
              bytes32 name = requiredAddresses[i];
              // Note: can only be invoked once the resolver has all the targets needed added
              address destination =
                  resolver.requireAndGetAddress(name, string(abi.encodePacked("Resolver missing target: ", name)));
              addressCache[name] = destination;
              emit CacheUpdated(name, destination);
          }
      }
  
      /* ========== VIEWS ========== */
  
      function isResolverCached() external view returns (bool) {
          bytes32[] memory requiredAddresses = resolverAddressesRequired();
          for (uint i = 0; i < requiredAddresses.length; i++) {
              bytes32 name = requiredAddresses[i];
              // false if our cache is invalid or if the resolver doesn't have the required address
              if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
                  return false;
              }
          }
  
          return true;
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function requireAndGetAddress(bytes32 name) internal view returns (address) {
          address _foundAddress = addressCache[name];
          require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name)));
          return _foundAddress;
      }
  
      /* ========== EVENTS ========== */
  
      event CacheUpdated(bytes32 name, address destination);
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iflexiblestorage
  interface IFlexibleStorage {
      // Views
      function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint);
  
      function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory);
  
      function getIntValue(bytes32 contractName, bytes32 record) external view returns (int);
  
      function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory);
  
      function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address);
  
      function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory);
  
      function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool);
  
      function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory);
  
      function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32);
  
      function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory);
  
      // Mutative functions
      function deleteUIntValue(bytes32 contractName, bytes32 record) external;
  
      function deleteIntValue(bytes32 contractName, bytes32 record) external;
  
      function deleteAddressValue(bytes32 contractName, bytes32 record) external;
  
      function deleteBoolValue(bytes32 contractName, bytes32 record) external;
  
      function deleteBytes32Value(bytes32 contractName, bytes32 record) external;
  
      function setUIntValue(
          bytes32 contractName,
          bytes32 record,
          uint value
      ) external;
  
      function setUIntValues(
          bytes32 contractName,
          bytes32[] calldata records,
          uint[] calldata values
      ) external;
  
      function setIntValue(
          bytes32 contractName,
          bytes32 record,
          int value
      ) external;
  
      function setIntValues(
          bytes32 contractName,
          bytes32[] calldata records,
          int[] calldata values
      ) external;
  
      function setAddressValue(
          bytes32 contractName,
          bytes32 record,
          address value
      ) external;
  
      function setAddressValues(
          bytes32 contractName,
          bytes32[] calldata records,
          address[] calldata values
      ) external;
  
      function setBoolValue(
          bytes32 contractName,
          bytes32 record,
          bool value
      ) external;
  
      function setBoolValues(
          bytes32 contractName,
          bytes32[] calldata records,
          bool[] calldata values
      ) external;
  
      function setBytes32Value(
          bytes32 contractName,
          bytes32 record,
          bytes32 value
      ) external;
  
      function setBytes32Values(
          bytes32 contractName,
          bytes32[] calldata records,
          bytes32[] calldata values
      ) external;
  }
  
  
  // Internal references
  
  
  // https://docs.peri.finance/contracts/source/contracts/mixinsystemsettings
  contract MixinSystemSettings is MixinResolver {
      bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings";
  
      bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs";
      bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor";
      bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio";
      bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration";
      bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold";
      bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay";
      bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio";
      bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
      bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
      bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
      bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime";
      bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags";
      bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled";
      bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime";
      bytes32 internal constant SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT = "crossDomainDepositGasLimit";
      bytes32 internal constant SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT = "crossDomainEscrowGasLimit";
      bytes32 internal constant SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT = "crossDomainRewardGasLimit";
      bytes32 internal constant SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT = "crossDomainWithdrawalGasLimit";
      bytes32 internal constant SETTING_EXTERNAL_TOKEN_QUOTA = "externalTokenQuota";
      bytes32 internal constant SETTING_BRIDGE_TRANSFER_GAS_COST = "bridgeTransferGasCost";
      bytes32 internal constant SETTING_BRIDGE_CLAIM_GAS_COST = "bridgeClaimGasCost";
      bytes32 internal constant SETTING_SYNC_STALE_THRESHOLD = "syncStalThreshold";
      bytes32 internal constant SETTING_EXTOKEN_ISSUANCE_RATIO = "exTokenIssuanceRatio";
      bytes32 internal constant SETTING_LIQUIDATION_RATIOS = "liquidationRatios";
  
      bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";
  
      enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal}
  
      constructor(address _resolver) internal MixinResolver(_resolver) {}
  
      function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
          addresses = new bytes32[](1);
          addresses[0] = CONTRACT_FLEXIBLESTORAGE;
      }
  
      function flexibleStorage() internal view returns (IFlexibleStorage) {
          return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE));
      }
  
      function _getGasLimitSetting(CrossDomainMessageGasLimits gasLimitType) internal pure returns (bytes32) {
          if (gasLimitType == CrossDomainMessageGasLimits.Deposit) {
              return SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT;
          } else if (gasLimitType == CrossDomainMessageGasLimits.Escrow) {
              return SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT;
          } else if (gasLimitType == CrossDomainMessageGasLimits.Reward) {
              return SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT;
          } else if (gasLimitType == CrossDomainMessageGasLimits.Withdrawal) {
              return SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT;
          } else {
              revert("Unknown gas limit type");
          }
      }
  
      function getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits gasLimitType) internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, _getGasLimitSetting(gasLimitType));
      }
  
      function getTradingRewardsEnabled() internal view returns (bool) {
          return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED);
      }
  
      function getWaitingPeriodSecs() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS);
      }
  
      function getPriceDeviationThresholdFactor() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR);
      }
  
      function getIssuanceRatio() internal view returns (uint) {
          // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO);
      }
  
      function getFeePeriodDuration() internal view returns (uint) {
          // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION);
      }
  
      function getTargetThreshold() internal view returns (uint) {
          // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD);
      }
  
      function getLiquidationDelay() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY);
      }
  
      function getLiquidationRatio() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO);
      }
  
      function getLiquidationPenalty() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
      }
  
      function getRateStalePeriod() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
      }
  
      function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
              );
      }
  
      function getMinimumStakeTime() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME);
      }
  
      function getAggregatorWarningFlags() internal view returns (address) {
          return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS);
      }
  
      function getDebtSnapshotStaleTime() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME);
      }
  
      function getExternalTokenQuota() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXTERNAL_TOKEN_QUOTA);
      }
  
      function getBridgeTransferGasCost() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_BRIDGE_TRANSFER_GAS_COST);
      }
  
      function getBridgeClaimGasCost() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_BRIDGE_CLAIM_GAS_COST);
      }
  
      function getSyncStaleThreshold() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_SYNC_STALE_THRESHOLD);
      }
  
      function getExTokenIssuanceRatio(bytes32 tokenKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_EXTOKEN_ISSUANCE_RATIO, tokenKey))
              );
      }
  
      function getLiquidationRatios(bytes32 types) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_LIQUIDATION_RATIOS, types))
              );
      }
  }
  
  
  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;
  }
  
  
  /**
   * @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/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;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iexchangestate
  interface IExchangeState {
      // Views
      struct ExchangeEntry {
          bytes32 src;
          uint amount;
          bytes32 dest;
          uint amountReceived;
          uint exchangeFeeRate;
          uint timestamp;
          uint roundIdForSrc;
          uint roundIdForDest;
      }
  
      function getLengthOfEntries(address account, bytes32 currencyKey) external view returns (uint);
  
      function getEntryAt(
          address account,
          bytes32 currencyKey,
          uint index
      )
          external
          view
          returns (
              bytes32 src,
              uint amount,
              bytes32 dest,
              uint amountReceived,
              uint exchangeFeeRate,
              uint timestamp,
              uint roundIdForSrc,
              uint roundIdForDest
          );
  
      function getMaxTimestamp(address account, bytes32 currencyKey) external view returns (uint);
  
      // Mutative functions
      function appendExchangeEntry(
          address account,
          bytes32 src,
          uint amount,
          bytes32 dest,
          uint amountReceived,
          uint exchangeFeeRate,
          uint timestamp,
          uint roundIdForSrc,
          uint roundIdForDest
      ) external;
  
      function removeEntries(address account, bytes32 currencyKey) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iexchangerates
  interface IExchangeRates {
      // Structs
      struct RateAndUpdatedTime {
          uint216 rate;
          uint40 time;
      }
  
      struct InversePricing {
          uint entryPoint;
          uint upperLimit;
          uint lowerLimit;
          bool frozenAtUpperLimit;
          bool frozenAtLowerLimit;
      }
  
      // Views
      function aggregators(bytes32 currencyKey) external view returns (address);
  
      function aggregatorWarningFlags() external view returns (address);
  
      function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
  
      function canFreezeRate(bytes32 currencyKey) external view returns (bool);
  
      function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
  
      function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
  
      function effectiveValue(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) external view returns (uint value);
  
      function effectiveValueAndRates(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      )
          external
          view
          returns (
              uint value,
              uint sourceRate,
              uint destinationRate
          );
  
      function effectiveValueAtRound(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          uint roundIdForSrc,
          uint roundIdForDest
      ) external view returns (uint value);
  
      function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
  
      function getLastRoundIdBeforeElapsedSecs(
          bytes32 currencyKey,
          uint startingRoundId,
          uint startingTimestamp,
          uint timediff
      ) external view returns (uint);
  
      function inversePricing(bytes32 currencyKey)
          external
          view
          returns (
              uint entryPoint,
              uint upperLimit,
              uint lowerLimit,
              bool frozenAtUpperLimit,
              bool frozenAtLowerLimit
          );
  
      function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
  
      function oracle() external view returns (address);
  
      function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
  
      function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);
  
      function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid);
  
      function rateForCurrency(bytes32 currencyKey) external view returns (uint);
  
      function rateIsFlagged(bytes32 currencyKey) external view returns (bool);
  
      function rateIsFrozen(bytes32 currencyKey) external view returns (bool);
  
      function rateIsInvalid(bytes32 currencyKey) external view returns (bool);
  
      function rateIsStale(bytes32 currencyKey) external view returns (bool);
  
      function rateStalePeriod() external view returns (uint);
  
      function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
          external
          view
          returns (uint[] memory rates, uint[] memory times);
  
      function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
          external
          view
          returns (uint[] memory rates, bool anyRateInvalid);
  
      function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
  
      // Mutative functions
      function freezeRate(bytes32 currencyKey) external;
  }
  
  
  // https://docs.peri.finance/contracts/source/interfaces/iperiFinance
  interface IPeriFinance {
      // Views
      function getRequiredAddress(bytes32 contractName) external view returns (address);
  
      // 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 collateral(address account) external view returns (uint);
  
      function collateralisationRatio(address issuer) external view returns (uint);
  
      function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint);
  
      function isWaitingPeriod(bytes32 currencyKey) external view returns (bool);
  
      function maxIssuablePynths(address issuer) external view returns (uint maxIssuable);
  
      function remainingIssuablePynths(address issuer)
          external
          view
          returns (
              uint maxIssuable,
              uint alreadyIssued,
              uint totalSystemDebt
          );
  
      // function pynths(bytes32 currencyKey) external view returns (IPynth);
  
      // function pynthsByAddress(address pynthAddress) external view returns (bytes32);
  
      function totalIssuedPynths(bytes32 currencyKey) external view returns (uint);
  
      function totalIssuedPynthsExcludeEtherCollateral(bytes32 currencyKey) external view returns (uint);
  
      function transferablePeriFinance(address account) external view returns (uint transferable);
  
      function amountsToFitClaimable(address account) external view returns (uint burnAmount, uint exTokenAmountToUnstake);
  
      // Mutative Functions
      function issuePynths(bytes32 _currencyKey, uint _issueAmount) external;
  
      function issueMaxPynths() external;
  
      function issuePynthsToMaxQuota(bytes32 _currencyKey) external;
  
      function burnPynths(bytes32 _currencyKey, uint _burnAmount) external;
  
      // function fitToClaimable() external payable;
  
      function exit() external;
  
      function exchange(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) external returns (uint amountReceived);
  
      function exchangeOnBehalf(
          address exchangeForAddress,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) external returns (uint amountReceived);
  
      function exchangeWithTracking(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address originator,
          bytes32 trackingCode
      ) external returns (uint amountReceived);
  
      function exchangeOnBehalfWithTracking(
          address exchangeForAddress,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address originator,
          bytes32 trackingCode
      ) external returns (uint amountReceived);
  
      function exchangeWithVirtual(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          bytes32 trackingCode
      ) external returns (uint amountReceived, IVirtualPynth vPynth);
  
      function mint(address _user, uint _amount) external returns (bool);
  
      function inflationalMint() external returns (bool);
  
      function settle(bytes32 currencyKey)
          external
          returns (
              uint reclaimed,
              uint refunded,
              uint numEntries
          );
  
      // Liquidations
      function liquidateDelinquentAccount(address account, uint pusdAmount) external returns (bool);
  
      // Restricted Functions
  
      function mintSecondary(address account, uint amount) external;
  
      function mintSecondaryRewards(uint amount) external;
  
      function burnSecondary(address account, uint amount) external;
  }
  
  
  // 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);
  }
  
  
  // 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/itradingrewards
  interface ITradingRewards {
      /* ========== VIEWS ========== */
  
      function getAvailableRewards() external view returns (uint);
  
      function getUnassignedRewards() external view returns (uint);
  
      function getRewardsToken() external view returns (address);
  
      function getPeriodController() external view returns (address);
  
      function getCurrentPeriod() external view returns (uint);
  
      function getPeriodIsClaimable(uint periodID) external view returns (bool);
  
      function getPeriodIsFinalized(uint periodID) external view returns (bool);
  
      function getPeriodRecordedFees(uint periodID) external view returns (uint);
  
      function getPeriodTotalRewards(uint periodID) external view returns (uint);
  
      function getPeriodAvailableRewards(uint periodID) external view returns (uint);
  
      function getUnaccountedFeesForAccountForPeriod(address account, uint periodID) external view returns (uint);
  
      function getAvailableRewardsForAccountForPeriod(address account, uint periodID) external view returns (uint);
  
      function getAvailableRewardsForAccountForPeriods(address account, uint[] calldata periodIDs)
          external
          view
          returns (uint totalRewards);
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      function claimRewardsForPeriod(uint periodID) external;
  
      function claimRewardsForPeriods(uint[] calldata periodIDs) external;
  
      /* ========== RESTRICTED FUNCTIONS ========== */
  
      function recordExchangeFeeForAccount(uint usdFeeAmount, address account) external;
  
      function closeCurrentPeriodWithRewards(uint rewards) external;
  
      function recoverTokens(address tokenAddress, address recoverAddress) external;
  
      function recoverUnassignedRewardTokens(address recoverAddress) external;
  
      function recoverAssignedRewardTokensAndDestroyPeriod(address recoverAddress, uint periodID) external;
  
      function setPeriodController(address newPeriodController) external;
  }
  
  
  // 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);
  }
  
  
  /**
   * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
   * the optional functions; to access them see `ERC20Detailed`.
   */
  interface IERC20 {
      /**
       * @dev Returns the amount of tokens in existence.
       */
      function totalSupply() external view returns (uint256);
  
      /**
       * @dev Returns the amount of tokens owned by `account`.
       */
      function balanceOf(address account) external view returns (uint256);
  
      /**
       * @dev Moves `amount` tokens from the caller's account to `recipient`.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a `Transfer` event.
       */
      function transfer(address recipient, uint256 amount) external returns (bool);
  
      /**
       * @dev Returns the remaining number of tokens that `spender` will be
       * allowed to spend on behalf of `owner` through `transferFrom`. This is
       * zero by default.
       *
       * This value changes when `approve` or `transferFrom` are called.
       */
      function allowance(address owner, address spender) external view returns (uint256);
  
      /**
       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * > Beware that changing an allowance with this method brings the risk
       * that someone may use both the old and the new allowance by unfortunate
       * transaction ordering. One possible solution to mitigate this race
       * condition is to first reduce the spender's allowance to 0 and set the
       * desired value afterwards:
       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
       *
       * Emits an `Approval` event.
       */
      function approve(address spender, uint256 amount) external returns (bool);
  
      /**
       * @dev Moves `amount` tokens from `sender` to `recipient` using the
       * allowance mechanism. `amount` is then deducted from the caller's
       * allowance.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a `Transfer` event.
       */
      function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
  
      /**
       * @dev Emitted when `value` tokens are moved from one account (`from`) to
       * another (`to`).
       *
       * Note that `value` may be zero.
       */
      event Transfer(address indexed from, address indexed to, uint256 value);
  
      /**
       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
       * a call to `approve`. `value` is the new allowance.
       */
      event Approval(address indexed owner, address indexed spender, uint256 value);
  }
  
  
  // Inheritance
  
  
  // Libraries
  
  
  // Internal references
  
  
  // import "./interfaces/ICrossChainManager.sol";
  
  
  // Note: use OZ's IERC20 here as using ours will complain about conflicting names
  // during the build (VirtualPynth has IERC20 from the OZ ERC20 implementation)
  
  
  // Used to have strongly-typed access to internal mutative functions in PeriFinance
  interface IPeriFinanceInternal {
      function emitExchangeTracking(
          bytes32 trackingCode,
          bytes32 toCurrencyKey,
          uint256 toAmount
      ) external;
  
      function emitPynthExchange(
          address account,
          bytes32 fromCurrencyKey,
          uint fromAmount,
          bytes32 toCurrencyKey,
          uint toAmount,
          address toAddress
      ) external;
  
      function emitExchangeReclaim(
          address account,
          bytes32 currencyKey,
          uint amount
      ) external;
  
      function emitExchangeRebate(
          address account,
          bytes32 currencyKey,
          uint amount
      ) external;
  }
  
  interface IExchangerInternalDebtCache {
      function updateCachedPynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
  
      function updateCachedPynthDebts(bytes32[] calldata currencyKeys) external;
  }
  
  interface IExchangerInternalVirtualPynthIssuer {
      function createVirtualPynth(
          IERC20 pynth,
          address recipient,
          uint amount,
          bytes32 currencyKey
      ) external returns (IVirtualPynth);
  }
  
  // https://docs.peri.finance/contracts/source/contracts/exchanger
  contract Exchanger is Owned, MixinSystemSettings, IExchanger {
      using SafeMath for uint;
      using SafeDecimalMath for uint;
  
      struct ExchangeEntrySettlement {
          bytes32 src;
          uint amount;
          bytes32 dest;
          uint reclaim;
          uint rebate;
          uint srcRoundIdAtPeriodEnd;
          uint destRoundIdAtPeriodEnd;
          uint timestamp;
      }
  
      bytes32 private constant pUSD = "pUSD";
  
      // SIP-65: Decentralized circuit breaker
      uint public constant CIRCUIT_BREAKER_SUSPENSION_REASON = 65;
  
      mapping(bytes32 => uint) public lastExchangeRate;
  
      /* ========== ADDRESS RESOLVER CONFIGURATION ========== */
  
      bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
      bytes32 private constant CONTRACT_EXCHANGESTATE = "ExchangeState";
      bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
      bytes32 private constant CONTRACT_PERIFINANCE = "PeriFinance";
      bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
      bytes32 private constant CONTRACT_TRADING_REWARDS = "TradingRewards";
      bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
      bytes32 private constant CONTRACT_ISSUER = "Issuer";
      bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache";
      // bytes32 private constant CONTRACT_CROSSCHAINMANAGER = "CrossChainManager";
      bytes32 private constant CONTRACT_VIRTUALPYNTHISSUER = "VirtualPynthIssuer";
  
      constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
  
      /* ========== VIEWS ========== */
  
      function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
          bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
          bytes32[] memory newAddresses = new bytes32[](10);
          newAddresses[0] = CONTRACT_SYSTEMSTATUS;
          newAddresses[1] = CONTRACT_EXCHANGESTATE;
          newAddresses[2] = CONTRACT_EXRATES;
          newAddresses[3] = CONTRACT_PERIFINANCE;
          newAddresses[4] = CONTRACT_FEEPOOL;
          newAddresses[5] = CONTRACT_TRADING_REWARDS;
          newAddresses[6] = CONTRACT_DELEGATEAPPROVALS;
          newAddresses[7] = CONTRACT_ISSUER;
          newAddresses[8] = CONTRACT_DEBTCACHE;
          // newAddresses[9] = CONTRACT_CROSSCHAINMANAGER;
          newAddresses[9] = CONTRACT_VIRTUALPYNTHISSUER;
          addresses = combineArrays(existingAddresses, newAddresses);
      }
  
      function systemStatus() internal view returns (ISystemStatus) {
          return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
      }
  
      function exchangeState() internal view returns (IExchangeState) {
          return IExchangeState(requireAndGetAddress(CONTRACT_EXCHANGESTATE));
      }
  
      function exchangeRates() internal view returns (IExchangeRates) {
          return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
      }
  
      function periFinance() internal view returns (IPeriFinance) {
          return IPeriFinance(requireAndGetAddress(CONTRACT_PERIFINANCE));
      }
  
      function feePool() internal view returns (IFeePool) {
          return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
      }
  
      function tradingRewards() internal view returns (ITradingRewards) {
          return ITradingRewards(requireAndGetAddress(CONTRACT_TRADING_REWARDS));
      }
  
      function delegateApprovals() internal view returns (IDelegateApprovals) {
          return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
      }
  
      function issuer() internal view returns (IIssuer) {
          return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
      }
  
      function debtCache() internal view returns (IExchangerInternalDebtCache) {
          return IExchangerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE));
      }
  
      // function crossChainManager() internal view returns (ICrossChainManager) {
      //     return ICrossChainManager(requireAndGetAddress(CONTRACT_CROSSCHAINMANAGER));
      // }
  
      function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) public view returns (uint) {
          return secsLeftInWaitingPeriodForExchange(exchangeState().getMaxTimestamp(account, currencyKey));
      }
  
      function waitingPeriodSecs() external view returns (uint) {
          return getWaitingPeriodSecs();
      }
  
      function tradingRewardsEnabled() external view returns (bool) {
          return getTradingRewardsEnabled();
      }
  
      function priceDeviationThresholdFactor() external view returns (uint) {
          return getPriceDeviationThresholdFactor();
      }
  
      function virtualPynthIssuer() internal view returns (IExchangerInternalVirtualPynthIssuer) {
          return IExchangerInternalVirtualPynthIssuer(requireAndGetAddress(CONTRACT_VIRTUALPYNTHISSUER));
      }
  
      function settlementOwing(address account, bytes32 currencyKey)
          public
          view
          returns (
              uint reclaimAmount,
              uint rebateAmount,
              uint numEntries
          )
      {
          (reclaimAmount, rebateAmount, numEntries, ) = _settlementOwing(account, currencyKey);
      }
  
      // Internal function to emit events for each individual rebate and reclaim entry
      function _settlementOwing(address account, bytes32 currencyKey)
          internal
          view
          returns (
              uint reclaimAmount,
              uint rebateAmount,
              uint numEntries,
              ExchangeEntrySettlement[] memory
          )
      {
          // Need to sum up all reclaim and rebate amounts for the user and the currency key
          numEntries = exchangeState().getLengthOfEntries(account, currencyKey);
  
          // For each unsettled exchange
          ExchangeEntrySettlement[] memory settlements = new ExchangeEntrySettlement[](numEntries);
          for (uint i = 0; i < numEntries; i++) {
              uint reclaim;
              uint rebate;
              // fetch the entry from storage
              IExchangeState.ExchangeEntry memory exchangeEntry = _getExchangeEntry(account, currencyKey, i);
  
              // determine the last round ids for src and dest pairs when period ended or latest if not over
              (uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd) = getRoundIdsAtPeriodEnd(exchangeEntry);
  
              // given these round ids, determine what effective value they should have received
              uint destinationAmount =
                  exchangeRates().effectiveValueAtRound(
                      exchangeEntry.src,
                      exchangeEntry.amount,
                      exchangeEntry.dest,
                      srcRoundIdAtPeriodEnd,
                      destRoundIdAtPeriodEnd
                  );
  
              // and deduct the fee from this amount using the exchangeFeeRate from storage
              uint amountShouldHaveReceived = _getAmountReceivedForExchange(destinationAmount, exchangeEntry.exchangeFeeRate);
  
              // SIP-65 settlements where the amount at end of waiting period is beyond the threshold, then
              // settle with no reclaim or rebate
              if (!_isDeviationAboveThreshold(exchangeEntry.amountReceived, amountShouldHaveReceived)) {
                  if (exchangeEntry.amountReceived > amountShouldHaveReceived) {
                      // if they received more than they should have, add to the reclaim tally
                      reclaim = exchangeEntry.amountReceived.sub(amountShouldHaveReceived);
                      reclaimAmount = reclaimAmount.add(reclaim);
                  } else if (amountShouldHaveReceived > exchangeEntry.amountReceived) {
                      // if less, add to the rebate tally
                      rebate = amountShouldHaveReceived.sub(exchangeEntry.amountReceived);
                      rebateAmount = rebateAmount.add(rebate);
                  }
              }
  
              settlements[i] = ExchangeEntrySettlement({
                  src: exchangeEntry.src,
                  amount: exchangeEntry.amount,
                  dest: exchangeEntry.dest,
                  reclaim: reclaim,
                  rebate: rebate,
                  srcRoundIdAtPeriodEnd: srcRoundIdAtPeriodEnd,
                  destRoundIdAtPeriodEnd: destRoundIdAtPeriodEnd,
                  timestamp: exchangeEntry.timestamp
              });
          }
  
          return (reclaimAmount, rebateAmount, numEntries, settlements);
      }
  
      function _getExchangeEntry(
          address account,
          bytes32 currencyKey,
          uint index
      ) internal view returns (IExchangeState.ExchangeEntry memory) {
          (
              bytes32 src,
              uint amount,
              bytes32 dest,
              uint amountReceived,
              uint exchangeFeeRate,
              uint timestamp,
              uint roundIdForSrc,
              uint roundIdForDest
          ) = exchangeState().getEntryAt(account, currencyKey, index);
  
          return
              IExchangeState.ExchangeEntry({
                  src: src,
                  amount: amount,
                  dest: dest,
                  amountReceived: amountReceived,
                  exchangeFeeRate: exchangeFeeRate,
                  timestamp: timestamp,
                  roundIdForSrc: roundIdForSrc,
                  roundIdForDest: roundIdForDest
              });
      }
  
      function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool) {
          if (maxSecsLeftInWaitingPeriod(account, currencyKey) != 0) {
              return true;
          }
  
          (uint reclaimAmount, , , ) = _settlementOwing(account, currencyKey);
  
          return reclaimAmount > 0;
      }
  
      /* ========== SETTERS ========== */
  
      function calculateAmountAfterSettlement(
          address from,
          bytes32 currencyKey,
          uint amount,
          uint refunded
      ) public view returns (uint amountAfterSettlement) {
          amountAfterSettlement = amount;
  
          // balance of a pynth will show an amount after settlement
          uint balanceOfSourceAfterSettlement = IERC20(address(issuer().pynths(currencyKey))).balanceOf(from);
  
          // when there isn't enough supply (either due to reclamation settlement or because the number is too high)
          if (amountAfterSettlement > balanceOfSourceAfterSettlement) {
              // then the amount to exchange is reduced to their remaining supply
              amountAfterSettlement = balanceOfSourceAfterSettlement;
          }
  
          if (refunded > 0) {
              amountAfterSettlement = amountAfterSettlement.add(refunded);
          }
      }
  
      function isPynthRateInvalid(bytes32 currencyKey) external view returns (bool) {
          return _isPynthRateInvalid(currencyKey, exchangeRates().rateForCurrency(currencyKey));
      }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
      function exchange(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress
      ) external onlyPeriFinanceorPynth returns (uint amountReceived) {
          uint fee;
          (amountReceived, fee, ) = _exchange(
              from,
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey,
              destinationAddress,
              false
          );
  
          _processTradingRewards(fee, destinationAddress);
      }
  
      function exchangeOnBehalf(
          address exchangeForAddress,
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) external onlyPeriFinanceorPynth returns (uint amountReceived) {
          require(delegateApprovals().canExchangeFor(exchangeForAddress, from), "Not approved to act on behalf");
  
          uint fee;
          (amountReceived, fee, ) = _exchange(
              exchangeForAddress,
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey,
              exchangeForAddress,
              false
          );
  
          _processTradingRewards(fee, exchangeForAddress);
      }
  
      function exchangeWithTracking(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          address originator,
          bytes32 trackingCode
      ) external onlyPeriFinanceorPynth returns (uint amountReceived) {
          uint fee;
          (amountReceived, fee, ) = _exchange(
              from,
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey,
              destinationAddress,
              false
          );
  
          _processTradingRewards(fee, originator);
  
          _emitTrackingEvent(trackingCode, destinationCurrencyKey, amountReceived);
      }
  
      function exchangeOnBehalfWithTracking(
          address exchangeForAddress,
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address originator,
          bytes32 trackingCode
      ) external onlyPeriFinanceorPynth returns (uint amountReceived) {
          require(delegateApprovals().canExchangeFor(exchangeForAddress, from), "Not approved to act on behalf");
  
          uint fee;
          (amountReceived, fee, ) = _exchange(
              exchangeForAddress,
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey,
              exchangeForAddress,
              false
          );
  
          _processTradingRewards(fee, originator);
  
          _emitTrackingEvent(trackingCode, destinationCurrencyKey, amountReceived);
      }
  
      function exchangeWithVirtual(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          bytes32 trackingCode
      ) external onlyPeriFinanceorPynth returns (uint amountReceived, IVirtualPynth vPynth) {
          uint fee;
          (amountReceived, fee, vPynth) = _exchange(
              from,
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey,
              destinationAddress,
              true
          );
  
          _processTradingRewards(fee, destinationAddress);
  
          if (trackingCode != bytes32(0)) {
              _emitTrackingEvent(trackingCode, destinationCurrencyKey, amountReceived);
          }
      }
  
      function _emitTrackingEvent(
          bytes32 trackingCode,
          bytes32 toCurrencyKey,
          uint256 toAmount
      ) internal {
          IPeriFinanceInternal(address(periFinance())).emitExchangeTracking(trackingCode, toCurrencyKey, toAmount);
      }
  
      function _processTradingRewards(uint fee, address originator) internal {
          if (fee > 0 && originator != address(0) && getTradingRewardsEnabled()) {
              tradingRewards().recordExchangeFeeForAccount(fee, originator);
          }
      }
  
      function _suspendIfRateInvalid(bytes32 currencyKey, uint rate) internal returns (bool circuitBroken) {
          if (_isPynthRateInvalid(currencyKey, rate)) {
              systemStatus().suspendPynth(currencyKey, CIRCUIT_BREAKER_SUSPENSION_REASON);
              circuitBroken = true;
          } else {
              lastExchangeRate[currencyKey] = rate;
          }
      }
  
      function _updatePERIIssuedDebtOnExchange(bytes32[2] memory currencyKeys, uint[2] memory currencyRates) internal {
          bool includesPUSD = currencyKeys[0] == pUSD || currencyKeys[1] == pUSD;
          uint numKeys = includesPUSD ? 2 : 3;
  
          bytes32[] memory keys = new bytes32[](numKeys);
          keys[0] = currencyKeys[0];
          keys[1] = currencyKeys[1];
  
          uint[] memory rates = new uint[](numKeys);
          rates[0] = currencyRates[0];
          rates[1] = currencyRates[1];
  
          if (!includesPUSD) {
              keys[2] = pUSD; // And we'll also update pUSD to account for any fees if it wasn't one of the exchanged currencies
              rates[2] = SafeDecimalMath.unit();
          }
  
          // Note that exchanges can't invalidate the debt cache, since if a rate is invalid,
          // the exchange will have failed already.
          debtCache().updateCachedPynthDebtsWithRates(keys, rates);
      }
  
      function _settleAndCalcSourceAmountRemaining(
          uint sourceAmount,
          address from,
          bytes32 sourceCurrencyKey
      ) internal returns (uint sourceAmountAfterSettlement) {
          (, uint refunded, uint numEntriesSettled) = _internalSettle(from, sourceCurrencyKey, false);
  
          sourceAmountAfterSettlement = sourceAmount;
  
          // when settlement was required
          if (numEntriesSettled > 0) {
              // ensure the sourceAmount takes this into account
              sourceAmountAfterSettlement = calculateAmountAfterSettlement(from, sourceCurrencyKey, sourceAmount, refunded);
          }
      }
  
      function _exchange(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          bool virtualPynth
      )
          internal
          returns (
              uint amountReceived,
              uint fee,
              IVirtualPynth vPynth
          )
      {
          _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
  
          uint sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(sourceAmount, from, sourceCurrencyKey);
          // uint sourceAmountAfterSettlement = sourceAmount;
  
          // If, after settlement the user has no balance left (highly unlikely), then return to prevent
          // emitting events of 0 and don't revert so as to ensure the settlement queue is emptied
          if (sourceAmountAfterSettlement == 0) {
              return (0, 0, IVirtualPynth(0));
          }
  
          uint exchangeFeeRate;
          uint sourceRate;
          uint destinationRate;
  
          // Note: `fee` is denominated in the destinationCurrencyKey.
          (amountReceived, fee, exchangeFeeRate, sourceRate, destinationRate) = _getAmountsForExchangeMinusFees(
              sourceAmountAfterSettlement,
              sourceCurrencyKey,
              destinationCurrencyKey
          );
  
          // SIP-65: Decentralized Circuit Breaker
          if (
              _suspendIfRateInvalid(sourceCurrencyKey, sourceRate) ||
              _suspendIfRateInvalid(destinationCurrencyKey, destinationRate)
          ) {
              return (0, 0, IVirtualPynth(0));
          }
  
          // Note: We don't need to check their balance as the burn() below will do a safe subtraction which requires
          // the subtraction to not overflow, which would happen if their balance is not sufficient.
  
          vPynth = _convert(
              sourceCurrencyKey,
              from,
              sourceAmountAfterSettlement,
              destinationCurrencyKey,
              amountReceived,
              destinationAddress,
              virtualPynth
          );
  
          // When using a virtual pynth, it becomes the destinationAddress for event and settlement tracking
          if (vPynth != IVirtualPynth(0)) {
              destinationAddress = address(vPynth);
          }
  
          // Remit the fee if required
          if (fee > 0) {
              // Normalize fee to pUSD
              // Note: `fee` is being reused to avoid stack too deep errors.
              fee = exchangeRates().effectiveValue(destinationCurrencyKey, fee, pUSD);
  
              // Remit the fee in pUSDs
              issuer().pynths(pUSD).issue(feePool().FEE_ADDRESS(), fee);
  
              // Tell the fee pool about this
              feePool().recordFeePaid(fee);
          }
  
          // Note: As of this point, `fee` is denominated in pUSD.
  
          // Nothing changes as far as issuance data goes because the total value in the system hasn't changed.
          // But we will update the debt snapshot in case exchange rates have fluctuated since the last exchange
          // in these currencies
          _updatePERIIssuedDebtOnExchange([sourceCurrencyKey, destinationCurrencyKey], [sourceRate, destinationRate]);
  
          // Let the DApps know there was a Pynth exchange
          IPeriFinanceInternal(address(periFinance())).emitPynthExchange(
              from,
              sourceCurrencyKey,
              sourceAmountAfterSettlement,
              destinationCurrencyKey,
              amountReceived,
              destinationAddress
          );
  
          // iff the waiting period is gt 0
          if (getWaitingPeriodSecs() > 0) {
              // persist the exchange information for the dest key
              appendExchange(
                  destinationAddress,
                  sourceCurrencyKey,
                  sourceAmountAfterSettlement,
                  destinationCurrencyKey,
                  amountReceived,
                  exchangeFeeRate,
                  sourceRate,
                  destinationRate
              );
          }
      }
  
      function _convert(
          bytes32 sourceCurrencyKey,
          address from,
          uint sourceAmountAfterSettlement,
          bytes32 destinationCurrencyKey,
          uint amountReceived,
          address recipient,
          bool virtualPynth
      ) internal returns (IVirtualPynth vPynth) {
          // Burn the source amount
          issuer().pynths(sourceCurrencyKey).burn(from, sourceAmountAfterSettlement);
  
          // Issue their new pynths
          IPynth dest = issuer().pynths(destinationCurrencyKey);
  
          if (virtualPynth) {
              Proxyable pynth = Proxyable(address(dest));
              vPynth = _createVirtualPynth(IERC20(address(pynth.proxy())), recipient, amountReceived, destinationCurrencyKey);
              dest.issue(address(vPynth), amountReceived);
          } else {
              dest.issue(recipient, amountReceived);
          }
      }
  
      function _createVirtualPynth(
          IERC20 pynth,
          address recipient,
          uint amount,
          bytes32 currencyKey
      ) internal returns (IVirtualPynth) {
          virtualPynthIssuer().createVirtualPynth(pynth, recipient, amount, currencyKey);
      }
  
      // Note: this function can intentionally be called by anyone on behalf of anyone else (the caller just pays the gas)
      function settle(address from, bytes32 currencyKey)
          external
          returns (
              uint reclaimed,
              uint refunded,
              uint numEntriesSettled
          )
      {
          systemStatus().requirePynthActive(currencyKey);
          return _internalSettle(from, currencyKey, true);
      }
  
      function suspendPynthWithInvalidRate(bytes32 currencyKey) external {
          systemStatus().requireSystemActive();
          require(issuer().pynths(currencyKey) != IPynth(0), "No such pynth");
          require(_isPynthRateInvalid(currencyKey, exchangeRates().rateForCurrency(currencyKey)), "Pynth price is valid");
          systemStatus().suspendPynth(currencyKey, CIRCUIT_BREAKER_SUSPENSION_REASON);
      }
  
      // SIP-78
      function setLastExchangeRateForPynth(bytes32 currencyKey, uint rate) external onlyExchangeRates {
          require(rate > 0, "Rate must be above 0");
          lastExchangeRate[currencyKey] = rate;
      }
  
      // SIP-139
      function resetLastExchangeRate(bytes32[] calldata currencyKeys) external onlyOwner {
          (uint[] memory rates, bool anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
  
          require(!anyRateInvalid, "Rates for given synths not valid");
  
          for (uint i; i < currencyKeys.length; i++) {
              lastExchangeRate[currencyKeys[i]] = rates[i];
          }
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function _ensureCanExchange(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      ) internal view {
          require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same pynth");
          require(sourceAmount > 0, "Zero amount");
  
          bytes32[] memory pynthKeys = new bytes32[](2);
          pynthKeys[0] = sourceCurrencyKey;
          pynthKeys[1] = destinationCurrencyKey;
          require(!exchangeRates().anyRateIsInvalid(pynthKeys), "Src/dest rate invalid or not found");
      }
  
      function _isPynthRateInvalid(bytes32 currencyKey, uint currentRate) internal view returns (bool) {
          if (currentRate == 0) {
              return true;
          }
  
          uint lastRateFromExchange = lastExchangeRate[currencyKey];
  
          if (lastRateFromExchange > 0) {
              return _isDeviationAboveThreshold(lastRateFromExchange, currentRate);
          }
  
          // if no last exchange for this pynth, then we need to look up last 3 rates (+1 for current rate)
          (uint[] memory rates, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4);
  
          // start at index 1 to ignore current rate
          for (uint i = 1; i < rates.length; i++) {
              // ignore any empty rates in the past (otherwise we will never be able to get validity)
              if (rates[i] > 0 && _isDeviationAboveThreshold(rates[i], currentRate)) {
                  return true;
              }
          }
  
          return false;
      }
  
      function _isDeviationAboveThreshold(uint base, uint comparison) internal view returns (bool) {
          if (base == 0 || comparison == 0) {
              return true;
          }
  
          uint factor;
          if (comparison > base) {
              factor = comparison.divideDecimal(base);
          } else {
              factor = base.divideDecimal(comparison);
          }
  
          return factor >= getPriceDeviationThresholdFactor();
      }
  
      function _internalSettle(
          address from,
          bytes32 currencyKey,
          bool updateCache
      )
          internal
          returns (
              uint reclaimed,
              uint refunded,
              uint numEntriesSettled
          )
      {
          require(maxSecsLeftInWaitingPeriod(from, currencyKey) == 0, "Cannot settle during waiting period");
  
          (uint reclaimAmount, uint rebateAmount, uint entries, ExchangeEntrySettlement[] memory settlements) =
              _settlementOwing(from, currencyKey);
  
          if (reclaimAmount > rebateAmount) {
              reclaimed = reclaimAmount.sub(rebateAmount);
              reclaim(from, currencyKey, reclaimed);
          } else if (rebateAmount > reclaimAmount) {
              refunded = rebateAmount.sub(reclaimAmount);
              refund(from, currencyKey, refunded);
          }
  
          if (updateCache) {
              bytes32[] memory key = new bytes32[](1);
              key[0] = currencyKey;
              debtCache().updateCachedPynthDebts(key);
          }
  
          // emit settlement event for each settled exchange entry
          for (uint i; i < settlements.length; i++) {
              emit ExchangeEntrySettled(
                  from,
                  settlements[i].src,
                  settlements[i].amount,
                  settlements[i].dest,
                  settlements[i].reclaim,
                  settlements[i].rebate,
                  settlements[i].srcRoundIdAtPeriodEnd,
                  settlements[i].destRoundIdAtPeriodEnd,
                  settlements[i].timestamp
              );
          }
  
          numEntriesSettled = entries;
  
          // Now remove all entries, even if no reclaim and no rebate
          exchangeState().removeEntries(from, currencyKey);
      }
  
      function reclaim(
          address from,
          bytes32 currencyKey,
          uint amount
      ) internal {
          // burn amount from user
          issuer().pynths(currencyKey).burn(from, amount);
          IPeriFinanceInternal(address(periFinance())).emitExchangeReclaim(from, currencyKey, amount);
  
          // updating crosschainmanager's debt is not required as it is only used for issued debt.
          // these 2 lines are kept for version compatibility
          // uint reclaimedDebtInpUSD = exchangeRates().effectiveValue(currencyKey, amount, pUSD);
          // crossChainManager().subtractTotalNetworkDebt(reclaimedDebtInpUSD);
      }
  
      function refund(
          address from,
          bytes32 currencyKey,
          uint amount
      ) internal {
          // issue amount to user
          issuer().pynths(currencyKey).issue(from, amount);
          IPeriFinanceInternal(address(periFinance())).emitExchangeRebate(from, currencyKey, amount);
  
          // updating crosschainmanager's debt is not required as it is only used for issued debt.
          // these 2 lines are kept for version compatibility
          // uint refundedDebtInpUSD = exchangeRates().effectiveValue(currencyKey, amount, pUSD);
          // crossChainManager().addTotalNetworkDebt(refundedDebtInpUSD);
      }
  
      function secsLeftInWaitingPeriodForExchange(uint timestamp) internal view returns (uint) {
          uint _waitingPeriodSecs = getWaitingPeriodSecs();
          if (timestamp == 0 || now >= timestamp.add(_waitingPeriodSecs)) {
              return 0;
          }
  
          return timestamp.add(_waitingPeriodSecs).sub(now);
      }
  
      function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
          external
          view
          returns (uint exchangeFeeRate)
      {
          exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);
      }
  
      function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
          internal
          view
          returns (uint exchangeFeeRate)
      {
          // Get the exchange fee rate as per destination currencyKey
          exchangeFeeRate = getExchangeFeeRate(destinationCurrencyKey);
  
          if (sourceCurrencyKey == pUSD || destinationCurrencyKey == pUSD) {
              return exchangeFeeRate;
          }
  
          // Is this a swing trade? long to short or short to long skipping pUSD.
          if (
              (sourceCurrencyKey[0] == 0x70 && destinationCurrencyKey[0] == 0x69) ||
              (sourceCurrencyKey[0] == 0x69 && destinationCurrencyKey[0] == 0x70)
          ) {
              // Double the exchange fee
              exchangeFeeRate = exchangeFeeRate.mul(2);
          }
  
          return exchangeFeeRate;
      }
  
      function getAmountsForExchange(
          uint sourceAmount,
          bytes32 sourceCurrencyKey,
          bytes32 destinationCurrencyKey
      )
          external
          view
          returns (
              uint amountReceived,
              uint fee,
              uint exchangeFeeRate
          )
      {
          (amountReceived, fee, exchangeFeeRate, , ) = _getAmountsForExchangeMinusFees(
              sourceAmount,
              sourceCurrencyKey,
              destinationCurrencyKey
          );
      }
  
      function _getAmountsForExchangeMinusFees(
          uint sourceAmount,
          bytes32 sourceCurrencyKey,
          bytes32 destinationCurrencyKey
      )
          internal
          view
          returns (
              uint amountReceived,
              uint fee,
              uint exchangeFeeRate,
              uint sourceRate,
              uint destinationRate
          )
      {
          uint destinationAmount;
          (destinationAmount, sourceRate, destinationRate) = exchangeRates().effectiveValueAndRates(
              sourceCurrencyKey,
              sourceAmount,
              destinationCurrencyKey
          );
          exchangeFeeRate = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);
          amountReceived = _getAmountReceivedForExchange(destinationAmount, exchangeFeeRate);
          fee = destinationAmount.sub(amountReceived);
      }
  
      function _getAmountReceivedForExchange(uint destinationAmount, uint exchangeFeeRate)
          internal
          pure
          returns (uint amountReceived)
      {
          amountReceived = destinationAmount.multiplyDecimal(SafeDecimalMath.unit().sub(exchangeFeeRate));
      }
  
      function appendExchange(
          address account,
          bytes32 src,
          uint amount,
          bytes32 dest,
          uint amountReceived,
          uint exchangeFeeRate,
          uint srcRate,
          uint destRate
      ) internal {
          IExchangeRates exRates = exchangeRates();
          uint roundIdForSrc = exRates.getCurrentRoundId(src);
          uint roundIdForDest = exRates.getCurrentRoundId(dest);
          exchangeState().appendExchangeEntry(
              account,
              src,
              amount,
              dest,
              amountReceived,
              exchangeFeeRate,
              now,
              roundIdForSrc,
              roundIdForDest
          );
  
          emit ExchangeEntryAppended(
              account,
              src,
              amount,
              dest,
              amountReceived,
              exchangeFeeRate,
              roundIdForSrc,
              roundIdForDest,
              srcRate,
              destRate
          );
      }
  
      function getRoundIdsAtPeriodEnd(IExchangeState.ExchangeEntry memory exchangeEntry)
          internal
          view
          returns (uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd)
      {
          IExchangeRates exRates = exchangeRates();
          uint _waitingPeriodSecs = getWaitingPeriodSecs();
  
          srcRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
              exchangeEntry.src,
              exchangeEntry.roundIdForSrc,
              exchangeEntry.timestamp,
              _waitingPeriodSecs
          );
          destRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
              exchangeEntry.dest,
              exchangeEntry.roundIdForDest,
              exchangeEntry.timestamp,
              _waitingPeriodSecs
          );
      }
  
      // ========== MODIFIERS ==========
  
      modifier onlyPeriFinanceorPynth() {
          require(
              msg.sender == address(periFinance()) || issuer().pynthsByAddress(msg.sender) != bytes32(0),
              "Exchanger: Only periFinance or a pynth contract can perform this action"
          );
          _;
      }
  
      modifier onlyExchangeRates() {
          IExchangeRates _exchangeRates = exchangeRates();
          require(msg.sender == address(_exchangeRates), "Restricted to ExchangeRates");
          _;
      }
  
      // ========== EVENTS ==========
      event ExchangeEntryAppended(
          address indexed account,
          bytes32 src,
          uint256 amount,
          bytes32 dest,
          uint256 amountReceived,
          uint256 exchangeFeeRate,
          uint256 roundIdForSrc,
          uint256 roundIdForDest,
          uint256 srcRate,
          uint256 destRate
      );
  
      event ExchangeEntrySettled(
          address indexed from,
          bytes32 src,
          uint256 amount,
          bytes32 dest,
          uint256 reclaim,
          uint256 rebate,
          uint256 srcRoundIdAtPeriodEnd,
          uint256 destRoundIdAtPeriodEnd,
          uint256 exchangeTimestamp
      );
  }
  
      