ETH Price: $3,403.62 (-3.86%)

Transaction Decoder

Block:
12381845 at May-06-2021 04:39:39 PM +UTC
Transaction Fee:
0.004250116 ETH $14.47
Gas Used:
45,214 Gas / 94 Gwei

Account State Difference:

  Address   Before After State Difference Code
(Spark Pool)
46.355716584337175627 Eth46.359966700337175627 Eth0.004250116
0xf4d35e47...AB6C0b554
0.042188484 Eth
Nonce: 15
0.037938368 Eth
Nonce: 16
0.004250116

Execution Trace

CErc20.redeemUnderlying( redeemAmount=145449069 )
  • FiatTokenProxy.70a08231( )
    • FiatTokenV2_1.balanceOf( account=0x39AA39c021dfbaE8faC545936693aC917d5E7563 ) => ( 769028003228455 )
      redeemUnderlying[CErc20 (ln:2440)]
      File 1 of 3: CErc20
      // File: contracts/ComptrollerInterface.sol
      
      pragma solidity ^0.5.8;
      
      interface ComptrollerInterface {
          /**
           * @notice Marker function used for light validation when updating the comptroller of a market
           * @dev Implementations should simply return true.
           * @return true
           */
          function isComptroller() external view returns (bool);
      
          /*** Assets You Are In ***/
      
          function enterMarkets(address[] calldata cTokens) external returns (uint[] memory);
          function exitMarket(address cToken) external returns (uint);
      
          /*** Policy Hooks ***/
      
          function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint);
          function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external;
      
          function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint);
          function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external;
      
          function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint);
          function borrowVerify(address cToken, address borrower, uint borrowAmount) external;
      
          function repayBorrowAllowed(
              address cToken,
              address payer,
              address borrower,
              uint repayAmount) external returns (uint);
          function repayBorrowVerify(
              address cToken,
              address payer,
              address borrower,
              uint repayAmount,
              uint borrowerIndex) external;
      
          function liquidateBorrowAllowed(
              address cTokenBorrowed,
              address cTokenCollateral,
              address liquidator,
              address borrower,
              uint repayAmount) external returns (uint);
          function liquidateBorrowVerify(
              address cTokenBorrowed,
              address cTokenCollateral,
              address liquidator,
              address borrower,
              uint repayAmount,
              uint seizeTokens) external;
      
          function seizeAllowed(
              address cTokenCollateral,
              address cTokenBorrowed,
              address liquidator,
              address borrower,
              uint seizeTokens) external returns (uint);
          function seizeVerify(
              address cTokenCollateral,
              address cTokenBorrowed,
              address liquidator,
              address borrower,
              uint seizeTokens) external;
      
          function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint);
          function transferVerify(address cToken, address src, address dst, uint transferTokens) external;
      
          /*** Liquidity/Liquidation Calculations ***/
      
          function liquidateCalculateSeizeTokens(
              address cTokenBorrowed,
              address cTokenCollateral,
              uint repayAmount) external view returns (uint, uint);
      }
      
      // File: contracts/ErrorReporter.sol
      
      pragma solidity ^0.5.8;
      
      contract ComptrollerErrorReporter {
          enum Error {
              NO_ERROR,
              UNAUTHORIZED,
              COMPTROLLER_MISMATCH,
              INSUFFICIENT_SHORTFALL,
              INSUFFICIENT_LIQUIDITY,
              INVALID_CLOSE_FACTOR,
              INVALID_COLLATERAL_FACTOR,
              INVALID_LIQUIDATION_INCENTIVE,
              MARKET_NOT_ENTERED,
              MARKET_NOT_LISTED,
              MARKET_ALREADY_LISTED,
              MATH_ERROR,
              NONZERO_BORROW_BALANCE,
              PRICE_ERROR,
              REJECTION,
              SNAPSHOT_ERROR,
              TOO_MANY_ASSETS,
              TOO_MUCH_REPAY
          }
      
          enum FailureInfo {
              ACCEPT_ADMIN_PENDING_ADMIN_CHECK,
              ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,
              EXIT_MARKET_BALANCE_OWED,
              EXIT_MARKET_REJECTION,
              SET_CLOSE_FACTOR_OWNER_CHECK,
              SET_CLOSE_FACTOR_VALIDATION,
              SET_COLLATERAL_FACTOR_OWNER_CHECK,
              SET_COLLATERAL_FACTOR_NO_EXISTS,
              SET_COLLATERAL_FACTOR_VALIDATION,
              SET_COLLATERAL_FACTOR_WITHOUT_PRICE,
              SET_IMPLEMENTATION_OWNER_CHECK,
              SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,
              SET_LIQUIDATION_INCENTIVE_VALIDATION,
              SET_MAX_ASSETS_OWNER_CHECK,
              SET_PENDING_ADMIN_OWNER_CHECK,
              SET_PENDING_IMPLEMENTATION_OWNER_CHECK,
              SET_PRICE_ORACLE_OWNER_CHECK,
              SUPPORT_MARKET_EXISTS,
              SUPPORT_MARKET_OWNER_CHECK,
              ZUNUSED
          }
      
          /**
            * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary
            * contract-specific code that enables us to report opaque error codes from upgradeable contracts.
            **/
          event Failure(uint error, uint info, uint detail);
      
          /**
            * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator
            */
          function fail(Error err, FailureInfo info) internal returns (uint) {
              emit Failure(uint(err), uint(info), 0);
      
              return uint(err);
          }
      
          /**
            * @dev use this when reporting an opaque error from an upgradeable collaborator contract
            */
          function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {
              emit Failure(uint(err), uint(info), opaqueError);
      
              return uint(err);
          }
      }
      
      contract TokenErrorReporter {
          enum Error {
              NO_ERROR,
              UNAUTHORIZED,
              BAD_INPUT,
              COMPTROLLER_REJECTION,
              COMPTROLLER_CALCULATION_ERROR,
              INTEREST_RATE_MODEL_ERROR,
              INVALID_ACCOUNT_PAIR,
              INVALID_CLOSE_AMOUNT_REQUESTED,
              INVALID_COLLATERAL_FACTOR,
              MATH_ERROR,
              MARKET_NOT_FRESH,
              MARKET_NOT_LISTED,
              TOKEN_INSUFFICIENT_ALLOWANCE,
              TOKEN_INSUFFICIENT_BALANCE,
              TOKEN_INSUFFICIENT_CASH,
              TOKEN_TRANSFER_IN_FAILED,
              TOKEN_TRANSFER_OUT_FAILED
          }
      
          /*
           * Note: FailureInfo (but not Error) is kept in alphabetical order
           *       This is because FailureInfo grows significantly faster, and
           *       the order of Error has some meaning, while the order of FailureInfo
           *       is entirely arbitrary.
           */
          enum FailureInfo {
              ACCEPT_ADMIN_PENDING_ADMIN_CHECK,
              ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,
              ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,
              ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,
              ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,
              ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,
              ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,
              BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,
              BORROW_ACCRUE_INTEREST_FAILED,
              BORROW_CASH_NOT_AVAILABLE,
              BORROW_FRESHNESS_CHECK,
              BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,
              BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,
              BORROW_MARKET_NOT_LISTED,
              BORROW_COMPTROLLER_REJECTION,
              LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,
              LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,
              LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,
              LIQUIDATE_COMPTROLLER_REJECTION,
              LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,
              LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,
              LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,
              LIQUIDATE_FRESHNESS_CHECK,
              LIQUIDATE_LIQUIDATOR_IS_BORROWER,
              LIQUIDATE_REPAY_BORROW_FRESH_FAILED,
              LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,
              LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,
              LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,
              LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,
              LIQUIDATE_SEIZE_TOO_MUCH,
              MINT_ACCRUE_INTEREST_FAILED,
              MINT_COMPTROLLER_REJECTION,
              MINT_EXCHANGE_CALCULATION_FAILED,
              MINT_EXCHANGE_RATE_READ_FAILED,
              MINT_FRESHNESS_CHECK,
              MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,
              MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,
              MINT_TRANSFER_IN_FAILED,
              MINT_TRANSFER_IN_NOT_POSSIBLE,
              REDEEM_ACCRUE_INTEREST_FAILED,
              REDEEM_COMPTROLLER_REJECTION,
              REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,
              REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,
              REDEEM_EXCHANGE_RATE_READ_FAILED,
              REDEEM_FRESHNESS_CHECK,
              REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,
              REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,
              REDEEM_TRANSFER_OUT_NOT_POSSIBLE,
              REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,
              REDUCE_RESERVES_ADMIN_CHECK,
              REDUCE_RESERVES_CASH_NOT_AVAILABLE,
              REDUCE_RESERVES_FRESH_CHECK,
              REDUCE_RESERVES_VALIDATION,
              REPAY_BEHALF_ACCRUE_INTEREST_FAILED,
              REPAY_BORROW_ACCRUE_INTEREST_FAILED,
              REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,
              REPAY_BORROW_COMPTROLLER_REJECTION,
              REPAY_BORROW_FRESHNESS_CHECK,
              REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,
              REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,
              REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,
              SET_COLLATERAL_FACTOR_OWNER_CHECK,
              SET_COLLATERAL_FACTOR_VALIDATION,
              SET_COMPTROLLER_OWNER_CHECK,
              SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,
              SET_INTEREST_RATE_MODEL_FRESH_CHECK,
              SET_INTEREST_RATE_MODEL_OWNER_CHECK,
              SET_MAX_ASSETS_OWNER_CHECK,
              SET_ORACLE_MARKET_NOT_LISTED,
              SET_PENDING_ADMIN_OWNER_CHECK,
              SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,
              SET_RESERVE_FACTOR_ADMIN_CHECK,
              SET_RESERVE_FACTOR_FRESH_CHECK,
              SET_RESERVE_FACTOR_BOUNDS_CHECK,
              TRANSFER_COMPTROLLER_REJECTION,
              TRANSFER_NOT_ALLOWED,
              TRANSFER_NOT_ENOUGH,
              TRANSFER_TOO_MUCH
          }
      
          /**
            * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary
            * contract-specific code that enables us to report opaque error codes from upgradeable contracts.
            **/
          event Failure(uint error, uint info, uint detail);
      
          /**
            * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator
            */
          function fail(Error err, FailureInfo info) internal returns (uint) {
              emit Failure(uint(err), uint(info), 0);
      
              return uint(err);
          }
      
          /**
            * @dev use this when reporting an opaque error from an upgradeable collaborator contract
            */
          function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {
              emit Failure(uint(err), uint(info), opaqueError);
      
              return uint(err);
          }
      }
      
      // File: contracts/CarefulMath.sol
      
      pragma solidity ^0.5.8;
      
      /**
        * @title Careful Math
        * @author Compound
        * @notice Derived from OpenZeppelin's SafeMath library
        *         https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol
        */
      contract CarefulMath {
      
          /**
           * @dev Possible error codes that we can return
           */
          enum MathError {
              NO_ERROR,
              DIVISION_BY_ZERO,
              INTEGER_OVERFLOW,
              INTEGER_UNDERFLOW
          }
      
          /**
          * @dev Multiplies two numbers, returns an error on overflow.
          */
          function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {
              if (a == 0) {
                  return (MathError.NO_ERROR, 0);
              }
      
              uint c = a * b;
      
              if (c / a != b) {
                  return (MathError.INTEGER_OVERFLOW, 0);
              } else {
                  return (MathError.NO_ERROR, c);
              }
          }
      
          /**
          * @dev Integer division of two numbers, truncating the quotient.
          */
          function divUInt(uint a, uint b) internal pure returns (MathError, uint) {
              if (b == 0) {
                  return (MathError.DIVISION_BY_ZERO, 0);
              }
      
              return (MathError.NO_ERROR, a / b);
          }
      
          /**
          * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).
          */
          function subUInt(uint a, uint b) internal pure returns (MathError, uint) {
              if (b <= a) {
                  return (MathError.NO_ERROR, a - b);
              } else {
                  return (MathError.INTEGER_UNDERFLOW, 0);
              }
          }
      
          /**
          * @dev Adds two numbers, returns an error on overflow.
          */
          function addUInt(uint a, uint b) internal pure returns (MathError, uint) {
              uint c = a + b;
      
              if (c >= a) {
                  return (MathError.NO_ERROR, c);
              } else {
                  return (MathError.INTEGER_OVERFLOW, 0);
              }
          }
      
          /**
          * @dev add a and b and then subtract c
          */
          function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {
              (MathError err0, uint sum) = addUInt(a, b);
      
              if (err0 != MathError.NO_ERROR) {
                  return (err0, 0);
              }
      
              return subUInt(sum, c);
          }
      }
      
      // File: contracts/Exponential.sol
      
      pragma solidity ^0.5.8;
      
      
      /**
       * @title Exponential module for storing fixed-decision decimals
       * @author Compound
       * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.
       *         Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:
       *         `Exp({mantissa: 5100000000000000000})`.
       */
      contract Exponential is CarefulMath {
          uint constant expScale = 1e18;
          uint constant halfExpScale = expScale/2;
          uint constant mantissaOne = expScale;
      
          struct Exp {
              uint mantissa;
          }
      
          /**
           * @dev Creates an exponential from numerator and denominator values.
           *      Note: Returns an error if (`num` * 10e18) > MAX_INT,
           *            or if `denom` is zero.
           */
          function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) {
              (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);
              if (err0 != MathError.NO_ERROR) {
                  return (err0, Exp({mantissa: 0}));
              }
      
              (MathError err1, uint rational) = divUInt(scaledNumerator, denom);
              if (err1 != MathError.NO_ERROR) {
                  return (err1, Exp({mantissa: 0}));
              }
      
              return (MathError.NO_ERROR, Exp({mantissa: rational}));
          }
      
          /**
           * @dev Adds two exponentials, returning a new exponential.
           */
          function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
              (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);
      
              return (error, Exp({mantissa: result}));
          }
      
          /**
           * @dev Subtracts two exponentials, returning a new exponential.
           */
          function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
              (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);
      
              return (error, Exp({mantissa: result}));
          }
      
          /**
           * @dev Multiply an Exp by a scalar, returning a new Exp.
           */
          function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) {
              (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);
              if (err0 != MathError.NO_ERROR) {
                  return (err0, Exp({mantissa: 0}));
              }
      
              return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa}));
          }
      
          /**
           * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.
           */
          function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) {
              (MathError err, Exp memory product) = mulScalar(a, scalar);
              if (err != MathError.NO_ERROR) {
                  return (err, 0);
              }
      
              return (MathError.NO_ERROR, truncate(product));
          }
      
          /**
           * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.
           */
          function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) {
              (MathError err, Exp memory product) = mulScalar(a, scalar);
              if (err != MathError.NO_ERROR) {
                  return (err, 0);
              }
      
              return addUInt(truncate(product), addend);
          }
      
          /**
           * @dev Divide an Exp by a scalar, returning a new Exp.
           */
          function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) {
              (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);
              if (err0 != MathError.NO_ERROR) {
                  return (err0, Exp({mantissa: 0}));
              }
      
              return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa}));
          }
      
          /**
           * @dev Divide a scalar by an Exp, returning a new Exp.
           */
          function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) {
              /*
                We are doing this as:
                getExp(mulUInt(expScale, scalar), divisor.mantissa)
      
                How it works:
                Exp = a / b;
                Scalar = s;
                `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`
              */
              (MathError err0, uint numerator) = mulUInt(expScale, scalar);
              if (err0 != MathError.NO_ERROR) {
                  return (err0, Exp({mantissa: 0}));
              }
              return getExp(numerator, divisor.mantissa);
          }
      
          /**
           * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.
           */
          function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) {
              (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);
              if (err != MathError.NO_ERROR) {
                  return (err, 0);
              }
      
              return (MathError.NO_ERROR, truncate(fraction));
          }
      
          /**
           * @dev Multiplies two exponentials, returning a new exponential.
           */
          function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
      
              (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);
              if (err0 != MathError.NO_ERROR) {
                  return (err0, Exp({mantissa: 0}));
              }
      
              // We add half the scale before dividing so that we get rounding instead of truncation.
              //  See "Listing 6" and text above it at https://accu.org/index.php/journals/1717
              // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.
              (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);
              if (err1 != MathError.NO_ERROR) {
                  return (err1, Exp({mantissa: 0}));
              }
      
              (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);
              // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.
              assert(err2 == MathError.NO_ERROR);
      
              return (MathError.NO_ERROR, Exp({mantissa: product}));
          }
      
          /**
           * @dev Multiplies two exponentials given their mantissas, returning a new exponential.
           */
          function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) {
              return mulExp(Exp({mantissa: a}), Exp({mantissa: b}));
          }
      
          /**
           * @dev Multiplies three exponentials, returning a new exponential.
           */
          function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) {
              (MathError err, Exp memory ab) = mulExp(a, b);
              if (err != MathError.NO_ERROR) {
                  return (err, ab);
              }
              return mulExp(ab, c);
          }
      
          /**
           * @dev Divides two exponentials, returning a new exponential.
           *     (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,
           *  which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)
           */
          function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
              return getExp(a.mantissa, b.mantissa);
          }
      
          /**
           * @dev Truncates the given exp to a whole number value.
           *      For example, truncate(Exp{mantissa: 15 * expScale}) = 15
           */
          function truncate(Exp memory exp) pure internal returns (uint) {
              // Note: We are not using careful math here as we're performing a division that cannot fail
              return exp.mantissa / expScale;
          }
      
          /**
           * @dev Checks if first Exp is less than second Exp.
           */
          function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) {
              return left.mantissa < right.mantissa; //TODO: Add some simple tests and this in another PR yo.
          }
      
          /**
           * @dev Checks if left Exp <= right Exp.
           */
          function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) {
              return left.mantissa <= right.mantissa;
          }
      
          /**
           * @dev returns true if Exp is exactly zero
           */
          function isZeroExp(Exp memory value) pure internal returns (bool) {
              return value.mantissa == 0;
          }
      }
      
      // File: contracts/EIP20Interface.sol
      
      pragma solidity ^0.5.8;
      
      /**
       * @title ERC 20 Token Standard Interface
       *  https://eips.ethereum.org/EIPS/eip-20
       */
      interface EIP20Interface {
      
          /**
            * @notice Get the total number of tokens in circulation
            * @return The supply of tokens
            */
          function totalSupply() external view returns (uint256);
      
          /**
           * @notice Gets the balance of the specified address
           * @param owner The address from which the balance will be retrieved
           * @return The balance
           */
          function balanceOf(address owner) external view returns (uint256 balance);
      
          /**
            * @notice Transfer `amount` tokens from `msg.sender` to `dst`
            * @param dst The address of the destination account
            * @param amount The number of tokens to transfer
            * @return Whether or not the transfer succeeded
            */
          function transfer(address dst, uint256 amount) external returns (bool success);
      
          /**
            * @notice Transfer `amount` tokens from `src` to `dst`
            * @param src The address of the source account
            * @param dst The address of the destination account
            * @param amount The number of tokens to transfer
            * @return Whether or not the transfer succeeded
            */
          function transferFrom(address src, address dst, uint256 amount) external returns (bool success);
      
          /**
            * @notice Approve `spender` to transfer up to `amount` from `src`
            * @dev This will overwrite the approval amount for `spender`
            *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
            * @param spender The address of the account which may transfer tokens
            * @param amount The number of tokens that are approved (-1 means infinite)
            * @return Whether or not the approval succeeded
            */
          function approve(address spender, uint256 amount) external returns (bool success);
      
          /**
            * @notice Get the current allowance from `owner` for `spender`
            * @param owner The address of the account which owns the tokens to be spent
            * @param spender The address of the account which may transfer tokens
            * @return The number of tokens allowed to be spent (-1 means infinite)
            */
          function allowance(address owner, address spender) external view returns (uint256 remaining);
      
          event Transfer(address indexed from, address indexed to, uint256 amount);
          event Approval(address indexed owner, address indexed spender, uint256 amount);
      }
      
      // File: contracts/EIP20NonStandardInterface.sol
      
      pragma solidity ^0.5.8;
      
      /**
       * @title EIP20NonStandardInterface
       * @dev Version of ERC20 with no return values for `transfer` and `transferFrom`
       *  See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
       */
      interface EIP20NonStandardInterface {
      
          /**
           * @notice Get the total number of tokens in circulation
           * @return The supply of tokens
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @notice Gets the balance of the specified address
           * @param owner The address from which the balance will be retrieved
           * @return The balance
           */
          function balanceOf(address owner) external view returns (uint256 balance);
      
          ///
          /// !!!!!!!!!!!!!!
          /// !!! NOTICE !!! `transfer` does not return a value, in violation of the ERC-20 specification
          /// !!!!!!!!!!!!!!
          ///
      
          /**
            * @notice Transfer `amount` tokens from `msg.sender` to `dst`
            * @param dst The address of the destination account
            * @param amount The number of tokens to transfer
            */
          function transfer(address dst, uint256 amount) external;
      
          ///
          /// !!!!!!!!!!!!!!
          /// !!! NOTICE !!! `transferFrom` does not return a value, in violation of the ERC-20 specification
          /// !!!!!!!!!!!!!!
          ///
      
          /**
            * @notice Transfer `amount` tokens from `src` to `dst`
            * @param src The address of the source account
            * @param dst The address of the destination account
            * @param amount The number of tokens to transfer
            */
          function transferFrom(address src, address dst, uint256 amount) external;
      
          /**
            * @notice Approve `spender` to transfer up to `amount` from `src`
            * @dev This will overwrite the approval amount for `spender`
            *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
            * @param spender The address of the account which may transfer tokens
            * @param amount The number of tokens that are approved
            * @return Whether or not the approval succeeded
            */
          function approve(address spender, uint256 amount) external returns (bool success);
      
          /**
            * @notice Get the current allowance from `owner` for `spender`
            * @param owner The address of the account which owns the tokens to be spent
            * @param spender The address of the account which may transfer tokens
            * @return The number of tokens allowed to be spent
            */
          function allowance(address owner, address spender) external view returns (uint256 remaining);
      
          event Transfer(address indexed from, address indexed to, uint256 amount);
          event Approval(address indexed owner, address indexed spender, uint256 amount);
      }
      
      // File: contracts/ReentrancyGuard.sol
      
      pragma solidity ^0.5.8;
      
      /**
       * @title Helps contracts guard against reentrancy attacks.
       * @author Remco Bloemen <remco@2π.com>, Eenae <[email protected]>
       * @dev If you mark a function `nonReentrant`, you should also
       * mark it `external`.
       */
      contract ReentrancyGuard {
          /// @dev counter to allow mutex lock with only one SSTORE operation
          uint256 private _guardCounter;
      
          constructor () internal {
              // The counter starts at one to prevent changing it from zero to a non-zero
              // value, which is a more expensive operation.
              _guardCounter = 1;
          }
      
          /**
           * @dev Prevents a contract from calling itself, directly or indirectly.
           * Calling a `nonReentrant` function from another `nonReentrant`
           * function is not supported. It is possible to prevent this from happening
           * by making the `nonReentrant` function external, and make it call a
           * `private` function that does the actual work.
           */
          modifier nonReentrant() {
              _guardCounter += 1;
              uint256 localCounter = _guardCounter;
              _;
              require(localCounter == _guardCounter, "re-entered");
          }
      }
      
      // File: contracts/InterestRateModel.sol
      
      pragma solidity ^0.5.8;
      
      /**
        * @title The Compound InterestRateModel Interface
        * @author Compound
        * @notice Any interest rate model should derive from this contract.
        * @dev These functions are specifically not marked `pure` as implementations of this
        *      contract may read from storage variables.
        */
      interface InterestRateModel {
          /**
            * @notice Gets the current borrow interest rate based on the given asset, total cash, total borrows
            *         and total reserves.
            * @dev The return value should be scaled by 1e18, thus a return value of
            *      `(true, 1000000000000)` implies an interest rate of 0.000001 or 0.0001% *per block*.
            * @param cash The total cash of the underlying asset in the CToken
            * @param borrows The total borrows of the underlying asset in the CToken
            * @param reserves The total reserves of the underlying asset in the CToken
            * @return Success or failure and the borrow interest rate per block scaled by 10e18
            */
          function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint, uint);
      
          /**
            * @notice Marker function used for light validation when updating the interest rate model of a market
            * @dev Marker function used for light validation when updating the interest rate model of a market. Implementations should simply return true.
            * @return Success or failure
            */
          function isInterestRateModel() external view returns (bool);
      }
      
      // File: contracts/CToken.sol
      
      pragma solidity ^0.5.8;
      
      
      
      
      
      
      
      
      /**
       * @title Compound's CToken Contract
       * @notice Abstract base for CTokens
       * @author Compound
       */
      contract CToken is EIP20Interface, Exponential, TokenErrorReporter, ReentrancyGuard {
          /**
           * @notice Indicator that this is a CToken contract (for inspection)
           */
          bool public constant isCToken = true;
      
          /**
           * @notice EIP-20 token name for this token
           */
          string public name;
      
          /**
           * @notice EIP-20 token symbol for this token
           */
          string public symbol;
      
          /**
           * @notice EIP-20 token decimals for this token
           */
          uint public decimals;
      
          /**
           * @notice Maximum borrow rate that can ever be applied (.0005% / block)
           */
          uint constant borrowRateMaxMantissa = 5e14;
      
          /**
           * @notice Maximum fraction of interest that can be set aside for reserves
           */
          uint constant reserveFactorMaxMantissa = 1e18;
      
          /**
           * @notice Administrator for this contract
           */
          address payable public admin;
      
          /**
           * @notice Pending administrator for this contract
           */
          address payable public pendingAdmin;
      
          /**
           * @notice Contract which oversees inter-cToken operations
           */
          ComptrollerInterface public comptroller;
      
          /**
           * @notice Model which tells what the current interest rate should be
           */
          InterestRateModel public interestRateModel;
      
          /**
           * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0)
           */
          uint public initialExchangeRateMantissa;
      
          /**
           * @notice Fraction of interest currently set aside for reserves
           */
          uint public reserveFactorMantissa;
      
          /**
           * @notice Block number that interest was last accrued at
           */
          uint public accrualBlockNumber;
      
          /**
           * @notice Accumulator of total earned interest since the opening of the market
           */
          uint public borrowIndex;
      
          /**
           * @notice Total amount of outstanding borrows of the underlying in this market
           */
          uint public totalBorrows;
      
          /**
           * @notice Total amount of reserves of the underlying held in this market
           */
          uint public totalReserves;
      
          /**
           * @notice Total number of tokens in circulation
           */
          uint256 public totalSupply;
      
          /**
           * @notice Official record of token balances for each account
           */
          mapping (address => uint256) accountTokens;
      
          /**
           * @notice Approved token transfer amounts on behalf of others
           */
          mapping (address => mapping (address => uint256)) transferAllowances;
      
          /**
           * @notice Container for borrow balance information
           * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action
           * @member interestIndex Global borrowIndex as of the most recent balance-changing action
           */
          struct BorrowSnapshot {
              uint principal;
              uint interestIndex;
          }
      
          /**
           * @notice Mapping of account addresses to outstanding borrow balances
           */
          mapping(address => BorrowSnapshot) accountBorrows;
      
      
          /*** Market Events ***/
      
          /**
           * @notice Event emitted when interest is accrued
           */
          event AccrueInterest(uint interestAccumulated, uint borrowIndex, uint totalBorrows);
      
          /**
           * @notice Event emitted when tokens are minted
           */
          event Mint(address minter, uint mintAmount, uint mintTokens);
      
          /**
           * @notice Event emitted when tokens are redeemed
           */
          event Redeem(address redeemer, uint redeemAmount, uint redeemTokens);
      
          /**
           * @notice Event emitted when underlying is borrowed
           */
          event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);
      
          /**
           * @notice Event emitted when a borrow is repaid
           */
          event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);
      
          /**
           * @notice Event emitted when a borrow is liquidated
           */
          event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens);
      
      
          /*** Admin Events ***/
      
          /**
           * @notice Event emitted when pendingAdmin is changed
           */
          event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
      
          /**
           * @notice Event emitted when pendingAdmin is accepted, which means admin is updated
           */
          event NewAdmin(address oldAdmin, address newAdmin);
      
          /**
           * @notice Event emitted when comptroller is changed
           */
          event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);
      
          /**
           * @notice Event emitted when interestRateModel is changed
           */
          event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel);
      
          /**
           * @notice Event emitted when the reserve factor is changed
           */
          event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);
      
          /**
           * @notice Event emitted when the reserves are reduced
           */
          event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves);
      
      
          /**
           * @notice Construct a new money market
           * @param comptroller_ The address of the Comptroller
           * @param interestRateModel_ The address of the interest rate model
           * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
           * @param name_ EIP-20 name of this token
           * @param symbol_ EIP-20 symbol of this token
           * @param decimals_ EIP-20 decimal precision of this token
           */
          constructor(ComptrollerInterface comptroller_,
                      InterestRateModel interestRateModel_,
                      uint initialExchangeRateMantissa_,
                      string memory name_,
                      string memory symbol_,
                      uint decimals_) internal {
              // Set admin to msg.sender
              admin = msg.sender;
      
              // Set initial exchange rate
              initialExchangeRateMantissa = initialExchangeRateMantissa_;
              require(initialExchangeRateMantissa > 0, "Initial exchange rate must be greater than zero.");
      
              // Set the comptroller
              uint err = _setComptroller(comptroller_);
              require(err == uint(Error.NO_ERROR), "Setting comptroller failed");
      
              // Initialize block number and borrow index (block number mocks depend on comptroller being set)
              accrualBlockNumber = getBlockNumber();
              borrowIndex = mantissaOne;
      
              // Set the interest rate model (depends on block number / borrow index)
              err = _setInterestRateModelFresh(interestRateModel_);
              require(err == uint(Error.NO_ERROR), "Setting interest rate model failed");
      
              name = name_;
              symbol = symbol_;
              decimals = decimals_;
          }
      
          /**
           * @notice Transfer `tokens` tokens from `src` to `dst` by `spender`
           * @dev Called by both `transfer` and `transferFrom` internally
           * @param spender The address of the account performing the transfer
           * @param src The address of the source account
           * @param dst The address of the destination account
           * @param tokens The number of tokens to transfer
           * @return Whether or not the transfer succeeded
           */
          function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {
              /* Fail if transfer not allowed */
              uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Do not allow self-transfers */
              if (src == dst) {
                  return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);
              }
      
              /* Get the allowance, infinite for the account owner */
              uint startingAllowance = 0;
              if (spender == src) {
                  startingAllowance = uint(-1);
              } else {
                  startingAllowance = transferAllowances[src][spender];
              }
      
              /* Do the calculations, checking for {under,over}flow */
              MathError mathErr;
              uint allowanceNew;
              uint srcTokensNew;
              uint dstTokensNew;
      
              (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);
              if (mathErr != MathError.NO_ERROR) {
                  return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);
              }
      
              (mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens);
              if (mathErr != MathError.NO_ERROR) {
                  return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);
              }
      
              (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);
              if (mathErr != MathError.NO_ERROR) {
                  return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              accountTokens[src] = srcTokensNew;
              accountTokens[dst] = dstTokensNew;
      
              /* Eat some of the allowance (if necessary) */
              if (startingAllowance != uint(-1)) {
                  transferAllowances[src][spender] = allowanceNew;
              }
      
              /* We emit a Transfer event */
              emit Transfer(src, dst, tokens);
      
              /* We call the defense hook (which checks for under-collateralization) */
              comptroller.transferVerify(address(this), src, dst, tokens);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Transfer `amount` tokens from `msg.sender` to `dst`
           * @param dst The address of the destination account
           * @param amount The number of tokens to transfer
           * @return Whether or not the transfer succeeded
           */
          function transfer(address dst, uint256 amount) external nonReentrant returns (bool) {
              return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Transfer `amount` tokens from `src` to `dst`
           * @param src The address of the source account
           * @param dst The address of the destination account
           * @param amount The number of tokens to transfer
           * @return Whether or not the transfer succeeded
           */
          function transferFrom(address src, address dst, uint256 amount) external nonReentrant returns (bool) {
              return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Approve `spender` to transfer up to `amount` from `src`
           * @dev This will overwrite the approval amount for `spender`
           *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
           * @param spender The address of the account which may transfer tokens
           * @param amount The number of tokens that are approved (-1 means infinite)
           * @return Whether or not the approval succeeded
           */
          function approve(address spender, uint256 amount) external returns (bool) {
              address src = msg.sender;
              transferAllowances[src][spender] = amount;
              emit Approval(src, spender, amount);
              return true;
          }
      
          /**
           * @notice Get the current allowance from `owner` for `spender`
           * @param owner The address of the account which owns the tokens to be spent
           * @param spender The address of the account which may transfer tokens
           * @return The number of tokens allowed to be spent (-1 means infinite)
           */
          function allowance(address owner, address spender) external view returns (uint256) {
              return transferAllowances[owner][spender];
          }
      
          /**
           * @notice Get the token balance of the `owner`
           * @param owner The address of the account to query
           * @return The number of tokens owned by `owner`
           */
          function balanceOf(address owner) external view returns (uint256) {
              return accountTokens[owner];
          }
      
          /**
           * @notice Get the underlying balance of the `owner`
           * @dev This also accrues interest in a transaction
           * @param owner The address of the account to query
           * @return The amount of underlying owned by `owner`
           */
          function balanceOfUnderlying(address owner) external returns (uint) {
              Exp memory exchangeRate = Exp({mantissa: exchangeRateCurrent()});
              (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);
              require(mErr == MathError.NO_ERROR);
              return balance;
          }
      
          /**
           * @notice Get a snapshot of the account's balances, and the cached exchange rate
           * @dev This is used by comptroller to more efficiently perform liquidity checks.
           * @param account Address of the account to snapshot
           * @return (possible error, token balance, borrow balance, exchange rate mantissa)
           */
          function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) {
              uint cTokenBalance = accountTokens[account];
              uint borrowBalance;
              uint exchangeRateMantissa;
      
              MathError mErr;
      
              (mErr, borrowBalance) = borrowBalanceStoredInternal(account);
              if (mErr != MathError.NO_ERROR) {
                  return (uint(Error.MATH_ERROR), 0, 0, 0);
              }
      
              (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();
              if (mErr != MathError.NO_ERROR) {
                  return (uint(Error.MATH_ERROR), 0, 0, 0);
              }
      
              return (uint(Error.NO_ERROR), cTokenBalance, borrowBalance, exchangeRateMantissa);
          }
      
          /**
           * @dev Function to simply retrieve block number
           *  This exists mainly for inheriting test contracts to stub this result.
           */
          function getBlockNumber() internal view returns (uint) {
              return block.number;
          }
      
          /**
           * @notice Returns the current per-block borrow interest rate for this cToken
           * @return The borrow interest rate per block, scaled by 1e18
           */
          function borrowRatePerBlock() external view returns (uint) {
              (uint opaqueErr, uint borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
              require(opaqueErr == 0, "borrowRatePerBlock: interestRateModel.borrowRate failed"); // semi-opaque
              return borrowRateMantissa;
          }
      
          /**
           * @notice Returns the current per-block supply interest rate for this cToken
           * @return The supply interest rate per block, scaled by 1e18
           */
          function supplyRatePerBlock() external view returns (uint) {
              /* We calculate the supply rate:
               *  underlying = totalSupply × exchangeRate
               *  borrowsPer = totalBorrows ÷ underlying
               *  supplyRate = borrowRate × (1-reserveFactor) × borrowsPer
               */
              uint exchangeRateMantissa = exchangeRateStored();
      
              (uint e0, uint borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
              require(e0 == 0, "supplyRatePerBlock: calculating borrowRate failed"); // semi-opaque
      
              (MathError e1, Exp memory underlying) = mulScalar(Exp({mantissa: exchangeRateMantissa}), totalSupply);
              require(e1 == MathError.NO_ERROR, "supplyRatePerBlock: calculating underlying failed");
      
              (MathError e2, Exp memory borrowsPer) = divScalarByExp(totalBorrows, underlying);
              require(e2 == MathError.NO_ERROR, "supplyRatePerBlock: calculating borrowsPer failed");
      
              (MathError e3, Exp memory oneMinusReserveFactor) = subExp(Exp({mantissa: mantissaOne}), Exp({mantissa: reserveFactorMantissa}));
              require(e3 == MathError.NO_ERROR, "supplyRatePerBlock: calculating oneMinusReserveFactor failed");
      
              (MathError e4, Exp memory supplyRate) = mulExp3(Exp({mantissa: borrowRateMantissa}), oneMinusReserveFactor, borrowsPer);
              require(e4 == MathError.NO_ERROR, "supplyRatePerBlock: calculating supplyRate failed");
      
              return supplyRate.mantissa;
          }
      
          /**
           * @notice Returns the current total borrows plus accrued interest
           * @return The total borrows with interest
           */
          function totalBorrowsCurrent() external nonReentrant returns (uint) {
              require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed");
              return totalBorrows;
          }
      
          /**
           * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex
           * @param account The address whose balance should be calculated after updating borrowIndex
           * @return The calculated balance
           */
          function borrowBalanceCurrent(address account) external nonReentrant returns (uint) {
              require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed");
              return borrowBalanceStored(account);
          }
      
          /**
           * @notice Return the borrow balance of account based on stored data
           * @param account The address whose balance should be calculated
           * @return The calculated balance
           */
          function borrowBalanceStored(address account) public view returns (uint) {
              (MathError err, uint result) = borrowBalanceStoredInternal(account);
              require(err == MathError.NO_ERROR, "borrowBalanceStored: borrowBalanceStoredInternal failed");
              return result;
          }
      
          /**
           * @notice Return the borrow balance of account based on stored data
           * @param account The address whose balance should be calculated
           * @return (error code, the calculated balance or 0 if error code is non-zero)
           */
          function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {
              /* Note: we do not assert that the market is up to date */
              MathError mathErr;
              uint principalTimesIndex;
              uint result;
      
              /* Get borrowBalance and borrowIndex */
              BorrowSnapshot storage borrowSnapshot = accountBorrows[account];
      
              /* If borrowBalance = 0 then borrowIndex is likely also 0.
               * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.
               */
              if (borrowSnapshot.principal == 0) {
                  return (MathError.NO_ERROR, 0);
              }
      
              /* Calculate new borrow balance using the interest index:
               *  recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex
               */
              (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);
              if (mathErr != MathError.NO_ERROR) {
                  return (mathErr, 0);
              }
      
              (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);
              if (mathErr != MathError.NO_ERROR) {
                  return (mathErr, 0);
              }
      
              return (MathError.NO_ERROR, result);
          }
      
          /**
           * @notice Accrue interest then return the up-to-date exchange rate
           * @return Calculated exchange rate scaled by 1e18
           */
          function exchangeRateCurrent() public nonReentrant returns (uint) {
              require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed");
              return exchangeRateStored();
          }
      
          /**
           * @notice Calculates the exchange rate from the underlying to the CToken
           * @dev This function does not accrue interest before calculating the exchange rate
           * @return Calculated exchange rate scaled by 1e18
           */
          function exchangeRateStored() public view returns (uint) {
              (MathError err, uint result) = exchangeRateStoredInternal();
              require(err == MathError.NO_ERROR, "exchangeRateStored: exchangeRateStoredInternal failed");
              return result;
          }
      
          /**
           * @notice Calculates the exchange rate from the underlying to the CToken
           * @dev This function does not accrue interest before calculating the exchange rate
           * @return (error code, calculated exchange rate scaled by 1e18)
           */
          function exchangeRateStoredInternal() internal view returns (MathError, uint) {
              if (totalSupply == 0) {
                  /*
                   * If there are no tokens minted:
                   *  exchangeRate = initialExchangeRate
                   */
                  return (MathError.NO_ERROR, initialExchangeRateMantissa);
              } else {
                  /*
                   * Otherwise:
                   *  exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply
                   */
                  uint totalCash = getCashPrior();
                  uint cashPlusBorrowsMinusReserves;
                  Exp memory exchangeRate;
                  MathError mathErr;
      
                  (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);
                  if (mathErr != MathError.NO_ERROR) {
                      return (mathErr, 0);
                  }
      
                  (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, totalSupply);
                  if (mathErr != MathError.NO_ERROR) {
                      return (mathErr, 0);
                  }
      
                  return (MathError.NO_ERROR, exchangeRate.mantissa);
              }
          }
      
          /**
           * @notice Get cash balance of this cToken in the underlying asset
           * @return The quantity of underlying asset owned by this contract
           */
          function getCash() external view returns (uint) {
              return getCashPrior();
          }
      
          struct AccrueInterestLocalVars {
              MathError mathErr;
              uint opaqueErr;
              uint borrowRateMantissa;
              uint currentBlockNumber;
              uint blockDelta;
      
              Exp simpleInterestFactor;
      
              uint interestAccumulated;
              uint totalBorrowsNew;
              uint totalReservesNew;
              uint borrowIndexNew;
          }
      
          /**
            * @notice Applies accrued interest to total borrows and reserves.
            * @dev This calculates interest accrued from the last checkpointed block
            *      up to the current block and writes new checkpoint to storage.
            */
          function accrueInterest() public returns (uint) {
              AccrueInterestLocalVars memory vars;
      
              /* Calculate the current borrow interest rate */
              (vars.opaqueErr, vars.borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
              require(vars.borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high");
              if (vars.opaqueErr != 0) {
                  return failOpaque(Error.INTEREST_RATE_MODEL_ERROR, FailureInfo.ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED, vars.opaqueErr);
              }
      
              /* Remember the initial block number */
              vars.currentBlockNumber = getBlockNumber();
      
              /* Calculate the number of blocks elapsed since the last accrual */
              (vars.mathErr, vars.blockDelta) = subUInt(vars.currentBlockNumber, accrualBlockNumber);
              assert(vars.mathErr == MathError.NO_ERROR); // Block delta should always succeed and if it doesn't, blow up.
      
              /*
               * Calculate the interest accumulated into borrows and reserves and the new index:
               *  simpleInterestFactor = borrowRate * blockDelta
               *  interestAccumulated = simpleInterestFactor * totalBorrows
               *  totalBorrowsNew = interestAccumulated + totalBorrows
               *  totalReservesNew = interestAccumulated * reserveFactor + totalReserves
               *  borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex
               */
              (vars.mathErr, vars.simpleInterestFactor) = mulScalar(Exp({mantissa: vars.borrowRateMantissa}), vars.blockDelta);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.interestAccumulated) = mulScalarTruncate(vars.simpleInterestFactor, totalBorrows);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.totalBorrowsNew) = addUInt(vars.interestAccumulated, totalBorrows);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa: reserveFactorMantissa}), vars.interestAccumulated, totalReserves);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.borrowIndexNew) = mulScalarTruncateAddUInt(vars.simpleInterestFactor, borrowIndex, borrowIndex);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /* We write the previously calculated values into storage */
              accrualBlockNumber = vars.currentBlockNumber;
              borrowIndex = vars.borrowIndexNew;
              totalBorrows = vars.totalBorrowsNew;
              totalReserves = vars.totalReservesNew;
      
              /* We emit an AccrueInterest event */
              emit AccrueInterest(vars.interestAccumulated, vars.borrowIndexNew, totalBorrows);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Sender supplies assets into the market and receives cTokens in exchange
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param mintAmount The amount of the underlying asset to supply
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function mintInternal(uint mintAmount) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
                  return fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED);
              }
              // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to
              return mintFresh(msg.sender, mintAmount);
          }
      
          struct MintLocalVars {
              Error err;
              MathError mathErr;
              uint exchangeRateMantissa;
              uint mintTokens;
              uint totalSupplyNew;
              uint accountTokensNew;
          }
      
          /**
           * @notice User supplies assets into the market and receives cTokens in exchange
           * @dev Assumes interest has already been accrued up to the current block
           * @param minter The address of the account which is supplying the assets
           * @param mintAmount The amount of the underlying asset to supply
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function mintFresh(address minter, uint mintAmount) internal returns (uint) {
              /* Fail if mint not allowed */
              uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Verify market's block number equals current block number */
              if (accrualBlockNumber != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK);
              }
      
              MintLocalVars memory vars;
      
              /* Fail if checkTransferIn fails */
              vars.err = checkTransferIn(minter, mintAmount);
              if (vars.err != Error.NO_ERROR) {
                  return fail(vars.err, FailureInfo.MINT_TRANSFER_IN_NOT_POSSIBLE);
              }
      
              /*
               * We get the current exchange rate and calculate the number of cTokens to be minted:
               *  mintTokens = mintAmount / exchangeRate
               */
              (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(mintAmount, Exp({mantissa: vars.exchangeRateMantissa}));
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /*
               * We calculate the new total supply of cTokens and minter token balance, checking for overflow:
               *  totalSupplyNew = totalSupply + mintTokens
               *  accountTokensNew = accountTokens[minter] + mintTokens
               */
              (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /*
               * We call doTransferIn for the minter and the mintAmount
               *  Note: The cToken must handle variations between ERC-20 and ETH underlying.
               *  On success, the cToken holds an additional mintAmount of cash.
               *  If doTransferIn fails despite the fact we checked pre-conditions,
               *   we revert because we can't be sure if side effects occurred.
               */
              vars.err = doTransferIn(minter, mintAmount);
              if (vars.err != Error.NO_ERROR) {
                  return fail(vars.err, FailureInfo.MINT_TRANSFER_IN_FAILED);
              }
      
              /* We write previously calculated values into storage */
              totalSupply = vars.totalSupplyNew;
              accountTokens[minter] = vars.accountTokensNew;
      
              /* We emit a Mint event, and a Transfer event */
              emit Mint(minter, mintAmount, vars.mintTokens);
              emit Transfer(address(this), minter, vars.mintTokens);
      
              /* We call the defense hook */
              comptroller.mintVerify(address(this), minter, mintAmount, vars.mintTokens);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Sender redeems cTokens in exchange for the underlying asset
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param redeemTokens The number of cTokens to redeem into underlying
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function redeemInternal(uint redeemTokens) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed
                  return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);
              }
              // redeemFresh emits redeem-specific logs on errors, so we don't need to
              return redeemFresh(msg.sender, redeemTokens, 0);
          }
      
          /**
           * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param redeemAmount The amount of underlying to redeem
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function redeemUnderlyingInternal(uint redeemAmount) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed
                  return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);
              }
              // redeemFresh emits redeem-specific logs on errors, so we don't need to
              return redeemFresh(msg.sender, 0, redeemAmount);
          }
      
          struct RedeemLocalVars {
              Error err;
              MathError mathErr;
              uint exchangeRateMantissa;
              uint redeemTokens;
              uint redeemAmount;
              uint totalSupplyNew;
              uint accountTokensNew;
          }
      
          /**
           * @notice User redeems cTokens in exchange for the underlying asset
           * @dev Assumes interest has already been accrued up to the current block
           * @param redeemer The address of the account which is redeeming the tokens
           * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be zero)
           * @param redeemAmountIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be zero)
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) {
              require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero");
      
              RedeemLocalVars memory vars;
      
              /* exchangeRate = invoke Exchange Rate Stored() */
              (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr));
              }
      
              /* If redeemTokensIn > 0: */
              if (redeemTokensIn > 0) {
                  /*
                   * We calculate the exchange rate and the amount of underlying to be redeemed:
                   *  redeemTokens = redeemTokensIn
                   *  redeemAmount = redeemTokensIn x exchangeRateCurrent
                   */
                  vars.redeemTokens = redeemTokensIn;
      
                  (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn);
                  if (vars.mathErr != MathError.NO_ERROR) {
                      return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr));
                  }
              } else {
                  /*
                   * We get the current exchange rate and calculate the amount to be redeemed:
                   *  redeemTokens = redeemAmountIn / exchangeRate
                   *  redeemAmount = redeemAmountIn
                   */
      
                  (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa}));
                  if (vars.mathErr != MathError.NO_ERROR) {
                      return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr));
                  }
      
                  vars.redeemAmount = redeemAmountIn;
              }
      
              /* Fail if redeem not allowed */
              uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Verify market's block number equals current block number */
              if (accrualBlockNumber != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK);
              }
      
              /*
               * We calculate the new total supply and redeemer balance, checking for underflow:
               *  totalSupplyNew = totalSupply - redeemTokens
               *  accountTokensNew = accountTokens[redeemer] - redeemTokens
               */
              (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /* Fail gracefully if protocol has insufficient cash */
              if (getCashPrior() < vars.redeemAmount) {
                  return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE);
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /*
               * We invoke doTransferOut for the redeemer and the redeemAmount.
               *  Note: The cToken must handle variations between ERC-20 and ETH underlying.
               *  On success, the cToken has redeemAmount less of cash.
               *  If doTransferOut fails despite the fact we checked pre-conditions,
               *   we revert because we can't be sure if side effects occurred.
               */
              vars.err = doTransferOut(redeemer, vars.redeemAmount);
              require(vars.err == Error.NO_ERROR, "redeem transfer out failed");
      
              /* We write previously calculated values into storage */
              totalSupply = vars.totalSupplyNew;
              accountTokens[redeemer] = vars.accountTokensNew;
      
              /* We emit a Transfer event, and a Redeem event */
              emit Transfer(redeemer, address(this), vars.redeemTokens);
              emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens);
      
              /* We call the defense hook */
              comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
            * @notice Sender borrows assets from the protocol to their own address
            * @param borrowAmount The amount of the underlying asset to borrow
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
                  return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);
              }
              // borrowFresh emits borrow-specific logs on errors, so we don't need to
              return borrowFresh(msg.sender, borrowAmount);
          }
      
          struct BorrowLocalVars {
              Error err;
              MathError mathErr;
              uint accountBorrows;
              uint accountBorrowsNew;
              uint totalBorrowsNew;
          }
      
          /**
            * @notice Users borrow assets from the protocol to their own address
            * @param borrowAmount The amount of the underlying asset to borrow
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function borrowFresh(address payable borrower, uint borrowAmount) internal returns (uint) {
              /* Fail if borrow not allowed */
              uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.BORROW_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Verify market's block number equals current block number */
              if (accrualBlockNumber != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.BORROW_FRESHNESS_CHECK);
              }
      
              /* Fail gracefully if protocol has insufficient underlying cash */
              if (getCashPrior() < borrowAmount) {
                  return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.BORROW_CASH_NOT_AVAILABLE);
              }
      
              BorrowLocalVars memory vars;
      
              /*
               * We calculate the new borrower and total borrow balances, failing on overflow:
               *  accountBorrowsNew = accountBorrows + borrowAmount
               *  totalBorrowsNew = totalBorrows + borrowAmount
               */
              (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /*
               * We invoke doTransferOut for the borrower and the borrowAmount.
               *  Note: The cToken must handle variations between ERC-20 and ETH underlying.
               *  On success, the cToken borrowAmount less of cash.
               *  If doTransferOut fails despite the fact we checked pre-conditions,
               *   we revert because we can't be sure if side effects occurred.
               */
              vars.err = doTransferOut(borrower, borrowAmount);
              require(vars.err == Error.NO_ERROR, "borrow transfer out failed");
      
              /* We write the previously calculated values into storage */
              accountBorrows[borrower].principal = vars.accountBorrowsNew;
              accountBorrows[borrower].interestIndex = borrowIndex;
              totalBorrows = vars.totalBorrowsNew;
      
              /* We emit a Borrow event */
              emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);
      
              /* We call the defense hook */
              comptroller.borrowVerify(address(this), borrower, borrowAmount);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Sender repays their own borrow
           * @param repayAmount The amount to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
                  return fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED);
              }
              // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to
              return repayBorrowFresh(msg.sender, msg.sender, repayAmount);
          }
      
          /**
           * @notice Sender repays a borrow belonging to borrower
           * @param borrower the account with the debt being payed off
           * @param repayAmount The amount to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
                  return fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED);
              }
              // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to
              return repayBorrowFresh(msg.sender, borrower, repayAmount);
          }
      
          struct RepayBorrowLocalVars {
              Error err;
              MathError mathErr;
              uint repayAmount;
              uint borrowerIndex;
              uint accountBorrows;
              uint accountBorrowsNew;
              uint totalBorrowsNew;
          }
      
          /**
           * @notice Borrows are repaid by another user (possibly the borrower).
           * @param payer the account paying off the borrow
           * @param borrower the account with the debt being payed off
           * @param repayAmount the amount of undelrying tokens being returned
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint) {
              /* Fail if repayBorrow not allowed */
              uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Verify market's block number equals current block number */
              if (accrualBlockNumber != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK);
              }
      
              RepayBorrowLocalVars memory vars;
      
              /* We remember the original borrowerIndex for verification purposes */
              vars.borrowerIndex = accountBorrows[borrower].interestIndex;
      
              /* We fetch the amount the borrower owes, with accumulated interest */
              (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /* If repayAmount == -1, repayAmount = accountBorrows */
              if (repayAmount == uint(-1)) {
                  vars.repayAmount = vars.accountBorrows;
              } else {
                  vars.repayAmount = repayAmount;
              }
      
              /* Fail if checkTransferIn fails */
              vars.err = checkTransferIn(payer, vars.repayAmount);
              if (vars.err != Error.NO_ERROR) {
                  return fail(vars.err, FailureInfo.REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE);
              }
      
              /*
               * We calculate the new borrower and total borrow balances, failing on underflow:
               *  accountBorrowsNew = accountBorrows - repayAmount
               *  totalBorrowsNew = totalBorrows - repayAmount
               */
              (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.repayAmount);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.repayAmount);
              if (vars.mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr));
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /*
               * We call doTransferIn for the payer and the repayAmount
               *  Note: The cToken must handle variations between ERC-20 and ETH underlying.
               *  On success, the cToken holds an additional repayAmount of cash.
               *  If doTransferIn fails despite the fact we checked pre-conditions,
               *   we revert because we can't be sure if side effects occurred.
               */
              vars.err = doTransferIn(payer, vars.repayAmount);
              require(vars.err == Error.NO_ERROR, "repay borrow transfer in failed");
      
              /* We write the previously calculated values into storage */
              accountBorrows[borrower].principal = vars.accountBorrowsNew;
              accountBorrows[borrower].interestIndex = borrowIndex;
              totalBorrows = vars.totalBorrowsNew;
      
              /* We emit a RepayBorrow event */
              emit RepayBorrow(payer, borrower, vars.repayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);
      
              /* We call the defense hook */
              comptroller.repayBorrowVerify(address(this), payer, borrower, vars.repayAmount, vars.borrowerIndex);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice The sender liquidates the borrowers collateral.
           *  The collateral seized is transferred to the liquidator.
           * @param borrower The borrower of this cToken to be liquidated
           * @param cTokenCollateral The market in which to seize collateral from the borrower
           * @param repayAmount The amount of the underlying borrowed asset to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function liquidateBorrowInternal(address borrower, uint repayAmount, CToken cTokenCollateral) internal nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
                  return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED);
              }
      
              error = cTokenCollateral.accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
                  return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED);
              }
      
              // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to
              return liquidateBorrowFresh(msg.sender, borrower, repayAmount, cTokenCollateral);
          }
      
          /**
           * @notice The liquidator liquidates the borrowers collateral.
           *  The collateral seized is transferred to the liquidator.
           * @param borrower The borrower of this cToken to be liquidated
           * @param liquidator The address repaying the borrow and seizing collateral
           * @param cTokenCollateral The market in which to seize collateral from the borrower
           * @param repayAmount The amount of the underlying borrowed asset to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function liquidateBorrowFresh(address liquidator, address borrower, uint repayAmount, CToken cTokenCollateral) internal returns (uint) {
              /* Fail if liquidate not allowed */
              uint allowed = comptroller.liquidateBorrowAllowed(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Verify market's block number equals current block number */
              if (accrualBlockNumber != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK);
              }
      
              /* Verify cTokenCollateral market's block number equals current block number */
              if (cTokenCollateral.accrualBlockNumber() != getBlockNumber()) {
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK);
              }
      
              /* Fail if borrower = liquidator */
              if (borrower == liquidator) {
                  return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER);
              }
      
              /* Fail if repayAmount = 0 */
              if (repayAmount == 0) {
                  return fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO);
              }
      
              /* Fail if repayAmount = -1 */
              if (repayAmount == uint(-1)) {
                  return fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX);
              }
      
              /* We calculate the number of collateral tokens that will be seized */
              (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(address(this), address(cTokenCollateral), repayAmount);
              if (amountSeizeError != 0) {
                  return failOpaque(Error.COMPTROLLER_CALCULATION_ERROR, FailureInfo.LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED, amountSeizeError);
              }
      
              /* Fail if seizeTokens > borrower collateral token balance */
              if (seizeTokens > cTokenCollateral.balanceOf(borrower)) {
                  return fail(Error.TOKEN_INSUFFICIENT_BALANCE, FailureInfo.LIQUIDATE_SEIZE_TOO_MUCH);
              }
      
              /* Fail if repayBorrow fails */
              uint repayBorrowError = repayBorrowFresh(liquidator, borrower, repayAmount);
              if (repayBorrowError != uint(Error.NO_ERROR)) {
                  return fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED);
              }
      
              /* Revert if seize tokens fails (since we cannot be sure of side effects) */
              uint seizeError = cTokenCollateral.seize(liquidator, borrower, seizeTokens);
              require(seizeError == uint(Error.NO_ERROR), "token seizure failed");
      
              /* We emit a LiquidateBorrow event */
              emit LiquidateBorrow(liquidator, borrower, repayAmount, address(cTokenCollateral), seizeTokens);
      
              /* We call the defense hook */
              comptroller.liquidateBorrowVerify(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount, seizeTokens);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Transfers collateral tokens (this market) to the liquidator.
           * @dev Will fail unless called by another cToken during the process of liquidation.
           *  Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter.
           * @param liquidator The account receiving seized collateral
           * @param borrower The account having collateral seized
           * @param seizeTokens The number of cTokens to seize
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function seize(address liquidator, address borrower, uint seizeTokens) external nonReentrant returns (uint) {
              /* Fail if seize not allowed */
              uint allowed = comptroller.seizeAllowed(address(this), msg.sender, liquidator, borrower, seizeTokens);
              if (allowed != 0) {
                  return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);
              }
      
              /* Fail if borrower = liquidator */
              if (borrower == liquidator) {
                  return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);
              }
      
              MathError mathErr;
              uint borrowerTokensNew;
              uint liquidatorTokensNew;
      
              /*
               * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:
               *  borrowerTokensNew = accountTokens[borrower] - seizeTokens
               *  liquidatorTokensNew = accountTokens[liquidator] + seizeTokens
               */
              (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);
              if (mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));
              }
      
              (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);
              if (mathErr != MathError.NO_ERROR) {
                  return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              /* We write the previously calculated values into storage */
              accountTokens[borrower] = borrowerTokensNew;
              accountTokens[liquidator] = liquidatorTokensNew;
      
              /* Emit a Transfer event */
              emit Transfer(borrower, liquidator, seizeTokens);
      
              /* We call the defense hook */
              comptroller.seizeVerify(address(this), msg.sender, liquidator, borrower, seizeTokens);
      
              return uint(Error.NO_ERROR);
          }
      
      
          /*** Admin Functions ***/
      
          /**
            * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
            * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
            * @param newPendingAdmin New pending admin.
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            *
            * TODO: Should we add a second arg to verify, like a checksum of `newAdmin` address?
            */
          function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) {
              // Check caller = admin
              if (msg.sender != admin) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK);
              }
      
              // Save current value, if any, for inclusion in log
              address oldPendingAdmin = pendingAdmin;
      
              // Store pendingAdmin with value newPendingAdmin
              pendingAdmin = newPendingAdmin;
      
              // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
              emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
            * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
            * @dev Admin function for pending admin to accept role and update admin
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function _acceptAdmin() external returns (uint) {
              // Check caller is pendingAdmin and pendingAdmin ≠ address(0)
              if (msg.sender != pendingAdmin || msg.sender == address(0)) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);
              }
      
              // Save current values for inclusion in log
              address oldAdmin = admin;
              address oldPendingAdmin = pendingAdmin;
      
              // Store admin with value pendingAdmin
              admin = pendingAdmin;
      
              // Clear the pending value
              pendingAdmin = address(0);
      
              emit NewAdmin(oldAdmin, admin);
              emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
            * @notice Sets a new comptroller for the market
            * @dev Admin function to set a new comptroller
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function _setComptroller(ComptrollerInterface newComptroller) public returns (uint) {
              // Check caller is admin
              if (msg.sender != admin) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.SET_COMPTROLLER_OWNER_CHECK);
              }
      
              ComptrollerInterface oldComptroller = comptroller;
              // Ensure invoke comptroller.isComptroller() returns true
              require(newComptroller.isComptroller(), "marker method returned false");
      
              // Set market's comptroller to newComptroller
              comptroller = newComptroller;
      
              // Emit NewComptroller(oldComptroller, newComptroller)
              emit NewComptroller(oldComptroller, newComptroller);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
            * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh
            * @dev Admin function to accrue interest and set a new reserve factor
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function _setReserveFactor(uint newReserveFactorMantissa) external nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.
                  return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);
              }
              // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.
              return _setReserveFactorFresh(newReserveFactorMantissa);
          }
      
          /**
            * @notice Sets a new reserve factor for the protocol (*requires fresh interest accrual)
            * @dev Admin function to set a new reserve factor
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {
              // Check caller is admin
              if (msg.sender != admin) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.SET_RESERVE_FACTOR_ADMIN_CHECK);
              }
      
              // Verify market's block number equals current block number
              if (accrualBlockNumber != getBlockNumber()) {
                  // TODO: static_assert + no error code?
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);
              }
      
              // Check newReserveFactor ≤ maxReserveFactor
              if (newReserveFactorMantissa > reserveFactorMaxMantissa) {
                  return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);
              }
      
              uint oldReserveFactorMantissa = reserveFactorMantissa;
              reserveFactorMantissa = newReserveFactorMantissa;
      
              emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice Accrues interest and reduces reserves by transferring to admin
           * @param reduceAmount Amount of reduction to reserves
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function _reduceReserves(uint reduceAmount) external nonReentrant returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.
                  return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);
              }
              // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.
              return _reduceReservesFresh(reduceAmount);
          }
      
          /**
           * @notice Reduces reserves by transferring to admin
           * @dev Requires fresh interest accrual
           * @param reduceAmount Amount of reduction to reserves
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function _reduceReservesFresh(uint reduceAmount) internal returns (uint) {
              Error err;
              // totalReserves - reduceAmount
              uint totalReservesNew;
      
              // Check caller is admin
              if (msg.sender != admin) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.REDUCE_RESERVES_ADMIN_CHECK);
              }
      
              // We fail gracefully unless market's block number equals current block number
              if (accrualBlockNumber != getBlockNumber()) {
                  // TODO: static_assert + no error code?
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);
              }
      
              // Fail gracefully if protocol has insufficient underlying cash
              if (getCashPrior() < reduceAmount) {
                  return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);
              }
      
              // Check reduceAmount ≤ reserves[n] (totalReserves)
              // TODO: I'm following the spec literally here but I think we should we just use SafeMath instead and fail on an error (which would be underflow)
              if (reduceAmount > totalReserves) {
                  return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);
              }
      
              /////////////////////////
              // EFFECTS & INTERACTIONS
              // (No safe failures beyond this point)
      
              totalReservesNew = totalReserves - reduceAmount;
              // We checked reduceAmount <= totalReserves above, so this should never revert.
              require(totalReservesNew <= totalReserves, "reduce reserves unexpected underflow");
      
              // Store reserves[n+1] = reserves[n] - reduceAmount
              totalReserves = totalReservesNew;
      
              // invoke doTransferOut(reduceAmount, admin)
              err = doTransferOut(admin, reduceAmount);
              // we revert on the failure of this command
              require(err == Error.NO_ERROR, "reduce reserves transfer out failed");
      
              emit ReservesReduced(admin, reduceAmount, totalReservesNew);
      
              return uint(Error.NO_ERROR);
          }
      
          /**
           * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh
           * @dev Admin function to accrue interest and update the interest rate model
           * @param newInterestRateModel the new interest rate model to use
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) {
              uint error = accrueInterest();
              if (error != uint(Error.NO_ERROR)) {
                  // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed
                  return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);
              }
              // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.
              return _setInterestRateModelFresh(newInterestRateModel);
          }
      
          /**
           * @notice updates the interest rate model (*requires fresh interest accrual)
           * @dev Admin function to update the interest rate model
           * @param newInterestRateModel the new interest rate model to use
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal returns (uint) {
      
              // Used to store old model for use in the event that is emitted on success
              InterestRateModel oldInterestRateModel;
      
              // Check caller is admin
              if (msg.sender != admin) {
                  return fail(Error.UNAUTHORIZED, FailureInfo.SET_INTEREST_RATE_MODEL_OWNER_CHECK);
              }
      
              // We fail gracefully unless market's block number equals current block number
              if (accrualBlockNumber != getBlockNumber()) {
                  // TODO: static_assert + no error code?
                  return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);
              }
      
              // Track the market's current interest rate model
              oldInterestRateModel = interestRateModel;
      
              // Ensure invoke newInterestRateModel.isInterestRateModel() returns true
              require(newInterestRateModel.isInterestRateModel(), "marker method returned false");
      
              // Set the interest rate model to newInterestRateModel
              interestRateModel = newInterestRateModel;
      
              // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)
              emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel);
      
              return uint(Error.NO_ERROR);
          }
      
          /*** Safe Token ***/
      
          /**
           * @notice Gets balance of this contract in terms of the underlying
           * @dev This excludes the value of the current message, if any
           * @return The quantity of underlying owned by this contract
           */
          function getCashPrior() internal view returns (uint);
      
          /**
           * @dev Checks whether or not there is sufficient allowance for this contract to move amount from `from` and
           *      whether or not `from` has a balance of at least `amount`. Does NOT do a transfer.
           */
          function checkTransferIn(address from, uint amount) internal view returns (Error);
      
          /**
           * @dev Performs a transfer in, ideally returning an explanatory error code upon failure rather than reverting.
           *  If caller has not called `checkTransferIn`, this may revert due to insufficient balance or insufficient allowance.
           *  If caller has called `checkTransferIn` successfully, this should not revert in normal conditions.
           */
          function doTransferIn(address from, uint amount) internal returns (Error);
      
          /**
           * @dev Performs a transfer out, ideally returning an explanatory error code upon failure tather than reverting.
           *  If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.
           *  If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.
           */
          function doTransferOut(address payable to, uint amount) internal returns (Error);
      }
      
      // File: contracts/CErc20.sol
      
      pragma solidity ^0.5.8;
      
      
      /**
       * @title Compound's CErc20 Contract
       * @notice CTokens which wrap an EIP-20 underlying
       * @author Compound
       */
      contract CErc20 is CToken {
      
          /**
           * @notice Underlying asset for this CToken
           */
          address public underlying;
      
          /**
           * @notice Construct a new money market
           * @param underlying_ The address of the underlying asset
           * @param comptroller_ The address of the Comptroller
           * @param interestRateModel_ The address of the interest rate model
           * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
           * @param name_ ERC-20 name of this token
           * @param symbol_ ERC-20 symbol of this token
           * @param decimals_ ERC-20 decimal precision of this token
           */
          constructor(address underlying_,
                      ComptrollerInterface comptroller_,
                      InterestRateModel interestRateModel_,
                      uint initialExchangeRateMantissa_,
                      string memory name_,
                      string memory symbol_,
                      uint decimals_) public
          CToken(comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_) {
              // Set underlying
              underlying = underlying_;
              EIP20Interface(underlying).totalSupply(); // Sanity check the underlying
          }
      
          /*** User Interface ***/
      
          /**
           * @notice Sender supplies assets into the market and receives cTokens in exchange
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param mintAmount The amount of the underlying asset to supply
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function mint(uint mintAmount) external returns (uint) {
              return mintInternal(mintAmount);
          }
      
          /**
           * @notice Sender redeems cTokens in exchange for the underlying asset
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param redeemTokens The number of cTokens to redeem into underlying
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function redeem(uint redeemTokens) external returns (uint) {
              return redeemInternal(redeemTokens);
          }
      
          /**
           * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset
           * @dev Accrues interest whether or not the operation succeeds, unless reverted
           * @param redeemAmount The amount of underlying to redeem
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function redeemUnderlying(uint redeemAmount) external returns (uint) {
              return redeemUnderlyingInternal(redeemAmount);
          }
      
          /**
            * @notice Sender borrows assets from the protocol to their own address
            * @param borrowAmount The amount of the underlying asset to borrow
            * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
            */
          function borrow(uint borrowAmount) external returns (uint) {
              return borrowInternal(borrowAmount);
          }
      
          /**
           * @notice Sender repays their own borrow
           * @param repayAmount The amount to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function repayBorrow(uint repayAmount) external returns (uint) {
              return repayBorrowInternal(repayAmount);
          }
      
          /**
           * @notice Sender repays a borrow belonging to borrower
           * @param borrower the account with the debt being payed off
           * @param repayAmount The amount to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) {
              return repayBorrowBehalfInternal(borrower, repayAmount);
          }
      
          /**
           * @notice The sender liquidates the borrowers collateral.
           *  The collateral seized is transferred to the liquidator.
           * @param borrower The borrower of this cToken to be liquidated
           * @param cTokenCollateral The market in which to seize collateral from the borrower
           * @param repayAmount The amount of the underlying borrowed asset to repay
           * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
           */
          function liquidateBorrow(address borrower, uint repayAmount, CToken cTokenCollateral) external returns (uint) {
              return liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral);
          }
      
          /*** Safe Token ***/
      
          /**
           * @notice Gets balance of this contract in terms of the underlying
           * @dev This excludes the value of the current message, if any
           * @return The quantity of underlying tokens owned by this contract
           */
          function getCashPrior() internal view returns (uint) {
              EIP20Interface token = EIP20Interface(underlying);
              return token.balanceOf(address(this));
          }
      
          /**
           * @dev Checks whether or not there is sufficient allowance for this contract to move amount from `from` and
           *      whether or not `from` has a balance of at least `amount`. Does NOT do a transfer.
           */
          function checkTransferIn(address from, uint amount) internal view returns (Error) {
              EIP20Interface token = EIP20Interface(underlying);
      
              if (token.allowance(from, address(this)) < amount) {
                  return Error.TOKEN_INSUFFICIENT_ALLOWANCE;
              }
      
              if (token.balanceOf(from) < amount) {
                  return Error.TOKEN_INSUFFICIENT_BALANCE;
              }
      
              return Error.NO_ERROR;
          }
      
          /**
           * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and returns an explanatory
           *      error code rather than reverting.  If caller has not called `checkTransferIn`, this may revert due to
           *      insufficient balance or insufficient allowance. If caller has called `checkTransferIn` prior to this call,
           *      and it returned Error.NO_ERROR, this should not revert in normal conditions.
           *
           *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
           *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
           */
          function doTransferIn(address from, uint amount) internal returns (Error) {
              EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying);
              bool result;
      
              token.transferFrom(from, address(this), amount);
      
              // solium-disable-next-line security/no-inline-assembly
              assembly {
                  switch returndatasize()
                      case 0 {                      // This is a non-standard ERC-20
                          result := not(0)          // set result to true
                      }
                      case 32 {                     // This is a complaint ERC-20
                          returndatacopy(0, 0, 32)
                          result := mload(0)        // Set `result = returndata` of external call
                      }
                      default {                     // This is an excessively non-compliant ERC-20, revert.
                          revert(0, 0)
                      }
              }
      
              if (!result) {
                  return Error.TOKEN_TRANSFER_IN_FAILED;
              }
      
              return Error.NO_ERROR;
          }
      
          /**
           * @dev Similar to EIP20 transfer, except it handles a False result from `transfer` and returns an explanatory
           *      error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to
           *      insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified
           *      it is >= amount, this should not revert in normal conditions.
           *
           *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
           *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
           */
          function doTransferOut(address payable to, uint amount) internal returns (Error) {
              EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying);
              bool result;
      
              token.transfer(to, amount);
      
              // solium-disable-next-line security/no-inline-assembly
              assembly {
                  switch returndatasize()
                      case 0 {                      // This is a non-standard ERC-20
                          result := not(0)          // set result to true
                      }
                      case 32 {                     // This is a complaint ERC-20
                          returndatacopy(0, 0, 32)
                          result := mload(0)        // Set `result = returndata` of external call
                      }
                      default {                     // This is an excessively non-compliant ERC-20, revert.
                          revert(0, 0)
                      }
              }
      
              if (!result) {
                  return Error.TOKEN_TRANSFER_OUT_FAILED;
              }
      
              return Error.NO_ERROR;
          }
      }
      

      File 2 of 3: FiatTokenProxy
      pragma solidity ^0.4.24;
      
      // File: zos-lib/contracts/upgradeability/Proxy.sol
      
      /**
       * @title Proxy
       * @dev Implements delegation of calls to other contracts, with proper
       * forwarding of return values and bubbling of failures.
       * It defines a fallback function that delegates all calls to the address
       * returned by the abstract _implementation() internal function.
       */
      contract Proxy {
        /**
         * @dev Fallback function.
         * Implemented entirely in `_fallback`.
         */
        function () payable external {
          _fallback();
        }
      
        /**
         * @return The Address of the implementation.
         */
        function _implementation() internal view returns (address);
      
        /**
         * @dev Delegates execution to an implementation contract.
         * This is a low level function that doesn't return to its internal call site.
         * It will return to the external caller whatever the implementation returns.
         * @param implementation Address to delegate.
         */
        function _delegate(address implementation) internal {
          assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize)
      
            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
      
            // Copy the returned data.
            returndatacopy(0, 0, returndatasize)
      
            switch result
            // delegatecall returns 0 on error.
            case 0 { revert(0, returndatasize) }
            default { return(0, returndatasize) }
          }
        }
      
        /**
         * @dev Function that is run as the first thing in the fallback function.
         * Can be redefined in derived contracts to add functionality.
         * Redefinitions must call super._willFallback().
         */
        function _willFallback() internal {
        }
      
        /**
         * @dev fallback implementation.
         * Extracted to enable manual triggering.
         */
        function _fallback() internal {
          _willFallback();
          _delegate(_implementation());
        }
      }
      
      // File: openzeppelin-solidity/contracts/AddressUtils.sol
      
      /**
       * Utility library of inline functions on addresses
       */
      library AddressUtils {
      
        /**
         * Returns whether the target address is a contract
         * @dev This function will return false if invoked during the constructor of a contract,
         * as the code is not actually created until after the constructor finishes.
         * @param addr address to check
         * @return whether the target address is a contract
         */
        function isContract(address addr) internal view returns (bool) {
          uint256 size;
          // XXX Currently there is no better way to check if there is a contract in an address
          // than to check the size of the code at that address.
          // See https://ethereum.stackexchange.com/a/14016/36603
          // for more details about how this works.
          // TODO Check this again before the Serenity release, because all addresses will be
          // contracts then.
          // solium-disable-next-line security/no-inline-assembly
          assembly { size := extcodesize(addr) }
          return size > 0;
        }
      
      }
      
      // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
      
      /**
       * @title UpgradeabilityProxy
       * @dev This contract implements a proxy that allows to change the
       * implementation address to which it will delegate.
       * Such a change is called an implementation upgrade.
       */
      contract UpgradeabilityProxy is Proxy {
        /**
         * @dev Emitted when the implementation is upgraded.
         * @param implementation Address of the new implementation.
         */
        event Upgraded(address implementation);
      
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
         * validated in the constructor.
         */
        bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
      
        /**
         * @dev Contract constructor.
         * @param _implementation Address of the initial implementation.
         */
        constructor(address _implementation) public {
          assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
      
          _setImplementation(_implementation);
        }
      
        /**
         * @dev Returns the current implementation.
         * @return Address of the current implementation
         */
        function _implementation() internal view returns (address impl) {
          bytes32 slot = IMPLEMENTATION_SLOT;
          assembly {
            impl := sload(slot)
          }
        }
      
        /**
         * @dev Upgrades the proxy to a new implementation.
         * @param newImplementation Address of the new implementation.
         */
        function _upgradeTo(address newImplementation) internal {
          _setImplementation(newImplementation);
          emit Upgraded(newImplementation);
        }
      
        /**
         * @dev Sets the implementation address of the proxy.
         * @param newImplementation Address of the new implementation.
         */
        function _setImplementation(address newImplementation) private {
          require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
      
          bytes32 slot = IMPLEMENTATION_SLOT;
      
          assembly {
            sstore(slot, newImplementation)
          }
        }
      }
      
      // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
      
      /**
       * @title AdminUpgradeabilityProxy
       * @dev This contract combines an upgradeability proxy with an authorization
       * mechanism for administrative tasks.
       * All external functions in this contract must be guarded by the
       * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
       * feature proposal that would enable this to be done automatically.
       */
      contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
        /**
         * @dev Emitted when the administration has been transferred.
         * @param previousAdmin Address of the previous admin.
         * @param newAdmin Address of the new admin.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
      
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
         * validated in the constructor.
         */
        bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
      
        /**
         * @dev Modifier to check whether the `msg.sender` is the admin.
         * If it is, it will run the function. Otherwise, it will delegate the call
         * to the implementation.
         */
        modifier ifAdmin() {
          if (msg.sender == _admin()) {
            _;
          } else {
            _fallback();
          }
        }
      
        /**
         * Contract constructor.
         * It sets the `msg.sender` as the proxy administrator.
         * @param _implementation address of the initial implementation.
         */
        constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
          assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
      
          _setAdmin(msg.sender);
        }
      
        /**
         * @return The address of the proxy admin.
         */
        function admin() external view ifAdmin returns (address) {
          return _admin();
        }
      
        /**
         * @return The address of the implementation.
         */
        function implementation() external view ifAdmin returns (address) {
          return _implementation();
        }
      
        /**
         * @dev Changes the admin of the proxy.
         * Only the current admin can call this function.
         * @param newAdmin Address to transfer proxy administration to.
         */
        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);
        }
      
        /**
         * @dev Upgrade the backing implementation of the proxy.
         * Only the admin can call this function.
         * @param newImplementation Address of the new implementation.
         */
        function upgradeTo(address newImplementation) external ifAdmin {
          _upgradeTo(newImplementation);
        }
      
        /**
         * @dev Upgrade the backing implementation of the proxy and call a function
         * on the new implementation.
         * This is useful to initialize the proxied contract.
         * @param newImplementation Address of the new implementation.
         * @param data Data to send as msg.data in the low level call.
         * It should include the signature and the parameters of the function to be
         * called, as described in
         * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
         */
        function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
          _upgradeTo(newImplementation);
          require(address(this).call.value(msg.value)(data));
        }
      
        /**
         * @return The admin slot.
         */
        function _admin() internal view returns (address adm) {
          bytes32 slot = ADMIN_SLOT;
          assembly {
            adm := sload(slot)
          }
        }
      
        /**
         * @dev Sets the address of the proxy admin.
         * @param newAdmin Address of the new proxy admin.
         */
        function _setAdmin(address newAdmin) internal {
          bytes32 slot = ADMIN_SLOT;
      
          assembly {
            sstore(slot, newAdmin)
          }
        }
      
        /**
         * @dev Only fall back when the sender is not the admin.
         */
        function _willFallback() internal {
          require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
          super._willFallback();
        }
      }
      
      // File: contracts/FiatTokenProxy.sol
      
      /**
      * Copyright CENTRE SECZ 2018
      *
      * 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 SOFTWARE.
      */
      
      pragma solidity ^0.4.24;
      
      
      /**
       * @title FiatTokenProxy
       * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
      */ 
      contract FiatTokenProxy is AdminUpgradeabilityProxy {
          constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
          }
      }

      File 3 of 3: FiatTokenV2_1
      // File: @openzeppelin/contracts/math/SafeMath.sol
      
      // SPDX-License-Identifier: MIT
      
      pragma solidity ^0.6.0;
      
      /**
       * @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) {
              return sub(a, b, "SafeMath: subtraction overflow");
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           *
           * - Subtraction cannot overflow.
           */
          function sub(
              uint256 a,
              uint256 b,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              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-contracts/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) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b > 0, errorMessage);
              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) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts with custom message 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,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
      
      pragma solidity ^0.6.0;
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `recipient`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address recipient, uint256 amount)
              external
              returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender)
              external
              view
              returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `sender` to `recipient` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address sender,
              address recipient,
              uint256 amount
          ) external returns (bool);
      
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(
              address indexed owner,
              address indexed spender,
              uint256 value
          );
      }
      
      // File: contracts/v1/AbstractFiatTokenV1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      abstract contract AbstractFiatTokenV1 is IERC20 {
          function _approve(
              address owner,
              address spender,
              uint256 value
          ) internal virtual;
      
          function _transfer(
              address from,
              address to,
              uint256 value
          ) internal virtual;
      }
      
      // File: contracts/v1/Ownable.sol
      
      /**
       * Copyright (c) 2018 zOS Global Limited.
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      pragma solidity 0.6.12;
      
      /**
       * @notice The Ownable contract has an owner address, and provides basic
       * authorization control functions
       * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
       * Modifications:
       * 1. Consolidate OwnableStorage into this contract (7/13/18)
       * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
       * 3. Make public functions external (5/27/20)
       */
      contract Ownable {
          // Owner of the contract
          address private _owner;
      
          /**
           * @dev Event to show ownership has been transferred
           * @param previousOwner representing the address of the previous owner
           * @param newOwner representing the address of the new owner
           */
          event OwnershipTransferred(address previousOwner, address newOwner);
      
          /**
           * @dev The constructor sets the original owner of the contract to the sender account.
           */
          constructor() public {
              setOwner(msg.sender);
          }
      
          /**
           * @dev Tells the address of the owner
           * @return the address of the owner
           */
          function owner() external view returns (address) {
              return _owner;
          }
      
          /**
           * @dev Sets a new owner address
           */
          function setOwner(address newOwner) internal {
              _owner = newOwner;
          }
      
          /**
           * @dev Throws if called by any account other than the owner.
           */
          modifier onlyOwner() {
              require(msg.sender == _owner, "Ownable: caller is not the owner");
              _;
          }
      
          /**
           * @dev Allows the current owner to transfer control of the contract to a newOwner.
           * @param newOwner The address to transfer ownership to.
           */
          function transferOwnership(address newOwner) external onlyOwner {
              require(
                  newOwner != address(0),
                  "Ownable: new owner is the zero address"
              );
              emit OwnershipTransferred(_owner, newOwner);
              setOwner(newOwner);
          }
      }
      
      // File: contracts/v1/Pausable.sol
      
      /**
       * Copyright (c) 2016 Smart Contract Solutions, Inc.
       * Copyright (c) 2018-2020 CENTRE SECZ0
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @notice Base contract which allows children to implement an emergency stop
       * mechanism
       * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
       * Modifications:
       * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
       * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
       * 3. Removed whenPaused (6/14/2018)
       * 4. Switches ownable library to use ZeppelinOS (7/12/18)
       * 5. Remove constructor (7/13/18)
       * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
       * 7. Make public functions external (5/27/20)
       */
      contract Pausable is Ownable {
          event Pause();
          event Unpause();
          event PauserChanged(address indexed newAddress);
      
          address public pauser;
          bool public paused = false;
      
          /**
           * @dev Modifier to make a function callable only when the contract is not paused.
           */
          modifier whenNotPaused() {
              require(!paused, "Pausable: paused");
              _;
          }
      
          /**
           * @dev throws if called by any account other than the pauser
           */
          modifier onlyPauser() {
              require(msg.sender == pauser, "Pausable: caller is not the pauser");
              _;
          }
      
          /**
           * @dev called by the owner to pause, triggers stopped state
           */
          function pause() external onlyPauser {
              paused = true;
              emit Pause();
          }
      
          /**
           * @dev called by the owner to unpause, returns to normal state
           */
          function unpause() external onlyPauser {
              paused = false;
              emit Unpause();
          }
      
          /**
           * @dev update the pauser role
           */
          function updatePauser(address _newPauser) external onlyOwner {
              require(
                  _newPauser != address(0),
                  "Pausable: new pauser is the zero address"
              );
              pauser = _newPauser;
              emit PauserChanged(pauser);
          }
      }
      
      // File: contracts/v1/Blacklistable.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title Blacklistable Token
       * @dev Allows accounts to be blacklisted by a "blacklister" role
       */
      contract Blacklistable is Ownable {
          address public blacklister;
          mapping(address => bool) internal blacklisted;
      
          event Blacklisted(address indexed _account);
          event UnBlacklisted(address indexed _account);
          event BlacklisterChanged(address indexed newBlacklister);
      
          /**
           * @dev Throws if called by any account other than the blacklister
           */
          modifier onlyBlacklister() {
              require(
                  msg.sender == blacklister,
                  "Blacklistable: caller is not the blacklister"
              );
              _;
          }
      
          /**
           * @dev Throws if argument account is blacklisted
           * @param _account The address to check
           */
          modifier notBlacklisted(address _account) {
              require(
                  !blacklisted[_account],
                  "Blacklistable: account is blacklisted"
              );
              _;
          }
      
          /**
           * @dev Checks if account is blacklisted
           * @param _account The address to check
           */
          function isBlacklisted(address _account) external view returns (bool) {
              return blacklisted[_account];
          }
      
          /**
           * @dev Adds account to blacklist
           * @param _account The address to blacklist
           */
          function blacklist(address _account) external onlyBlacklister {
              blacklisted[_account] = true;
              emit Blacklisted(_account);
          }
      
          /**
           * @dev Removes account from blacklist
           * @param _account The address to remove from the blacklist
           */
          function unBlacklist(address _account) external onlyBlacklister {
              blacklisted[_account] = false;
              emit UnBlacklisted(_account);
          }
      
          function updateBlacklister(address _newBlacklister) external onlyOwner {
              require(
                  _newBlacklister != address(0),
                  "Blacklistable: new blacklister is the zero address"
              );
              blacklister = _newBlacklister;
              emit BlacklisterChanged(blacklister);
          }
      }
      
      // File: contracts/v1/FiatTokenV1.sol
      
      /**
       *
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatToken
       * @dev ERC20 Token backed by fiat reserves
       */
      contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
          using SafeMath for uint256;
      
          string public name;
          string public symbol;
          uint8 public decimals;
          string public currency;
          address public masterMinter;
          bool internal initialized;
      
          mapping(address => uint256) internal balances;
          mapping(address => mapping(address => uint256)) internal allowed;
          uint256 internal totalSupply_ = 0;
          mapping(address => bool) internal minters;
          mapping(address => uint256) internal minterAllowed;
      
          event Mint(address indexed minter, address indexed to, uint256 amount);
          event Burn(address indexed burner, uint256 amount);
          event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
          event MinterRemoved(address indexed oldMinter);
          event MasterMinterChanged(address indexed newMasterMinter);
      
          function initialize(
              string memory tokenName,
              string memory tokenSymbol,
              string memory tokenCurrency,
              uint8 tokenDecimals,
              address newMasterMinter,
              address newPauser,
              address newBlacklister,
              address newOwner
          ) public {
              require(!initialized, "FiatToken: contract is already initialized");
              require(
                  newMasterMinter != address(0),
                  "FiatToken: new masterMinter is the zero address"
              );
              require(
                  newPauser != address(0),
                  "FiatToken: new pauser is the zero address"
              );
              require(
                  newBlacklister != address(0),
                  "FiatToken: new blacklister is the zero address"
              );
              require(
                  newOwner != address(0),
                  "FiatToken: new owner is the zero address"
              );
      
              name = tokenName;
              symbol = tokenSymbol;
              currency = tokenCurrency;
              decimals = tokenDecimals;
              masterMinter = newMasterMinter;
              pauser = newPauser;
              blacklister = newBlacklister;
              setOwner(newOwner);
              initialized = true;
          }
      
          /**
           * @dev Throws if called by any account other than a minter
           */
          modifier onlyMinters() {
              require(minters[msg.sender], "FiatToken: caller is not a minter");
              _;
          }
      
          /**
           * @dev Function to mint tokens
           * @param _to The address that will receive the minted tokens.
           * @param _amount The amount of tokens to mint. Must be less than or equal
           * to the minterAllowance of the caller.
           * @return A boolean that indicates if the operation was successful.
           */
          function mint(address _to, uint256 _amount)
              external
              whenNotPaused
              onlyMinters
              notBlacklisted(msg.sender)
              notBlacklisted(_to)
              returns (bool)
          {
              require(_to != address(0), "FiatToken: mint to the zero address");
              require(_amount > 0, "FiatToken: mint amount not greater than 0");
      
              uint256 mintingAllowedAmount = minterAllowed[msg.sender];
              require(
                  _amount <= mintingAllowedAmount,
                  "FiatToken: mint amount exceeds minterAllowance"
              );
      
              totalSupply_ = totalSupply_.add(_amount);
              balances[_to] = balances[_to].add(_amount);
              minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
              emit Mint(msg.sender, _to, _amount);
              emit Transfer(address(0), _to, _amount);
              return true;
          }
      
          /**
           * @dev Throws if called by any account other than the masterMinter
           */
          modifier onlyMasterMinter() {
              require(
                  msg.sender == masterMinter,
                  "FiatToken: caller is not the masterMinter"
              );
              _;
          }
      
          /**
           * @dev Get minter allowance for an account
           * @param minter The address of the minter
           */
          function minterAllowance(address minter) external view returns (uint256) {
              return minterAllowed[minter];
          }
      
          /**
           * @dev Checks if account is a minter
           * @param account The address to check
           */
          function isMinter(address account) external view returns (bool) {
              return minters[account];
          }
      
          /**
           * @notice Amount of remaining tokens spender is allowed to transfer on
           * behalf of the token owner
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @return Allowance amount
           */
          function allowance(address owner, address spender)
              external
              override
              view
              returns (uint256)
          {
              return allowed[owner][spender];
          }
      
          /**
           * @dev Get totalSupply of token
           */
          function totalSupply() external override view returns (uint256) {
              return totalSupply_;
          }
      
          /**
           * @dev Get token balance of an account
           * @param account address The account
           */
          function balanceOf(address account)
              external
              override
              view
              returns (uint256)
          {
              return balances[account];
          }
      
          /**
           * @notice Set spender's allowance over the caller's tokens to be a given
           * value.
           * @param spender   Spender's address
           * @param value     Allowance amount
           * @return True if successful
           */
          function approve(address spender, uint256 value)
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _approve(msg.sender, spender, value);
              return true;
          }
      
          /**
           * @dev Internal function to set allowance
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param value     Allowance amount
           */
          function _approve(
              address owner,
              address spender,
              uint256 value
          ) internal override {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
              allowed[owner][spender] = value;
              emit Approval(owner, spender, value);
          }
      
          /**
           * @notice Transfer tokens by spending allowance
           * @param from  Payer's address
           * @param to    Payee's address
           * @param value Transfer amount
           * @return True if successful
           */
          function transferFrom(
              address from,
              address to,
              uint256 value
          )
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(from)
              notBlacklisted(to)
              returns (bool)
          {
              require(
                  value <= allowed[from][msg.sender],
                  "ERC20: transfer amount exceeds allowance"
              );
              _transfer(from, to, value);
              allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
              return true;
          }
      
          /**
           * @notice Transfer tokens from the caller
           * @param to    Payee's address
           * @param value Transfer amount
           * @return True if successful
           */
          function transfer(address to, uint256 value)
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(to)
              returns (bool)
          {
              _transfer(msg.sender, to, value);
              return true;
          }
      
          /**
           * @notice Internal function to process transfers
           * @param from  Payer's address
           * @param to    Payee's address
           * @param value Transfer amount
           */
          function _transfer(
              address from,
              address to,
              uint256 value
          ) internal override {
              require(from != address(0), "ERC20: transfer from the zero address");
              require(to != address(0), "ERC20: transfer to the zero address");
              require(
                  value <= balances[from],
                  "ERC20: transfer amount exceeds balance"
              );
      
              balances[from] = balances[from].sub(value);
              balances[to] = balances[to].add(value);
              emit Transfer(from, to, value);
          }
      
          /**
           * @dev Function to add/update a new minter
           * @param minter The address of the minter
           * @param minterAllowedAmount The minting amount allowed for the minter
           * @return True if the operation was successful.
           */
          function configureMinter(address minter, uint256 minterAllowedAmount)
              external
              whenNotPaused
              onlyMasterMinter
              returns (bool)
          {
              minters[minter] = true;
              minterAllowed[minter] = minterAllowedAmount;
              emit MinterConfigured(minter, minterAllowedAmount);
              return true;
          }
      
          /**
           * @dev Function to remove a minter
           * @param minter The address of the minter to remove
           * @return True if the operation was successful.
           */
          function removeMinter(address minter)
              external
              onlyMasterMinter
              returns (bool)
          {
              minters[minter] = false;
              minterAllowed[minter] = 0;
              emit MinterRemoved(minter);
              return true;
          }
      
          /**
           * @dev allows a minter to burn some of its own tokens
           * Validates that caller is a minter and that sender is not blacklisted
           * amount is less than or equal to the minter's account balance
           * @param _amount uint256 the amount of tokens to be burned
           */
          function burn(uint256 _amount)
              external
              whenNotPaused
              onlyMinters
              notBlacklisted(msg.sender)
          {
              uint256 balance = balances[msg.sender];
              require(_amount > 0, "FiatToken: burn amount not greater than 0");
              require(balance >= _amount, "FiatToken: burn amount exceeds balance");
      
              totalSupply_ = totalSupply_.sub(_amount);
              balances[msg.sender] = balance.sub(_amount);
              emit Burn(msg.sender, _amount);
              emit Transfer(msg.sender, address(0), _amount);
          }
      
          function updateMasterMinter(address _newMasterMinter) external onlyOwner {
              require(
                  _newMasterMinter != address(0),
                  "FiatToken: new masterMinter is the zero address"
              );
              masterMinter = _newMasterMinter;
              emit MasterMinterChanged(masterMinter);
          }
      }
      
      // File: @openzeppelin/contracts/utils/Address.sol
      
      pragma solidity ^0.6.2;
      
      /**
       * @dev Collection of functions related to the address type
       */
      library Address {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * [IMPORTANT]
           * ====
           * It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           *
           * Among others, `isContract` will return false for the following
           * types of addresses:
           *
           *  - an externally-owned account
           *  - a contract in construction
           *  - an address where a contract will be created
           *  - an address where a contract lived, but was destroyed
           * ====
           */
          function isContract(address account) internal view returns (bool) {
              // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
              // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
              // for accounts without code, i.e. `keccak256('')`
              bytes32 codehash;
      
                  bytes32 accountHash
               = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              // solhint-disable-next-line no-inline-assembly
              assembly {
                  codehash := extcodehash(account)
              }
              return (codehash != accountHash && codehash != 0x0);
          }
      
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(
                  address(this).balance >= amount,
                  "Address: insufficient balance"
              );
      
              // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
              (bool success, ) = recipient.call{ value: amount }("");
              require(
                  success,
                  "Address: unable to send value, recipient may have reverted"
              );
          }
      
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain`call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data)
              internal
              returns (bytes memory)
          {
              return functionCall(target, data, "Address: low-level call failed");
          }
      
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              return _functionCallWithValue(target, data, 0, errorMessage);
          }
      
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value
          ) internal returns (bytes memory) {
              return
                  functionCallWithValue(
                      target,
                      data,
                      value,
                      "Address: low-level call with value failed"
                  );
          }
      
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(
                  address(this).balance >= value,
                  "Address: insufficient balance for call"
              );
              return _functionCallWithValue(target, data, value, errorMessage);
          }
      
          function _functionCallWithValue(
              address target,
              bytes memory data,
              uint256 weiValue,
              string memory errorMessage
          ) private returns (bytes memory) {
              require(isContract(target), "Address: call to non-contract");
      
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.call{
                  value: weiValue
              }(data);
              if (success) {
                  return returndata;
              } else {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
      
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
      }
      
      // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
      
      pragma solidity ^0.6.0;
      
      /**
       * @title SafeERC20
       * @dev Wrappers around ERC20 operations that throw on failure (when the token
       * contract returns false). Tokens that return no value (and instead revert or
       * throw on failure) are also supported, non-reverting calls are assumed to be
       * successful.
       * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
       * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
       */
      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)
              );
          }
      
          /**
           * @dev Deprecated. This function has issues similar to the ones found in
           * {IERC20-approve}, and its usage is discouraged.
           *
           * Whenever possible, use {safeIncreaseAllowance} and
           * {safeDecreaseAllowance} instead.
           */
          function safeApprove(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              // safeApprove should only be called when setting an initial allowance,
              // or when resetting it to zero. To increase and decrease it, use
              // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
              // solhint-disable-next-line max-line-length
              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
                  )
              );
          }
      
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           */
          function _callOptionalReturn(IERC20 token, bytes memory data) private {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
              // the target address contains contract code and also asserts for success in the low-level call.
      
              bytes memory returndata = address(token).functionCall(
                  data,
                  "SafeERC20: low-level call failed"
              );
              if (returndata.length > 0) {
                  // Return data is optional
                  // solhint-disable-next-line max-line-length
                  require(
                      abi.decode(returndata, (bool)),
                      "SafeERC20: ERC20 operation did not succeed"
                  );
              }
          }
      }
      
      // File: contracts/v1.1/Rescuable.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      contract Rescuable is Ownable {
          using SafeERC20 for IERC20;
      
          address private _rescuer;
      
          event RescuerChanged(address indexed newRescuer);
      
          /**
           * @notice Returns current rescuer
           * @return Rescuer's address
           */
          function rescuer() external view returns (address) {
              return _rescuer;
          }
      
          /**
           * @notice Revert if called by any account other than the rescuer.
           */
          modifier onlyRescuer() {
              require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
              _;
          }
      
          /**
           * @notice Rescue ERC20 tokens locked up in this contract.
           * @param tokenContract ERC20 token contract address
           * @param to        Recipient address
           * @param amount    Amount to withdraw
           */
          function rescueERC20(
              IERC20 tokenContract,
              address to,
              uint256 amount
          ) external onlyRescuer {
              tokenContract.safeTransfer(to, amount);
          }
      
          /**
           * @notice Assign the rescuer role to a given address.
           * @param newRescuer New rescuer's address
           */
          function updateRescuer(address newRescuer) external onlyOwner {
              require(
                  newRescuer != address(0),
                  "Rescuable: new rescuer is the zero address"
              );
              _rescuer = newRescuer;
              emit RescuerChanged(newRescuer);
          }
      }
      
      // File: contracts/v1.1/FiatTokenV1_1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatTokenV1_1
       * @dev ERC20 Token backed by fiat reserves
       */
      contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
      
      }
      
      // File: contracts/v2/AbstractFiatTokenV2.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
          function _increaseAllowance(
              address owner,
              address spender,
              uint256 increment
          ) internal virtual;
      
          function _decreaseAllowance(
              address owner,
              address spender,
              uint256 decrement
          ) internal virtual;
      }
      
      // File: contracts/util/ECRecover.sol
      
      /**
       * Copyright (c) 2016-2019 zOS Global Limited
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title ECRecover
       * @notice A library that provides a safe ECDSA recovery function
       */
      library ECRecover {
          /**
           * @notice Recover signer's address from a signed message
           * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
           * Modifications: Accept v, r, and s as separate arguments
           * @param digest    Keccak-256 hash digest of the signed message
           * @param v         v of the signature
           * @param r         r of the signature
           * @param s         s of the signature
           * @return Signer address
           */
          function recover(
              bytes32 digest,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal pure returns (address) {
              // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
              // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
              // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
              // signatures from current libraries generate a unique signature with an s-value in the lower half order.
              //
              // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
              // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
              // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
              // these malleable signatures as well.
              if (
                  uint256(s) >
                  0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
              ) {
                  revert("ECRecover: invalid signature 's' value");
              }
      
              if (v != 27 && v != 28) {
                  revert("ECRecover: invalid signature 'v' value");
              }
      
              // If the signature is valid (and not malleable), return the signer address
              address signer = ecrecover(digest, v, r, s);
              require(signer != address(0), "ECRecover: invalid signature");
      
              return signer;
          }
      }
      
      // File: contracts/util/EIP712.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP712
       * @notice A library that provides EIP712 helper functions
       */
      library EIP712 {
          /**
           * @notice Make EIP712 domain separator
           * @param name      Contract name
           * @param version   Contract version
           * @return Domain separator
           */
          function makeDomainSeparator(string memory name, string memory version)
              internal
              view
              returns (bytes32)
          {
              uint256 chainId;
              assembly {
                  chainId := chainid()
              }
              return
                  keccak256(
                      abi.encode(
                          // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                          0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                          keccak256(bytes(name)),
                          keccak256(bytes(version)),
                          chainId,
                          address(this)
                      )
                  );
          }
      
          /**
           * @notice Recover signer's address from a EIP712 signature
           * @param domainSeparator   Domain separator
           * @param v                 v of the signature
           * @param r                 r of the signature
           * @param s                 s of the signature
           * @param typeHashAndData   Type hash concatenated with data
           * @return Signer's address
           */
          function recover(
              bytes32 domainSeparator,
              uint8 v,
              bytes32 r,
              bytes32 s,
              bytes memory typeHashAndData
          ) internal pure returns (address) {
              bytes32 digest = keccak256(
                  abi.encodePacked(
                      "\x19\x01",
                      domainSeparator,
                      keccak256(typeHashAndData)
                  )
              );
              return ECRecover.recover(digest, v, r, s);
          }
      }
      
      // File: contracts/v2/EIP712Domain.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP712 Domain
       */
      contract EIP712Domain {
          /**
           * @dev EIP712 Domain Separator
           */
          bytes32 public DOMAIN_SEPARATOR;
      }
      
      // File: contracts/v2/EIP3009.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP-3009
       * @notice Provide internal implementation for gas-abstracted transfers
       * @dev Contracts that inherit from this must wrap these with publicly
       * accessible functions, optionally adding modifiers where necessary
       */
      abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
          // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
          bytes32
              public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
      
          // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
          bytes32
              public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
      
          // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
          bytes32
              public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
      
          /**
           * @dev authorizer address => nonce => bool (true if nonce is used)
           */
          mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
      
          event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
          event AuthorizationCanceled(
              address indexed authorizer,
              bytes32 indexed nonce
          );
      
          /**
           * @notice Returns the state of an authorization
           * @dev Nonces are randomly generated 32-byte data unique to the
           * authorizer's address
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @return True if the nonce is used
           */
          function authorizationState(address authorizer, bytes32 nonce)
              external
              view
              returns (bool)
          {
              return _authorizationStates[authorizer][nonce];
          }
      
          /**
           * @notice Execute a transfer with a signed authorization
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _transferWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              _requireValidAuthorization(from, nonce, validAfter, validBefore);
      
              bytes memory data = abi.encode(
                  TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                  "FiatTokenV2: invalid signature"
              );
      
              _markAuthorizationAsUsed(from, nonce);
              _transfer(from, to, value);
          }
      
          /**
           * @notice Receive a transfer with a signed authorization from the payer
           * @dev This has an additional check to ensure that the payee's address
           * matches the caller of this function to prevent front-running attacks.
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _receiveWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              require(to == msg.sender, "FiatTokenV2: caller must be the payee");
              _requireValidAuthorization(from, nonce, validAfter, validBefore);
      
              bytes memory data = abi.encode(
                  RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                  "FiatTokenV2: invalid signature"
              );
      
              _markAuthorizationAsUsed(from, nonce);
              _transfer(from, to, value);
          }
      
          /**
           * @notice Attempt to cancel an authorization
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _cancelAuthorization(
              address authorizer,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              _requireUnusedAuthorization(authorizer, nonce);
      
              bytes memory data = abi.encode(
                  CANCEL_AUTHORIZATION_TYPEHASH,
                  authorizer,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer,
                  "FiatTokenV2: invalid signature"
              );
      
              _authorizationStates[authorizer][nonce] = true;
              emit AuthorizationCanceled(authorizer, nonce);
          }
      
          /**
           * @notice Check that an authorization is unused
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           */
          function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
              private
              view
          {
              require(
                  !_authorizationStates[authorizer][nonce],
                  "FiatTokenV2: authorization is used or canceled"
              );
          }
      
          /**
           * @notice Check that authorization is valid
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           */
          function _requireValidAuthorization(
              address authorizer,
              bytes32 nonce,
              uint256 validAfter,
              uint256 validBefore
          ) private view {
              require(
                  now > validAfter,
                  "FiatTokenV2: authorization is not yet valid"
              );
              require(now < validBefore, "FiatTokenV2: authorization is expired");
              _requireUnusedAuthorization(authorizer, nonce);
          }
      
          /**
           * @notice Mark an authorization as used
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           */
          function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
              private
          {
              _authorizationStates[authorizer][nonce] = true;
              emit AuthorizationUsed(authorizer, nonce);
          }
      }
      
      // File: contracts/v2/EIP2612.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP-2612
       * @notice Provide internal implementation for gas-abstracted approvals
       */
      abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
          // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
          bytes32
              public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
      
          mapping(address => uint256) private _permitNonces;
      
          /**
           * @notice Nonces for permit
           * @param owner Token owner's address (Authorizer)
           * @return Next nonce
           */
          function nonces(address owner) external view returns (uint256) {
              return _permitNonces[owner];
          }
      
          /**
           * @notice Verify a signed approval permit and execute if valid
           * @param owner     Token owner's address (Authorizer)
           * @param spender   Spender's address
           * @param value     Amount of allowance
           * @param deadline  The time at which this expires (unix time)
           * @param v         v of the signature
           * @param r         r of the signature
           * @param s         s of the signature
           */
          function _permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              require(deadline >= now, "FiatTokenV2: permit is expired");
      
              bytes memory data = abi.encode(
                  PERMIT_TYPEHASH,
                  owner,
                  spender,
                  value,
                  _permitNonces[owner]++,
                  deadline
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner,
                  "EIP2612: invalid signature"
              );
      
              _approve(owner, spender, value);
          }
      }
      
      // File: contracts/v2/FiatTokenV2.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatToken V2
       * @notice ERC20 Token backed by fiat reserves, version 2
       */
      contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
          uint8 internal _initializedVersion;
      
          /**
           * @notice Initialize v2
           * @param newName   New token name
           */
          function initializeV2(string calldata newName) external {
              // solhint-disable-next-line reason-string
              require(initialized && _initializedVersion == 0);
              name = newName;
              DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
              _initializedVersion = 1;
          }
      
          /**
           * @notice Increase the allowance by a given increment
           * @param spender   Spender's address
           * @param increment Amount of increase in allowance
           * @return True if successful
           */
          function increaseAllowance(address spender, uint256 increment)
              external
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _increaseAllowance(msg.sender, spender, increment);
              return true;
          }
      
          /**
           * @notice Decrease the allowance by a given decrement
           * @param spender   Spender's address
           * @param decrement Amount of decrease in allowance
           * @return True if successful
           */
          function decreaseAllowance(address spender, uint256 decrement)
              external
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _decreaseAllowance(msg.sender, spender, decrement);
              return true;
          }
      
          /**
           * @notice Execute a transfer with a signed authorization
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function transferWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
              _transferWithAuthorization(
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce,
                  v,
                  r,
                  s
              );
          }
      
          /**
           * @notice Receive a transfer with a signed authorization from the payer
           * @dev This has an additional check to ensure that the payee's address
           * matches the caller of this function to prevent front-running attacks.
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function receiveWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
              _receiveWithAuthorization(
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce,
                  v,
                  r,
                  s
              );
          }
      
          /**
           * @notice Attempt to cancel an authorization
           * @dev Works only if the authorization is not yet used.
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function cancelAuthorization(
              address authorizer,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused {
              _cancelAuthorization(authorizer, nonce, v, r, s);
          }
      
          /**
           * @notice Update allowance with a signed permit
           * @param owner       Token owner's address (Authorizer)
           * @param spender     Spender's address
           * @param value       Amount of allowance
           * @param deadline    Expiration time, seconds since the epoch
           * @param v           v of the signature
           * @param r           r of the signature
           * @param s           s of the signature
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) {
              _permit(owner, spender, value, deadline, v, r, s);
          }
      
          /**
           * @notice Internal function to increase the allowance by a given increment
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param increment Amount of increase
           */
          function _increaseAllowance(
              address owner,
              address spender,
              uint256 increment
          ) internal override {
              _approve(owner, spender, allowed[owner][spender].add(increment));
          }
      
          /**
           * @notice Internal function to decrease the allowance by a given decrement
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param decrement Amount of decrease
           */
          function _decreaseAllowance(
              address owner,
              address spender,
              uint256 decrement
          ) internal override {
              _approve(
                  owner,
                  spender,
                  allowed[owner][spender].sub(
                      decrement,
                      "ERC20: decreased allowance below zero"
                  )
              );
          }
      }
      
      // File: contracts/v2/FiatTokenV2_1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * 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
       * 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
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      // solhint-disable func-name-mixedcase
      
      /**
       * @title FiatToken V2.1
       * @notice ERC20 Token backed by fiat reserves, version 2.1
       */
      contract FiatTokenV2_1 is FiatTokenV2 {
          /**
           * @notice Initialize v2.1
           * @param lostAndFound  The address to which the locked funds are sent
           */
          function initializeV2_1(address lostAndFound) external {
              // solhint-disable-next-line reason-string
              require(_initializedVersion == 1);
      
              uint256 lockedAmount = balances[address(this)];
              if (lockedAmount > 0) {
                  _transfer(address(this), lostAndFound, lockedAmount);
              }
              blacklisted[address(this)] = true;
      
              _initializedVersion = 2;
          }
      
          /**
           * @notice Version string for the EIP712 domain separator
           * @return Version string
           */
          function version() external view returns (string memory) {
              return "2";
          }
      }