creation_sourcecode: /*
     ____            __   __        __   _
    / __/__ __ ___  / /_ / /  ___  / /_ (_)__ __
   _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
  /___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
       /___/
  
  * Synthetix: migrations/Migration_Acrab.sol
  *
  * Latest source (may be newer): https://github.com/Synthetixio/synthetix/blob/master/contracts/migrations/Migration_Acrab.sol
  * Docs: https://docs.synthetix.io/contracts/migrations/Migration_Acrab
  *
  * Contract Dependencies: 
  *	- BaseDebtCache
  *	- BaseMigration
  *	- IAddressResolver
  *	- IDebtCache
  *	- IIssuer
  *	- ISystemStatus
  *	- MixinResolver
  *	- MixinSystemSettings
  *	- Owned
  * Libraries: 
  *	- SafeCast
  *	- SafeDecimalMath
  *	- SafeMath
  *
  * MIT License
  * ===========
  *
  * Copyright (c) 2024 Synthetix
  *
  * 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.synthetix.io/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);
  }
  
  
  contract BaseMigration is Owned {
      constructor(address _owner) internal Owned(_owner) {}
  
      // safety value to return ownership (anyone can invoke)
      function returnOwnership(address forContract) public {
          bytes memory payload = abi.encodeWithSignature("nominateNewOwner(address)", owner);
  
          // solhint-disable avoid-low-level-calls
          (bool success, ) = forContract.call(payload);
  
          if (!success) {
              // then try legacy way
              bytes memory legacyPayload = abi.encodeWithSignature("nominateOwner(address)", owner);
  
              // solhint-disable avoid-low-level-calls
              (bool legacySuccess, ) = forContract.call(legacyPayload);
  
              require(legacySuccess, "Legacy nomination failed");
          }
      }
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/iaddressresolver
  interface IAddressResolver {
      function getAddress(bytes32 name) external view returns (address);
  
      function getSynth(bytes32 key) external view returns (address);
  
      function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/isynth
  interface ISynth {
      // Views
      function currencyKey() external view returns (bytes32);
  
      function transferableSynths(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 Synthetix
      function burn(address account, uint amount) external;
  
      function issue(address account, uint amount) external;
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/iissuer
  interface IIssuer {
      // Views
  
      function allNetworksDebtInfo()
          external
          view
          returns (
              uint256 debt,
              uint256 sharesSupply,
              bool isStale
          );
  
      function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid);
  
      function availableCurrencyKeys() external view returns (bytes32[] memory);
  
      function availableSynthCount() external view returns (uint);
  
      function availableSynths(uint index) external view returns (ISynth);
  
      function canBurnSynths(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 issuanceRatio() external view returns (uint);
  
      function lastIssueEvent(address account) external view returns (uint);
  
      function maxIssuableSynths(address issuer) external view returns (uint maxIssuable);
  
      function minimumStakeTime() external view returns (uint);
  
      function remainingIssuableSynths(address issuer)
          external
          view
          returns (
              uint maxIssuable,
              uint alreadyIssued,
              uint totalSystemDebt
          );
  
      function synths(bytes32 currencyKey) external view returns (ISynth);
  
      function getSynths(bytes32[] calldata currencyKeys) external view returns (ISynth[] memory);
  
      function synthsByAddress(address synthAddress) external view returns (bytes32);
  
      function totalIssuedSynths(bytes32 currencyKey, bool excludeOtherCollateral) external view returns (uint);
  
      function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
          external
          view
          returns (uint transferable, bool anyRateIsInvalid);
  
      function liquidationAmounts(address account, bool isSelfLiquidation)
          external
          view
          returns (
              uint totalRedeemed,
              uint debtToRemove,
              uint escrowToLiquidate,
              uint initialDebtBalance
          );
  
      // Restricted: used internally to Synthetix
      function addSynths(ISynth[] calldata synthsToAdd) external;
  
      function issueSynths(address from, uint amount) external;
  
      function issueSynthsOnBehalf(
          address issueFor,
          address from,
          uint amount
      ) external;
  
      function issueMaxSynths(address from) external;
  
      function issueMaxSynthsOnBehalf(address issueFor, address from) external;
  
      function burnSynths(address from, uint amount) external;
  
      function burnSynthsOnBehalf(
          address burnForAddress,
          address from,
          uint amount
      ) external;
  
      function burnSynthsToTarget(address from) external;
  
      function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external;
  
      function burnForRedemption(
          address deprecatedSynthProxy,
          address account,
          uint balance
      ) external;
  
      function setCurrentPeriodId(uint128 periodId) external;
  
      function liquidateAccount(address account, bool isSelfLiquidation)
          external
          returns (
              uint totalRedeemed,
              uint debtRemoved,
              uint escrowToLiquidate
          );
  
      function issueSynthsWithoutDebt(
          bytes32 currencyKey,
          address to,
          uint amount
      ) external returns (bool rateInvalid);
  
      function burnSynthsWithoutDebt(
          bytes32 currencyKey,
          address to,
          uint amount
      ) external returns (bool rateInvalid);
  
      function burnAndIssueSynthsWithoutDebtCache(
          address account,
          bytes32 currencyKey,
          uint amountOfSynth,
          uint amountInsUSD
      ) external;
  
      function modifyDebtSharesForMigration(address account, uint amount) external;
  }
  
  
  // Internal references
  
  
  // https://docs.synthetix.io/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);
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.synthetix.io/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 getSynth(bytes32 key) external view returns (address) {
          IIssuer issuer = IIssuer(repository["Issuer"]);
          require(address(issuer) != address(0), "Cannot find Issuer address");
          return address(issuer.synths(key));
      }
  
      /* ========== EVENTS ========== */
  
      event AddressImported(bytes32 name, address destination);
  }
  
  
  // https://docs.synthetix.io/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 systemSuspended() external view returns (bool);
  
      function requireIssuanceActive() external view;
  
      function requireExchangeActive() external view;
  
      function requireFuturesActive() external view;
  
      function requireFuturesMarketActive(bytes32 marketKey) external view;
  
      function requireExchangeBetweenSynthsAllowed(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
  
      function requireSynthActive(bytes32 currencyKey) external view;
  
      function synthSuspended(bytes32 currencyKey) external view returns (bool);
  
      function requireSynthsActive(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 futuresSuspension() external view returns (bool suspended, uint248 reason);
  
      function synthExchangeSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
  
      function synthSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
  
      function futuresMarketSuspension(bytes32 marketKey) external view returns (bool suspended, uint248 reason);
  
      function getSynthExchangeSuspensions(bytes32[] calldata synths)
          external
          view
          returns (bool[] memory exchangeSuspensions, uint256[] memory reasons);
  
      function getSynthSuspensions(bytes32[] calldata synths)
          external
          view
          returns (bool[] memory suspensions, uint256[] memory reasons);
  
      function getFuturesMarketSuspensions(bytes32[] calldata marketKeys)
          external
          view
          returns (bool[] memory suspensions, uint256[] memory reasons);
  
      // Restricted functions
      function suspendIssuance(uint256 reason) external;
  
      function suspendSynth(bytes32 currencyKey, uint256 reason) external;
  
      function suspendFuturesMarket(bytes32 marketKey, uint256 reason) external;
  
      function updateAccessControl(
          bytes32 section,
          address account,
          bool canSuspend,
          bool canResume
      ) external;
  }
  
  
  // Inheritance
  
  
  // https://docs.synthetix.io/contracts/source/contracts/systemstatus
  contract SystemStatus is Owned, ISystemStatus {
      mapping(bytes32 => mapping(address => Status)) public accessControl;
  
      uint248 public constant SUSPENSION_REASON_UPGRADE = 1;
  
      bytes32 public constant SECTION_SYSTEM = "System";
      bytes32 public constant SECTION_ISSUANCE = "Issuance";
      bytes32 public constant SECTION_EXCHANGE = "Exchange";
      bytes32 public constant SECTION_FUTURES = "Futures";
      bytes32 public constant SECTION_SYNTH_EXCHANGE = "SynthExchange";
      bytes32 public constant SECTION_SYNTH = "Synth";
  
      bytes32 public constant CONTRACT_NAME = "SystemStatus";
  
      Suspension public systemSuspension;
  
      Suspension public issuanceSuspension;
  
      Suspension public exchangeSuspension;
  
      Suspension public futuresSuspension;
  
      mapping(bytes32 => Suspension) public synthExchangeSuspension;
  
      mapping(bytes32 => Suspension) public synthSuspension;
  
      mapping(bytes32 => Suspension) public futuresMarketSuspension;
  
      constructor(address _owner) public Owned(_owner) {}
  
      /* ========== VIEWS ========== */
      function requireSystemActive() external view {
          _internalRequireSystemActive();
      }
  
      function systemSuspended() external view returns (bool) {
          return systemSuspension.suspended;
      }
  
      function requireIssuanceActive() external view {
          // Issuance requires the system be active
          _internalRequireSystemActive();
  
          // and issuance itself of course
          _internalRequireIssuanceActive();
      }
  
      function requireExchangeActive() external view {
          // Exchanging requires the system be active
          _internalRequireSystemActive();
  
          // and exchanging itself of course
          _internalRequireExchangeActive();
      }
  
      function requireSynthExchangeActive(bytes32 currencyKey) external view {
          // Synth exchange and transfer requires the system be active
          _internalRequireSystemActive();
          _internalRequireSynthExchangeActive(currencyKey);
      }
  
      function requireFuturesActive() external view {
          _internalRequireSystemActive();
          _internalRequireExchangeActive();
          _internalRequireFuturesActive();
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function requireFuturesMarketActive(bytes32 marketKey) external view {
          _internalRequireSystemActive();
          _internalRequireExchangeActive(); // exchanging implicitely used
          _internalRequireFuturesActive(); // futures global flag
          _internalRequireFuturesMarketActive(marketKey); // specific futures market flag
      }
  
      function synthSuspended(bytes32 currencyKey) external view returns (bool) {
          return systemSuspension.suspended || synthSuspension[currencyKey].suspended;
      }
  
      function requireSynthActive(bytes32 currencyKey) external view {
          // Synth exchange and transfer requires the system be active
          _internalRequireSystemActive();
          _internalRequireSynthActive(currencyKey);
      }
  
      function requireSynthsActive(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view {
          // Synth exchange and transfer requires the system be active
          _internalRequireSystemActive();
          _internalRequireSynthActive(sourceCurrencyKey);
          _internalRequireSynthActive(destinationCurrencyKey);
      }
  
      function requireExchangeBetweenSynthsAllowed(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view {
          // Synth exchange and transfer requires the system be active
          _internalRequireSystemActive();
  
          // and exchanging must be active
          _internalRequireExchangeActive();
  
          // and the synth exchanging between the synths must be active
          _internalRequireSynthExchangeActive(sourceCurrencyKey);
          _internalRequireSynthExchangeActive(destinationCurrencyKey);
  
          // and finally, the synths cannot be suspended
          _internalRequireSynthActive(sourceCurrencyKey);
          _internalRequireSynthActive(destinationCurrencyKey);
      }
  
      function isSystemUpgrading() external view returns (bool) {
          return systemSuspension.suspended && systemSuspension.reason == SUSPENSION_REASON_UPGRADE;
      }
  
      function getSynthExchangeSuspensions(bytes32[] calldata synths)
          external
          view
          returns (bool[] memory exchangeSuspensions, uint256[] memory reasons)
      {
          exchangeSuspensions = new bool[](synths.length);
          reasons = new uint256[](synths.length);
  
          for (uint i = 0; i < synths.length; i++) {
              exchangeSuspensions[i] = synthExchangeSuspension[synths[i]].suspended;
              reasons[i] = synthExchangeSuspension[synths[i]].reason;
          }
      }
  
      function getSynthSuspensions(bytes32[] calldata synths)
          external
          view
          returns (bool[] memory suspensions, uint256[] memory reasons)
      {
          suspensions = new bool[](synths.length);
          reasons = new uint256[](synths.length);
  
          for (uint i = 0; i < synths.length; i++) {
              suspensions[i] = synthSuspension[synths[i]].suspended;
              reasons[i] = synthSuspension[synths[i]].reason;
          }
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function getFuturesMarketSuspensions(bytes32[] calldata marketKeys)
          external
          view
          returns (bool[] memory suspensions, uint256[] memory reasons)
      {
          suspensions = new bool[](marketKeys.length);
          reasons = new uint256[](marketKeys.length);
  
          for (uint i = 0; i < marketKeys.length; i++) {
              suspensions[i] = futuresMarketSuspension[marketKeys[i]].suspended;
              reasons[i] = futuresMarketSuspension[marketKeys[i]].reason;
          }
      }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
      function updateAccessControl(
          bytes32 section,
          address account,
          bool canSuspend,
          bool canResume
      ) external onlyOwner {
          _internalUpdateAccessControl(section, account, canSuspend, canResume);
      }
  
      function updateAccessControls(
          bytes32[] calldata sections,
          address[] calldata accounts,
          bool[] calldata canSuspends,
          bool[] calldata canResumes
      ) external onlyOwner {
          require(
              sections.length == accounts.length &&
                  accounts.length == canSuspends.length &&
                  canSuspends.length == canResumes.length,
              "Input array lengths must match"
          );
          for (uint i = 0; i < sections.length; i++) {
              _internalUpdateAccessControl(sections[i], accounts[i], canSuspends[i], canResumes[i]);
          }
      }
  
      function suspendSystem(uint256 reason) external {
          _requireAccessToSuspend(SECTION_SYSTEM);
          systemSuspension.suspended = true;
          systemSuspension.reason = uint248(reason);
          emit SystemSuspended(systemSuspension.reason);
      }
  
      function resumeSystem() external {
          _requireAccessToResume(SECTION_SYSTEM);
          systemSuspension.suspended = false;
          emit SystemResumed(uint256(systemSuspension.reason));
          systemSuspension.reason = 0;
      }
  
      function suspendIssuance(uint256 reason) external {
          _requireAccessToSuspend(SECTION_ISSUANCE);
          issuanceSuspension.suspended = true;
          issuanceSuspension.reason = uint248(reason);
          emit IssuanceSuspended(reason);
      }
  
      function resumeIssuance() external {
          _requireAccessToResume(SECTION_ISSUANCE);
          issuanceSuspension.suspended = false;
          emit IssuanceResumed(uint256(issuanceSuspension.reason));
          issuanceSuspension.reason = 0;
      }
  
      function suspendExchange(uint256 reason) external {
          _requireAccessToSuspend(SECTION_EXCHANGE);
          exchangeSuspension.suspended = true;
          exchangeSuspension.reason = uint248(reason);
          emit ExchangeSuspended(reason);
      }
  
      function resumeExchange() external {
          _requireAccessToResume(SECTION_EXCHANGE);
          exchangeSuspension.suspended = false;
          emit ExchangeResumed(uint256(exchangeSuspension.reason));
          exchangeSuspension.reason = 0;
      }
  
      function suspendFutures(uint256 reason) external {
          _requireAccessToSuspend(SECTION_FUTURES);
          futuresSuspension.suspended = true;
          futuresSuspension.reason = uint248(reason);
          emit FuturesSuspended(reason);
      }
  
      function resumeFutures() external {
          _requireAccessToResume(SECTION_FUTURES);
          futuresSuspension.suspended = false;
          emit FuturesResumed(uint256(futuresSuspension.reason));
          futuresSuspension.reason = 0;
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function suspendFuturesMarket(bytes32 marketKey, uint256 reason) external {
          bytes32[] memory marketKeys = new bytes32[](1);
          marketKeys[0] = marketKey;
          _internalSuspendFuturesMarkets(marketKeys, reason);
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function suspendFuturesMarkets(bytes32[] calldata marketKeys, uint256 reason) external {
          _internalSuspendFuturesMarkets(marketKeys, reason);
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function resumeFuturesMarket(bytes32 marketKey) external {
          bytes32[] memory marketKeys = new bytes32[](1);
          marketKeys[0] = marketKey;
          _internalResumeFuturesMarkets(marketKeys);
      }
  
      /// @notice marketKey doesn't necessarily correspond to asset key
      function resumeFuturesMarkets(bytes32[] calldata marketKeys) external {
          _internalResumeFuturesMarkets(marketKeys);
      }
  
      function suspendSynthExchange(bytes32 currencyKey, uint256 reason) external {
          bytes32[] memory currencyKeys = new bytes32[](1);
          currencyKeys[0] = currencyKey;
          _internalSuspendSynthExchange(currencyKeys, reason);
      }
  
      function suspendSynthsExchange(bytes32[] calldata currencyKeys, uint256 reason) external {
          _internalSuspendSynthExchange(currencyKeys, reason);
      }
  
      function resumeSynthExchange(bytes32 currencyKey) external {
          bytes32[] memory currencyKeys = new bytes32[](1);
          currencyKeys[0] = currencyKey;
          _internalResumeSynthsExchange(currencyKeys);
      }
  
      function resumeSynthsExchange(bytes32[] calldata currencyKeys) external {
          _internalResumeSynthsExchange(currencyKeys);
      }
  
      function suspendSynth(bytes32 currencyKey, uint256 reason) external {
          bytes32[] memory currencyKeys = new bytes32[](1);
          currencyKeys[0] = currencyKey;
          _internalSuspendSynths(currencyKeys, reason);
      }
  
      function suspendSynths(bytes32[] calldata currencyKeys, uint256 reason) external {
          _internalSuspendSynths(currencyKeys, reason);
      }
  
      function resumeSynth(bytes32 currencyKey) external {
          bytes32[] memory currencyKeys = new bytes32[](1);
          currencyKeys[0] = currencyKey;
          _internalResumeSynths(currencyKeys);
      }
  
      function resumeSynths(bytes32[] calldata currencyKeys) external {
          _internalResumeSynths(currencyKeys);
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function _requireAccessToSuspend(bytes32 section) internal view {
          require(accessControl[section][msg.sender].canSuspend, "Restricted to access control list");
      }
  
      function _requireAccessToResume(bytes32 section) internal view {
          require(accessControl[section][msg.sender].canResume, "Restricted to access control list");
      }
  
      function _internalRequireSystemActive() internal view {
          require(
              !systemSuspension.suspended,
              systemSuspension.reason == SUSPENSION_REASON_UPGRADE
                  ? "Synthetix is suspended, upgrade in progress... please stand by"
                  : "Synthetix is suspended. Operation prohibited"
          );
      }
  
      function _internalRequireIssuanceActive() internal view {
          require(!issuanceSuspension.suspended, "Issuance is suspended. Operation prohibited");
      }
  
      function _internalRequireExchangeActive() internal view {
          require(!exchangeSuspension.suspended, "Exchange is suspended. Operation prohibited");
      }
  
      function _internalRequireFuturesActive() internal view {
          require(!futuresSuspension.suspended, "Futures markets are suspended. Operation prohibited");
      }
  
      function _internalRequireSynthExchangeActive(bytes32 currencyKey) internal view {
          require(!synthExchangeSuspension[currencyKey].suspended, "Synth exchange suspended. Operation prohibited");
      }
  
      function _internalRequireSynthActive(bytes32 currencyKey) internal view {
          require(!synthSuspension[currencyKey].suspended, "Synth is suspended. Operation prohibited");
      }
  
      function _internalRequireFuturesMarketActive(bytes32 marketKey) internal view {
          require(!futuresMarketSuspension[marketKey].suspended, "Market suspended");
      }
  
      function _internalSuspendSynths(bytes32[] memory currencyKeys, uint256 reason) internal {
          _requireAccessToSuspend(SECTION_SYNTH);
          for (uint i = 0; i < currencyKeys.length; i++) {
              bytes32 currencyKey = currencyKeys[i];
              synthSuspension[currencyKey].suspended = true;
              synthSuspension[currencyKey].reason = uint248(reason);
              emit SynthSuspended(currencyKey, reason);
          }
      }
  
      function _internalResumeSynths(bytes32[] memory currencyKeys) internal {
          _requireAccessToResume(SECTION_SYNTH);
          for (uint i = 0; i < currencyKeys.length; i++) {
              bytes32 currencyKey = currencyKeys[i];
              emit SynthResumed(currencyKey, uint256(synthSuspension[currencyKey].reason));
              delete synthSuspension[currencyKey];
          }
      }
  
      function _internalSuspendSynthExchange(bytes32[] memory currencyKeys, uint256 reason) internal {
          _requireAccessToSuspend(SECTION_SYNTH_EXCHANGE);
          for (uint i = 0; i < currencyKeys.length; i++) {
              bytes32 currencyKey = currencyKeys[i];
              synthExchangeSuspension[currencyKey].suspended = true;
              synthExchangeSuspension[currencyKey].reason = uint248(reason);
              emit SynthExchangeSuspended(currencyKey, reason);
          }
      }
  
      function _internalResumeSynthsExchange(bytes32[] memory currencyKeys) internal {
          _requireAccessToResume(SECTION_SYNTH_EXCHANGE);
          for (uint i = 0; i < currencyKeys.length; i++) {
              bytes32 currencyKey = currencyKeys[i];
              emit SynthExchangeResumed(currencyKey, uint256(synthExchangeSuspension[currencyKey].reason));
              delete synthExchangeSuspension[currencyKey];
          }
      }
  
      function _internalSuspendFuturesMarkets(bytes32[] memory marketKeys, uint256 reason) internal {
          _requireAccessToSuspend(SECTION_FUTURES);
          for (uint i = 0; i < marketKeys.length; i++) {
              bytes32 marketKey = marketKeys[i];
              futuresMarketSuspension[marketKey].suspended = true;
              futuresMarketSuspension[marketKey].reason = uint248(reason);
              emit FuturesMarketSuspended(marketKey, reason);
          }
      }
  
      function _internalResumeFuturesMarkets(bytes32[] memory marketKeys) internal {
          _requireAccessToResume(SECTION_FUTURES);
          for (uint i = 0; i < marketKeys.length; i++) {
              bytes32 marketKey = marketKeys[i];
              emit FuturesMarketResumed(marketKey, uint256(futuresMarketSuspension[marketKey].reason));
              delete futuresMarketSuspension[marketKey];
          }
      }
  
      function _internalUpdateAccessControl(
          bytes32 section,
          address account,
          bool canSuspend,
          bool canResume
      ) internal {
          require(
              section == SECTION_SYSTEM ||
                  section == SECTION_ISSUANCE ||
                  section == SECTION_EXCHANGE ||
                  section == SECTION_FUTURES ||
                  section == SECTION_SYNTH_EXCHANGE ||
                  section == SECTION_SYNTH,
              "Invalid section supplied"
          );
          accessControl[section][account].canSuspend = canSuspend;
          accessControl[section][account].canResume = canResume;
          emit AccessControlUpdated(section, account, canSuspend, canResume);
      }
  
      /* ========== EVENTS ========== */
  
      event SystemSuspended(uint256 reason);
      event SystemResumed(uint256 reason);
  
      event IssuanceSuspended(uint256 reason);
      event IssuanceResumed(uint256 reason);
  
      event ExchangeSuspended(uint256 reason);
      event ExchangeResumed(uint256 reason);
  
      event FuturesSuspended(uint256 reason);
      event FuturesResumed(uint256 reason);
  
      event SynthExchangeSuspended(bytes32 currencyKey, uint256 reason);
      event SynthExchangeResumed(bytes32 currencyKey, uint256 reason);
  
      event SynthSuspended(bytes32 currencyKey, uint256 reason);
      event SynthResumed(bytes32 currencyKey, uint256 reason);
  
      event FuturesMarketSuspended(bytes32 marketKey, uint256 reason);
      event FuturesMarketResumed(bytes32 marketKey, uint256 reason);
  
      event AccessControlUpdated(bytes32 indexed section, address indexed account, bool canSuspend, bool canResume);
  }
  
  
  /**
   * @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.synthetix.io/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;
      }
  
      // Computes `a - b`, setting the value to 0 if b > a.
      function floorsub(uint a, uint b) internal pure returns (uint) {
          return b >= a ? 0 : a - b;
      }
  
      /* ---------- Utilities ---------- */
      /*
       * Absolute value of the input, returned as a signed number.
       */
      function signedAbs(int x) internal pure returns (int) {
          return x < 0 ? -x : x;
      }
  
      /*
       * Absolute value of the input, returned as an unsigned number.
       */
      function abs(int x) internal pure returns (uint) {
          return uint(signedAbs(x));
      }
  }
  
  
  // https://docs.synthetix.io/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.synthetix.io/contracts/source/contracts/mixinsystemsettings
  contract MixinSystemSettings is MixinResolver {
      // must match the one defined SystemSettingsLib, defined in both places due to sol v0.5 limitations
      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_ESCROW_DURATION = "liquidationEscrowDuration";
      bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
      bytes32 internal constant SETTING_SNX_LIQUIDATION_PENALTY = "snxLiquidationPenalty";
      bytes32 internal constant SETTING_SELF_LIQUIDATION_PENALTY = "selfLiquidationPenalty";
      bytes32 internal constant SETTING_FLAG_REWARD = "flagReward";
      bytes32 internal constant SETTING_LIQUIDATE_REWARD = "liquidateReward";
      bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
      /* ========== Exchange Fees Related ========== */
      bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
      bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD = "exchangeDynamicFeeThreshold";
      bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY = "exchangeDynamicFeeWeightDecay";
      bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS = "exchangeDynamicFeeRounds";
      bytes32 internal constant SETTING_EXCHANGE_MAX_DYNAMIC_FEE = "exchangeMaxDynamicFee";
      /* ========== End Exchange Fees Related ========== */
      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_CROSS_DOMAIN_FEE_PERIOD_CLOSE_GAS_LIMIT = "crossDomainCloseGasLimit";
      bytes32 internal constant SETTING_CROSS_DOMAIN_RELAY_GAS_LIMIT = "crossDomainRelayGasLimit";
      bytes32 internal constant SETTING_ETHER_WRAPPER_MAX_ETH = "etherWrapperMaxETH";
      bytes32 internal constant SETTING_ETHER_WRAPPER_MINT_FEE_RATE = "etherWrapperMintFeeRate";
      bytes32 internal constant SETTING_ETHER_WRAPPER_BURN_FEE_RATE = "etherWrapperBurnFeeRate";
      bytes32 internal constant SETTING_WRAPPER_MAX_TOKEN_AMOUNT = "wrapperMaxTokens";
      bytes32 internal constant SETTING_WRAPPER_MINT_FEE_RATE = "wrapperMintFeeRate";
      bytes32 internal constant SETTING_WRAPPER_BURN_FEE_RATE = "wrapperBurnFeeRate";
      bytes32 internal constant SETTING_INTERACTION_DELAY = "interactionDelay";
      bytes32 internal constant SETTING_COLLAPSE_FEE_RATE = "collapseFeeRate";
      bytes32 internal constant SETTING_ATOMIC_MAX_VOLUME_PER_BLOCK = "atomicMaxVolumePerBlock";
      bytes32 internal constant SETTING_ATOMIC_TWAP_WINDOW = "atomicTwapWindow";
      bytes32 internal constant SETTING_ATOMIC_EQUIVALENT_FOR_DEX_PRICING = "atomicEquivalentForDexPricing";
      bytes32 internal constant SETTING_ATOMIC_EXCHANGE_FEE_RATE = "atomicExchangeFeeRate";
      bytes32 internal constant SETTING_ATOMIC_VOLATILITY_CONSIDERATION_WINDOW = "atomicVolConsiderationWindow";
      bytes32 internal constant SETTING_ATOMIC_VOLATILITY_UPDATE_THRESHOLD = "atomicVolUpdateThreshold";
      bytes32 internal constant SETTING_PURE_CHAINLINK_PRICE_FOR_ATOMIC_SWAPS_ENABLED = "pureChainlinkForAtomicsEnabled";
      bytes32 internal constant SETTING_CROSS_SYNTH_TRANSFER_ENABLED = "crossChainSynthTransferEnabled";
  
      bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";
  
      enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal, CloseFeePeriod, Relay}
  
      struct DynamicFeeConfig {
          uint threshold;
          uint weightDecay;
          uint rounds;
          uint maxFee;
      }
  
      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 if (gasLimitType == CrossDomainMessageGasLimits.Relay) {
              return SETTING_CROSS_DOMAIN_RELAY_GAS_LIMIT;
          } else if (gasLimitType == CrossDomainMessageGasLimits.CloseFeePeriod) {
              return SETTING_CROSS_DOMAIN_FEE_PERIOD_CLOSE_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 getLiquidationEscrowDuration() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_ESCROW_DURATION);
      }
  
      function getLiquidationPenalty() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
      }
  
      function getSnxLiquidationPenalty() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_SNX_LIQUIDATION_PENALTY);
      }
  
      function getSelfLiquidationPenalty() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_SELF_LIQUIDATION_PENALTY);
      }
  
      function getFlagReward() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FLAG_REWARD);
      }
  
      function getLiquidateReward() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATE_REWARD);
      }
  
      function getRateStalePeriod() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
      }
  
      /* ========== Exchange Related Fees ========== */
      function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
              );
      }
  
      /// @notice Get exchange dynamic fee related keys
      /// @return threshold, weight decay, rounds, and max fee
      function getExchangeDynamicFeeConfig() internal view returns (DynamicFeeConfig memory) {
          bytes32[] memory keys = new bytes32[](4);
          keys[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD;
          keys[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY;
          keys[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS;
          keys[3] = SETTING_EXCHANGE_MAX_DYNAMIC_FEE;
          uint[] memory values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys);
          return DynamicFeeConfig({threshold: values[0], weightDecay: values[1], rounds: values[2], maxFee: values[3]});
      }
  
      /* ========== End Exchange Related Fees ========== */
  
      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 getEtherWrapperMaxETH() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_MAX_ETH);
      }
  
      function getEtherWrapperMintFeeRate() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_MINT_FEE_RATE);
      }
  
      function getEtherWrapperBurnFeeRate() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_BURN_FEE_RATE);
      }
  
      function getWrapperMaxTokenAmount(address wrapper) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_WRAPPER_MAX_TOKEN_AMOUNT, wrapper))
              );
      }
  
      function getWrapperMintFeeRate(address wrapper) internal view returns (int) {
          return
              flexibleStorage().getIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_WRAPPER_MINT_FEE_RATE, wrapper))
              );
      }
  
      function getWrapperBurnFeeRate(address wrapper) internal view returns (int) {
          return
              flexibleStorage().getIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_WRAPPER_BURN_FEE_RATE, wrapper))
              );
      }
  
      function getInteractionDelay(address collateral) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_INTERACTION_DELAY, collateral))
              );
      }
  
      function getCollapseFeeRate(address collateral) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_COLLAPSE_FEE_RATE, collateral))
              );
      }
  
      function getAtomicMaxVolumePerBlock() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ATOMIC_MAX_VOLUME_PER_BLOCK);
      }
  
      function getAtomicTwapWindow() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ATOMIC_TWAP_WINDOW);
      }
  
      function getAtomicEquivalentForDexPricing(bytes32 currencyKey) internal view returns (address) {
          return
              flexibleStorage().getAddressValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_ATOMIC_EQUIVALENT_FOR_DEX_PRICING, currencyKey))
              );
      }
  
      function getAtomicExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_ATOMIC_EXCHANGE_FEE_RATE, currencyKey))
              );
      }
  
      function getAtomicVolatilityConsiderationWindow(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_ATOMIC_VOLATILITY_CONSIDERATION_WINDOW, currencyKey))
              );
      }
  
      function getAtomicVolatilityUpdateThreshold(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_ATOMIC_VOLATILITY_UPDATE_THRESHOLD, currencyKey))
              );
      }
  
      function getPureChainlinkPriceForAtomicSwapsEnabled(bytes32 currencyKey) internal view returns (bool) {
          return
              flexibleStorage().getBoolValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_PURE_CHAINLINK_PRICE_FOR_ATOMIC_SWAPS_ENABLED, currencyKey))
              );
      }
  
      function getCrossChainSynthTransferEnabled(bytes32 currencyKey) internal view returns (uint) {
          return
              flexibleStorage().getUIntValue(
                  SETTING_CONTRACT_NAME,
                  keccak256(abi.encodePacked(SETTING_CROSS_SYNTH_TRANSFER_ENABLED, currencyKey))
              );
      }
  
      function getExchangeMaxDynamicFee() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_MAX_DYNAMIC_FEE);
      }
  
      function getExchangeDynamicFeeRounds() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS);
      }
  
      function getExchangeDynamicFeeThreshold() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD);
      }
  
      function getExchangeDynamicFeeWeightDecay() internal view returns (uint) {
          return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY);
      }
  }
  
  
  interface IDebtCache {
      // Views
  
      function cachedDebt() external view returns (uint);
  
      function cachedSynthDebt(bytes32 currencyKey) external view returns (uint);
  
      function cacheTimestamp() external view returns (uint);
  
      function cacheInvalid() external view returns (bool);
  
      function cacheStale() external view returns (bool);
  
      function isInitialized() external view returns (bool);
  
      function currentSynthDebts(bytes32[] calldata currencyKeys)
          external
          view
          returns (
              uint[] memory debtValues,
              uint futuresDebt,
              uint excludedDebt,
              bool anyRateIsInvalid
          );
  
      function cachedSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory debtValues);
  
      function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid);
  
      function currentDebt() external view returns (uint debt, bool anyRateIsInvalid);
  
      function cacheInfo()
          external
          view
          returns (
              uint debt,
              uint timestamp,
              bool isInvalid,
              bool isStale
          );
  
      function excludedIssuedDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory excludedDebts);
  
      // Mutative functions
  
      function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external;
  
      function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external;
  
      function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
  
      function updateDebtCacheValidity(bool currentlyInvalid) external;
  
      function purgeCachedSynthDebt(bytes32 currencyKey) external;
  
      function takeDebtSnapshot() external;
  
      function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external;
  
      function updateCachedsUSDDebt(int amount) external;
  
      function importExcludedIssuedDebts(IDebtCache prevDebtCache, IIssuer prevIssuer) external;
  }
  
  
  interface IVirtualSynth {
      // 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 synth() external view returns (ISynth);
  
      // Mutative functions
      function settle(address account) external;
  }
  
  
  pragma experimental ABIEncoderV2;
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/iexchanger
  interface IExchanger {
      struct ExchangeEntrySettlement {
          bytes32 src;
          uint amount;
          bytes32 dest;
          uint reclaim;
          uint rebate;
          uint srcRoundIdAtPeriodEnd;
          uint destRoundIdAtPeriodEnd;
          uint timestamp;
      }
  
      struct ExchangeEntry {
          uint sourceRate;
          uint destinationRate;
          uint destinationAmount;
          uint exchangeFeeRate;
          uint exchangeDynamicFeeRate;
          uint roundIdForSrc;
          uint roundIdForDest;
          uint sourceAmountAfterSettlement;
      }
  
      // Views
      function calculateAmountAfterSettlement(
          address from,
          bytes32 currencyKey,
          uint amount,
          uint refunded
      ) external view returns (uint amountAfterSettlement);
  
      function isSynthRateInvalid(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);
  
      function dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
          external
          view
          returns (uint feeRate, bool tooVolatile);
  
      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);
  
      function lastExchangeRate(bytes32 currencyKey) external view returns (uint);
  
      // Mutative functions
      function exchange(
          address exchangeForAddress,
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          bool virtualSynth,
          address rewardAddress,
          bytes32 trackingCode
      ) external returns (uint amountReceived, IVirtualSynth vSynth);
  
      function exchangeAtomically(
          address from,
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          address destinationAddress,
          bytes32 trackingCode,
          uint minAmount
      ) external returns (uint amountReceived);
  
      function settle(address from, bytes32 currencyKey)
          external
          returns (
              uint reclaimed,
              uint refunded,
              uint numEntries
          );
  }
  
  // Used to have strongly-typed access to internal mutative functions in Synthetix
  interface ISynthetixInternal {
      function emitExchangeTracking(
          bytes32 trackingCode,
          bytes32 toCurrencyKey,
          uint256 toAmount,
          uint256 fee
      ) external;
  
      function emitSynthExchange(
          address account,
          bytes32 fromCurrencyKey,
          uint fromAmount,
          bytes32 toCurrencyKey,
          uint toAmount,
          address toAddress
      ) external;
  
      function emitAtomicSynthExchange(
          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 updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
  
      function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external;
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/IDirectIntegration
  interface IDirectIntegrationManager {
      struct ParameterIntegrationSettings {
          bytes32 currencyKey;
          address dexPriceAggregator;
          address atomicEquivalentForDexPricing;
          uint atomicExchangeFeeRate;
          uint atomicTwapWindow;
          uint atomicMaxVolumePerBlock;
          uint atomicVolatilityConsiderationWindow;
          uint atomicVolatilityUpdateThreshold;
          uint exchangeFeeRate;
          uint exchangeMaxDynamicFee;
          uint exchangeDynamicFeeRounds;
          uint exchangeDynamicFeeThreshold;
          uint exchangeDynamicFeeWeightDecay;
      }
  
      function getExchangeParameters(address integration, bytes32 key)
          external
          view
          returns (ParameterIntegrationSettings memory settings);
  
      function setExchangeParameters(
          address integration,
          bytes32[] calldata currencyKeys,
          ParameterIntegrationSettings calldata params
      ) external;
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/iexchangerates
  interface IExchangeRates {
      // Structs
      struct RateAndUpdatedTime {
          uint216 rate;
          uint40 time;
      }
  
      // 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 anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds) external view returns (bool);
  
      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 effectiveValueAndRatesAtRound(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey,
          uint roundIdForSrc,
          uint roundIdForDest
      )
          external
          view
          returns (
              uint value,
              uint sourceRate,
              uint destinationRate
          );
  
      function effectiveAtomicValueAndRates(
          bytes32 sourceCurrencyKey,
          uint sourceAmount,
          bytes32 destinationCurrencyKey
      )
          external
          view
          returns (
              uint value,
              uint systemValue,
              uint systemSourceRate,
              uint systemDestinationRate
          );
  
      function effectiveAtomicValueAndRates(
          IDirectIntegrationManager.ParameterIntegrationSettings calldata sourceSettings,
          uint sourceAmount,
          IDirectIntegrationManager.ParameterIntegrationSettings calldata destinationSettings,
          IDirectIntegrationManager.ParameterIntegrationSettings calldata usdSettings
      )
          external
          view
          returns (
              uint value,
              uint systemValue,
              uint systemSourceRate,
              uint systemDestinationRate
          );
  
      function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
  
      function getLastRoundIdBeforeElapsedSecs(
          bytes32 currencyKey,
          uint startingRoundId,
          uint startingTimestamp,
          uint timediff
      ) external view returns (uint);
  
      function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
  
      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 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,
          uint roundId
      ) 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);
  
      function synthTooVolatileForAtomicExchange(bytes32 currencyKey) external view returns (bool);
  
      function synthTooVolatileForAtomicExchange(IDirectIntegrationManager.ParameterIntegrationSettings calldata settings)
          external
          view
          returns (bool);
  
      function rateWithSafetyChecks(bytes32 currencyKey)
          external
          returns (
              uint rate,
              bool broken,
              bool invalid
          );
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/ierc20
  interface IERC20 {
      // ERC20 Optional Views
      function name() external view returns (string memory);
  
      function symbol() external view returns (string memory);
  
      function decimals() external view returns (uint8);
  
      // Views
      function totalSupply() external view returns (uint);
  
      function balanceOf(address owner) external view returns (uint);
  
      function allowance(address owner, address spender) external view returns (uint);
  
      // Mutative functions
      function transfer(address to, uint value) external returns (bool);
  
      function approve(address spender, uint value) external returns (bool);
  
      function transferFrom(
          address from,
          address to,
          uint value
      ) external returns (bool);
  
      // Events
      event Transfer(address indexed from, address indexed to, uint value);
  
      event Approval(address indexed owner, address indexed spender, uint value);
  }
  
  
  interface ICollateralManager {
      // Manager information
      function hasCollateral(address collateral) external view returns (bool);
  
      function isSynthManaged(bytes32 currencyKey) external view returns (bool);
  
      // State information
      function long(bytes32 synth) external view returns (uint amount);
  
      function short(bytes32 synth) external view returns (uint amount);
  
      function totalLong() external view returns (uint susdValue, bool anyRateIsInvalid);
  
      function totalShort() external view returns (uint susdValue, bool anyRateIsInvalid);
  
      function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid);
  
      function getShortRate(bytes32 synth) external view returns (uint shortRate, bool rateIsInvalid);
  
      function getRatesAndTime(uint index)
          external
          view
          returns (
              uint entryRate,
              uint lastRate,
              uint lastUpdated,
              uint newIndex
          );
  
      function getShortRatesAndTime(bytes32 currency, uint index)
          external
          view
          returns (
              uint entryRate,
              uint lastRate,
              uint lastUpdated,
              uint newIndex
          );
  
      function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid);
  
      function areSynthsAndCurrenciesSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
          external
          view
          returns (bool);
  
      function areShortableSynthsSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
          external
          view
          returns (bool);
  
      // Loans
      function getNewLoanId() external returns (uint id);
  
      // Manager mutative
      function addCollaterals(address[] calldata collaterals) external;
  
      function removeCollaterals(address[] calldata collaterals) external;
  
      function addSynths(bytes32[] calldata synthNamesInResolver, bytes32[] calldata synthKeys) external;
  
      function removeSynths(bytes32[] calldata synths, bytes32[] calldata synthKeys) external;
  
      function addShortableSynths(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys) external;
  
      function removeShortableSynths(bytes32[] calldata synths) external;
  
      // State mutative
  
      function incrementLongs(bytes32 synth, uint amount) external;
  
      function decrementLongs(bytes32 synth, uint amount) external;
  
      function incrementShorts(bytes32 synth, uint amount) external;
  
      function decrementShorts(bytes32 synth, uint amount) external;
  
      function accrueInterest(
          uint interestIndex,
          bytes32 currency,
          bool isShort
      ) external returns (uint difference, uint index);
  
      function updateBorrowRatesCollateral(uint rate) external;
  
      function updateShortRatesCollateral(bytes32 currency, uint rate) external;
  }
  
  
  interface IWETH {
      // ERC20 Optional Views
      function name() external view returns (string memory);
  
      function symbol() external view returns (string memory);
  
      function decimals() external view returns (uint8);
  
      // Views
      function totalSupply() external view returns (uint);
  
      function balanceOf(address owner) external view returns (uint);
  
      function allowance(address owner, address spender) external view returns (uint);
  
      // Mutative functions
      function transfer(address to, uint value) external returns (bool);
  
      function approve(address spender, uint value) external returns (bool);
  
      function transferFrom(
          address from,
          address to,
          uint value
      ) external returns (bool);
  
      // WETH-specific functions.
      function deposit() external payable;
  
      function withdraw(uint amount) external;
  
      // Events
      event Transfer(address indexed from, address indexed to, uint value);
      event Approval(address indexed owner, address indexed spender, uint value);
      event Deposit(address indexed to, uint amount);
      event Withdrawal(address indexed to, uint amount);
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/ietherwrapper
  contract IEtherWrapper {
      function mint(uint amount) external;
  
      function burn(uint amount) external;
  
      function distributeFees() external;
  
      function capacity() external view returns (uint);
  
      function getReserves() external view returns (uint);
  
      function totalIssuedSynths() external view returns (uint);
  
      function calculateMintFee(uint amount) public view returns (uint);
  
      function calculateBurnFee(uint amount) public view returns (uint);
  
      function maxETH() public view returns (uint256);
  
      function mintFeeRate() public view returns (uint256);
  
      function burnFeeRate() public view returns (uint256);
  
      function weth() public view returns (IWETH);
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/iwrapperfactory
  interface IWrapperFactory {
      function isWrapper(address possibleWrapper) external view returns (bool);
  
      function createWrapper(
          IERC20 token,
          bytes32 currencyKey,
          bytes32 synthContractName
      ) external returns (address);
  
      function distributeFees() external;
  }
  
  
  interface IFuturesMarketManager {
      function markets(uint index, uint pageSize) external view returns (address[] memory);
  
      function markets(
          uint index,
          uint pageSize,
          bool proxiedMarkets
      ) external view returns (address[] memory);
  
      function numMarkets() external view returns (uint);
  
      function numMarkets(bool proxiedMarkets) external view returns (uint);
  
      function allMarkets() external view returns (address[] memory);
  
      function allMarkets(bool proxiedMarkets) external view returns (address[] memory);
  
      function marketForKey(bytes32 marketKey) external view returns (address);
  
      function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory);
  
      function totalDebt() external view returns (uint debt, bool isInvalid);
  
      function isEndorsed(address account) external view returns (bool);
  
      function allEndorsedAddresses() external view returns (address[] memory);
  
      function addEndorsedAddresses(address[] calldata addresses) external;
  
      function removeEndorsedAddresses(address[] calldata addresses) external;
  }
  
  
  interface IDynamicSynthRedeemer {
      function suspendRedemption() external;
  
      function resumeRedemption() external;
  
      // Rate applied to chainlink price for redemptions
      function getDiscountRate() external view returns (uint);
  
      function redeem(bytes32 currencyKey) external;
  
      function redeemAll(bytes32[] calldata currencyKeys) external;
  
      function redeemPartial(bytes32 currencyKey, uint amountOfSynth) external;
  }
  
  
  // Inheritance
  
  
  // Libraries
  
  
  // Internal references
  
  
  // https://docs.synthetix.io/contracts/source/contracts/debtcache
  contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
      using SafeMath for uint;
      using SafeDecimalMath for uint;
  
      uint internal _cachedDebt;
      mapping(bytes32 => uint) internal _cachedSynthDebt;
      mapping(bytes32 => uint) internal _excludedIssuedDebt;
      uint internal _cacheTimestamp;
      bool internal _cacheInvalid = true;
  
      // flag to ensure importing excluded debt is invoked only once
      bool public isInitialized = false; // public to avoid needing an event
  
      /* ========== ENCODED NAMES ========== */
  
      bytes32 internal constant sUSD = "sUSD";
      bytes32 internal constant sETH = "sETH";
  
      /* ========== ADDRESS RESOLVER CONFIGURATION ========== */
  
      bytes32 private constant CONTRACT_ISSUER = "Issuer";
      bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
      bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
      bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
      bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
      bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
      bytes32 private constant CONTRACT_FUTURESMARKETMANAGER = "FuturesMarketManager";
      bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";
      bytes32 private constant CONTRACT_DYNAMICSYNTHREDEEMER = "DynamicSynthRedeemer";
  
      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[](9);
          newAddresses[0] = CONTRACT_ISSUER;
          newAddresses[1] = CONTRACT_EXCHANGER;
          newAddresses[2] = CONTRACT_EXRATES;
          newAddresses[3] = CONTRACT_SYSTEMSTATUS;
          newAddresses[4] = CONTRACT_COLLATERALMANAGER;
          newAddresses[5] = CONTRACT_WRAPPER_FACTORY;
          newAddresses[6] = CONTRACT_ETHER_WRAPPER;
          newAddresses[7] = CONTRACT_FUTURESMARKETMANAGER;
          newAddresses[8] = CONTRACT_DYNAMICSYNTHREDEEMER;
          addresses = combineArrays(existingAddresses, newAddresses);
      }
  
      function issuer() internal view returns (IIssuer) {
          return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
      }
  
      function exchanger() internal view returns (IExchanger) {
          return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
      }
  
      function exchangeRates() internal view returns (IExchangeRates) {
          return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
      }
  
      function systemStatus() internal view returns (ISystemStatus) {
          return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
      }
  
      function collateralManager() internal view returns (ICollateralManager) {
          return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER));
      }
  
      function etherWrapper() internal view returns (IEtherWrapper) {
          return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
      }
  
      function futuresMarketManager() internal view returns (IFuturesMarketManager) {
          return IFuturesMarketManager(requireAndGetAddress(CONTRACT_FUTURESMARKETMANAGER));
      }
  
      function wrapperFactory() internal view returns (IWrapperFactory) {
          return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
      }
  
      function dynamicSynthRedeemer() internal view returns (IDynamicSynthRedeemer) {
          return IDynamicSynthRedeemer(requireAndGetAddress(CONTRACT_DYNAMICSYNTHREDEEMER));
      }
  
      function debtSnapshotStaleTime() external view returns (uint) {
          return getDebtSnapshotStaleTime();
      }
  
      function cachedDebt() external view returns (uint) {
          return _cachedDebt;
      }
  
      function cachedSynthDebt(bytes32 currencyKey) external view returns (uint) {
          return _cachedSynthDebt[currencyKey];
      }
  
      function cacheTimestamp() external view returns (uint) {
          return _cacheTimestamp;
      }
  
      function cacheInvalid() external view returns (bool) {
          return _cacheInvalid;
      }
  
      function _cacheStale(uint timestamp) internal view returns (bool) {
          // Note a 0 timestamp means that the cache is uninitialised.
          // We'll keep the check explicitly in case the stale time is
          // ever set to something higher than the current unix time (e.g. to turn off staleness).
          return getDebtSnapshotStaleTime() < block.timestamp - timestamp || timestamp == 0;
      }
  
      function cacheStale() external view returns (bool) {
          return _cacheStale(_cacheTimestamp);
      }
  
      function _issuedSynthValues(bytes32[] memory currencyKeys, uint[] memory rates)
          internal
          view
          returns (uint[] memory values)
      {
          uint numValues = currencyKeys.length;
          values = new uint[](numValues);
          ISynth[] memory synths = issuer().getSynths(currencyKeys);
          uint discountRate = dynamicSynthRedeemer().getDiscountRate();
  
          for (uint i = 0; i < numValues; i++) {
              address synthAddress = address(synths[i]);
              require(synthAddress != address(0), "Synth does not exist");
              uint supply = IERC20(synthAddress).totalSupply();
              uint value = supply.multiplyDecimalRound(rates[i]);
              uint multiplier = (synths[i].currencyKey() != sUSD) ? discountRate : SafeDecimalMath.unit();
              values[i] = value.multiplyDecimalRound(multiplier);
          }
  
          return (values);
      }
  
      function _currentSynthDebts(bytes32[] memory currencyKeys)
          internal
          view
          returns (
              uint[] memory snxIssuedDebts,
              uint _futuresDebt,
              uint _excludedDebt,
              bool anyRateIsInvalid
          )
      {
          (uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
          uint[] memory values = _issuedSynthValues(currencyKeys, rates);
          (uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);
          (uint futuresDebt, bool futuresDebtIsInvalid) = futuresMarketManager().totalDebt();
  
          return (values, futuresDebt, excludedDebt, isInvalid || futuresDebtIsInvalid || isAnyNonSnxDebtRateInvalid);
      }
  
      function currentSynthDebts(bytes32[] calldata currencyKeys)
          external
          view
          returns (
              uint[] memory debtValues,
              uint futuresDebt,
              uint excludedDebt,
              bool anyRateIsInvalid
          )
      {
          return _currentSynthDebts(currencyKeys);
      }
  
      function _cachedSynthDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) {
          uint numKeys = currencyKeys.length;
          uint[] memory debts = new uint[](numKeys);
          for (uint i = 0; i < numKeys; i++) {
              debts[i] = _cachedSynthDebt[currencyKeys[i]];
          }
          return debts;
      }
  
      function cachedSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory snxIssuedDebts) {
          return _cachedSynthDebts(currencyKeys);
      }
  
      function _excludedIssuedDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) {
          uint numKeys = currencyKeys.length;
          uint[] memory debts = new uint[](numKeys);
          for (uint i = 0; i < numKeys; i++) {
              debts[i] = _excludedIssuedDebt[currencyKeys[i]];
          }
          return debts;
      }
  
      function excludedIssuedDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory excludedDebts) {
          return _excludedIssuedDebts(currencyKeys);
      }
  
      /// used when migrating to new DebtCache instance in order to import the excluded debt records
      /// If this method is not run after upgrading the contract, the debt will be
      /// incorrect w.r.t to wrapper factory assets until the values are imported from
      /// previous instance of the contract
      /// Also, in addition to this method it's possible to use recordExcludedDebtChange since
      /// it's accessible to owner in case additional adjustments are required
      function importExcludedIssuedDebts(IDebtCache prevDebtCache, IIssuer prevIssuer) external onlyOwner {
          // this can only be run once so that recorded debt deltas aren't accidentally
          // lost or double counted
          require(!isInitialized, "already initialized");
          isInitialized = true;
  
          // get the currency keys from **previous** issuer, in case current issuer
          // doesn't have all the synths at this point
          // warning: if a synth won't be added to the current issuer before the next upgrade of this contract,
          // its entry will be lost (because it won't be in the prevIssuer for next time).
          // if for some reason this is a problem, it should be possible to use recordExcludedDebtChange() to amend
          bytes32[] memory keys = prevIssuer.availableCurrencyKeys();
  
          require(keys.length > 0, "previous Issuer has no synths");
  
          // query for previous debt records
          uint[] memory debts = prevDebtCache.excludedIssuedDebts(keys);
  
          // store the values
          for (uint i = 0; i < keys.length; i++) {
              if (debts[i] > 0) {
                  // adding the values instead of overwriting in case some deltas were recorded in this
                  // contract already (e.g. if the upgrade was not atomic)
                  _excludedIssuedDebt[keys[i]] = _excludedIssuedDebt[keys[i]].add(debts[i]);
              }
          }
      }
  
      // Returns the total sUSD debt backed by non-SNX collateral.
      function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid) {
          bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
          (uint[] memory rates, bool ratesAreInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
  
          return _totalNonSnxBackedDebt(currencyKeys, rates, ratesAreInvalid);
      }
  
      function _totalNonSnxBackedDebt(
          bytes32[] memory currencyKeys,
          uint[] memory rates,
          bool ratesAreInvalid
      ) internal view returns (uint excludedDebt, bool isInvalid) {
          // Calculate excluded debt.
          // 1. MultiCollateral long debt + short debt.
          (uint longValue, bool anyTotalLongRateIsInvalid) = collateralManager().totalLong();
          (uint shortValue, bool anyTotalShortRateIsInvalid) = collateralManager().totalShort();
          isInvalid = ratesAreInvalid || anyTotalLongRateIsInvalid || anyTotalShortRateIsInvalid;
          excludedDebt = longValue.add(shortValue);
  
          // 2. EtherWrapper.
          // Subtract sETH and sUSD issued by EtherWrapper.
          excludedDebt = excludedDebt.add(etherWrapper().totalIssuedSynths());
  
          // 3. WrapperFactory.
          // Get the debt issued by the Wrappers.
          for (uint i = 0; i < currencyKeys.length; i++) {
              excludedDebt = excludedDebt.add(_excludedIssuedDebt[currencyKeys[i]].multiplyDecimalRound(rates[i]));
          }
  
          return (excludedDebt, isInvalid);
      }
  
      function _currentDebt() internal view returns (uint debt, bool anyRateIsInvalid) {
          bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
          (uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
  
          // Sum all issued synth values based on their supply.
          uint[] memory values = _issuedSynthValues(currencyKeys, rates);
          (uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);
  
          uint numValues = values.length;
          uint total;
          for (uint i; i < numValues; i++) {
              total = total.add(values[i]);
          }
  
          // Add in the debt accounted for by futures
          (uint futuresDebt, bool futuresDebtIsInvalid) = futuresMarketManager().totalDebt();
          total = total.add(futuresDebt);
  
          // Ensure that if the excluded non-SNX debt exceeds SNX-backed debt, no overflow occurs
          total = total < excludedDebt ? 0 : total.sub(excludedDebt);
  
          return (total, isInvalid || futuresDebtIsInvalid || isAnyNonSnxDebtRateInvalid);
      }
  
      function currentDebt() external view returns (uint debt, bool anyRateIsInvalid) {
          return _currentDebt();
      }
  
      function cacheInfo()
          external
          view
          returns (
              uint debt,
              uint timestamp,
              bool isInvalid,
              bool isStale
          )
      {
          uint time = _cacheTimestamp;
          return (_cachedDebt, time, _cacheInvalid, _cacheStale(time));
      }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      // Stub out all mutative functions as no-ops;
      // since they do nothing, there are no restrictions
  
      function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external {}
  
      function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external {}
  
      function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external {}
  
      function updateDebtCacheValidity(bool currentlyInvalid) external {}
  
      function purgeCachedSynthDebt(bytes32 currencyKey) external {}
  
      function takeDebtSnapshot() external {}
  
      function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external {}
  
      function updateCachedsUSDDebt(int amount) external {}
  
      /* ========== MODIFIERS ========== */
  
      function _requireSystemActiveIfNotOwner() internal view {
          if (msg.sender != owner) {
              systemStatus().requireSystemActive();
          }
      }
  
      modifier requireSystemActiveIfNotOwner() {
          _requireSystemActiveIfNotOwner();
          _;
      }
  
      function _onlyIssuer() internal view {
          require(msg.sender == address(issuer()), "Sender is not Issuer");
      }
  
      modifier onlyIssuer() {
          _onlyIssuer();
          _;
      }
  
      function _onlyIssuerOrExchanger() internal view {
          require(msg.sender == address(issuer()) || msg.sender == address(exchanger()), "Sender is not Issuer or Exchanger");
      }
  
      modifier onlyIssuerOrExchanger() {
          _onlyIssuerOrExchanger();
          _;
      }
  
      function _onlyDebtIssuer() internal view {
          bool isWrapper = wrapperFactory().isWrapper(msg.sender);
  
          // owner included for debugging and fixing in emergency situation
          bool isOwner = msg.sender == owner;
  
          require(isOwner || isWrapper, "Only debt issuers may call this");
      }
  
      modifier onlyDebtIssuer() {
          _onlyDebtIssuer();
          _;
      }
  }
  
  
  // Libraries
  
  
  // Inheritance
  
  
  // https://docs.synthetix.io/contracts/source/contracts/debtcache
  contract DebtCache is BaseDebtCache {
      using SafeDecimalMath for uint;
  
      bytes32 public constant CONTRACT_NAME = "DebtCache";
  
      constructor(address _owner, address _resolver) public BaseDebtCache(_owner, _resolver) {}
  
      bytes32 internal constant EXCLUDED_DEBT_KEY = "EXCLUDED_DEBT";
      bytes32 internal constant FUTURES_DEBT_KEY = "FUTURES_DEBT";
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      // This function exists in case a synth is ever somehow removed without its snapshot being updated.
      function purgeCachedSynthDebt(bytes32 currencyKey) external onlyOwner {
          require(issuer().synths(currencyKey) == ISynth(0), "Synth exists");
          delete _cachedSynthDebt[currencyKey];
      }
  
      function takeDebtSnapshot() external requireSystemActiveIfNotOwner {
          bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
          (uint[] memory values, uint futuresDebt, uint excludedDebt, bool isInvalid) = _currentSynthDebts(currencyKeys);
  
          // The total SNX-backed debt is the debt of futures markets plus the debt of circulating synths.
          uint snxCollateralDebt = futuresDebt;
          _cachedSynthDebt[FUTURES_DEBT_KEY] = futuresDebt;
          uint numValues = values.length;
          for (uint i; i < numValues; i++) {
              uint value = values[i];
              snxCollateralDebt = snxCollateralDebt.add(value);
              _cachedSynthDebt[currencyKeys[i]] = value;
          }
  
          // Subtract out the excluded non-SNX backed debt from our total
          _cachedSynthDebt[EXCLUDED_DEBT_KEY] = excludedDebt;
          uint newDebt = snxCollateralDebt.floorsub(excludedDebt);
          _cachedDebt = newDebt;
          _cacheTimestamp = block.timestamp;
          emit DebtCacheUpdated(newDebt);
          emit DebtCacheSnapshotTaken(block.timestamp);
  
          // (in)validate the cache if necessary
          _updateDebtCacheValidity(isInvalid);
      }
  
      function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external requireSystemActiveIfNotOwner {
          (uint[] memory rates, bool anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
          _updateCachedSynthDebtsWithRates(currencyKeys, rates, anyRateInvalid);
      }
  
      function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external onlyIssuer {
          bytes32[] memory synthKeyArray = new bytes32[](1);
          synthKeyArray[0] = currencyKey;
          uint[] memory synthRateArray = new uint[](1);
          synthRateArray[0] = currencyRate;
          _updateCachedSynthDebtsWithRates(synthKeyArray, synthRateArray, false);
      }
  
      function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates)
          external
          onlyIssuerOrExchanger
      {
          _updateCachedSynthDebtsWithRates(currencyKeys, currencyRates, false);
      }
  
      function updateDebtCacheValidity(bool currentlyInvalid) external onlyIssuer {
          _updateDebtCacheValidity(currentlyInvalid);
      }
  
      function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external onlyDebtIssuer {
          int256 newExcludedDebt = int256(_excludedIssuedDebt[currencyKey]) + delta;
  
          require(newExcludedDebt >= 0, "Excluded debt cannot become negative");
  
          _excludedIssuedDebt[currencyKey] = uint(newExcludedDebt);
      }
  
      function updateCachedsUSDDebt(int amount) external onlyIssuer {
          uint delta = SafeDecimalMath.abs(amount);
          if (amount > 0) {
              _cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].add(delta);
              _cachedDebt = _cachedDebt.add(delta);
          } else {
              _cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].sub(delta);
              _cachedDebt = _cachedDebt.sub(delta);
          }
  
          emit DebtCacheUpdated(_cachedDebt);
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function _updateDebtCacheValidity(bool currentlyInvalid) internal {
          if (_cacheInvalid != currentlyInvalid) {
              _cacheInvalid = currentlyInvalid;
              emit DebtCacheValidityChanged(currentlyInvalid);
          }
      }
  
      // Updated the global debt according to a rate/supply change in a subset of issued synths.
      function _updateCachedSynthDebtsWithRates(
          bytes32[] memory currencyKeys,
          uint[] memory currentRates,
          bool anyRateIsInvalid
      ) internal {
          uint numKeys = currencyKeys.length;
          require(numKeys == currentRates.length, "Input array lengths differ");
  
          // Compute the cached and current debt sum for the subset of synths provided.
          uint cachedSum;
          uint currentSum;
          uint[] memory currentValues = _issuedSynthValues(currencyKeys, currentRates);
  
          for (uint i = 0; i < numKeys; i++) {
              bytes32 key = currencyKeys[i];
              uint currentSynthDebt = currentValues[i];
  
              cachedSum = cachedSum.add(_cachedSynthDebt[key]);
              currentSum = currentSum.add(currentSynthDebt);
  
              _cachedSynthDebt[key] = currentSynthDebt;
          }
  
          // Apply the debt update.
          if (cachedSum != currentSum) {
              uint debt = _cachedDebt;
              // apply the delta between the cachedSum and currentSum
              // add currentSum before sub cachedSum to prevent overflow as cachedSum > debt for large amount of excluded debt
              debt = debt.add(currentSum).sub(cachedSum);
              _cachedDebt = debt;
              emit DebtCacheUpdated(debt);
          }
  
          // Invalidate the cache if necessary
          if (anyRateIsInvalid) {
              _updateDebtCacheValidity(anyRateIsInvalid);
          }
      }
  
      /* ========== EVENTS ========== */
  
      event DebtCacheUpdated(uint cachedDebt);
      event DebtCacheSnapshotTaken(uint timestamp);
      event DebtCacheValidityChanged(bool indexed isInvalid);
  }
  
  
  // SPDX-License-Identifier: MIT
  
  
  /**
   * @dev Wrappers over Solidity's uintXX casting operators with added overflow
   * checks.
   *
   * Downcasting from uint256 in Solidity does not revert on overflow. This can
   * easily result in undesired exploitation or bugs, since developers usually
   * assume that overflows raise errors. `SafeCast` restores this intuition by
   * reverting the transaction when such 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.
   *
   * Can be combined with {SafeMath} to extend it to smaller types, by performing
   * all math on `uint256` and then downcasting.
   */
  library SafeCast {
      /**
       * @dev Returns the downcasted uint128 from uint256, reverting on
       * overflow (when the input is greater than largest uint128).
       *
       * Counterpart to Solidity's `uint128` operator.
       *
       * Requirements:
       *
       * - input must fit into 128 bits
       */
      function toUint128(uint256 value) internal pure returns (uint128) {
          require(value < 2**128, "SafeCast: value doesn't fit in 128 bits");
          return uint128(value);
      }
  
      /**
       * @dev Returns the downcasted uint64 from uint256, reverting on
       * overflow (when the input is greater than largest uint64).
       *
       * Counterpart to Solidity's `uint64` operator.
       *
       * Requirements:
       *
       * - input must fit into 64 bits
       */
      function toUint64(uint256 value) internal pure returns (uint64) {
          require(value < 2**64, "SafeCast: value doesn't fit in 64 bits");
          return uint64(value);
      }
  
      /**
       * @dev Returns the downcasted uint32 from uint256, reverting on
       * overflow (when the input is greater than largest uint32).
       *
       * Counterpart to Solidity's `uint32` operator.
       *
       * Requirements:
       *
       * - input must fit into 32 bits
       */
      function toUint32(uint256 value) internal pure returns (uint32) {
          require(value < 2**32, "SafeCast: value doesn't fit in 32 bits");
          return uint32(value);
      }
  
      /**
       * @dev Returns the downcasted uint16 from uint256, reverting on
       * overflow (when the input is greater than largest uint16).
       *
       * Counterpart to Solidity's `uint16` operator.
       *
       * Requirements:
       *
       * - input must fit into 16 bits
       */
      function toUint16(uint256 value) internal pure returns (uint16) {
          require(value < 2**16, "SafeCast: value doesn't fit in 16 bits");
          return uint16(value);
      }
  
      /**
       * @dev Returns the downcasted uint8 from uint256, reverting on
       * overflow (when the input is greater than largest uint8).
       *
       * Counterpart to Solidity's `uint8` operator.
       *
       * Requirements:
       *
       * - input must fit into 8 bits.
       */
      function toUint8(uint256 value) internal pure returns (uint8) {
          require(value < 2**8, "SafeCast: value doesn't fit in 8 bits");
          return uint8(value);
      }
  
      /**
       * @dev Converts a signed int256 into an unsigned uint256.
       *
       * Requirements:
       *
       * - input must be greater than or equal to 0.
       */
      function toUint256(int256 value) internal pure returns (uint256) {
          require(value >= 0, "SafeCast: value must be positive");
          return uint256(value);
      }
  
      /**
       * @dev Converts an unsigned uint256 into a signed int256.
       *
       * Requirements:
       *
       * - input must be less than or equal to maxInt256.
       */
      function toInt256(uint256 value) internal pure returns (int256) {
          require(value < 2**255, "SafeCast: value doesn't fit in an int256");
          return int256(value);
      }
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/isynthetixdebtshare
  interface ISynthetixDebtShare {
      // Views
  
      function currentPeriodId() external view returns (uint128);
  
      function allowance(address account, address spender) external view returns (uint);
  
      function balanceOf(address account) external view returns (uint);
  
      function balanceOfOnPeriod(address account, uint periodId) external view returns (uint);
  
      function totalSupply() external view returns (uint);
  
      function sharePercent(address account) external view returns (uint);
  
      function sharePercentOnPeriod(address account, uint periodId) external view returns (uint);
  
      // Mutative functions
  
      function takeSnapshot(uint128 id) external;
  
      function mintShare(address account, uint256 amount) external;
  
      function burnShare(address account, uint256 amount) external;
  
      function approve(address, uint256) external pure returns (bool);
  
      function transfer(address to, uint256 amount) external pure returns (bool);
  
      function transferFrom(
          address from,
          address to,
          uint256 amount
      ) external returns (bool);
  
      function addAuthorizedBroker(address target) external;
  
      function removeAuthorizedBroker(address target) external;
  
      function addAuthorizedToSnapshot(address target) external;
  
      function removeAuthorizedToSnapshot(address target) external;
  }
  
  
  // https://docs.synthetix.io/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.synthetix.io/contracts/source/interfaces/ICircuitBreaker
  interface ICircuitBreaker {
      // Views
      function isInvalid(address oracleAddress, uint value) external view returns (bool);
  
      function priceDeviationThresholdFactor() external view returns (uint);
  
      function isDeviationAboveThreshold(uint base, uint comparison) external view returns (bool);
  
      function lastValue(address oracleAddress) external view returns (uint);
  
      function circuitBroken(address oracleAddress) external view returns (bool);
  
      // Mutative functions
      function resetLastValue(address[] calldata oracleAddresses, uint[] calldata values) external;
  
      function probeCircuitBreaker(address oracleAddress, uint value) external returns (bool circuitBroken);
  }
  
  
  // https://docs.synthetix.io/contracts/source/interfaces/ihasbalance
  interface IHasBalance {
      // Views
      function balanceOf(address account) external view returns (uint);
  }
  
  
  interface ILiquidator {
      // Views
      function issuanceRatio() external view returns (uint);
  
      function liquidationDelay() external view returns (uint);
  
      function liquidationRatio() external view returns (uint);
  
      function liquidationEscrowDuration() external view returns (uint);
  
      function liquidationPenalty() external view returns (uint);
  
      function selfLiquidationPenalty() external view returns (uint);
  
      function liquidateReward() external view returns (uint);
  
      function flagReward() external view returns (uint);
  
      function liquidationCollateralRatio() external view returns (uint);
  
      function getLiquidationDeadlineForAccount(address account) external view returns (uint);
  
      function getLiquidationCallerForAccount(address account) external view returns (address);
  
      function isLiquidationOpen(address account, bool isSelfLiquidation) external view returns (bool);
  
      function isLiquidationDeadlinePassed(address account) external view returns (bool);
  
      function calculateAmountToFixCollateral(
          uint debtBalance,
          uint collateral,
          uint penalty
      ) external view returns (uint);
  
      function liquidationAmounts(address account, bool isSelfLiquidation)
          external
          view
          returns (
              uint totalRedeemed,
              uint debtToRemove,
              uint escrowToLiquidate,
              uint initialDebtBalance
          );
  
      // Mutative Functions
      function flagAccountForLiquidation(address account) external;
  
      // Restricted: used internally to Synthetix contracts
      function removeAccountInLiquidation(address account) external;
  
      function checkAndRemoveAccountInLiquidation(address account) external;
  }
  
  
  interface ILiquidatorRewards {
      // Views
  
      function earned(address account) external view returns (uint256);
  
      // Mutative
  
      function getReward(address account) external;
  
      function notifyRewardAmount(uint256 reward) external;
  
      function updateEntry(address account) external;
  }
  
  
  interface ISynthRedeemer {
      // Rate of redemption - 0 for none
      function redemptions(address synthProxy) external view returns (uint redeemRate);
  
      // sUSD balance of deprecated token holder
      function balanceOf(IERC20 synthProxy, address account) external view returns (uint balanceOfInsUSD);
  
      // Full sUSD supply of token
      function totalSupply(IERC20 synthProxy) external view returns (uint totalSupplyInsUSD);
  
      function redeem(IERC20 synthProxy) external;
  
      function redeemAll(IERC20[] calldata synthProxies) external;
  
      function redeemPartial(IERC20 synthProxy, uint amountOfSynth) external;
  
      // Restricted to Issuer
      function deprecate(IERC20 synthProxy, uint rateToRedeem) external;
  }
  
  
  // Inheritance
  
  
  // Internal references
  
  
  // https://docs.synthetix.io/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.synthetix.io/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;
  
      /* 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 setMessageSender(address sender) external onlyProxy {
          messageSender = sender;
      }
  
      modifier onlyProxy {
          _onlyProxy();
          _;
      }
  
      function _onlyProxy() private view {
          require(Proxy(msg.sender) == proxy, "Only the proxy can call");
      }
  
      modifier optionalProxy {
          _optionalProxy();
          _;
      }
  
      function _optionalProxy() private {
          if (Proxy(msg.sender) != proxy && 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 && messageSender != msg.sender) {
              messageSender = msg.sender;
          }
          require(messageSender == owner, "Owner only function");
      }
  
      event ProxyUpdated(address proxyAddress);
  }
  
  
  interface AggregatorInterface {
    function latestAnswer() external view returns (int256);
    function latestTimestamp() external view returns (uint256);
    function latestRound() external view returns (uint256);
    function getAnswer(uint256 roundId) external view returns (int256);
    function getTimestamp(uint256 roundId) external view returns (uint256);
  
    event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp);
    event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
  }
  
  
  interface AggregatorV3Interface {
  
    function decimals() external view returns (uint8);
    function description() external view returns (string memory);
    function version() external view returns (uint256);
  
    // getRoundData and latestRoundData should both raise "No data present"
    // if they do not have data to report, instead of returning unset values
    // which could be misinterpreted as actual reported values.
    function getRoundData(uint80 _roundId)
      external
      view
      returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
      );
    function latestRoundData()
      external
      view
      returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
      );
  
  }
  
  
  /**
   * @title The V2 & V3 Aggregator Interface
   * @notice Solidity V0.5 does not allow interfaces to inherit from other
   * interfaces so this contract is a combination of v0.5 AggregatorInterface.sol
   * and v0.5 AggregatorV3Interface.sol.
   */
  interface AggregatorV2V3Interface {
    //
    // V2 Interface:
    //
    function latestAnswer() external view returns (int256);
    function latestTimestamp() external view returns (uint256);
    function latestRound() external view returns (uint256);
    function getAnswer(uint256 roundId) external view returns (int256);
    function getTimestamp(uint256 roundId) external view returns (uint256);
  
    event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp);
    event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
  
    //
    // V3 Interface:
    //
    function decimals() external view returns (uint8);
    function description() external view returns (string memory);
    function version() external view returns (uint256);
  
    // getRoundData and latestRoundData should both raise "No data present"
    // if they do not have data to report, instead of returning unset values
    // which could be misinterpreted as actual reported values.
    function getRoundData(uint80 _roundId)
      external
      view
      returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
      );
    function latestRoundData()
      external
      view
      returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
      );
  
  }
  
  
  // Inheritance
  
  
  // Libraries
  
  
  // Internal references
  
  
  interface IProxy {
      function target() external view returns (address);
  }
  
  interface IIssuerInternalDebtCache {
      function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external;
  
      function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
  
      function updateDebtCacheValidity(bool currentlyInvalid) external;
  
      function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid);
  
      function cacheInfo()
          external
          view
          returns (
              uint cachedDebt,
              uint timestamp,
              bool isInvalid,
              bool isStale
          );
  
      function updateCachedsUSDDebt(int amount) external;
  }
  
  // https://docs.synthetix.io/contracts/source/contracts/issuer
  contract Issuer is Owned, MixinSystemSettings, IIssuer {
      using SafeMath for uint;
      using SafeDecimalMath for uint;
  
      bytes32 public constant CONTRACT_NAME = "Issuer";
  
      // Available Synths which can be used with the system
      ISynth[] public availableSynths;
      mapping(bytes32 => ISynth) public synths;
      mapping(address => bytes32) public synthsByAddress;
  
      /* ========== ENCODED NAMES ========== */
  
      bytes32 internal constant sUSD = "sUSD";
      bytes32 internal constant SNX = "SNX";
  
      // Flexible storage names
  
      bytes32 internal constant LAST_ISSUE_EVENT = "lastIssueEvent";
  
      /* ========== ADDRESS RESOLVER CONFIGURATION ========== */
  
      bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
      bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
      bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
      bytes32 private constant CONTRACT_CIRCUIT_BREAKER = "CircuitBreaker";
      bytes32 private constant CONTRACT_SYNTHETIXDEBTSHARE = "SynthetixDebtShare";
      bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
      bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
      bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
      bytes32 private constant CONTRACT_LIQUIDATOR = "Liquidator";
      bytes32 private constant CONTRACT_LIQUIDATOR_REWARDS = "LiquidatorRewards";
      bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache";
      bytes32 private constant CONTRACT_DYNAMICSYNTHREDEEMER = "DynamicSynthRedeemer";
      bytes32 private constant CONTRACT_SYNTHREDEEMER = "SynthRedeemer";
      bytes32 private constant CONTRACT_SYNTHETIXBRIDGETOOPTIMISM = "SynthetixBridgeToOptimism";
      bytes32 private constant CONTRACT_SYNTHETIXBRIDGETOBASE = "SynthetixBridgeToBase";
      bytes32 private constant CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM = "DebtMigratorOnEthereum";
      bytes32 private constant CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM = "DebtMigratorOnOptimism";
  
      bytes32 private constant CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS = "ext:AggregatorIssuedSynths";
      bytes32 private constant CONTRACT_EXT_AGGREGATOR_DEBT_RATIO = "ext:AggregatorDebtRatio";
  
      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[](15);
          newAddresses[0] = CONTRACT_SYNTHETIX;
          newAddresses[1] = CONTRACT_EXCHANGER;
          newAddresses[2] = CONTRACT_EXRATES;
          newAddresses[3] = CONTRACT_CIRCUIT_BREAKER;
          newAddresses[4] = CONTRACT_SYNTHETIXDEBTSHARE;
          newAddresses[5] = CONTRACT_FEEPOOL;
          newAddresses[6] = CONTRACT_DELEGATEAPPROVALS;
          newAddresses[7] = CONTRACT_REWARDESCROW_V2;
          newAddresses[8] = CONTRACT_LIQUIDATOR;
          newAddresses[9] = CONTRACT_LIQUIDATOR_REWARDS;
          newAddresses[10] = CONTRACT_DEBTCACHE;
          newAddresses[11] = CONTRACT_SYNTHREDEEMER;
          newAddresses[12] = CONTRACT_DYNAMICSYNTHREDEEMER;
          newAddresses[13] = CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS;
          newAddresses[14] = CONTRACT_EXT_AGGREGATOR_DEBT_RATIO;
          return combineArrays(existingAddresses, newAddresses);
      }
  
      function synthetixERC20() internal view returns (IERC20) {
          return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
      }
  
      function exchanger() internal view returns (IExchanger) {
          return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
      }
  
      function exchangeRates() internal view returns (IExchangeRates) {
          return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
      }
  
      function circuitBreaker() internal view returns (ICircuitBreaker) {
          return ICircuitBreaker(requireAndGetAddress(CONTRACT_CIRCUIT_BREAKER));
      }
  
      function synthetixDebtShare() internal view returns (ISynthetixDebtShare) {
          return ISynthetixDebtShare(requireAndGetAddress(CONTRACT_SYNTHETIXDEBTSHARE));
      }
  
      function liquidator() internal view returns (ILiquidator) {
          return ILiquidator(requireAndGetAddress(CONTRACT_LIQUIDATOR));
      }
  
      function liquidatorRewards() internal view returns (ILiquidatorRewards) {
          return ILiquidatorRewards(requireAndGetAddress(CONTRACT_LIQUIDATOR_REWARDS));
      }
  
      function delegateApprovals() internal view returns (IDelegateApprovals) {
          return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
      }
  
      function rewardEscrowV2() internal view returns (IHasBalance) {
          return IHasBalance(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
      }
  
      function debtCache() internal view returns (IIssuerInternalDebtCache) {
          return IIssuerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE));
      }
  
      function synthRedeemer() internal view returns (ISynthRedeemer) {
          return ISynthRedeemer(requireAndGetAddress(CONTRACT_SYNTHREDEEMER));
      }
  
      function allNetworksDebtInfo()
          public
          view
          returns (
              uint256 debt,
              uint256 sharesSupply,
              bool isStale
          )
      {
          (, int256 rawIssuedSynths, , uint issuedSynthsUpdatedAt, ) =
              _latestRoundData(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS));
  
          (uint rawRatio, uint ratioUpdatedAt) = _rawDebtRatioAndUpdatedAt();
  
          debt = uint(rawIssuedSynths);
          sharesSupply = rawRatio == 0 ? 0 : debt.divideDecimalRoundPrecise(uint(rawRatio));
  
          uint stalePeriod = getRateStalePeriod();
  
          isStale =
              stalePeriod < block.timestamp &&
              (block.timestamp - stalePeriod > issuedSynthsUpdatedAt || block.timestamp - stalePeriod > ratioUpdatedAt);
      }
  
      function issuanceRatio() external view returns (uint) {
          return getIssuanceRatio();
      }
  
      function _rateAndInvalid(bytes32 currencyKey) internal view returns (uint, bool) {
          return exchangeRates().rateAndInvalid(currencyKey);
      }
  
      function _latestRoundData(address aggregator)
          internal
          view
          returns (
              uint80,
              int256,
              uint256,
              uint256,
              uint80
          )
      {
          return AggregatorV2V3Interface(aggregator).latestRoundData();
      }
  
      function _rawDebtRatioAndUpdatedAt() internal view returns (uint, uint) {
          (, int256 rawRatioInt, , uint ratioUpdatedAt, ) =
              _latestRoundData(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_DEBT_RATIO));
          return (uint(rawRatioInt), ratioUpdatedAt);
      }
  
      function _sharesForDebt(uint debtAmount) internal view returns (uint) {
          (uint rawRatio, ) = _rawDebtRatioAndUpdatedAt();
          return rawRatio == 0 ? 0 : debtAmount.divideDecimalRoundPrecise(rawRatio);
      }
  
      function _debtForShares(uint sharesAmount) internal view returns (uint) {
          (uint rawRatio, ) = _rawDebtRatioAndUpdatedAt();
          return sharesAmount.multiplyDecimalRoundPrecise(rawRatio);
      }
  
      function _debtShareBalanceOf(address account) internal view returns (uint) {
          return synthetixDebtShare().balanceOf(account);
      }
  
      function _snxBalanceOf(address account) internal view returns (uint) {
          return synthetixERC20().balanceOf(account);
      }
  
      function _rewardEscrowBalanceOf(address account) internal view returns (uint) {
          return rewardEscrowV2().balanceOf(account);
      }
  
      function _availableCurrencyKeysWithOptionalSNX(bool withSNX) internal view returns (bytes32[] memory) {
          bytes32[] memory currencyKeys = new bytes32[](availableSynths.length + (withSNX ? 1 : 0));
  
          for (uint i = 0; i < availableSynths.length; i++) {
              currencyKeys[i] = synthsByAddress[address(availableSynths[i])];
          }
  
          if (withSNX) {
              currencyKeys[availableSynths.length] = SNX;
          }
  
          return currencyKeys;
      }
  
      // Returns the total value of the debt pool in currency specified by `currencyKey`.
      // To return only the SNX-backed debt, set `excludeCollateral` to true.
      function _totalIssuedSynths(bytes32 currencyKey, bool excludeCollateral)
          internal
          view
          returns (uint totalIssued, bool anyRateIsInvalid)
      {
          (uint debt, , bool cacheIsInvalid, bool cacheIsStale) = debtCache().cacheInfo();
          anyRateIsInvalid = cacheIsInvalid || cacheIsStale;
  
          // Add total issued synths from non snx collateral back into the total if not excluded
          if (!excludeCollateral) {
              (uint nonSnxDebt, bool invalid) = debtCache().totalNonSnxBackedDebt();
              debt = debt.add(nonSnxDebt);
              anyRateIsInvalid = anyRateIsInvalid || invalid;
          }
  
          if (currencyKey == sUSD) {
              return (debt, anyRateIsInvalid);
          }
  
          (uint currencyRate, bool currencyRateInvalid) = _rateAndInvalid(currencyKey);
          return (debt.divideDecimalRound(currencyRate), anyRateIsInvalid || currencyRateInvalid);
      }
  
      function _debtBalanceOfAndTotalDebt(uint debtShareBalance, bytes32 currencyKey)
          internal
          view
          returns (
              uint debtBalance,
              uint totalSystemValue,
              bool anyRateIsInvalid
          )
      {
          // What's the total value of the system excluding ETH backed synths in their requested currency?
          (uint snxBackedAmount, , bool debtInfoStale) = allNetworksDebtInfo();
  
          if (debtShareBalance == 0) {
              return (0, snxBackedAmount, debtInfoStale);
          }
  
          // existing functionality requires for us to convert into the exchange rate specified by `currencyKey`
          (uint currencyRate, bool currencyRateInvalid) = _rateAndInvalid(currencyKey);
  
          debtBalance = _debtForShares(debtShareBalance).divideDecimalRound(currencyRate);
          totalSystemValue = snxBackedAmount;
  
          anyRateIsInvalid = currencyRateInvalid || debtInfoStale;
      }
  
      function _canBurnSynths(address account) internal view returns (bool) {
          return now >= _lastIssueEvent(account).add(getMinimumStakeTime());
      }
  
      function _lastIssueEvent(address account) internal view returns (uint) {
          //  Get the timestamp of the last issue this account made
          return flexibleStorage().getUIntValue(CONTRACT_NAME, keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)));
      }
  
      function _remainingIssuableSynths(address _issuer)
          internal
          view
          returns (
              uint maxIssuable,
              uint alreadyIssued,
              uint totalSystemDebt,
              bool anyRateIsInvalid
          )
      {
          (alreadyIssued, totalSystemDebt, anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(_issuer), sUSD);
          (uint issuable, bool isInvalid) = _maxIssuableSynths(_issuer);
          maxIssuable = issuable;
          anyRateIsInvalid = anyRateIsInvalid || isInvalid;
  
          if (alreadyIssued >= maxIssuable) {
              maxIssuable = 0;
          } else {
              maxIssuable = maxIssuable.sub(alreadyIssued);
          }
      }
  
      function _snxToUSD(uint amount, uint snxRate) internal pure returns (uint) {
          return amount.multiplyDecimalRound(snxRate);
      }
  
      function _usdToSnx(uint amount, uint snxRate) internal pure returns (uint) {
          return amount.divideDecimalRound(snxRate);
      }
  
      function _maxIssuableSynths(address _issuer) internal view returns (uint, bool) {
          // What is the value of their SNX balance in sUSD
          (uint snxRate, bool isInvalid) = _rateAndInvalid(SNX);
          uint destinationValue = _snxToUSD(_collateral(_issuer), snxRate);
  
          // They're allowed to issue up to issuanceRatio of that value
          return (destinationValue.multiplyDecimal(getIssuanceRatio()), isInvalid);
      }
  
      function _collateralisationRatio(address _issuer) internal view returns (uint, bool) {
          uint totalOwnedSynthetix = _collateral(_issuer);
  
          (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(_issuer), SNX);
  
          // it's more gas intensive to put this check here if they have 0 SNX, but it complies with the interface
          if (totalOwnedSynthetix == 0) return (0, anyRateIsInvalid);
  
          return (debtBalance.divideDecimalRound(totalOwnedSynthetix), anyRateIsInvalid);
      }
  
      function _collateral(address account) internal view returns (uint) {
          return _snxBalanceOf(account).add(_rewardEscrowBalanceOf(account)).add(liquidatorRewards().earned(account));
      }
  
      function minimumStakeTime() external view returns (uint) {
          return getMinimumStakeTime();
      }
  
      function canBurnSynths(address account) external view returns (bool) {
          return _canBurnSynths(account);
      }
  
      function availableCurrencyKeys() external view returns (bytes32[] memory) {
          return _availableCurrencyKeysWithOptionalSNX(false);
      }
  
      function availableSynthCount() external view returns (uint) {
          return availableSynths.length;
      }
  
      function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid) {
          (, anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(_availableCurrencyKeysWithOptionalSNX(true));
      }
  
      function totalIssuedSynths(bytes32 currencyKey, bool excludeOtherCollateral) external view returns (uint totalIssued) {
          (totalIssued, ) = _totalIssuedSynths(currencyKey, excludeOtherCollateral);
      }
  
      function lastIssueEvent(address account) external view returns (uint) {
          return _lastIssueEvent(account);
      }
  
      function collateralisationRatio(address _issuer) external view returns (uint cratio) {
          (cratio, ) = _collateralisationRatio(_issuer);
      }
  
      function collateralisationRatioAndAnyRatesInvalid(address _issuer)
          external
          view
          returns (uint cratio, bool anyRateIsInvalid)
      {
          return _collateralisationRatio(_issuer);
      }
  
      function collateral(address account) external view returns (uint) {
          return _collateral(account);
      }
  
      function debtBalanceOf(address _issuer, bytes32 currencyKey) external view returns (uint debtBalance) {
          // What was their initial debt ownership?
          uint debtShareBalance = _debtShareBalanceOf(_issuer);
  
          // If it's zero, they haven't issued, and they have no debt.
          if (debtShareBalance == 0) return 0;
  
          (debtBalance, , ) = _debtBalanceOfAndTotalDebt(debtShareBalance, currencyKey);
      }
  
      function remainingIssuableSynths(address _issuer)
          external
          view
          returns (
              uint maxIssuable,
              uint alreadyIssued,
              uint totalSystemDebt
          )
      {
          (maxIssuable, alreadyIssued, totalSystemDebt, ) = _remainingIssuableSynths(_issuer);
      }
  
      function maxIssuableSynths(address _issuer) external view returns (uint) {
          (uint maxIssuable, ) = _maxIssuableSynths(_issuer);
          return maxIssuable;
      }
  
      function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
          external
          view
          returns (uint transferable, bool anyRateIsInvalid)
      {
          // How many SNX do they have, excluding escrow?
          // Note: We're excluding escrow here because we're interested in their transferable amount
          // and escrowed SNX are not transferable.
  
          // How many of those will be locked by the amount they've issued?
          // Assuming issuance ratio is 20%, then issuing 20 SNX of value would require
          // 100 SNX to be locked in their wallet to maintain their collateralisation ratio
          // The locked synthetix value can exceed their balance.
          uint debtBalance;
          (debtBalance, , anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(account), SNX);
          uint lockedSynthetixValue = debtBalance.divideDecimalRound(getIssuanceRatio());
  
          // If we exceed the balance, no SNX are transferable, otherwise the difference is.
          if (lockedSynthetixValue >= balance) {
              transferable = 0;
          } else {
              transferable = balance.sub(lockedSynthetixValue);
          }
      }
  
      function getSynths(bytes32[] calldata currencyKeys) external view returns (ISynth[] memory) {
          uint numKeys = currencyKeys.length;
          ISynth[] memory addresses = new ISynth[](numKeys);
  
          for (uint i = 0; i < numKeys; i++) {
              addresses[i] = synths[currencyKeys[i]];
          }
  
          return addresses;
      }
  
      /// @notice Provide the results that would be returned by the mutative liquidateAccount() method (that's reserved to Synthetix)
      /// @param account The account to be liquidated
      /// @param isSelfLiquidation boolean to determine if this is a forced or self-invoked liquidation
      /// @return totalRedeemed the total amount of collateral (SNX) to redeem (liquid and escrow)
      /// @return debtToRemove the amount of debt (sUSD) to burn in order to fix the account's c-ratio
      /// @return escrowToLiquidate the amount of escrow SNX that will be revoked during liquidation
      /// @return initialDebtBalance the amount of initial (sUSD) debt the account has
      function liquidationAmounts(address account, bool isSelfLiquidation)
          external
          view
          returns (
              uint totalRedeemed,
              uint debtToRemove,
              uint escrowToLiquidate,
              uint initialDebtBalance
          )
      {
          return _liquidationAmounts(account, isSelfLiquidation);
      }
  
      /* ========== MUTATIVE FUNCTIONS ========== */
  
      function _addSynth(ISynth synth) internal {
          bytes32 currencyKey = synth.currencyKey();
          require(synths[currencyKey] == ISynth(0), "Synth exists");
          require(synthsByAddress[address(synth)] == bytes32(0), "Synth address already exists");
  
          availableSynths.push(synth);
          synths[currencyKey] = synth;
          synthsByAddress[address(synth)] = currencyKey;
  
          emit SynthAdded(currencyKey, address(synth));
      }
  
      function addSynth(ISynth synth) external onlyOwner {
          _addSynth(synth);
          // Invalidate the cache to force a snapshot to be recomputed. If a synth were to be added
          // back to the system and it still somehow had cached debt, this would force the value to be
          // updated.
          debtCache().updateDebtCacheValidity(true);
      }
  
      function addSynths(ISynth[] calldata synthsToAdd) external onlyOwner {
          uint numSynths = synthsToAdd.length;
          for (uint i = 0; i < numSynths; i++) {
              _addSynth(synthsToAdd[i]);
          }
  
          // Invalidate the cache to force a snapshot to be recomputed.
          debtCache().updateDebtCacheValidity(true);
      }
  
      function _removeSynth(bytes32 currencyKey) internal {
          address synthToRemove = address(synths[currencyKey]);
          require(synthToRemove != address(0), "Synth does not exist");
          require(currencyKey != sUSD, "Cannot remove synth");
  
          uint synthSupply = IERC20(synthToRemove).totalSupply();
  
          if (synthSupply > 0) {
              (uint amountOfsUSD, uint rateToRedeem, ) =
                  exchangeRates().effectiveValueAndRates(currencyKey, synthSupply, "sUSD");
              require(rateToRedeem > 0, "Cannot remove without rate");
              ISynthRedeemer _synthRedeemer = synthRedeemer();
              synths[sUSD].issue(address(_synthRedeemer), amountOfsUSD);
              // ensure the debt cache is aware of the new sUSD issued
              debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amountOfsUSD));
              _synthRedeemer.deprecate(IERC20(address(Proxyable(synthToRemove).proxy())), rateToRedeem);
          }
  
          // Remove the synth from the availableSynths array.
          for (uint i = 0; i < availableSynths.length; i++) {
              if (address(availableSynths[i]) == synthToRemove) {
                  delete availableSynths[i];
  
                  // Copy the last synth into the place of the one we just deleted
                  // If there's only one synth, this is synths[0] = synths[0].
                  // If we're deleting the last one, it's also a NOOP in the same way.
                  availableSynths[i] = availableSynths[availableSynths.length - 1];
  
                  // Decrease the size of the array by one.
                  availableSynths.length--;
  
                  break;
              }
          }
  
          // And remove it from the synths mapping
          delete synthsByAddress[synthToRemove];
          delete synths[currencyKey];
  
          emit SynthRemoved(currencyKey, synthToRemove);
      }
  
      function removeSynth(bytes32 currencyKey) external onlyOwner {
          // Remove its contribution from the debt pool snapshot, and
          // invalidate the cache to force a new snapshot.
          IIssuerInternalDebtCache cache = debtCache();
          cache.updateCachedSynthDebtWithRate(currencyKey, 0);
          cache.updateDebtCacheValidity(true);
  
          _removeSynth(currencyKey);
      }
  
      function removeSynths(bytes32[] calldata currencyKeys) external onlyOwner {
          uint numKeys = currencyKeys.length;
  
          // Remove their contributions from the debt pool snapshot, and
          // invalidate the cache to force a new snapshot.
          IIssuerInternalDebtCache cache = debtCache();
          uint[] memory zeroRates = new uint[](numKeys);
          cache.updateCachedSynthDebtsWithRates(currencyKeys, zeroRates);
          cache.updateDebtCacheValidity(true);
  
          for (uint i = 0; i < numKeys; i++) {
              _removeSynth(currencyKeys[i]);
          }
      }
  
      function issueSynthsWithoutDebt(
          bytes32 currencyKey,
          address to,
          uint amount
      ) external onlyTrustedMinters returns (bool rateInvalid) {
          require(address(synths[currencyKey]) != address(0), "synth doesn't exist");
          require(amount > 0, "cannot issue 0 synths");
  
          // record issue timestamp
          _setLastIssueEvent(to);
  
          // Create their synths
          synths[currencyKey].issue(to, amount);
  
          // Account for the issued debt in the cache
          (uint rate, bool rateInvalid) = _rateAndInvalid(currencyKey);
          debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amount.multiplyDecimal(rate)));
  
          // returned so that the caller can decide what to do if the rate is invalid
          return rateInvalid;
      }
  
      function burnSynthsWithoutDebt(
          bytes32 currencyKey,
          address from,
          uint amount
      ) external onlyTrustedMinters returns (bool rateInvalid) {
          require(address(synths[currencyKey]) != address(0), "synth doesn't exist");
          require(amount > 0, "cannot issue 0 synths");
  
          exchanger().settle(from, currencyKey);
  
          // Burn some synths
          synths[currencyKey].burn(from, amount);
  
          // Account for the burnt debt in the cache. If rate is invalid, the user won't be able to exchange
          (uint rate, bool rateInvalid) = _rateAndInvalid(currencyKey);
          debtCache().updateCachedsUSDDebt(-SafeCast.toInt256(amount.multiplyDecimal(rate)));
  
          // returned so that the caller can decide what to do if the rate is invalid
          return rateInvalid;
      }
  
      /**
       * SIP-2059: Dynamic Redemption
       * Function used to burn spot synths and issue the equivalent in sUSD at chainlink price * discount rate
       * @param account The address of the account that is redeeming
       * @param currencyKey The synth to be redeemed
       * @param amountOfSynth The amount of redeeming synth to burn
       * @param amountInsUSD The amount of sUSD to issue
       */
      function burnAndIssueSynthsWithoutDebtCache(
          address account,
          bytes32 currencyKey,
          uint amountOfSynth,
          uint amountInsUSD
      ) external onlySynthRedeemer {
          exchanger().settle(account, currencyKey);
  
          // Burn their redeemed synths
          synths[currencyKey].burn(account, amountOfSynth);
  
          // record issue timestamp
          _setLastIssueEvent(account);
  
          // Issuer their sUSD equivalent
          synths[sUSD].issue(account, amountInsUSD);
      }
  
      /**
       * SIP-237: Debt Migration
       * Function used for the one-way migration of all debt and liquid + escrowed SNX from L1 -> L2
       * @param account The address of the account that is being migrated
       * @param amount The amount of debt shares moving across layers
       */
      function modifyDebtSharesForMigration(address account, uint amount) external onlyTrustedMigrators {
          ISynthetixDebtShare sds = synthetixDebtShare();
  
          if (msg.sender == resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM)) {
              sds.burnShare(account, amount);
          } else if (msg.sender == resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM)) {
              sds.mintShare(account, amount);
          }
      }
  
      function issueSynths(address from, uint amount) external onlySynthetix {
          require(amount > 0, "cannot issue 0 synths");
  
          _issueSynths(from, amount, false);
      }
  
      function issueMaxSynths(address from) external onlySynthetix {
          _issueSynths(from, 0, true);
      }
  
      function issueSynthsOnBehalf(
          address issueForAddress,
          address from,
          uint amount
      ) external onlySynthetix {
          _requireCanIssueOnBehalf(issueForAddress, from);
          _issueSynths(issueForAddress, amount, false);
      }
  
      function issueMaxSynthsOnBehalf(address issueForAddress, address from) external onlySynthetix {
          _requireCanIssueOnBehalf(issueForAddress, from);
          _issueSynths(issueForAddress, 0, true);
      }
  
      function burnSynths(address from, uint amount) external onlySynthetix {
          _voluntaryBurnSynths(from, amount, false);
      }
  
      function burnSynthsOnBehalf(
          address burnForAddress,
          address from,
          uint amount
      ) external onlySynthetix {
          _requireCanBurnOnBehalf(burnForAddress, from);
          _voluntaryBurnSynths(burnForAddress, amount, false);
      }
  
      function burnSynthsToTarget(address from) external onlySynthetix {
          _voluntaryBurnSynths(from, 0, true);
      }
  
      function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external onlySynthetix {
          _requireCanBurnOnBehalf(burnForAddress, from);
          _voluntaryBurnSynths(burnForAddress, 0, true);
      }
  
      function burnForRedemption(
          address deprecatedSynthProxy,
          address account,
          uint balance
      ) external onlySynthRedeemer {
          ISynth(IProxy(deprecatedSynthProxy).target()).burn(account, balance);
      }
  
      // SIP-148: Upgraded Liquidation Mechanism
      /// @notice This is where the core internal liquidation logic resides. This function can only be invoked by Synthetix.
      /// Reverts if liquidator().isLiquidationOpen() returns false (e.g. c-ratio is too high, delay hasn't passed,
      ///     account wasn't flagged etc)
      /// @param account The account to be liquidated
      /// @param isSelfLiquidation boolean to determine if this is a forced or self-invoked liquidation
      /// @return totalRedeemed the total amount of collateral (SNX) to redeem (liquid and escrow)
      /// @return debtRemoved the amount of debt (sUSD) to burn in order to fix the account's c-ratio
      /// @return escrowToLiquidate the amount of escrow SNX that will be revoked during liquidation
      function liquidateAccount(address account, bool isSelfLiquidation)
          external
          onlySynthetix
          returns (
              uint totalRedeemed,
              uint debtRemoved,
              uint escrowToLiquidate
          )
      {
          require(liquidator().isLiquidationOpen(account, isSelfLiquidation), "Not open for liquidation");
  
          // liquidationAmounts checks isLiquidationOpen for the account
          uint initialDebtBalance;
          (totalRedeemed, debtRemoved, escrowToLiquidate, initialDebtBalance) = _liquidationAmounts(
              account,
              isSelfLiquidation
          );
  
          // Reduce debt shares by amount to liquidate.
          _removeFromDebtRegister(account, debtRemoved, initialDebtBalance);
  
          if (!isSelfLiquidation) {
              // In case of forced liquidation only, remove the liquidation flag.
              liquidator().removeAccountInLiquidation(account);
          }
          // Note: To remove the flag after self liquidation, burn to target and then call Liquidator.checkAndRemoveAccountInLiquidation(account).
      }
  
      function _liquidationAmounts(address account, bool isSelfLiquidation)
          internal
          view
          returns (
              uint totalRedeemed,
              uint debtToRemove,
              uint escrowToLiquidate,
              uint debtBalance
          )
      {
          // Get the account's debt balance
          bool anyRateIsInvalid;
          (debtBalance, , anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(account), sUSD);
  
          // Get the SNX rate
          (uint snxRate, bool snxRateInvalid) = _rateAndInvalid(SNX);
          _requireRatesNotInvalid(anyRateIsInvalid || snxRateInvalid);
  
          uint penalty;
          if (isSelfLiquidation) {
              // Get self liquidation penalty
              penalty = getSelfLiquidationPenalty();
  
              // Calculate the amount of debt to remove and SNX to redeem for a self liquidation
              debtToRemove = liquidator().calculateAmountToFixCollateral(
                  debtBalance,
                  _snxToUSD(_collateral(account), snxRate),
                  penalty
              );
  
              // Get the minimum values for both totalRedeemed and debtToRemove
              totalRedeemed = _getMinValue(
                  _usdToSnx(debtToRemove, snxRate).multiplyDecimal(SafeDecimalMath.unit().add(penalty)),
                  _snxBalanceOf(account)
              );
              debtToRemove = _getMinValue(
                  _snxToUSD(totalRedeemed, snxRate).divideDecimal(SafeDecimalMath.unit().add(penalty)),
                  debtToRemove
              );
  
              // Return escrow as zero since it cannot be self liquidated
              return (totalRedeemed, debtToRemove, 0, debtBalance);
          } else {
              // In the case of forced Liquidation
              // Get the forced liquidation penalty and sum of the flag and liquidate rewards.
              penalty = getSnxLiquidationPenalty();
              uint rewardsSum = getLiquidateReward().add(getFlagReward());
  
              // Get the total USD value of their SNX collateral (including escrow and rewards minus the flag and liquidate rewards)
              uint collateralForAccountUSD = _snxToUSD(_collateral(account).sub(rewardsSum), snxRate);
  
              // Calculate the amount of debt to remove and the sUSD value of the SNX required to liquidate.
              debtToRemove = liquidator().calculateAmountToFixCollateral(debtBalance, collateralForAccountUSD, penalty);
              uint redeemTarget = _usdToSnx(debtToRemove, snxRate).multiplyDecimal(SafeDecimalMath.unit().add(penalty));
  
              if (redeemTarget.add(rewardsSum) >= _collateral(account)) {
                  // need to wipe out the account
                  debtToRemove = debtBalance;
                  totalRedeemed = _collateral(account).sub(rewardsSum);
                  escrowToLiquidate = _rewardEscrowBalanceOf(account);
                  return (totalRedeemed, debtToRemove, escrowToLiquidate, debtBalance);
              } else {
                  // normal forced liquidation
                  (totalRedeemed, escrowToLiquidate) = _redeemableCollateralForTarget(account, redeemTarget, rewardsSum);
                  return (totalRedeemed, debtToRemove, escrowToLiquidate, debtBalance);
              }
          }
      }
  
      // SIP-252
      // calculates the amount of SNX that can be force liquidated (redeemed)
      // for the various cases of transferrable & escrowed collateral
      function _redeemableCollateralForTarget(
          address account,
          uint redeemTarget,
          uint rewardsSum
      ) internal view returns (uint totalRedeemed, uint escrowToLiquidate) {
          // The balanceOf here can be considered "transferable" since it's not escrowed,
          // and it is the only SNX that can potentially be transfered if unstaked.
          uint transferable = _snxBalanceOf(account);
          if (redeemTarget.add(rewardsSum) <= transferable) {
              // transferable is enough
              return (redeemTarget, 0);
          } else {
              // if transferable is not enough
              // need only part of the escrow, add the needed part to redeemed
              escrowToLiquidate = redeemTarget.add(rewardsSum).sub(transferable);
              return (redeemTarget, escrowToLiquidate);
          }
      }
  
      function _getMinValue(uint x, uint y) internal pure returns (uint) {
          return x < y ? x : y;
      }
  
      function setCurrentPeriodId(uint128 periodId) external {
          require(msg.sender == requireAndGetAddress(CONTRACT_FEEPOOL), "Must be fee pool");
  
          ISynthetixDebtShare sds = synthetixDebtShare();
  
          if (sds.currentPeriodId() < periodId) {
              sds.takeSnapshot(periodId);
          }
      }
  
      /* ========== INTERNAL FUNCTIONS ========== */
  
      function _requireRatesNotInvalid(bool anyRateIsInvalid) internal pure {
          require(!anyRateIsInvalid, "A synth or SNX rate is invalid");
      }
  
      function _requireCanIssueOnBehalf(address issueForAddress, address from) internal view {
          require(delegateApprovals().canIssueFor(issueForAddress, from), "Not approved to act on behalf");
      }
  
      function _requireCanBurnOnBehalf(address burnForAddress, address from) internal view {
          require(delegateApprovals().canBurnFor(burnForAddress, from), "Not approved to act on behalf");
      }
  
      function _issueSynths(
          address from,
          uint amount,
          bool issueMax
      ) internal {
          if (_verifyCircuitBreakers()) {
              return;
          }
  
          (uint maxIssuable, , , bool anyRateIsInvalid) = _remainingIssuableSynths(from);
          _requireRatesNotInvalid(anyRateIsInvalid);
  
          if (!issueMax) {
              require(amount <= maxIssuable, "Amount too large");
          } else {
              amount = maxIssuable;
          }
  
          // Keep track of the debt they're about to create
          _addToDebtRegister(from, amount);
  
          // record issue timestamp
          _setLastIssueEvent(from);
  
          // Create their synths
          synths[sUSD].issue(from, amount);
  
          // Account for the issued debt in the cache
          debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amount));
      }
  
      function _burnSynths(
          address debtAccount,
          address burnAccount,
          uint amount,
          uint existingDebt
      ) internal returns (uint amountBurnt) {
          if (_verifyCircuitBreakers()) {
              return 0;
          }
  
          // liquidation requires sUSD to be already settled / not in waiting period
  
          // If they're trying to burn more debt than they actually owe, rather than fail the transaction, let's just
          // clear their debt and leave them be.
          amountBurnt = existingDebt < amount ? existingDebt : amount;
  
          // Remove liquidated debt from the ledger
          _removeFromDebtRegister(debtAccount, amountBurnt, existingDebt);
  
          // synth.burn does a safe subtraction on balance (so it will revert if there are not enough synths).
          synths[sUSD].burn(burnAccount, amountBurnt);
  
          // Account for the burnt debt in the cache.
          debtCache().updateCachedsUSDDebt(-SafeCast.toInt256(amountBurnt));
      }
  
      // If burning to target, `amount` is ignored, and the correct quantity of sUSD is burnt to reach the target
      // c-ratio, allowing fees to be claimed. In this case, pending settlements will be skipped as the user
      // will still have debt remaining after reaching their target.
      function _voluntaryBurnSynths(
          address from,
          uint amount,
          bool burnToTarget
      ) internal {
          if (_verifyCircuitBreakers()) {
              return;
          }
  
          if (!burnToTarget) {
              // If not burning to target, then burning requires that the minimum stake time has elapsed.
              require(_canBurnSynths(from), "Minimum stake time not reached");
              // First settle anything pending into sUSD as burning or issuing impacts the size of the debt pool
              (, uint refunded, uint numEntriesSettled) = exchanger().settle(from, sUSD);
              if (numEntriesSettled > 0) {
                  amount = exchanger().calculateAmountAfterSettlement(from, sUSD, amount, refunded);
              }
          }
  
          (uint existingDebt, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(from), sUSD);
          (uint maxIssuableSynthsForAccount, bool snxRateInvalid) = _maxIssuableSynths(from);
          _requireRatesNotInvalid(anyRateIsInvalid || snxRateInvalid);
          require(existingDebt > 0, "No debt to forgive");
  
          if (burnToTarget) {
              amount = existingDebt.sub(maxIssuableSynthsForAccount);
          }
  
          uint amountBurnt = _burnSynths(from, from, amount, existingDebt);
  
          // Check and remove liquidation if existingDebt after burning is <= maxIssuableSynths
          // Issuance ratio is fixed so should remove any liquidations
          if (existingDebt.sub(amountBurnt) <= maxIssuableSynthsForAccount) {
              liquidator().removeAccountInLiquidation(from);
          }
      }
  
      function _setLastIssueEvent(address account) internal {
          // Set the timestamp of the last issueSynths
          flexibleStorage().setUIntValue(
              CONTRACT_NAME,
              keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)),
              block.timestamp
          );
      }
  
      function _addToDebtRegister(address from, uint amount) internal {
          // important: this has to happen before any updates to user's debt shares
          liquidatorRewards().updateEntry(from);
  
          ISynthetixDebtShare sds = synthetixDebtShare();
  
          // it is possible (eg in tests, system initialized with extra debt) to have issued debt without any shares issued
          // in which case, the first account to mint gets the debt. yw.
          uint debtShares = _sharesForDebt(amount);
          if (debtShares == 0) {
              sds.mintShare(from, amount);
          } else {
              sds.mintShare(from, debtShares);
          }
      }
  
      function _removeFromDebtRegister(
          address from,
          uint debtToRemove,
          uint existingDebt
      ) internal {
          // important: this has to happen before any updates to user's debt shares
          liquidatorRewards().updateEntry(from);
  
          ISynthetixDebtShare sds = synthetixDebtShare();
  
          uint currentDebtShare = _debtShareBalanceOf(from);
  
          if (debtToRemove == existingDebt) {
              sds.burnShare(from, currentDebtShare);
          } else {
              uint sharesToRemove = _sharesForDebt(debtToRemove);
              sds.burnShare(from, sharesToRemove < currentDebtShare ? sharesToRemove : currentDebtShare);
          }
      }
  
      // trips the breaker and returns boolean, where true means the breaker has tripped state
      function _verifyCircuitBreakers() internal returns (bool) {
          address debtRatioAggregator = requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_DEBT_RATIO);
          (, int256 rawRatio, , , ) = AggregatorV2V3Interface(debtRatioAggregator).latestRoundData();
          (, bool broken, ) = exchangeRates().rateWithSafetyChecks(SNX);
  
          return circuitBreaker().probeCircuitBreaker(debtRatioAggregator, uint(rawRatio)) || broken;
      }
  
      /* ========== MODIFIERS ========== */
      modifier onlySynthetix() {
          require(msg.sender == address(synthetixERC20()), "Only Synthetix");
          _;
      }
  
      modifier onlyTrustedMinters() {
          address bridgeL1 = resolver.getAddress(CONTRACT_SYNTHETIXBRIDGETOOPTIMISM);
          address bridgeL2 = resolver.getAddress(CONTRACT_SYNTHETIXBRIDGETOBASE);
          address feePool = resolver.getAddress(CONTRACT_FEEPOOL);
          require(msg.sender == bridgeL1 || msg.sender == bridgeL2 || msg.sender == feePool, "only trusted minters");
          _;
      }
  
      modifier onlyTrustedMigrators() {
          address migratorL1 = resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM);
          address migratorL2 = resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM);
          require(msg.sender == migratorL1 || msg.sender == migratorL2, "only trusted migrators");
          require(migratorL1 == address(0) || migratorL2 == address(0), "one migrator must be 0x0");
          _;
      }
  
      function _onlySynthRedeemer() internal view {
          require(
              msg.sender == address(synthRedeemer()) || msg.sender == resolver.getAddress(CONTRACT_DYNAMICSYNTHREDEEMER),
              "Only SynthRedeemer"
          );
      }
  
      modifier onlySynthRedeemer() {
          _onlySynthRedeemer();
          _;
      }
  
      /* ========== EVENTS ========== */
  
      event SynthAdded(bytes32 currencyKey, address synth);
      event SynthRemoved(bytes32 currencyKey, address synth);
  }
  
  
  interface ISynthetixNamedContract {
      // solhint-disable func-name-mixedcase
      function CONTRACT_NAME() external view returns (bytes32);
  }
  
  // solhint-disable contract-name-camelcase
  contract Migration_Acrab is BaseMigration {
      // https://etherscan.io/address/0xEb3107117FEAd7de89Cd14D463D340A2E6917769;
      address public constant OWNER = 0xEb3107117FEAd7de89Cd14D463D340A2E6917769;
  
      // ----------------------------
      // EXISTING SYNTHETIX CONTRACTS
      // ----------------------------
  
      // https://etherscan.io/address/0x823bE81bbF96BEc0e25CA13170F5AaCb5B79ba83
      AddressResolver public constant addressresolver_i = AddressResolver(0x823bE81bbF96BEc0e25CA13170F5AaCb5B79ba83);
      // https://etherscan.io/address/0x696c905F8F8c006cA46e9808fE7e00049507798F
      SystemStatus public constant systemstatus_i = SystemStatus(0x696c905F8F8c006cA46e9808fE7e00049507798F);
      // https://etherscan.io/address/0xd1cad1A569E70d2Df4C8ed43d3cC93DBE16285dE
      DebtCache public constant debtcache_i = DebtCache(0xd1cad1A569E70d2Df4C8ed43d3cC93DBE16285dE);
      // https://etherscan.io/address/0xab4688E54A216aB3813438D30603Bf855648AF8d
      Issuer public constant issuer_i = Issuer(0xab4688E54A216aB3813438D30603Bf855648AF8d);
  
      // ----------------------------------
      // NEW CONTRACTS DEPLOYED TO BE ADDED
      // ----------------------------------
  
      // https://etherscan.io/address/0xd1cad1A569E70d2Df4C8ed43d3cC93DBE16285dE
      address public constant new_DebtCache_contract = 0xd1cad1A569E70d2Df4C8ed43d3cC93DBE16285dE;
      // https://etherscan.io/address/0xab4688E54A216aB3813438D30603Bf855648AF8d
      address public constant new_Issuer_contract = 0xab4688E54A216aB3813438D30603Bf855648AF8d;
      // https://etherscan.io/address/0x38c8A4d93757D3D2E0110Cfb5f18B9cC293e0fdA
      address public constant new_DynamicSynthRedeemer_contract = 0x38c8A4d93757D3D2E0110Cfb5f18B9cC293e0fdA;
  
      constructor() public BaseMigration(OWNER) {}
  
      function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
          contracts = new address[](4);
          contracts[0] = address(addressresolver_i);
          contracts[1] = address(systemstatus_i);
          contracts[2] = address(debtcache_i);
          contracts[3] = address(issuer_i);
      }
  
      function migrate() external onlyOwner {
          // ACCEPT OWNERSHIP for all contracts that require ownership to make changes
          acceptAll();
  
          // MIGRATION
          // Import all new contracts into the address resolver;
          addressresolver_importAddresses_0();
          // Rebuild the resolver caches in all MixinResolver contracts - batch 1;
          addressresolver_rebuildCaches_1();
          // Rebuild the resolver caches in all MixinResolver contracts - batch 2;
          addressresolver_rebuildCaches_2();
          // Ensure Issuer contract can suspend issuance - see SIP-165;
          systemstatus_i.updateAccessControl("Issuance", new_Issuer_contract, true, false);
          // Import excluded-debt records from existing DebtCache;
          debtcache_i.importExcludedIssuedDebts(
              IDebtCache(0x1620Aa736939597891C1940CF0d28b82566F9390),
              IIssuer(0xca68a3D663483515a9D434E854AB59A41b3A523c)
          );
          // Add synths to the Issuer contract - batch 1;
          issuer_addSynths_8();
  
          // NOMINATE OWNERSHIP back to owner for aforementioned contracts
          nominateAll();
      }
  
      function acceptAll() internal {
          address[] memory contracts = contractsRequiringOwnership();
          for (uint i = 0; i < contracts.length; i++) {
              Owned(contracts[i]).acceptOwnership();
          }
      }
  
      function nominateAll() internal {
          address[] memory contracts = contractsRequiringOwnership();
          for (uint i = 0; i < contracts.length; i++) {
              returnOwnership(contracts[i]);
          }
      }
  
      function addressresolver_importAddresses_0() internal {
          bytes32[] memory addressresolver_importAddresses_names_0_0 = new bytes32[](3);
          addressresolver_importAddresses_names_0_0[0] = bytes32("DebtCache");
          addressresolver_importAddresses_names_0_0[1] = bytes32("Issuer");
          addressresolver_importAddresses_names_0_0[2] = bytes32("DynamicSynthRedeemer");
          address[] memory addressresolver_importAddresses_destinations_0_1 = new address[](3);
          addressresolver_importAddresses_destinations_0_1[0] = address(new_DebtCache_contract);
          addressresolver_importAddresses_destinations_0_1[1] = address(new_Issuer_contract);
          addressresolver_importAddresses_destinations_0_1[2] = address(new_DynamicSynthRedeemer_contract);
          addressresolver_i.importAddresses(
              addressresolver_importAddresses_names_0_0,
              addressresolver_importAddresses_destinations_0_1
          );
      }
  
      function addressresolver_rebuildCaches_1() internal {
          MixinResolver[] memory addressresolver_rebuildCaches_destinations_1_0 = new MixinResolver[](20);
          addressresolver_rebuildCaches_destinations_1_0[0] = MixinResolver(0xaeA0065E146FD75Dc24465961a583827284D405a);
          addressresolver_rebuildCaches_destinations_1_0[1] = MixinResolver(new_Issuer_contract);
          addressresolver_rebuildCaches_destinations_1_0[2] = MixinResolver(new_DebtCache_contract);
          addressresolver_rebuildCaches_destinations_1_0[3] = MixinResolver(0xd711709eFc452152B7ad11DbD01ed4B69c9421B3);
          addressresolver_rebuildCaches_destinations_1_0[4] = MixinResolver(0xFAd53Cc9480634563E8ec71E8e693Ffd07981d38);
          addressresolver_rebuildCaches_destinations_1_0[5] = MixinResolver(0x89FCb32F29e509cc42d0C8b6f058C993013A843F);
          addressresolver_rebuildCaches_destinations_1_0[6] = MixinResolver(0x8e9757479D5ad4E7f9d951B60d39F5220b893d6c);
          addressresolver_rebuildCaches_destinations_1_0[7] = MixinResolver(0xf79603a71144e415730C1A6f57F366E4Ea962C00);
          addressresolver_rebuildCaches_destinations_1_0[8] = MixinResolver(0x83105D7CDd2fd9b8185BFF1cb56bB1595a618618);
          addressresolver_rebuildCaches_destinations_1_0[9] = MixinResolver(0x9f231dBE53D460f359B2B8CC47574493caA5B7Bf);
          addressresolver_rebuildCaches_destinations_1_0[10] = MixinResolver(0xeAcaEd9581294b1b5cfb6B941d4B8B81B2005437);
          addressresolver_rebuildCaches_destinations_1_0[11] = MixinResolver(0x39Ea01a0298C315d149a490E34B59Dbf2EC7e48F);
          addressresolver_rebuildCaches_destinations_1_0[12] = MixinResolver(0x94f864e55c77E07C2C7BF7bFBc334b7a8123442A);
          addressresolver_rebuildCaches_destinations_1_0[13] = MixinResolver(0xe533139Af961c9747356D947838c98451015e234);
          addressresolver_rebuildCaches_destinations_1_0[14] = MixinResolver(new_DynamicSynthRedeemer_contract);
          addressresolver_rebuildCaches_destinations_1_0[15] = MixinResolver(0x10A5F7D9D65bCc2734763444D4940a31b109275f);
          addressresolver_rebuildCaches_destinations_1_0[16] = MixinResolver(0xa8E31E3C38aDD6052A9407298FAEB8fD393A6cF9);
          addressresolver_rebuildCaches_destinations_1_0[17] = MixinResolver(0xE1cc2332852B2Ac0dA59A1f9D3051829f4eF3c1C);
          addressresolver_rebuildCaches_destinations_1_0[18] = MixinResolver(0xfb020CA7f4e8C4a5bBBe060f59a249c6275d2b69);
          addressresolver_rebuildCaches_destinations_1_0[19] = MixinResolver(0xdc883b9d9Ee16f74bE08826E68dF4C9D9d26e8bD);
          addressresolver_i.rebuildCaches(addressresolver_rebuildCaches_destinations_1_0);
      }
  
      function addressresolver_rebuildCaches_2() internal {
          MixinResolver[] memory addressresolver_rebuildCaches_destinations_2_0 = new MixinResolver[](7);
          addressresolver_rebuildCaches_destinations_2_0[0] = MixinResolver(0xBb5b03E920cF702De5A3bA9Fc1445aF4B3919c88);
          addressresolver_rebuildCaches_destinations_2_0[1] = MixinResolver(0xdAe6C79c46aB3B280Ca28259000695529cbD1339);
          addressresolver_rebuildCaches_destinations_2_0[2] = MixinResolver(0x1cB004a8e84a5CE95C1fF895EE603BaC8EC506c7);
          addressresolver_rebuildCaches_destinations_2_0[3] = MixinResolver(0x5D4C724BFe3a228Ff0E29125Ac1571FE093700a4);
          addressresolver_rebuildCaches_destinations_2_0[4] = MixinResolver(0x07C1E81C345A7c58d7c24072EFc5D929BD0647AD);
          addressresolver_rebuildCaches_destinations_2_0[5] = MixinResolver(0xC1AAE9d18bBe386B102435a8632C8063d31e747C);
          addressresolver_rebuildCaches_destinations_2_0[6] = MixinResolver(0x067e398605E84F2D0aEEC1806e62768C5110DCc6);
          addressresolver_i.rebuildCaches(addressresolver_rebuildCaches_destinations_2_0);
      }
  
      function issuer_addSynths_8() internal {
          ISynth[] memory issuer_addSynths_synthsToAdd_8_0 = new ISynth[](10);
          issuer_addSynths_synthsToAdd_8_0[0] = ISynth(0x10A5F7D9D65bCc2734763444D4940a31b109275f);
          issuer_addSynths_synthsToAdd_8_0[1] = ISynth(0xa8E31E3C38aDD6052A9407298FAEB8fD393A6cF9);
          issuer_addSynths_synthsToAdd_8_0[2] = ISynth(0xE1cc2332852B2Ac0dA59A1f9D3051829f4eF3c1C);
          issuer_addSynths_synthsToAdd_8_0[3] = ISynth(0xfb020CA7f4e8C4a5bBBe060f59a249c6275d2b69);
          issuer_addSynths_synthsToAdd_8_0[4] = ISynth(0xdc883b9d9Ee16f74bE08826E68dF4C9D9d26e8bD);
          issuer_addSynths_synthsToAdd_8_0[5] = ISynth(0xBb5b03E920cF702De5A3bA9Fc1445aF4B3919c88);
          issuer_addSynths_synthsToAdd_8_0[6] = ISynth(0xdAe6C79c46aB3B280Ca28259000695529cbD1339);
          issuer_addSynths_synthsToAdd_8_0[7] = ISynth(0x1cB004a8e84a5CE95C1fF895EE603BaC8EC506c7);
          issuer_addSynths_synthsToAdd_8_0[8] = ISynth(0x5D4C724BFe3a228Ff0E29125Ac1571FE093700a4);
          issuer_addSynths_synthsToAdd_8_0[9] = ISynth(0x07C1E81C345A7c58d7c24072EFc5D929BD0647AD);
          issuer_i.addSynths(issuer_addSynths_synthsToAdd_8_0);
      }
  }
  
      