ETH Price: $2,442.58 (+4.26%)

Transaction Decoder

Block:
11514569 at Dec-24-2020 06:04:11 AM +UTC
Transaction Fee:
0.00218859732 ETH $5.35
Gas Used:
29,692 Gas / 73.71 Gwei

Account State Difference:

  Address   Before After State Difference Code
(2Miners: PPLNS)
238.257000358544314272 Eth238.259188955864314272 Eth0.00218859732
0x62f7A1F9...7d452565B
7.91780534 Eth
Nonce: 1
7.91561674268 Eth
Nonce: 2
0.00218859732

Execution Trace

CollateralErc20.open( collateral=100000000, amount=500000000000000000000, currency=7355534400000000000000000000000000000000000000000000000000000000 )
  • RenBTC.dd62ed3e( )
    • RenERC20LogicV1.allowance( owner=0x62f7A1F94aba23eD2dD108F8D23Aa3e7d452565B, spender=0x3B3812BB9f6151bEb6fa10783F1ae848a77a0d46 ) => ( 0 )
      File 1 of 3: CollateralErc20
      /*
         ____            __   __        __   _
        / __/__ __ ___  / /_ / /  ___  / /_ (_)__ __
       _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
      /___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
           /___/
      
      * Synthetix: CollateralErc20.sol
      *
      * Latest source (may be newer): https://github.com/Synthetixio/synthetix/blob/master/contracts/CollateralErc20.sol
      * Docs: https://docs.synthetix.io/contracts/CollateralErc20
      *
      * Contract Dependencies: 
      *	- Collateral
      *	- IAddressResolver
      *	- ICollateralErc20
      *	- ICollateralLoan
      *	- MixinResolver
      *	- MixinSystemSettings
      *	- Owned
      *	- State
      * Libraries: 
      *	- SafeDecimalMath
      *	- SafeMath
      *
      * MIT License
      * ===========
      *
      * Copyright (c) 2020 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);
      }
      
      
      // 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 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 excludeEtherCollateral) external view returns (uint);
      
          function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
              external
              view
              returns (uint transferable, bool anyRateIsInvalid);
      
          // Restricted: used internally to Synthetix
          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 liquidateDelinquentAccount(
              address account,
              uint susdAmount,
              address liquidator
          ) external returns (uint totalRedeemed, uint amountToLiquidate);
      }
      
      
      // 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);
      }
      
      
      // solhint-disable payable-fallback
      
      // https://docs.synthetix.io/contracts/source/contracts/readproxy
      contract ReadProxy is Owned {
          address public target;
      
          constructor(address _owner) public Owned(_owner) {}
      
          function setTarget(address _target) external onlyOwner {
              target = _target;
              emit TargetUpdated(target);
          }
      
          function() external {
              // The basics of a proxy read call
              // Note that msg.sender in the underlying will always be the address of this contract.
              assembly {
                  calldatacopy(0, 0, calldatasize)
      
                  // Use of staticcall - this will revert if the underlying function mutates state
                  let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0)
                  returndatacopy(0, 0, returndatasize)
      
                  if iszero(result) {
                      revert(0, returndatasize)
                  }
                  return(0, returndatasize)
              }
          }
      
          event TargetUpdated(address newTarget);
      }
      
      
      // Inheritance
      
      
      // Internal references
      
      
      // https://docs.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);
      }
      
      
      // 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 {
          bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings";
      
          bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs";
          bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor";
          bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio";
          bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration";
          bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold";
          bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay";
          bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio";
          bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
          bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
          bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
          bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime";
          bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags";
          bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled";
          bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime";
          bytes32 internal constant SETTING_CROSS_DOMAIN_MESSAGE_GAS_LIMIT = "crossDomainMessageGasLimit";
      
          bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";
      
          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 getCrossDomainMessageGasLimit() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_CROSS_DOMAIN_MESSAGE_GAS_LIMIT);
          }
      
          function getTradingRewardsEnabled() internal view returns (bool) {
              return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED);
          }
      
          function getWaitingPeriodSecs() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS);
          }
      
          function getPriceDeviationThresholdFactor() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR);
          }
      
          function getIssuanceRatio() internal view returns (uint) {
              // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO);
          }
      
          function getFeePeriodDuration() internal view returns (uint) {
              // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION);
          }
      
          function getTargetThreshold() internal view returns (uint) {
              // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD);
          }
      
          function getLiquidationDelay() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY);
          }
      
          function getLiquidationRatio() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO);
          }
      
          function getLiquidationPenalty() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
          }
      
          function getRateStalePeriod() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
          }
      
          function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
              return
                  flexibleStorage().getUIntValue(
                      SETTING_CONTRACT_NAME,
                      keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
                  );
          }
      
          function getMinimumStakeTime() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME);
          }
      
          function getAggregatorWarningFlags() internal view returns (address) {
              return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS);
          }
      
          function getDebtSnapshotStaleTime() internal view returns (uint) {
              return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME);
          }
      }
      
      
      pragma experimental ABIEncoderV2;
      
      interface ICollateralLoan {
          struct Loan {
              // ID for the loan
              uint id;
              //  Acccount that created the loan
              address payable account;
              //  Amount of collateral deposited
              uint collateral;
              // The synth that was borowed
              bytes32 currency;
              //  Amount of synths borrowed
              uint amount;
              // Indicates if the position was short sold
              bool short;
              // interest amounts accrued
              uint accruedInterest;
              // last interest index
              uint interestIndex;
              // time of last interaction.
              uint lastInteraction;
          }
      }
      
      
      /**
       * @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;
          }
      }
      
      
      // Inheritance
      
      
      // https://docs.synthetix.io/contracts/source/contracts/state
      contract State is Owned {
          // the address of the contract that can modify variables
          // this can only be changed by the owner of this contract
          address public associatedContract;
      
          constructor(address _associatedContract) internal {
              // This contract is abstract, and thus cannot be instantiated directly
              require(owner != address(0), "Owner must be set");
      
              associatedContract = _associatedContract;
              emit AssociatedContractUpdated(_associatedContract);
          }
      
          /* ========== SETTERS ========== */
      
          // Change the associated contract to a new address
          function setAssociatedContract(address _associatedContract) external onlyOwner {
              associatedContract = _associatedContract;
              emit AssociatedContractUpdated(_associatedContract);
          }
      
          /* ========== MODIFIERS ========== */
      
          modifier onlyAssociatedContract {
              require(msg.sender == associatedContract, "Only the associated contract can perform this action");
              _;
          }
      
          /* ========== EVENTS ========== */
      
          event AssociatedContractUpdated(address associatedContract);
      }
      
      
      // Inheritance
      
      
      // Libraries
      
      
      contract CollateralState is Owned, State, ICollateralLoan {
          using SafeMath for uint;
          using SafeDecimalMath for uint;
      
          mapping(address => Loan[]) public loans;
      
          constructor(address _owner, address _associatedContract) public Owned(_owner) State(_associatedContract) {}
      
          /* ========== VIEWS ========== */
          // If we do not find the loan, this returns a struct with 0'd values.
          function getLoan(address account, uint256 loanID) external view returns (Loan memory) {
              Loan[] memory accountLoans = loans[account];
              for (uint i = 0; i < accountLoans.length; i++) {
                  if (accountLoans[i].id == loanID) {
                      return (accountLoans[i]);
                  }
              }
          }
      
          function getNumLoans(address account) external view returns (uint numLoans) {
              return loans[account].length;
          }
      
          /* ========== MUTATIVE FUNCTIONS ========== */
      
          function createLoan(Loan memory loan) public onlyAssociatedContract {
              loans[loan.account].push(loan);
          }
      
          function updateLoan(Loan memory loan) public onlyAssociatedContract {
              Loan[] storage accountLoans = loans[loan.account];
              for (uint i = 0; i < accountLoans.length; i++) {
                  if (accountLoans[i].id == loan.id) {
                      loans[loan.account][i] = loan;
                  }
              }
          }
      }
      
      
      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[2][] calldata requiredSynthAndInverseNamesInResolver, bytes32[] calldata synthKeys)
              external;
      
          function removeShortableSynths(bytes32[] calldata synths) external;
      
          // State mutative
          function updateBorrowRates(uint rate) external;
      
          function updateShortRates(bytes32 currency, uint rate) external;
      
          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;
      }
      
      
      // 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 requireIssuanceActive() external view;
      
          function requireExchangeActive() external view;
      
          function requireSynthActive(bytes32 currencyKey) external view;
      
          function requireSynthsActive(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
      
          function synthSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
      
          // Restricted functions
          function suspendSynth(bytes32 currencyKey, uint256 reason) external;
      
          function updateAccessControl(
              bytes32 section,
              address account,
              bool canSuspend,
              bool canResume
          ) external;
      }
      
      
      // https://docs.synthetix.io/contracts/source/interfaces/ifeepool
      interface IFeePool {
          // Views
      
          // solhint-disable-next-line func-name-mixedcase
          function FEE_ADDRESS() external view returns (address);
      
          function feesAvailable(address account) external view returns (uint, uint);
      
          function feePeriodDuration() external view returns (uint);
      
          function isFeesClaimable(address account) external view returns (bool);
      
          function targetThreshold() external view returns (uint);
      
          function totalFeesAvailable() external view returns (uint);
      
          function totalRewardsAvailable() external view returns (uint);
      
          // Mutative Functions
          function claimFees() external returns (bool);
      
          function claimOnBehalf(address claimingForAddress) external returns (bool);
      
          function closeCurrentFeePeriod() external;
      
          // Restricted: used internally to Synthetix
          function appendAccountIssuanceRecord(
              address account,
              uint lockedAmount,
              uint debtEntryIndex
          ) external;
      
          function recordFeePaid(uint sUSDAmount) external;
      
          function setRewardsToDistribute(uint amount) external;
      }
      
      
      // 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);
      }
      
      
      // https://docs.synthetix.io/contracts/source/interfaces/iexchangerates
      interface IExchangeRates {
          // Structs
          struct RateAndUpdatedTime {
              uint216 rate;
              uint40 time;
          }
      
          struct InversePricing {
              uint entryPoint;
              uint upperLimit;
              uint lowerLimit;
              bool frozenAtUpperLimit;
              bool frozenAtLowerLimit;
          }
      
          // Views
          function aggregators(bytes32 currencyKey) external view returns (address);
      
          function aggregatorWarningFlags() external view returns (address);
      
          function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
      
          function canFreezeRate(bytes32 currencyKey) external view returns (bool);
      
          function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
      
          function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
      
          function effectiveValue(
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey
          ) external view returns (uint value);
      
          function effectiveValueAndRates(
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey
          )
              external
              view
              returns (
                  uint value,
                  uint sourceRate,
                  uint destinationRate
              );
      
          function effectiveValueAtRound(
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey,
              uint roundIdForSrc,
              uint roundIdForDest
          ) external view returns (uint value);
      
          function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
      
          function getLastRoundIdBeforeElapsedSecs(
              bytes32 currencyKey,
              uint startingRoundId,
              uint startingTimestamp,
              uint timediff
          ) external view returns (uint);
      
          function inversePricing(bytes32 currencyKey)
              external
              view
              returns (
                  uint entryPoint,
                  uint upperLimit,
                  uint lowerLimit,
                  bool frozenAtUpperLimit,
                  bool frozenAtLowerLimit
              );
      
          function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
      
          function oracle() external view returns (address);
      
          function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
      
          function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);
      
          function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid);
      
          function rateForCurrency(bytes32 currencyKey) external view returns (uint);
      
          function rateIsFlagged(bytes32 currencyKey) external view returns (bool);
      
          function rateIsFrozen(bytes32 currencyKey) external view returns (bool);
      
          function rateIsInvalid(bytes32 currencyKey) external view returns (bool);
      
          function rateIsStale(bytes32 currencyKey) external view returns (bool);
      
          function rateStalePeriod() external view returns (uint);
      
          function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
              external
              view
              returns (uint[] memory rates, uint[] memory times);
      
          function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
              external
              view
              returns (uint[] memory rates, bool anyRateInvalid);
      
          function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
      
          // Mutative functions
          function freezeRate(bytes32 currencyKey) external;
      }
      
      
      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;
      }
      
      
      // https://docs.synthetix.io/contracts/source/interfaces/iexchanger
      interface IExchanger {
          // 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 exchangeFeeRate);
      
          function getAmountsForExchange(
              uint sourceAmount,
              bytes32 sourceCurrencyKey,
              bytes32 destinationCurrencyKey
          )
              external
              view
              returns (
                  uint amountReceived,
                  uint fee,
                  uint exchangeFeeRate
              );
      
          function priceDeviationThresholdFactor() external view returns (uint);
      
          function waitingPeriodSecs() external view returns (uint);
      
          // Mutative functions
          function exchange(
              address from,
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey,
              address destinationAddress
          ) external returns (uint amountReceived);
      
          function exchangeOnBehalf(
              address exchangeForAddress,
              address from,
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey
          ) external returns (uint amountReceived);
      
          function exchangeWithTracking(
              address from,
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey,
              address destinationAddress,
              address originator,
              bytes32 trackingCode
          ) external returns (uint amountReceived);
      
          function exchangeOnBehalfWithTracking(
              address exchangeForAddress,
              address from,
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey,
              address originator,
              bytes32 trackingCode
          ) external returns (uint amountReceived);
      
          function exchangeWithVirtual(
              address from,
              bytes32 sourceCurrencyKey,
              uint sourceAmount,
              bytes32 destinationCurrencyKey,
              address destinationAddress,
              bytes32 trackingCode
          ) external returns (uint amountReceived, IVirtualSynth vSynth);
      
          function settle(address from, bytes32 currencyKey)
              external
              returns (
                  uint reclaimed,
                  uint refunded,
                  uint numEntries
              );
      
          function setLastExchangeRateForSynth(bytes32 currencyKey, uint rate) external;
      
          function suspendSynthWithInvalidRate(bytes32 currencyKey) external;
      }
      
      
      // https://docs.synthetix.io/contracts/source/interfaces/istakingrewards
      interface IShortingRewards {
          // Views
          function lastTimeRewardApplicable() external view returns (uint256);
      
          function rewardPerToken() external view returns (uint256);
      
          function earned(address account) external view returns (uint256);
      
          function getRewardForDuration() external view returns (uint256);
      
          function totalSupply() external view returns (uint256);
      
          function balanceOf(address account) external view returns (uint256);
      
          // Mutative
      
          function enrol(address account, uint256 amount) external;
      
          function withdraw(address account, uint256 amount) external;
      
          function getReward(address account) external;
      
          function exit(address account) external;
      }
      
      
      // Inheritance
      
      
      // Libraries
      
      
      // Internal references
      
      
      contract Collateral is ICollateralLoan, Owned, MixinSystemSettings {
          /* ========== LIBRARIES ========== */
          using SafeMath for uint;
          using SafeDecimalMath for uint;
      
          /* ========== CONSTANTS ========== */
      
          bytes32 private constant sUSD = "sUSD";
      
          // ========== STATE VARIABLES ==========
      
          // The synth corresponding to the collateral.
          bytes32 public collateralKey;
      
          // Stores loans
          CollateralState public state;
      
          address public manager;
      
          // The synths that this contract can issue.
          bytes32[] public synths;
      
          // Map from currency key to synth contract name.
          mapping(bytes32 => bytes32) public synthsByKey;
      
          // Map from currency key to the shorting rewards contract
          mapping(bytes32 => address) public shortingRewards;
      
          // ========== SETTER STATE VARIABLES ==========
      
          // The minimum collateral ratio required to avoid liquidation.
          uint public minCratio;
      
          // The minimum amount of collateral to create a loan.
          uint public minCollateral;
      
          // The fee charged for issuing a loan.
          uint public issueFeeRate;
      
          // The maximum number of loans that an account can create with this collateral.
          uint public maxLoansPerAccount = 50;
      
          // Time in seconds that a user must wait between interacting with a loan.
          // Provides front running and flash loan protection.
          uint public interactionDelay = 300;
      
          bool public canOpenLoans = true;
      
          /* ========== ADDRESS RESOLVER CONFIGURATION ========== */
      
          bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
          bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
          bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
          bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
          bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
      
          /* ========== CONSTRUCTOR ========== */
      
          constructor(
              CollateralState _state,
              address _owner,
              address _manager,
              address _resolver,
              bytes32 _collateralKey,
              uint _minCratio,
              uint _minCollateral
          ) public Owned(_owner) MixinSystemSettings(_resolver) {
              manager = _manager;
              state = _state;
              collateralKey = _collateralKey;
              minCratio = _minCratio;
              minCollateral = _minCollateral;
          }
      
          /* ========== VIEWS ========== */
      
          function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
              bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
              bytes32[] memory newAddresses = new bytes32[](5);
              newAddresses[0] = CONTRACT_FEEPOOL;
              newAddresses[1] = CONTRACT_EXRATES;
              newAddresses[2] = CONTRACT_EXCHANGER;
              newAddresses[3] = CONTRACT_SYSTEMSTATUS;
              newAddresses[4] = CONTRACT_SYNTHSUSD;
      
              bytes32[] memory combined = combineArrays(existingAddresses, newAddresses);
      
              addresses = combineArrays(combined, synths);
          }
      
          /* ---------- Related Contracts ---------- */
      
          function _systemStatus() internal view returns (ISystemStatus) {
              return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
          }
      
          function _synth(bytes32 synthName) internal view returns (ISynth) {
              return ISynth(requireAndGetAddress(synthName));
          }
      
          function _synthsUSD() internal view returns (ISynth) {
              return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD));
          }
      
          function _exchangeRates() internal view returns (IExchangeRates) {
              return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
          }
      
          function _exchanger() internal view returns (IExchanger) {
              return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
          }
      
          function _feePool() internal view returns (IFeePool) {
              return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
          }
      
          function _manager() internal view returns (ICollateralManager) {
              return ICollateralManager(manager);
          }
      
          /* ---------- Public Views ---------- */
      
          function collateralRatio(Loan memory loan) public view returns (uint cratio) {
              uint cvalue = _exchangeRates().effectiveValue(collateralKey, loan.collateral, sUSD);
              uint dvalue = _exchangeRates().effectiveValue(loan.currency, loan.amount.add(loan.accruedInterest), sUSD);
              cratio = cvalue.divideDecimal(dvalue);
          }
      
          // The maximum number of synths issuable for this amount of collateral
          function maxLoan(uint amount, bytes32 currency) public view returns (uint max) {
              max = issuanceRatio().multiplyDecimal(_exchangeRates().effectiveValue(collateralKey, amount, currency));
          }
      
          /**
           * r = target issuance ratio
           * D = debt value in sUSD
           * V = Collateral VALUE in sUSD
           * P = liquidation penalty
           * Calculates amount of synths = (D - V * r) / (1 - (1 + P) * r)
           * Note: if you pass a loan in here that is not eligible for liquidation it will revert.
           * We check the ratio first in liquidateInternal and only pass eligible loans in.
           */
          function liquidationAmount(Loan memory loan) public view returns (uint amount) {
              uint liquidationPenalty = getLiquidationPenalty();
              uint debtValue = loan.amount.add(loan.accruedInterest).multiplyDecimal(
                  _exchangeRates().rateForCurrency(loan.currency)
              );
              uint collateralValue = loan.collateral.multiplyDecimal(_exchangeRates().rateForCurrency(collateralKey));
              uint unit = SafeDecimalMath.unit();
      
              uint dividend = debtValue.sub(collateralValue.divideDecimal(minCratio));
              uint divisor = unit.sub(unit.add(liquidationPenalty).divideDecimal(minCratio));
      
              return dividend.divideDecimal(divisor);
          }
      
          // amount is the amount of synths we are liquidating
          function collateralRedeemed(bytes32 currency, uint amount) public view returns (uint collateral) {
              uint liquidationPenalty = getLiquidationPenalty();
              collateral = _exchangeRates().effectiveValue(currency, amount, collateralKey);
      
              collateral = collateral.multiplyDecimal(SafeDecimalMath.unit().add(liquidationPenalty));
          }
      
          function areSynthsAndCurrenciesSet(bytes32[] calldata _synthNamesInResolver, bytes32[] calldata _synthKeys)
              external
              view
              returns (bool)
          {
              if (synths.length != _synthNamesInResolver.length) {
                  return false;
              }
      
              for (uint i = 0; i < _synthNamesInResolver.length; i++) {
                  bytes32 synthName = _synthNamesInResolver[i];
                  if (synths[i] != synthName) {
                      return false;
                  }
                  if (synthsByKey[_synthKeys[i]] != synths[i]) {
                      return false;
                  }
              }
      
              return true;
          }
      
          /* ---------- UTILITIES ---------- */
      
          // Check the account has enough of the synth to make the payment
          function _checkSynthBalance(
              address payer,
              bytes32 key,
              uint amount
          ) internal view {
              require(IERC20(address(_synth(synthsByKey[key]))).balanceOf(payer) >= amount, "Not enough synth balance");
          }
      
          // We set the interest index to 0 to indicate the loan has been closed.
          function _checkLoanAvailable(Loan memory _loan) internal view {
              require(_loan.interestIndex > 0, "Loan does not exist");
              require(_loan.lastInteraction.add(interactionDelay) <= block.timestamp, "Loan recently interacted with");
          }
      
          function issuanceRatio() internal view returns (uint ratio) {
              ratio = SafeDecimalMath.unit().divideDecimalRound(minCratio);
          }
      
          /* ========== MUTATIVE FUNCTIONS ========== */
      
          /* ---------- Synths ---------- */
      
          function addSynths(bytes32[] calldata _synthNamesInResolver, bytes32[] calldata _synthKeys) external onlyOwner {
              require(_synthNamesInResolver.length == _synthKeys.length, "Input array length mismatch");
      
              for (uint i = 0; i < _synthNamesInResolver.length; i++) {
                  bytes32 synthName = _synthNamesInResolver[i];
                  synths.push(synthName);
                  synthsByKey[_synthKeys[i]] = synthName;
              }
      
              // ensure cache has the latest
              rebuildCache();
          }
      
          /* ---------- Rewards Contracts ---------- */
      
          function addRewardsContracts(address rewardsContract, bytes32 synth) external onlyOwner {
              shortingRewards[synth] = rewardsContract;
          }
      
          /* ---------- SETTERS ---------- */
      
          function setMinCratio(uint _minCratio) external onlyOwner {
              require(_minCratio > SafeDecimalMath.unit(), "Must be greater than 1");
              minCratio = _minCratio;
              emit MinCratioRatioUpdated(minCratio);
          }
      
          function setIssueFeeRate(uint _issueFeeRate) external onlyOwner {
              issueFeeRate = _issueFeeRate;
              emit IssueFeeRateUpdated(issueFeeRate);
          }
      
          function setInteractionDelay(uint _interactionDelay) external onlyOwner {
              require(_interactionDelay <= SafeDecimalMath.unit() * 3600, "Max 1 hour");
              interactionDelay = _interactionDelay;
              emit InteractionDelayUpdated(interactionDelay);
          }
      
          function setManager(address _newManager) external onlyOwner {
              manager = _newManager;
              emit ManagerUpdated(manager);
          }
      
          function setCanOpenLoans(bool _canOpenLoans) external onlyOwner {
              canOpenLoans = _canOpenLoans;
              emit CanOpenLoansUpdated(canOpenLoans);
          }
      
          /* ---------- LOAN INTERACTIONS ---------- */
      
          function openInternal(
              uint collateral,
              uint amount,
              bytes32 currency,
              bool short
          ) internal returns (uint id) {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              require(canOpenLoans, "Opening is disabled");
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. We can only issue certain synths.
              require(synthsByKey[currency] > 0, "Not allowed to issue this synth");
      
              // 3. Make sure the synth rate is not invalid.
              require(!_exchangeRates().rateIsInvalid(currency), "Currency rate is invalid");
      
              // 4. Collateral >= minimum collateral size.
              require(collateral >= minCollateral, "Not enough collateral to open");
      
              // 5. Cap the number of loans so that the array doesn't get too big.
              require(state.getNumLoans(msg.sender) < maxLoansPerAccount, "Max loans exceeded");
      
              // 6. Check we haven't hit the debt cap for non snx collateral.
              (bool canIssue, bool anyRateIsInvalid) = _manager().exceedsDebtLimit(amount, currency);
      
              require(canIssue && !anyRateIsInvalid, "Debt limit or invalid rate");
      
              // 7. Require requested loan < max loan
              require(amount <= maxLoan(collateral, currency), "Exceeds max borrowing power");
      
              // 8. This fee is denominated in the currency of the loan
              uint issueFee = amount.multiplyDecimalRound(issueFeeRate);
      
              // 9. Calculate the minting fee and subtract it from the loan amount
              uint loanAmountMinusFee = amount.sub(issueFee);
      
              // 10. Get a Loan ID
              id = _manager().getNewLoanId();
      
              // 11. Create the loan struct.
              Loan memory loan = Loan({
                  id: id,
                  account: msg.sender,
                  collateral: collateral,
                  currency: currency,
                  amount: amount,
                  short: short,
                  accruedInterest: 0,
                  interestIndex: 0,
                  lastInteraction: block.timestamp
              });
      
              // 12. Accrue interest on the loan.
              loan = accrueInterest(loan);
      
              // 13. Save the loan to storage
              state.createLoan(loan);
      
              // 14. Pay the minting fees to the fee pool
              _payFees(issueFee, currency);
      
              // 15. If its short, convert back to sUSD, otherwise issue the loan.
              if (short) {
                  _synthsUSD().issue(msg.sender, _exchangeRates().effectiveValue(currency, loanAmountMinusFee, sUSD));
                  _manager().incrementShorts(currency, amount);
      
                  if (shortingRewards[currency] != address(0)) {
                      IShortingRewards(shortingRewards[currency]).enrol(msg.sender, amount);
                  }
              } else {
                  _synth(synthsByKey[currency]).issue(msg.sender, loanAmountMinusFee);
                  _manager().incrementLongs(currency, amount);
              }
      
              // 16. Emit event
              emit LoanCreated(msg.sender, id, amount, collateral, currency, issueFee);
          }
      
          function closeInternal(address borrower, uint id) internal returns (uint collateral) {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. Get the loan.
              Loan memory loan = state.getLoan(borrower, id);
      
              // 3. Check loan is open and the last interaction time.
              _checkLoanAvailable(loan);
      
              // 4. Accrue interest on the loan.
              loan = accrueInterest(loan);
      
              // 5. Work out the total amount owing on the loan.
              uint total = loan.amount.add(loan.accruedInterest);
      
              // 6. Check they have enough balance to close the loan.
              _checkSynthBalance(loan.account, loan.currency, total);
      
              // 7. Burn the synths
              require(
                  !_exchanger().hasWaitingPeriodOrSettlementOwing(borrower, loan.currency),
                  "Waiting secs or settlement owing"
              );
              _synth(synthsByKey[loan.currency]).burn(borrower, total);
      
              // 8. Tell the manager.
              if (loan.short) {
                  _manager().decrementShorts(loan.currency, loan.amount);
      
                  if (shortingRewards[loan.currency] != address(0)) {
                      IShortingRewards(shortingRewards[loan.currency]).withdraw(borrower, loan.amount);
                  }
              } else {
                  _manager().decrementLongs(loan.currency, loan.amount);
              }
      
              // 9. Assign the collateral to be returned.
              collateral = loan.collateral;
      
              // 10. Pay fees
              _payFees(loan.accruedInterest, loan.currency);
      
              // 11. Record loan as closed
              loan.amount = 0;
              loan.collateral = 0;
              loan.accruedInterest = 0;
              loan.interestIndex = 0;
              loan.lastInteraction = block.timestamp;
              state.updateLoan(loan);
      
              // 12. Emit the event
              emit LoanClosed(borrower, id);
          }
      
          function closeByLiquidationInternal(
              address borrower,
              address liquidator,
              Loan memory loan
          ) internal returns (uint collateral) {
              // 1. Work out the total amount owing on the loan.
              uint total = loan.amount.add(loan.accruedInterest);
      
              // 2. Store this for the event.
              uint amount = loan.amount;
      
              // 3. Return collateral to the child class so it knows how much to transfer.
              collateral = loan.collateral;
      
              // 4. Burn the synths
              require(!_exchanger().hasWaitingPeriodOrSettlementOwing(liquidator, loan.currency), "Waiting or settlement owing");
              _synth(synthsByKey[loan.currency]).burn(liquidator, total);
      
              // 5. Tell the manager.
              if (loan.short) {
                  _manager().decrementShorts(loan.currency, loan.amount);
      
                  if (shortingRewards[loan.currency] != address(0)) {
                      IShortingRewards(shortingRewards[loan.currency]).withdraw(borrower, loan.amount);
                  }
              } else {
                  _manager().decrementLongs(loan.currency, loan.amount);
              }
      
              // 6. Pay fees
              _payFees(loan.accruedInterest, loan.currency);
      
              // 7. Record loan as closed
              loan.amount = 0;
              loan.collateral = 0;
              loan.accruedInterest = 0;
              loan.interestIndex = 0;
              loan.lastInteraction = block.timestamp;
              state.updateLoan(loan);
      
              // 8. Emit the event.
              emit LoanClosedByLiquidation(borrower, loan.id, liquidator, amount, collateral);
          }
      
          function depositInternal(
              address account,
              uint id,
              uint amount
          ) internal {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. They sent some value > 0
              require(amount > 0, "Deposit must be greater than 0");
      
              // 3. Get the loan
              Loan memory loan = state.getLoan(account, id);
      
              // 4. Check loan is open and last interaction time.
              _checkLoanAvailable(loan);
      
              // 5. Accrue interest
              loan = accrueInterest(loan);
      
              // 6. Add the collateral
              loan.collateral = loan.collateral.add(amount);
      
              // 7. Update the last interaction time.
              loan.lastInteraction = block.timestamp;
      
              // 8. Store the loan
              state.updateLoan(loan);
      
              // 9. Emit the event
              emit CollateralDeposited(account, id, amount, loan.collateral);
          }
      
          function withdrawInternal(uint id, uint amount) internal returns (uint withdraw) {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. Get the loan.
              Loan memory loan = state.getLoan(msg.sender, id);
      
              // 3. Check loan is open and last interaction time.
              _checkLoanAvailable(loan);
      
              // 4. Accrue interest.
              loan = accrueInterest(loan);
      
              // 5. Subtract the collateral.
              loan.collateral = loan.collateral.sub(amount);
      
              // 6. Update the last interaction time.
              loan.lastInteraction = block.timestamp;
      
              // 7. Check that the new amount does not put them under the minimum c ratio.
              require(collateralRatio(loan) > minCratio, "Cratio too low");
      
              // 8. Store the loan.
              state.updateLoan(loan);
      
              // 9. Assign the return variable.
              withdraw = amount;
      
              // 10. Emit the event.
              emit CollateralWithdrawn(msg.sender, id, amount, loan.collateral);
          }
      
          function liquidateInternal(
              address borrower,
              uint id,
              uint payment
          ) internal returns (uint collateralLiquidated) {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. Check the payment amount.
              require(payment > 0, "Payment must be greater than 0");
      
              // 3. Get the loan.
              Loan memory loan = state.getLoan(borrower, id);
      
              // 4. Check loan is open and last interaction time.
              _checkLoanAvailable(loan);
      
              // 5. Accrue interest.
              loan = accrueInterest(loan);
      
              // 6. Check they have enough balance to make the payment.
              _checkSynthBalance(msg.sender, loan.currency, payment);
      
              // 7. Check they are eligible for liquidation.
              require(collateralRatio(loan) < minCratio, "Cratio above liquidation ratio");
      
              // 8. Determine how much needs to be liquidated to fix their c ratio.
              uint liqAmount = liquidationAmount(loan);
      
              // 9. Only allow them to liquidate enough to fix the c ratio.
              uint amountToLiquidate = liqAmount < payment ? liqAmount : payment;
      
              // 10. Work out the total amount owing on the loan.
              uint amountOwing = loan.amount.add(loan.accruedInterest);
      
              // 11. If its greater than the amount owing, we need to close the loan.
              if (amountToLiquidate >= amountOwing) {
                  return closeByLiquidationInternal(borrower, msg.sender, loan);
              }
      
              // 12. Process the payment to workout interest/principal split.
              loan = _processPayment(loan, amountToLiquidate);
      
              // 13. Work out how much collateral to redeem.
              collateralLiquidated = collateralRedeemed(loan.currency, amountToLiquidate);
              loan.collateral = loan.collateral.sub(collateralLiquidated);
      
              // 14. Update the last interaction time.
              loan.lastInteraction = block.timestamp;
      
              // 15. Burn the synths from the liquidator.
              require(!_exchanger().hasWaitingPeriodOrSettlementOwing(msg.sender, loan.currency), "Waiting or settlement owing");
              _synth(synthsByKey[loan.currency]).burn(msg.sender, amountToLiquidate);
      
              // 16. Store the loan.
              state.updateLoan(loan);
      
              // 17. Emit the event
              emit LoanPartiallyLiquidated(borrower, id, msg.sender, amountToLiquidate, collateralLiquidated);
          }
      
          function repayInternal(
              address borrower,
              address repayer,
              uint id,
              uint payment
          ) internal {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. Check the payment amount.
              require(payment > 0, "Payment must be greater than 0");
      
              // 3. Get loan
              Loan memory loan = state.getLoan(borrower, id);
      
              // 4. Check loan is open and last interaction time.
              _checkLoanAvailable(loan);
      
              // 5. Accrue interest.
              loan = accrueInterest(loan);
      
              // 6. Check the spender has enough synths to make the repayment
              _checkSynthBalance(repayer, loan.currency, payment);
      
              // 7. Process the payment.
              loan = _processPayment(loan, payment);
      
              // 8. Update the last interaction time.
              loan.lastInteraction = block.timestamp;
      
              // 9. Burn synths from the payer
              require(!_exchanger().hasWaitingPeriodOrSettlementOwing(repayer, loan.currency), "Waiting or settlement owing");
              _synth(synthsByKey[loan.currency]).burn(repayer, payment);
      
              // 10. Store the loan
              state.updateLoan(loan);
      
              // 11. Emit the event.
              emit LoanRepaymentMade(borrower, repayer, id, payment, loan.amount);
          }
      
          function drawInternal(uint id, uint amount) internal {
              // 0. Check the system is active.
              _systemStatus().requireIssuanceActive();
      
              // 1. Make sure the collateral rate is valid.
              require(!_exchangeRates().rateIsInvalid(collateralKey), "Collateral rate is invalid");
      
              // 2. Get loan.
              Loan memory loan = state.getLoan(msg.sender, id);
      
              // 3. Check loan is open and last interaction time.
              _checkLoanAvailable(loan);
      
              // 4. Accrue interest.
              loan = accrueInterest(loan);
      
              // 5. Add the requested amount.
              loan.amount = loan.amount.add(amount);
      
              // 6. If it is below the minimum, don't allow this draw.
              require(collateralRatio(loan) > minCratio, "Cannot draw this much");
      
              // 7. This fee is denominated in the currency of the loan
              uint issueFee = amount.multiplyDecimalRound(issueFeeRate);
      
              // 8. Calculate the minting fee and subtract it from the draw amount
              uint amountMinusFee = amount.sub(issueFee);
      
              // 9. If its short, let the child handle it, otherwise issue the synths.
              if (loan.short) {
                  _manager().incrementShorts(loan.currency, amount);
                  _synthsUSD().issue(msg.sender, _exchangeRates().effectiveValue(loan.currency, amountMinusFee, sUSD));
      
                  if (shortingRewards[loan.currency] != address(0)) {
                      IShortingRewards(shortingRewards[loan.currency]).enrol(msg.sender, amount);
                  }
              } else {
                  _manager().incrementLongs(loan.currency, amount);
                  _synth(synthsByKey[loan.currency]).issue(msg.sender, amountMinusFee);
              }
      
              // 10. Pay the minting fees to the fee pool
              _payFees(issueFee, loan.currency);
      
              // 11. Update the last interaction time.
              loan.lastInteraction = block.timestamp;
      
              // 12. Store the loan
              state.updateLoan(loan);
      
              // 13. Emit the event.
              emit LoanDrawnDown(msg.sender, id, amount);
          }
      
          // Update the cumulative interest rate for the currency that was interacted with.
          function accrueInterest(Loan memory loan) internal returns (Loan memory loanAfter) {
              loanAfter = loan;
      
              // 1. Get the rates we need.
              (uint entryRate, uint lastRate, uint lastUpdated, uint newIndex) = loan.short
                  ? _manager().getShortRatesAndTime(loan.currency, loan.interestIndex)
                  : _manager().getRatesAndTime(loan.interestIndex);
      
              // 2. Get the instantaneous rate.
              (uint rate, bool invalid) = loan.short
                  ? _manager().getShortRate(synthsByKey[loan.currency])
                  : _manager().getBorrowRate();
      
              require(!invalid, "Rates are invalid");
      
              // 3. Get the time since we last updated the rate.
              uint timeDelta = block.timestamp.sub(lastUpdated).mul(SafeDecimalMath.unit());
      
              // 4. Get the latest cumulative rate. F_n+1 = F_n + F_last
              uint latestCumulative = lastRate.add(rate.multiplyDecimal(timeDelta));
      
              // 5. If the loan was just opened, don't record any interest. Otherwise multiple by the amount outstanding.
              uint interest = loan.interestIndex == 0 ? 0 : loan.amount.multiplyDecimal(latestCumulative.sub(entryRate));
      
              // 7. Update rates with the lastest cumulative rate. This also updates the time.
              loan.short
                  ? _manager().updateShortRates(loan.currency, latestCumulative)
                  : _manager().updateBorrowRates(latestCumulative);
      
              // 8. Update loan
              loanAfter.accruedInterest = loan.accruedInterest.add(interest);
              loanAfter.interestIndex = newIndex;
              state.updateLoan(loanAfter);
          }
      
          // Works out the amount of interest and principal after a repayment is made.
          function _processPayment(Loan memory loanBefore, uint payment) internal returns (Loan memory loanAfter) {
              loanAfter = loanBefore;
      
              if (payment > 0 && loanBefore.accruedInterest > 0) {
                  uint interestPaid = payment > loanBefore.accruedInterest ? loanBefore.accruedInterest : payment;
                  loanAfter.accruedInterest = loanBefore.accruedInterest.sub(interestPaid);
                  payment = payment.sub(interestPaid);
      
                  _payFees(interestPaid, loanBefore.currency);
              }
      
              // If there is more payment left after the interest, pay down the principal.
              if (payment > 0) {
                  loanAfter.amount = loanBefore.amount.sub(payment);
      
                  // And get the manager to reduce the total long/short balance.
                  if (loanAfter.short) {
                      _manager().decrementShorts(loanAfter.currency, payment);
      
                      if (shortingRewards[loanAfter.currency] != address(0)) {
                          IShortingRewards(shortingRewards[loanAfter.currency]).withdraw(loanAfter.account, payment);
                      }
                  } else {
                      _manager().decrementLongs(loanAfter.currency, payment);
                  }
              }
          }
      
          // Take an amount of fees in a certain synth and convert it to sUSD before paying the fee pool.
          function _payFees(uint amount, bytes32 synth) internal {
              if (amount > 0) {
                  if (synth != sUSD) {
                      amount = _exchangeRates().effectiveValue(synth, amount, sUSD);
                  }
                  _synthsUSD().issue(_feePool().FEE_ADDRESS(), amount);
                  _feePool().recordFeePaid(amount);
              }
          }
      
          // ========== EVENTS ==========
          // Setters
          event MinCratioRatioUpdated(uint minCratio);
          event MinCollateralUpdated(uint minCollateral);
          event IssueFeeRateUpdated(uint issueFeeRate);
          event MaxLoansPerAccountUpdated(uint maxLoansPerAccount);
          event InteractionDelayUpdated(uint interactionDelay);
          event ManagerUpdated(address manager);
          event CanOpenLoansUpdated(bool canOpenLoans);
      
          // Loans
          event LoanCreated(address indexed account, uint id, uint amount, uint collateral, bytes32 currency, uint issuanceFee);
          event LoanClosed(address indexed account, uint id);
          event CollateralDeposited(address indexed account, uint id, uint amountDeposited, uint collateralAfter);
          event CollateralWithdrawn(address indexed account, uint id, uint amountWithdrawn, uint collateralAfter);
          event LoanRepaymentMade(address indexed account, address indexed repayer, uint id, uint amountRepaid, uint amountAfter);
          event LoanDrawnDown(address indexed account, uint id, uint amount);
          event LoanPartiallyLiquidated(
              address indexed account,
              uint id,
              address liquidator,
              uint amountLiquidated,
              uint collateralLiquidated
          );
          event LoanClosedByLiquidation(
              address indexed account,
              uint id,
              address indexed liquidator,
              uint amountLiquidated,
              uint collateralLiquidated
          );
      }
      
      
      interface ICollateralErc20 {
          function open(uint collateral, uint amount, bytes32 currency) external;
      
          function close(uint id) external;
      
          function deposit(address borrower, uint id, uint collateral) external;
      
          function withdraw(uint id, uint amount) external;
      
          function repay(address borrower, uint id, uint amount) external;
      
          function draw(uint id, uint amount) external;
      
          function liquidate(address borrower, uint id, uint amount) external;
      }
      
      
      // Inheritance
      
      
      // Internal references
      
      
      // This contract handles the specific ERC20 implementation details of managing a loan.
      contract CollateralErc20 is ICollateralErc20, Collateral {
          // The underlying asset for this ERC20 collateral
          address public underlyingContract;
      
          uint public underlyingContractDecimals;
      
          constructor(
              CollateralState _state,
              address _owner,
              address _manager,
              address _resolver,
              bytes32 _collateralKey,
              uint _minCratio,
              uint _minCollateral,
              address _underlyingContract,
              uint _underlyingDecimals
          ) public Collateral(_state, _owner, _manager, _resolver, _collateralKey, _minCratio, _minCollateral) {
              underlyingContract = _underlyingContract;
      
              underlyingContractDecimals = _underlyingDecimals;
          }
      
          function open(
              uint collateral,
              uint amount,
              bytes32 currency
          ) external {
              require(collateral <= IERC20(underlyingContract).allowance(msg.sender, address(this)), "Allowance not high enough");
      
              // only transfer the actual collateral
              IERC20(underlyingContract).transferFrom(msg.sender, address(this), collateral);
      
              // scale up before entering the system.
              uint scaledCollateral = scaleUpCollateral(collateral);
      
              openInternal(scaledCollateral, amount, currency, false);
          }
      
          function close(uint id) external {
              uint collateral = closeInternal(msg.sender, id);
      
              // scale down before transferring back.
              uint scaledCollateral = scaleDownCollateral(collateral);
      
              IERC20(underlyingContract).transfer(msg.sender, scaledCollateral);
          }
      
          function deposit(
              address borrower,
              uint id,
              uint amount
          ) external {
              require(amount <= IERC20(underlyingContract).allowance(msg.sender, address(this)), "Allowance not high enough");
      
              IERC20(underlyingContract).transferFrom(msg.sender, address(this), amount);
      
              // scale up before entering the system.
              uint scaledAmount = scaleUpCollateral(amount);
      
              depositInternal(borrower, id, scaledAmount);
          }
      
          function withdraw(uint id, uint amount) external {
              // scale up before entering the system.
              uint scaledAmount = scaleUpCollateral(amount);
      
              uint withdrawnAmount = withdrawInternal(id, scaledAmount);
      
              // scale down before transferring back.
              uint scaledWithdraw = scaleDownCollateral(withdrawnAmount);
      
              IERC20(underlyingContract).transfer(msg.sender, scaledWithdraw);
          }
      
          function repay(
              address borrower,
              uint id,
              uint amount
          ) external {
              repayInternal(borrower, msg.sender, id, amount);
          }
      
          function draw(uint id, uint amount) external {
              drawInternal(id, amount);
          }
      
          function liquidate(
              address borrower,
              uint id,
              uint amount
          ) external {
              uint collateralLiquidated = liquidateInternal(borrower, id, amount);
      
              // scale down before transferring back.
              uint scaledCollateral = scaleDownCollateral(collateralLiquidated);
      
              IERC20(underlyingContract).transfer(msg.sender, scaledCollateral);
          }
      
          function scaleUpCollateral(uint collateral) public view returns (uint scaledUp) {
              uint conversionFactor = 10**uint(SafeMath.sub(18, underlyingContractDecimals));
      
              scaledUp = uint(uint(collateral).mul(conversionFactor));
          }
      
          function scaleDownCollateral(uint collateral) public view returns (uint scaledDown) {
              uint conversionFactor = 10**uint(SafeMath.sub(18, underlyingContractDecimals));
      
              scaledDown = collateral.div(conversionFactor);
          }
      }
      
          

      File 2 of 3: RenBTC
      /**
      
      Deployed by Ren Project, https://renproject.io
      
      Commit hash: 9068f80
      Repository: https://github.com/renproject/darknode-sol
      Issues: https://github.com/renproject/darknode-sol/issues
      
      Licenses
      @openzeppelin/contracts: (MIT) https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/LICENSE
      darknode-sol: (GNU GPL V3) https://github.com/renproject/darknode-sol/blob/master/LICENSE
      
      */
      
      pragma solidity 0.5.16;
      
      
      contract Initializable {
      
        
        bool private initialized;
      
        
        bool private initializing;
      
        
        modifier initializer() {
          require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
      
          bool isTopLevelCall = !initializing;
          if (isTopLevelCall) {
            initializing = true;
            initialized = true;
          }
      
          _;
      
          if (isTopLevelCall) {
            initializing = false;
          }
        }
      
        
        function isConstructor() private view returns (bool) {
          
          
          
          
          
          address self = address(this);
          uint256 cs;
          assembly { cs := extcodesize(self) }
          return cs == 0;
        }
      
        
        uint256[50] private ______gap;
      }
      
      contract Context is Initializable {
          
          
          constructor () internal { }
          
      
          function _msgSender() internal view returns (address payable) {
              return msg.sender;
          }
      
          function _msgData() internal view returns (bytes memory) {
              this; 
              return msg.data;
          }
      }
      
      contract Ownable is Initializable, Context {
          address private _owner;
      
          event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
      
          
          function initialize(address sender) public initializer {
              _owner = sender;
              emit OwnershipTransferred(address(0), _owner);
          }
      
          
          function owner() public view returns (address) {
              return _owner;
          }
      
          
          modifier onlyOwner() {
              require(isOwner(), "Ownable: caller is not the owner");
              _;
          }
      
          
          function isOwner() public view returns (bool) {
              return _msgSender() == _owner;
          }
      
          
          function renounceOwnership() public onlyOwner {
              emit OwnershipTransferred(_owner, address(0));
              _owner = address(0);
          }
      
          
          function transferOwnership(address newOwner) public onlyOwner {
              _transferOwnership(newOwner);
          }
      
          
          function _transferOwnership(address newOwner) internal {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              emit OwnershipTransferred(_owner, newOwner);
              _owner = newOwner;
          }
      
          uint256[50] private ______gap;
      }
      
      contract Proxy {
        
        function () payable external {
          _fallback();
        }
      
        
        function _implementation() internal view returns (address);
      
        
        function _delegate(address implementation) internal {
          assembly {
            
            
            
            calldatacopy(0, 0, calldatasize)
      
            
            
            let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
      
            
            returndatacopy(0, 0, returndatasize)
      
            switch result
            
            case 0 { revert(0, returndatasize) }
            default { return(0, returndatasize) }
          }
        }
      
        
        function _willFallback() internal {
        }
      
        
        function _fallback() internal {
          _willFallback();
          _delegate(_implementation());
        }
      }
      
      library OpenZeppelinUpgradesAddress {
          
          function isContract(address account) internal view returns (bool) {
              uint256 size;
              
              
              
              
              
              
              
              assembly { size := extcodesize(account) }
              return size > 0;
          }
      }
      
      contract BaseUpgradeabilityProxy is Proxy {
        
        event Upgraded(address indexed implementation);
      
        
        bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
      
        
        function _implementation() internal view returns (address impl) {
          bytes32 slot = IMPLEMENTATION_SLOT;
          assembly {
            impl := sload(slot)
          }
        }
      
        
        function _upgradeTo(address newImplementation) internal {
          _setImplementation(newImplementation);
          emit Upgraded(newImplementation);
        }
      
        
        function _setImplementation(address newImplementation) internal {
          require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
      
          bytes32 slot = IMPLEMENTATION_SLOT;
      
          assembly {
            sstore(slot, newImplementation)
          }
        }
      }
      
      contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        constructor(address _logic, bytes memory _data) public payable {
          assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
          _setImplementation(_logic);
          if(_data.length > 0) {
            (bool success,) = _logic.delegatecall(_data);
            require(success);
          }
        }  
      }
      
      contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        event AdminChanged(address previousAdmin, address newAdmin);
      
        
      
        bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
      
        
        modifier ifAdmin() {
          if (msg.sender == _admin()) {
            _;
          } else {
            _fallback();
          }
        }
      
        
        function admin() external ifAdmin returns (address) {
          return _admin();
        }
      
        
        function implementation() external ifAdmin returns (address) {
          return _implementation();
        }
      
        
        function changeAdmin(address newAdmin) external ifAdmin {
          require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
          emit AdminChanged(_admin(), newAdmin);
          _setAdmin(newAdmin);
        }
      
        
        function upgradeTo(address newImplementation) external ifAdmin {
          _upgradeTo(newImplementation);
        }
      
        
        function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
          _upgradeTo(newImplementation);
          (bool success,) = newImplementation.delegatecall(data);
          require(success);
        }
      
        
        function _admin() internal view returns (address adm) {
          bytes32 slot = ADMIN_SLOT;
          assembly {
            adm := sload(slot)
          }
        }
      
        
        function _setAdmin(address newAdmin) internal {
          bytes32 slot = ADMIN_SLOT;
      
          assembly {
            sstore(slot, newAdmin)
          }
        }
      
        
        function _willFallback() internal {
          require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
          super._willFallback();
        }
      }
      
      contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        function initialize(address _logic, bytes memory _data) public payable {
          require(_implementation() == address(0));
          assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
          _setImplementation(_logic);
          if(_data.length > 0) {
            (bool success,) = _logic.delegatecall(_data);
            require(success);
          }
        }  
      }
      
      contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
        
        function initialize(address _logic, address _admin, bytes memory _data) public payable {
          require(_implementation() == address(0));
          InitializableUpgradeabilityProxy.initialize(_logic, _data);
          assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
          _setAdmin(_admin);
        }
      }
      
      interface IERC20 {
          
          function totalSupply() external view returns (uint256);
      
          
          function balanceOf(address account) external view returns (uint256);
      
          
          function transfer(address recipient, uint256 amount) external returns (bool);
      
          
          function allowance(address owner, address spender) external view returns (uint256);
      
          
          function approve(address spender, uint256 amount) external returns (bool);
      
          
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
      
          
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      library SafeMath {
          
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              return sub(a, b, "SafeMath: subtraction overflow");
          }
      
          
          function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              uint256 c = a - b;
      
              return c;
          }
      
          
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              
              
              
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          
          function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              
              require(b > 0, errorMessage);
              uint256 c = a / b;
              
      
              return c;
          }
      
          
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          
          function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      contract ERC20 is Initializable, Context, IERC20 {
          using SafeMath for uint256;
      
          mapping (address => uint256) private _balances;
      
          mapping (address => mapping (address => uint256)) private _allowances;
      
          uint256 private _totalSupply;
      
          
          function totalSupply() public view returns (uint256) {
              return _totalSupply;
          }
      
          
          function balanceOf(address account) public view returns (uint256) {
              return _balances[account];
          }
      
          
          function transfer(address recipient, uint256 amount) public returns (bool) {
              _transfer(_msgSender(), recipient, amount);
              return true;
          }
      
          
          function allowance(address owner, address spender) public view returns (uint256) {
              return _allowances[owner][spender];
          }
      
          
          function approve(address spender, uint256 amount) public returns (bool) {
              _approve(_msgSender(), spender, amount);
              return true;
          }
      
          
          function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
              _transfer(sender, recipient, amount);
              _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
              return true;
          }
      
          
          function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
              _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
              return true;
          }
      
          
          function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
              _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
              return true;
          }
      
          
          function _transfer(address sender, address recipient, uint256 amount) internal {
              require(sender != address(0), "ERC20: transfer from the zero address");
              require(recipient != address(0), "ERC20: transfer to the zero address");
      
              _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
              _balances[recipient] = _balances[recipient].add(amount);
              emit Transfer(sender, recipient, amount);
          }
      
          
          function _mint(address account, uint256 amount) internal {
              require(account != address(0), "ERC20: mint to the zero address");
      
              _totalSupply = _totalSupply.add(amount);
              _balances[account] = _balances[account].add(amount);
              emit Transfer(address(0), account, amount);
          }
      
           
          function _burn(address account, uint256 amount) internal {
              require(account != address(0), "ERC20: burn from the zero address");
      
              _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
              _totalSupply = _totalSupply.sub(amount);
              emit Transfer(account, address(0), amount);
          }
      
          
          function _approve(address owner, address spender, uint256 amount) internal {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
      
              _allowances[owner][spender] = amount;
              emit Approval(owner, spender, amount);
          }
      
          
          function _burnFrom(address account, uint256 amount) internal {
              _burn(account, amount);
              _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
          }
      
          uint256[50] private ______gap;
      }
      
      contract ERC20Detailed is Initializable, IERC20 {
          string private _name;
          string private _symbol;
          uint8 private _decimals;
      
          
          function initialize(string memory name, string memory symbol, uint8 decimals) public initializer {
              _name = name;
              _symbol = symbol;
              _decimals = decimals;
          }
      
          
          function name() public view returns (string memory) {
              return _name;
          }
      
          
          function symbol() public view returns (string memory) {
              return _symbol;
          }
      
          
          function decimals() public view returns (uint8) {
              return _decimals;
          }
      
          uint256[50] private ______gap;
      }
      
      contract Claimable is Initializable, Ownable {
          address public pendingOwner;
      
          function initialize(address _nextOwner) public initializer {
              Ownable.initialize(_nextOwner);
          }
      
          modifier onlyPendingOwner() {
              require(
                  _msgSender() == pendingOwner,
                  "Claimable: caller is not the pending owner"
              );
              _;
          }
      
          function transferOwnership(address newOwner) public onlyOwner {
              require(
                  newOwner != owner() && newOwner != pendingOwner,
                  "Claimable: invalid new owner"
              );
              pendingOwner = newOwner;
          }
      
          function claimOwnership() public onlyPendingOwner {
              _transferOwnership(pendingOwner);
              delete pendingOwner;
          }
      }
      
      library Address {
          
          function isContract(address account) internal view returns (bool) {
              
              
              
      
              
              
              
              bytes32 codehash;
              bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              
              assembly { codehash := extcodehash(account) }
              return (codehash != 0x0 && codehash != accountHash);
          }
      
          
          function toPayable(address account) internal pure returns (address payable) {
              return address(uint160(account));
          }
      
          
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
      
              
              (bool success, ) = recipient.call.value(amount)("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
      }
      
      library SafeERC20 {
          using SafeMath for uint256;
          using Address for address;
      
          function safeTransfer(IERC20 token, address to, uint256 value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
          }
      
          function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
          }
      
          function safeApprove(IERC20 token, address spender, uint256 value) internal {
              
              
              
              
              require((value == 0) || (token.allowance(address(this), spender) == 0),
                  "SafeERC20: approve from non-zero to non-zero allowance"
              );
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
          }
      
          function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).add(value);
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
      
          function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
      
          
          function callOptionalReturn(IERC20 token, bytes memory data) private {
              
              
      
              
              
              
              
              
              require(address(token).isContract(), "SafeERC20: call to non-contract");
      
              
              (bool success, bytes memory returndata) = address(token).call(data);
              require(success, "SafeERC20: low-level call failed");
      
              if (returndata.length > 0) { 
                  
                  require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
              }
          }
      }
      
      contract CanReclaimTokens is Claimable {
          using SafeERC20 for ERC20;
      
          mapping(address => bool) private recoverableTokensBlacklist;
      
          function initialize(address _nextOwner) public initializer {
              Claimable.initialize(_nextOwner);
          }
      
          function blacklistRecoverableToken(address _token) public onlyOwner {
              recoverableTokensBlacklist[_token] = true;
          }
      
          
          
          function recoverTokens(address _token) external onlyOwner {
              require(
                  !recoverableTokensBlacklist[_token],
                  "CanReclaimTokens: token is not recoverable"
              );
      
              if (_token == address(0x0)) {
                  msg.sender.transfer(address(this).balance);
              } else {
                  ERC20(_token).safeTransfer(
                      msg.sender,
                      ERC20(_token).balanceOf(address(this))
                  );
              }
          }
      }
      
      contract ERC20WithRate is Initializable, Ownable, ERC20 {
          using SafeMath for uint256;
      
          uint256 public constant _rateScale = 1e18;
          uint256 internal _rate;
      
          event LogRateChanged(uint256 indexed _rate);
      
          
          function initialize(address _nextOwner, uint256 _initialRate)
              public
              initializer
          {
              Ownable.initialize(_nextOwner);
              _setRate(_initialRate);
          }
      
          function setExchangeRate(uint256 _nextRate) public onlyOwner {
              _setRate(_nextRate);
          }
      
          function exchangeRateCurrent() public view returns (uint256) {
              require(_rate != 0, "ERC20WithRate: rate has not been initialized");
              return _rate;
          }
      
          function _setRate(uint256 _nextRate) internal {
              require(_nextRate > 0, "ERC20WithRate: rate must be greater than zero");
              _rate = _nextRate;
          }
      
          function balanceOfUnderlying(address _account)
              public
              view
              returns (uint256)
          {
              return toUnderlying(balanceOf(_account));
          }
      
          function toUnderlying(uint256 _amount) public view returns (uint256) {
              return _amount.mul(_rate).div(_rateScale);
          }
      
          function fromUnderlying(uint256 _amountUnderlying)
              public
              view
              returns (uint256)
          {
              return _amountUnderlying.mul(_rateScale).div(_rate);
          }
      }
      
      contract ERC20WithPermit is Initializable, ERC20, ERC20Detailed {
          using SafeMath for uint256;
      
          mapping(address => uint256) public nonces;
      
          
          
          string public version;
      
          
          bytes32 public DOMAIN_SEPARATOR;
          
          
          bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
      
          function initialize(
              uint256 _chainId,
              string memory _version,
              string memory _name,
              string memory _symbol,
              uint8 _decimals
          ) public initializer {
              ERC20Detailed.initialize(_name, _symbol, _decimals);
              version = _version;
              DOMAIN_SEPARATOR = keccak256(
                  abi.encode(
                      keccak256(
                          "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                      ),
                      keccak256(bytes(name())),
                      keccak256(bytes(version)),
                      _chainId,
                      address(this)
                  )
              );
          }
      
          
          function permit(
              address holder,
              address spender,
              uint256 nonce,
              uint256 expiry,
              bool allowed,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external {
              bytes32 digest = keccak256(
                  abi.encodePacked(
                      "\x19\x01",
                      DOMAIN_SEPARATOR,
                      keccak256(
                          abi.encode(
                              PERMIT_TYPEHASH,
                              holder,
                              spender,
                              nonce,
                              expiry,
                              allowed
                          )
                      )
                  )
              );
      
              require(holder != address(0), "ERC20WithRate: address must not be 0x0");
              require(
                  holder == ecrecover(digest, v, r, s),
                  "ERC20WithRate: invalid signature"
              );
              require(
                  expiry == 0 || now <= expiry,
                  "ERC20WithRate: permit has expired"
              );
              require(nonce == nonces[holder]++, "ERC20WithRate: invalid nonce");
              uint256 amount = allowed ? uint256(-1) : 0;
              _approve(holder, spender, amount);
          }
      }
      
      contract RenERC20LogicV1 is
          Initializable,
          ERC20,
          ERC20Detailed,
          ERC20WithRate,
          ERC20WithPermit,
          Claimable,
          CanReclaimTokens
      {
          
          function initialize(
              uint256 _chainId,
              address _nextOwner,
              uint256 _initialRate,
              string memory _version,
              string memory _name,
              string memory _symbol,
              uint8 _decimals
          ) public initializer {
              ERC20Detailed.initialize(_name, _symbol, _decimals);
              ERC20WithRate.initialize(_nextOwner, _initialRate);
              ERC20WithPermit.initialize(
                  _chainId,
                  _version,
                  _name,
                  _symbol,
                  _decimals
              );
              Claimable.initialize(_nextOwner);
              CanReclaimTokens.initialize(_nextOwner);
          }
      
          
          
          function mint(address _to, uint256 _amount) public onlyOwner {
              _mint(_to, _amount);
          }
      
          
          
          function burn(address _from, uint256 _amount) public onlyOwner {
              _burn(_from, _amount);
          }
      
          function transfer(address recipient, uint256 amount) public returns (bool) {
              
              
              
              require(
                  recipient != address(this),
                  "RenERC20: can't transfer to token address"
              );
              return super.transfer(recipient, amount);
          }
      
          function transferFrom(address sender, address recipient, uint256 amount)
              public
              returns (bool)
          {
              
              
              require(
                  recipient != address(this),
                  "RenERC20: can't transfer to token address"
              );
              return super.transferFrom(sender, recipient, amount);
          }
      }
      
      contract RenBTC is InitializableAdminUpgradeabilityProxy {}
      
      contract RenZEC is InitializableAdminUpgradeabilityProxy {}
      
      contract RenBCH is InitializableAdminUpgradeabilityProxy {}

      File 3 of 3: RenERC20LogicV1
      /**
      
      Deployed by Ren Project, https://renproject.io
      
      Commit hash: 9068f80
      Repository: https://github.com/renproject/darknode-sol
      Issues: https://github.com/renproject/darknode-sol/issues
      
      Licenses
      @openzeppelin/contracts: (MIT) https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/LICENSE
      darknode-sol: (GNU GPL V3) https://github.com/renproject/darknode-sol/blob/master/LICENSE
      
      */
      
      pragma solidity 0.5.16;
      
      
      contract Initializable {
      
        
        bool private initialized;
      
        
        bool private initializing;
      
        
        modifier initializer() {
          require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
      
          bool isTopLevelCall = !initializing;
          if (isTopLevelCall) {
            initializing = true;
            initialized = true;
          }
      
          _;
      
          if (isTopLevelCall) {
            initializing = false;
          }
        }
      
        
        function isConstructor() private view returns (bool) {
          
          
          
          
          
          address self = address(this);
          uint256 cs;
          assembly { cs := extcodesize(self) }
          return cs == 0;
        }
      
        
        uint256[50] private ______gap;
      }
      
      contract Context is Initializable {
          
          
          constructor () internal { }
          
      
          function _msgSender() internal view returns (address payable) {
              return msg.sender;
          }
      
          function _msgData() internal view returns (bytes memory) {
              this; 
              return msg.data;
          }
      }
      
      contract Ownable is Initializable, Context {
          address private _owner;
      
          event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
      
          
          function initialize(address sender) public initializer {
              _owner = sender;
              emit OwnershipTransferred(address(0), _owner);
          }
      
          
          function owner() public view returns (address) {
              return _owner;
          }
      
          
          modifier onlyOwner() {
              require(isOwner(), "Ownable: caller is not the owner");
              _;
          }
      
          
          function isOwner() public view returns (bool) {
              return _msgSender() == _owner;
          }
      
          
          function renounceOwnership() public onlyOwner {
              emit OwnershipTransferred(_owner, address(0));
              _owner = address(0);
          }
      
          
          function transferOwnership(address newOwner) public onlyOwner {
              _transferOwnership(newOwner);
          }
      
          
          function _transferOwnership(address newOwner) internal {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              emit OwnershipTransferred(_owner, newOwner);
              _owner = newOwner;
          }
      
          uint256[50] private ______gap;
      }
      
      contract Proxy {
        
        function () payable external {
          _fallback();
        }
      
        
        function _implementation() internal view returns (address);
      
        
        function _delegate(address implementation) internal {
          assembly {
            
            
            
            calldatacopy(0, 0, calldatasize)
      
            
            
            let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
      
            
            returndatacopy(0, 0, returndatasize)
      
            switch result
            
            case 0 { revert(0, returndatasize) }
            default { return(0, returndatasize) }
          }
        }
      
        
        function _willFallback() internal {
        }
      
        
        function _fallback() internal {
          _willFallback();
          _delegate(_implementation());
        }
      }
      
      library OpenZeppelinUpgradesAddress {
          
          function isContract(address account) internal view returns (bool) {
              uint256 size;
              
              
              
              
              
              
              
              assembly { size := extcodesize(account) }
              return size > 0;
          }
      }
      
      contract BaseUpgradeabilityProxy is Proxy {
        
        event Upgraded(address indexed implementation);
      
        
        bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
      
        
        function _implementation() internal view returns (address impl) {
          bytes32 slot = IMPLEMENTATION_SLOT;
          assembly {
            impl := sload(slot)
          }
        }
      
        
        function _upgradeTo(address newImplementation) internal {
          _setImplementation(newImplementation);
          emit Upgraded(newImplementation);
        }
      
        
        function _setImplementation(address newImplementation) internal {
          require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
      
          bytes32 slot = IMPLEMENTATION_SLOT;
      
          assembly {
            sstore(slot, newImplementation)
          }
        }
      }
      
      contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        constructor(address _logic, bytes memory _data) public payable {
          assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
          _setImplementation(_logic);
          if(_data.length > 0) {
            (bool success,) = _logic.delegatecall(_data);
            require(success);
          }
        }  
      }
      
      contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        event AdminChanged(address previousAdmin, address newAdmin);
      
        
      
        bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
      
        
        modifier ifAdmin() {
          if (msg.sender == _admin()) {
            _;
          } else {
            _fallback();
          }
        }
      
        
        function admin() external ifAdmin returns (address) {
          return _admin();
        }
      
        
        function implementation() external ifAdmin returns (address) {
          return _implementation();
        }
      
        
        function changeAdmin(address newAdmin) external ifAdmin {
          require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
          emit AdminChanged(_admin(), newAdmin);
          _setAdmin(newAdmin);
        }
      
        
        function upgradeTo(address newImplementation) external ifAdmin {
          _upgradeTo(newImplementation);
        }
      
        
        function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
          _upgradeTo(newImplementation);
          (bool success,) = newImplementation.delegatecall(data);
          require(success);
        }
      
        
        function _admin() internal view returns (address adm) {
          bytes32 slot = ADMIN_SLOT;
          assembly {
            adm := sload(slot)
          }
        }
      
        
        function _setAdmin(address newAdmin) internal {
          bytes32 slot = ADMIN_SLOT;
      
          assembly {
            sstore(slot, newAdmin)
          }
        }
      
        
        function _willFallback() internal {
          require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
          super._willFallback();
        }
      }
      
      contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
        
        function initialize(address _logic, bytes memory _data) public payable {
          require(_implementation() == address(0));
          assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
          _setImplementation(_logic);
          if(_data.length > 0) {
            (bool success,) = _logic.delegatecall(_data);
            require(success);
          }
        }  
      }
      
      contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
        
        function initialize(address _logic, address _admin, bytes memory _data) public payable {
          require(_implementation() == address(0));
          InitializableUpgradeabilityProxy.initialize(_logic, _data);
          assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
          _setAdmin(_admin);
        }
      }
      
      interface IERC20 {
          
          function totalSupply() external view returns (uint256);
      
          
          function balanceOf(address account) external view returns (uint256);
      
          
          function transfer(address recipient, uint256 amount) external returns (bool);
      
          
          function allowance(address owner, address spender) external view returns (uint256);
      
          
          function approve(address spender, uint256 amount) external returns (bool);
      
          
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
      
          
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      library SafeMath {
          
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              return sub(a, b, "SafeMath: subtraction overflow");
          }
      
          
          function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              uint256 c = a - b;
      
              return c;
          }
      
          
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              
              
              
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          
          function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              
              require(b > 0, errorMessage);
              uint256 c = a / b;
              
      
              return c;
          }
      
          
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          
          function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      contract ERC20 is Initializable, Context, IERC20 {
          using SafeMath for uint256;
      
          mapping (address => uint256) private _balances;
      
          mapping (address => mapping (address => uint256)) private _allowances;
      
          uint256 private _totalSupply;
      
          
          function totalSupply() public view returns (uint256) {
              return _totalSupply;
          }
      
          
          function balanceOf(address account) public view returns (uint256) {
              return _balances[account];
          }
      
          
          function transfer(address recipient, uint256 amount) public returns (bool) {
              _transfer(_msgSender(), recipient, amount);
              return true;
          }
      
          
          function allowance(address owner, address spender) public view returns (uint256) {
              return _allowances[owner][spender];
          }
      
          
          function approve(address spender, uint256 amount) public returns (bool) {
              _approve(_msgSender(), spender, amount);
              return true;
          }
      
          
          function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
              _transfer(sender, recipient, amount);
              _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
              return true;
          }
      
          
          function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
              _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
              return true;
          }
      
          
          function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
              _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
              return true;
          }
      
          
          function _transfer(address sender, address recipient, uint256 amount) internal {
              require(sender != address(0), "ERC20: transfer from the zero address");
              require(recipient != address(0), "ERC20: transfer to the zero address");
      
              _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
              _balances[recipient] = _balances[recipient].add(amount);
              emit Transfer(sender, recipient, amount);
          }
      
          
          function _mint(address account, uint256 amount) internal {
              require(account != address(0), "ERC20: mint to the zero address");
      
              _totalSupply = _totalSupply.add(amount);
              _balances[account] = _balances[account].add(amount);
              emit Transfer(address(0), account, amount);
          }
      
           
          function _burn(address account, uint256 amount) internal {
              require(account != address(0), "ERC20: burn from the zero address");
      
              _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
              _totalSupply = _totalSupply.sub(amount);
              emit Transfer(account, address(0), amount);
          }
      
          
          function _approve(address owner, address spender, uint256 amount) internal {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
      
              _allowances[owner][spender] = amount;
              emit Approval(owner, spender, amount);
          }
      
          
          function _burnFrom(address account, uint256 amount) internal {
              _burn(account, amount);
              _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
          }
      
          uint256[50] private ______gap;
      }
      
      contract ERC20Detailed is Initializable, IERC20 {
          string private _name;
          string private _symbol;
          uint8 private _decimals;
      
          
          function initialize(string memory name, string memory symbol, uint8 decimals) public initializer {
              _name = name;
              _symbol = symbol;
              _decimals = decimals;
          }
      
          
          function name() public view returns (string memory) {
              return _name;
          }
      
          
          function symbol() public view returns (string memory) {
              return _symbol;
          }
      
          
          function decimals() public view returns (uint8) {
              return _decimals;
          }
      
          uint256[50] private ______gap;
      }
      
      contract Claimable is Initializable, Ownable {
          address public pendingOwner;
      
          function initialize(address _nextOwner) public initializer {
              Ownable.initialize(_nextOwner);
          }
      
          modifier onlyPendingOwner() {
              require(
                  _msgSender() == pendingOwner,
                  "Claimable: caller is not the pending owner"
              );
              _;
          }
      
          function transferOwnership(address newOwner) public onlyOwner {
              require(
                  newOwner != owner() && newOwner != pendingOwner,
                  "Claimable: invalid new owner"
              );
              pendingOwner = newOwner;
          }
      
          function claimOwnership() public onlyPendingOwner {
              _transferOwnership(pendingOwner);
              delete pendingOwner;
          }
      }
      
      library Address {
          
          function isContract(address account) internal view returns (bool) {
              
              
              
      
              
              
              
              bytes32 codehash;
              bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              
              assembly { codehash := extcodehash(account) }
              return (codehash != 0x0 && codehash != accountHash);
          }
      
          
          function toPayable(address account) internal pure returns (address payable) {
              return address(uint160(account));
          }
      
          
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
      
              
              (bool success, ) = recipient.call.value(amount)("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
      }
      
      library SafeERC20 {
          using SafeMath for uint256;
          using Address for address;
      
          function safeTransfer(IERC20 token, address to, uint256 value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
          }
      
          function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
          }
      
          function safeApprove(IERC20 token, address spender, uint256 value) internal {
              
              
              
              
              require((value == 0) || (token.allowance(address(this), spender) == 0),
                  "SafeERC20: approve from non-zero to non-zero allowance"
              );
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
          }
      
          function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).add(value);
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
      
          function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
              callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
      
          
          function callOptionalReturn(IERC20 token, bytes memory data) private {
              
              
      
              
              
              
              
              
              require(address(token).isContract(), "SafeERC20: call to non-contract");
      
              
              (bool success, bytes memory returndata) = address(token).call(data);
              require(success, "SafeERC20: low-level call failed");
      
              if (returndata.length > 0) { 
                  
                  require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
              }
          }
      }
      
      contract CanReclaimTokens is Claimable {
          using SafeERC20 for ERC20;
      
          mapping(address => bool) private recoverableTokensBlacklist;
      
          function initialize(address _nextOwner) public initializer {
              Claimable.initialize(_nextOwner);
          }
      
          function blacklistRecoverableToken(address _token) public onlyOwner {
              recoverableTokensBlacklist[_token] = true;
          }
      
          
          
          function recoverTokens(address _token) external onlyOwner {
              require(
                  !recoverableTokensBlacklist[_token],
                  "CanReclaimTokens: token is not recoverable"
              );
      
              if (_token == address(0x0)) {
                  msg.sender.transfer(address(this).balance);
              } else {
                  ERC20(_token).safeTransfer(
                      msg.sender,
                      ERC20(_token).balanceOf(address(this))
                  );
              }
          }
      }
      
      contract ERC20WithRate is Initializable, Ownable, ERC20 {
          using SafeMath for uint256;
      
          uint256 public constant _rateScale = 1e18;
          uint256 internal _rate;
      
          event LogRateChanged(uint256 indexed _rate);
      
          
          function initialize(address _nextOwner, uint256 _initialRate)
              public
              initializer
          {
              Ownable.initialize(_nextOwner);
              _setRate(_initialRate);
          }
      
          function setExchangeRate(uint256 _nextRate) public onlyOwner {
              _setRate(_nextRate);
          }
      
          function exchangeRateCurrent() public view returns (uint256) {
              require(_rate != 0, "ERC20WithRate: rate has not been initialized");
              return _rate;
          }
      
          function _setRate(uint256 _nextRate) internal {
              require(_nextRate > 0, "ERC20WithRate: rate must be greater than zero");
              _rate = _nextRate;
          }
      
          function balanceOfUnderlying(address _account)
              public
              view
              returns (uint256)
          {
              return toUnderlying(balanceOf(_account));
          }
      
          function toUnderlying(uint256 _amount) public view returns (uint256) {
              return _amount.mul(_rate).div(_rateScale);
          }
      
          function fromUnderlying(uint256 _amountUnderlying)
              public
              view
              returns (uint256)
          {
              return _amountUnderlying.mul(_rateScale).div(_rate);
          }
      }
      
      contract ERC20WithPermit is Initializable, ERC20, ERC20Detailed {
          using SafeMath for uint256;
      
          mapping(address => uint256) public nonces;
      
          
          
          string public version;
      
          
          bytes32 public DOMAIN_SEPARATOR;
          
          
          bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
      
          function initialize(
              uint256 _chainId,
              string memory _version,
              string memory _name,
              string memory _symbol,
              uint8 _decimals
          ) public initializer {
              ERC20Detailed.initialize(_name, _symbol, _decimals);
              version = _version;
              DOMAIN_SEPARATOR = keccak256(
                  abi.encode(
                      keccak256(
                          "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                      ),
                      keccak256(bytes(name())),
                      keccak256(bytes(version)),
                      _chainId,
                      address(this)
                  )
              );
          }
      
          
          function permit(
              address holder,
              address spender,
              uint256 nonce,
              uint256 expiry,
              bool allowed,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external {
              bytes32 digest = keccak256(
                  abi.encodePacked(
                      "\x19\x01",
                      DOMAIN_SEPARATOR,
                      keccak256(
                          abi.encode(
                              PERMIT_TYPEHASH,
                              holder,
                              spender,
                              nonce,
                              expiry,
                              allowed
                          )
                      )
                  )
              );
      
              require(holder != address(0), "ERC20WithRate: address must not be 0x0");
              require(
                  holder == ecrecover(digest, v, r, s),
                  "ERC20WithRate: invalid signature"
              );
              require(
                  expiry == 0 || now <= expiry,
                  "ERC20WithRate: permit has expired"
              );
              require(nonce == nonces[holder]++, "ERC20WithRate: invalid nonce");
              uint256 amount = allowed ? uint256(-1) : 0;
              _approve(holder, spender, amount);
          }
      }
      
      contract RenERC20LogicV1 is
          Initializable,
          ERC20,
          ERC20Detailed,
          ERC20WithRate,
          ERC20WithPermit,
          Claimable,
          CanReclaimTokens
      {
          
          function initialize(
              uint256 _chainId,
              address _nextOwner,
              uint256 _initialRate,
              string memory _version,
              string memory _name,
              string memory _symbol,
              uint8 _decimals
          ) public initializer {
              ERC20Detailed.initialize(_name, _symbol, _decimals);
              ERC20WithRate.initialize(_nextOwner, _initialRate);
              ERC20WithPermit.initialize(
                  _chainId,
                  _version,
                  _name,
                  _symbol,
                  _decimals
              );
              Claimable.initialize(_nextOwner);
              CanReclaimTokens.initialize(_nextOwner);
          }
      
          
          
          function mint(address _to, uint256 _amount) public onlyOwner {
              _mint(_to, _amount);
          }
      
          
          
          function burn(address _from, uint256 _amount) public onlyOwner {
              _burn(_from, _amount);
          }
      
          function transfer(address recipient, uint256 amount) public returns (bool) {
              
              
              
              require(
                  recipient != address(this),
                  "RenERC20: can't transfer to token address"
              );
              return super.transfer(recipient, amount);
          }
      
          function transferFrom(address sender, address recipient, uint256 amount)
              public
              returns (bool)
          {
              
              
              require(
                  recipient != address(this),
                  "RenERC20: can't transfer to token address"
              );
              return super.transferFrom(sender, recipient, amount);
          }
      }
      
      contract RenBTC is InitializableAdminUpgradeabilityProxy {}
      
      contract RenZEC is InitializableAdminUpgradeabilityProxy {}
      
      contract RenBCH is InitializableAdminUpgradeabilityProxy {}