false
true
0

Contract Address Details

0x00000000580090B7b5B593AB408000b1AbB5f78d

Contract Name
DharmaDaiImplementationV1
Creator
0x000000–439497 at 0x4deebe–02563b
Balance
0 PLS ( )
Tokens
Fetching tokens...
Transactions
Fetching transactions...
Transfers
Fetching transfers...
Gas Used
Fetching gas used...
Last Balance Update
26347262
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
This contract has been partially verified via Sourcify. View contract in Sourcify repository
Contract name:
DharmaDaiImplementationV1




Optimization enabled
true
Compiler version
v0.5.11+commit.c082d0b4




Optimization runs
200
EVM Version
petersburg




Verified at
2026-04-22T01:58:48.170096Z

DharmaDaiImplementationV1.sol

pragma solidity 0.5.11; // optimization runs: 200, evm version: petersburg


/**
 * @title DTokenInterface
 * @author 0age
 * @notice Interface for dTokens (in addition to the standard ERC20 interface).
 */
interface DTokenInterface {
  // Events bear similarity to Compound's supply-related events.
  event Mint(address minter, uint256 mintAmount, uint256 mintDTokens);
  event Redeem(address redeemer, uint256 redeemAmount, uint256 redeemDTokens);
  event Accrue(uint256 dTokenExchangeRate, uint256 cTokenExchangeRate);
  event CollectSurplus(uint256 surplusAmount, uint256 surplusCTokens);

  // The block number and cToken + dToken exchange rates are updated on accrual.
  struct AccrualIndex {
    uint112 dTokenExchangeRate;
    uint112 cTokenExchangeRate;
    uint32 block;
  }

  // These external functions trigger accrual on the dToken and backing cToken.
  function mint(uint256 underlyingToSupply) external returns (uint256 dTokensMinted);
  function redeem(uint256 dTokensToBurn) external returns (uint256 underlyingReceived);
  function redeemUnderlying(uint256 underelyingToReceive) external returns (uint256 dTokensBurned);
  function pullSurplus() external returns (uint256 cTokenSurplus);

  // These external functions only trigger accrual on the dToken.
  function mintViaCToken(uint256 cTokensToSupply) external returns (uint256 dTokensMinted);
  function redeemToCToken(uint256 dTokensToBurn) external returns (uint256 cTokensReceived);
  function redeemUnderlyingToCToken(uint256 underlyingToReceive) external returns (uint256 dTokensBurned);
  function accrueInterest() external;
  function transferUnderlying(
    address recipient, uint256 underlyingEquivalentAmount
  ) external returns (bool success);
  function transferUnderlyingFrom(
    address sender, address recipient, uint256 underlyingEquivalentAmount
  ) external returns (bool success);

  // This function provides basic meta-tx support and does not trigger accrual.
  function modifyAllowanceViaMetaTransaction(
    address owner,
    address spender,
    uint256 value,
    bool increase,
    uint256 expiration,
    bytes32 salt,
    bytes calldata signatures
  ) external returns (bool success);

  // View and pure functions do not trigger accrual on the dToken or the cToken.
  function getMetaTransactionMessageHash(
    bytes4 functionSelector, bytes calldata arguments, uint256 expiration, bytes32 salt
  ) external view returns (bytes32 digest, bool valid);
  function totalSupplyUnderlying() external view returns (uint256);
  function balanceOfUnderlying(address account) external view returns (uint256 underlyingBalance);
  function exchangeRateCurrent() external view returns (uint256 dTokenExchangeRate);
  function supplyRatePerBlock() external view returns (uint256 dTokenInterestRate);
  function accrualBlockNumber() external view returns (uint256 blockNumber);
  function getSurplus() external view returns (uint256 cTokenSurplus);
  function getSurplusUnderlying() external view returns (uint256 underlyingSurplus);
  function getSpreadPerBlock() external view returns (uint256 rateSpread);
  function getVersion() external pure returns (uint256 version);
  function getCToken() external pure returns (address cToken);
  function getUnderlying() external pure returns (address underlying);
}


interface ERC20Interface {
  event Transfer(address indexed from, address indexed to, uint256 value);
  event Approval(address indexed owner, address indexed spender, uint256 value);

  function transfer(address recipient, uint256 amount) external returns (bool);
  function allowance(address owner, address spender) external view returns (uint256);
  function approve(address spender, uint256 amount) external returns (bool);
  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

  function totalSupply() external view returns (uint256);
  function balanceOf(address account) external view returns (uint256);
}


interface ERC1271Interface {
  function isValidSignature(
    bytes calldata data, bytes calldata signature
  ) external view returns (bytes4 magicValue);
}


interface CTokenInterface {
  function mint(uint256 mintAmount) external returns (uint256 err);
  function redeem(uint256 redeemAmount) external returns (uint256 err);
  function redeemUnderlying(uint256 redeemAmount) external returns (uint256 err);
  function accrueInterest() external returns (uint256 err);
  function transfer(address recipient, uint256 value) external returns (bool);
  function transferFrom(address sender, address recipient, uint256 value) external returns (bool);
  function approve(address spender, uint256 amount) external returns (bool);
  function balanceOfUnderlying(address account) external returns (uint256 balance);
  function exchangeRateCurrent() external returns (uint256 exchangeRate);

  function getCash() external view returns (uint256);
  function totalSupply() external view returns (uint256 supply);
  function totalBorrows() external view returns (uint256 borrows);
  function totalReserves() external view returns (uint256 reserves);
  function interestRateModel() external view returns (address model);
  function reserveFactorMantissa() external view returns (uint256 factor);
  function supplyRatePerBlock() external view returns (uint256 rate);
  function exchangeRateStored() external view returns (uint256 rate);
  function accrualBlockNumber() external view returns (uint256 blockNumber);
  function balanceOf(address account) external view returns (uint256 balance);
  function allowance(address owner, address spender) external view returns (uint256);
}


interface CDaiInterestRateModelInterface {
  function getBorrowRate(
    uint256 cash, uint256 borrows, uint256 reserves
  ) external view returns (uint256 borrowRate);

  function getSupplyRate(
    uint256 cash, uint256 borrows, uint256 reserves, uint256 reserveFactor
  ) external view returns (uint256 supplyRate);
}


interface PotInterface {
  function chi() external view returns (uint256);
  function dsr() external view returns (uint256);
  function rho() external view returns (uint256);
  function pie(address account) external view returns (uint256);
}


library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}


/**
 * @title DharmaTokenOverrides
 * @author 0age
 * @notice A collection of internal view and pure functions that should be
 * overridden by the ultimate Dharma Token implementation.
 */
contract DharmaTokenOverrides {
  /**
   * @notice Internal view function to get the current cToken exchange rate and
   * supply rate per block. This function is meant to be overridden by the
   * dToken that inherits this contract.
   * @return The current cToken exchange rate, or amount of underlying tokens
   * that are redeemable for each cToken, and the cToken supply rate per block
   * (with 18 decimal places added to each returned rate).
   */
  function _getCurrentCTokenRates() internal view returns (
    uint256 exchangeRate, uint256 supplyRate
  );

  /**
   * @notice Internal pure function to supply the name of the underlying token.
   * @return The name of the underlying token.
   */
  function _getUnderlyingName() internal pure returns (string memory underlyingName);

  /**
   * @notice Internal pure function to supply the address of the underlying
   * token.
   * @return The address of the underlying token.
   */
  function _getUnderlying() internal pure returns (address underlying);

  /**
   * @notice Internal pure function to supply the symbol of the backing cToken.
   * @return The symbol of the backing cToken.
   */
  function _getCTokenSymbol() internal pure returns (string memory cTokenSymbol);

  /**
   * @notice Internal pure function to supply the address of the backing cToken.
   * @return The address of the backing cToken.
   */
  function _getCToken() internal pure returns (address cToken);

  /**
   * @notice Internal pure function to supply the name of the dToken.
   * @return The name of the dToken.
   */
  function _getDTokenName() internal pure returns (string memory dTokenName);

  /**
   * @notice Internal pure function to supply the symbol of the dToken.
   * @return The symbol of the dToken.
   */
  function _getDTokenSymbol() internal pure returns (string memory dTokenSymbol);

  /**
   * @notice Internal pure function to supply the address of the vault that
   * receives surplus cTokens whenever the surplus is pulled.
   * @return The address of the vault.
   */
  function _getVault() internal pure returns (address vault);
}


/**
 * @title DharmaTokenHelpers
 * @author 0age
 * @notice A collection of constants and internal pure functions used by Dharma
 * Tokens.
 */
contract DharmaTokenHelpers is DharmaTokenOverrides {
  using SafeMath for uint256;

  uint8 internal constant _DECIMALS = 8; // matches cToken decimals
  uint256 internal constant _SCALING_FACTOR = 1e18;
  uint256 internal constant _SCALING_FACTOR_MINUS_ONE = 999999999999999999;
  uint256 internal constant _HALF_OF_SCALING_FACTOR = 5e17;
  uint256 internal constant _COMPOUND_SUCCESS = 0;
  uint256 internal constant _MAX_UINT_112 = 5192296858534827628530496329220095;
  /* solhint-disable separate-by-one-line-in-contract */
  uint256 internal constant _MAX_UNMALLEABLE_S = (
    0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
  );
  /* solhint-enable separate-by-one-line-in-contract */

  /**
   * @notice Internal pure function to determine if a call to Compound succeeded
   * and to revert, supplying the reason, if it failed. Failure can be caused by
   * a call that reverts, or by a call that does not revert but returns a
   * non-zero error code.
   * @param functionSelector bytes4 The function selector that was called.
   * @param ok bool A boolean representing whether the call returned or
   * reverted.
   * @param data bytes The data provided by the returned or reverted call.
   */
  function _checkCompoundInteraction(
    bytes4 functionSelector, bool ok, bytes memory data
  ) internal pure {
    CTokenInterface cToken;
    if (ok) {
      if (
        functionSelector == cToken.transfer.selector ||
        functionSelector == cToken.transferFrom.selector
      ) {
        require(
          abi.decode(data, (bool)), string(
            abi.encodePacked(
              "Compound ",
              _getCTokenSymbol(),
              " contract returned false on calling ",
              _getFunctionName(functionSelector),
              "."
            )
          )
        );
      } else {
        uint256 compoundError = abi.decode(data, (uint256)); // throw on no data
        if (compoundError != _COMPOUND_SUCCESS) {
          revert(
            string(
              abi.encodePacked(
                "Compound ",
                _getCTokenSymbol(),
                " contract returned error code ",
                uint8((compoundError / 10) + 48),
                uint8((compoundError % 10) + 48),
                " on calling ",
                _getFunctionName(functionSelector),
                "."
              )
            )
          );
        }
      }
    } else {
      revert(
        string(
          abi.encodePacked(
            "Compound ",
            _getCTokenSymbol(),
            " contract reverted while attempting to call ",
            _getFunctionName(functionSelector),
            ": ",
            _decodeRevertReason(data)
          )
        )
      );
    }
  }

  /**
   * @notice Internal pure function to get a Compound function name based on the
   * selector.
   * @param functionSelector bytes4 The function selector.
   * @return The name of the function as a string.
   */
  function _getFunctionName(
    bytes4 functionSelector
  ) internal pure returns (string memory functionName) {
    CTokenInterface cToken;
    if (functionSelector == cToken.mint.selector) {
      functionName = "mint";
    } else if (functionSelector == cToken.redeem.selector) {
      functionName = "redeem";
    } else if (functionSelector == cToken.redeemUnderlying.selector) {
      functionName = "redeemUnderlying";
    } else if (functionSelector == cToken.transferFrom.selector) {
      functionName = "transferFrom";
    } else if (functionSelector == cToken.transfer.selector) {
      functionName = "transfer";
    } else if (functionSelector == cToken.accrueInterest.selector) {
      functionName = "accrueInterest";
    } else {
      functionName = "an unknown function";
    }
  }

  /**
   * @notice Internal pure function to decode revert reasons. The revert reason
   * prefix is removed and the remaining string argument is decoded.
   * @param revertData bytes The raw data supplied alongside the revert.
   * @return The decoded revert reason string.
   */
  function _decodeRevertReason(
    bytes memory revertData
  ) internal pure returns (string memory revertReason) {
    // Solidity prefixes revert reason with 0x08c379a0 -> Error(string) selector
    if (
      revertData.length > 68 && // prefix (4) + position (32) + length (32)
      revertData[0] == byte(0x08) &&
      revertData[1] == byte(0xc3) &&
      revertData[2] == byte(0x79) &&
      revertData[3] == byte(0xa0)
    ) {
      // Get the revert reason without the prefix from the revert data.
      bytes memory revertReasonBytes = new bytes(revertData.length - 4);
      for (uint256 i = 4; i < revertData.length; i++) {
        revertReasonBytes[i - 4] = revertData[i];
      }

      // Decode the resultant revert reason as a string.
      revertReason = abi.decode(revertReasonBytes, (string));
    } else {
      // Simply return the default, with no revert reason.
      revertReason = "(no revert reason)";
    }
  }

  /**
   * @notice Internal pure function to construct a failure message string for
   * the revert reason on transfers of underlying tokens that do not succeed.
   * @return The failure message.
   */
  function _getTransferFailureMessage() internal pure returns (
    string memory message
  ) {
    message = string(
      abi.encodePacked(_getUnderlyingName(), " transfer failed.")
    );
  }

  /**
   * @notice Internal pure function to convert a uint256 to a uint112, reverting
   * if the conversion would cause an overflow.
   * @param input uint256 The unsigned integer to convert.
   * @return The converted unsigned integer.
   */
  function _safeUint112(uint256 input) internal pure returns (uint112 output) {
    require(input <= _MAX_UINT_112, "Overflow on conversion to uint112.");
    output = uint112(input);
  }

  /**
   * @notice Internal pure function to convert an underlying amount to a dToken
   * or cToken amount using an exchange rate and fixed-point arithmetic.
   * @param underlying uint256 The underlying amount to convert.
   * @param exchangeRate uint256 The exchange rate (multiplied by 10^18).
   * @param roundUp bool Whether the final amount should be rounded up - it will
   * instead be truncated (rounded down) if this value is false.
   * @return The cToken or dToken amount.
   */
  function _fromUnderlying(
    uint256 underlying, uint256 exchangeRate, bool roundUp
  ) internal pure returns (uint256 amount) {
    if (roundUp) {
      amount = (
        (underlying.mul(_SCALING_FACTOR)).add(exchangeRate.sub(1))
      ).div(exchangeRate);
    } else {
      amount = (underlying.mul(_SCALING_FACTOR)).div(exchangeRate);
    }
  }

  /**
   * @notice Internal pure function to convert a dToken or cToken amount to the
   * underlying amount using an exchange rate and fixed-point arithmetic.
   * @param amount uint256 The cToken or dToken amount to convert.
   * @param exchangeRate uint256 The exchange rate (multiplied by 10^18).
   * @param roundUp bool Whether the final amount should be rounded up - it will
   * instead be truncated (rounded down) if this value is false.
   * @return The underlying amount.
   */
  function _toUnderlying(
    uint256 amount, uint256 exchangeRate, bool roundUp
  ) internal pure returns (uint256 underlying) {
    if (roundUp) {
      underlying = (
        (amount.mul(exchangeRate).add(_SCALING_FACTOR_MINUS_ONE)
      ) / _SCALING_FACTOR);
    } else {
      underlying = amount.mul(exchangeRate) / _SCALING_FACTOR;
    }
  }

  /**
   * @notice Internal pure function to convert an underlying amount to a dToken
   * or cToken amount and back to the underlying, so as to properly capture
   * rounding errors, by using an exchange rate and fixed-point arithmetic.
   * @param underlying uint256 The underlying amount to convert.
   * @param exchangeRate uint256 The exchange rate (multiplied by 10^18).
   * @param roundUpOne bool Whether the intermediate dToken or cToken amount
   * should be rounded up - it will instead be truncated (rounded down) if this
   * value is false.
   * @param roundUpTwo bool Whether the final underlying amount should be
   * rounded up - it will instead be truncated (rounded down) if this value is
   * false.
   * @return The intermediate cToken or dToken amount and the final underlying
   * amount.
   */
  function _fromUnderlyingAndBack(
    uint256 underlying, uint256 exchangeRate, bool roundUpOne, bool roundUpTwo
  ) internal pure returns (uint256 amount, uint256 adjustedUnderlying) {
    amount = _fromUnderlying(underlying, exchangeRate, roundUpOne);
    adjustedUnderlying = _toUnderlying(amount, exchangeRate, roundUpTwo);
  }
}


/**
 * @title DharmaTokenV1
 * @author 0age (dToken mechanics derived from Compound cTokens, ERC20 mechanics
 * derived from Open Zeppelin's ERC20 contract)
 * @notice A Dharma Token (or dToken) is an upgradeable ERC20 token with support
 * for meta-transactions that earns interest with respect to a given stablecoin,
 * and is backed by that stablecoin's respective Compound cToken. The V1 dToken
 * exchange rate will grow at 90% the rate of the backing cToken exchange rate.
 * This abstract contract contains functionality shared by each dToken - those
 * implementations will then inherit this contract and override any relevant,
 * unimplemented internal functions with implementation-specific ones.
 */
contract DharmaTokenV1 is ERC20Interface, DTokenInterface, DharmaTokenHelpers {
  // Set the version of the Dharma Token as a constant.
  uint256 private constant _DTOKEN_VERSION = 1;

  // Set block number and dToken + cToken exchange rate in slot zero on accrual.
  AccrualIndex private _accrualIndex;

  // Slot one tracks the total issued dTokens.
  uint256 private _totalSupply;

  // Slots two and three are entrypoints into balance and allowance mappings.
  mapping (address => uint256) private _balances;
  mapping (address => mapping (address => uint256)) private _allowances;

  // Slot four is an entrypoint into a mapping for used meta-transaction hashes.
  mapping (bytes32 => bool) private _executedMetaTxs;

  /**
   * @notice Transfer `underlyingToSupply` underlying tokens from `msg.sender`
   * to this contract, use them to mint cTokens as backing, and mint dTokens to
   * `msg.sender`. Ensure that this contract has been approved to transfer the
   * underlying on behalf of the caller before calling this function.
   * @param underlyingToSupply uint256 The amount of underlying to provide as
   * part of minting.
   * @return The amount of dTokens received in return for the supplied
   * underlying tokens.
   */
  function mint(
    uint256 underlyingToSupply
  ) external returns (uint256 dTokensMinted) {
    // Instantiate interfaces for the underlying token and the backing cToken.
    ERC20Interface underlying = ERC20Interface(_getUnderlying());
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Pull in underlying - ensure that this contract has sufficient allowance.
    require(
      underlying.transferFrom(msg.sender, address(this), underlyingToSupply),
      _getTransferFailureMessage()
    );

    // Use underlying to mint cTokens and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.mint.selector, underlyingToSupply
    ));
    _checkCompoundInteraction(cToken.mint.selector, ok, data);

    // Accrue after the Compound mint to avoid duplicating accrual calculations.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(false);

    // Get underlying equivalent of minted cTokens to prevent "dust" griefing.
    (, uint256 underlyingEquivalent) = _fromUnderlyingAndBack(
      underlyingToSupply, cTokenExchangeRate, false, false
    );

    // Determine dTokens to mint using underlying equivalent and exchange rate.
    dTokensMinted = _fromUnderlying(
      underlyingEquivalent, dTokenExchangeRate, false
    );

    // Mint dTokens to the caller.
    _mint(msg.sender, underlyingToSupply, dTokensMinted);
  }

  /**
   * @notice Transfer `cTokensToSupply` cTokens from `msg.sender` to this
   * contract and mint dTokens to `msg.sender`. Ensure that this contract has
   * been approved to transfer the cTokens on behalf of the caller before
   * calling this function.
   * @param cTokensToSupply uint256 The amount of cTokens to provide as part of
   * minting.
   * @return The amount of dTokens received in return for the supplied cTokens.
   */
  function mintViaCToken(
    uint256 cTokensToSupply
  ) external returns (uint256 dTokensMinted) {
    // Instantiate the interface for the backing cToken.
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Pull in cTokens - ensure that this contract has sufficient allowance.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.transferFrom.selector, msg.sender, address(this), cTokensToSupply
    ));
    _checkCompoundInteraction(cToken.transferFrom.selector, ok, data);

    // Accrue interest and retrieve current cToken and dToken exchange rates.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(true);

    // Determine the underlying equivalent of the supplied cToken amount.
    uint256 underlyingEquivalent = _toUnderlying(
      cTokensToSupply, cTokenExchangeRate, false
    );

    // Determine dTokens to mint using underlying equivalent and exchange rate.
    dTokensMinted = _fromUnderlying(
      underlyingEquivalent, dTokenExchangeRate, false
    );

    // Mint dTokens to the caller.
    _mint(msg.sender, underlyingEquivalent, dTokensMinted);
  }

  /**
   * @notice Redeem `dTokensToBurn` dTokens from `msg.sender`, use the
   * corresponding cTokens to redeem the required underlying, and transfer the
   * redeemed underlying tokens to `msg.sender`.
   * @param dTokensToBurn uint256 The amount of dTokens to provide in exchange
   * for underlying tokens.
   * @return The amount of underlying received in return for the provided
   * dTokens.
   */
  function redeem(
    uint256 dTokensToBurn
  ) external returns (uint256 underlyingReceived) {
    // Instantiate interfaces for the underlying token and the backing cToken.
    ERC20Interface underlying = ERC20Interface(_getUnderlying());
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Accrue interest and retrieve current dToken and cToken exchange rates.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(true);

    // Determine the equivalent underlying value of the dTokens to be burned.
    uint256 underlyingEquivalent = _toUnderlying(
      dTokensToBurn, dTokenExchangeRate, false
    );

    // Get minted cTokens and underlying equivalent to prevent "dust" griefing.
    uint256 cTokenEquivalent;
    (cTokenEquivalent, underlyingReceived) = _fromUnderlyingAndBack(
      underlyingEquivalent, cTokenExchangeRate, false, false
    );

    // Burn the dTokens.
    _burn(msg.sender, underlyingReceived, dTokensToBurn);

    // Use cTokens to redeem underlying and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.redeem.selector, cTokenEquivalent
    ));
    _checkCompoundInteraction(cToken.redeem.selector, ok, data);

    // Send the redeemed underlying tokens to the caller.
    require(
      underlying.transfer(msg.sender, underlyingReceived),
      _getTransferFailureMessage()
    );
  }

  /**
   * @notice Redeem `dTokensToBurn` dTokens from `msg.sender` and transfer the
   * corresponding amount of cTokens to `msg.sender`.
   * @param dTokensToBurn uint256 The amount of dTokens to provide in exchange
   * for the cTokens.
   * @return The amount of cTokens received in return for the provided dTokens.
   */
  function redeemToCToken(
    uint256 dTokensToBurn
  ) external returns (uint256 cTokensReceived) {
    // Instantiate the interface for the backing cToken.
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Accrue interest and retrieve current cToken and dToken exchange rates.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(true);

    // Determine the underlying token value of the dTokens to be burned.
    uint256 underlyingEquivalent = _toUnderlying(
      dTokensToBurn, dTokenExchangeRate, false
    );

    // Determine amount of cTokens corresponding to underlying equivalent value.
    cTokensReceived = _fromUnderlying(
      underlyingEquivalent, cTokenExchangeRate, false
    );

    // Burn the dTokens.
    _burn(msg.sender, underlyingEquivalent, dTokensToBurn);

    // Transfer cTokens to the caller and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.transfer.selector, msg.sender, cTokensReceived
    ));
    _checkCompoundInteraction(cToken.transfer.selector, ok, data);
  }

  /**
   * @notice Redeem the dToken equivalent value of the underlying token amount
   * `underlyingToReceive` from `msg.sender`, use the corresponding cTokens to
   * redeem the underlying, and transfer the underlying to `msg.sender`.
   * @param underlyingToReceive uint256 The amount, denominated in the
   * underlying token, of the cToken to redeem in exchange for the received
   * underlying token.
   * @return The amount of dTokens burned in exchange for the returned
   * underlying tokens.
   */
  function redeemUnderlying(
    uint256 underlyingToReceive
  ) external returns (uint256 dTokensBurned) {
    // Instantiate interfaces for the underlying token and the backing cToken.
    ERC20Interface underlying = ERC20Interface(_getUnderlying());
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Use cTokens to redeem underlying and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.redeemUnderlying.selector, underlyingToReceive
    ));
    _checkCompoundInteraction(cToken.redeemUnderlying.selector, ok, data);

    // Accrue after the Compound redeem to avoid duplicating calculations.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(false);

    // Get underlying equivalent of redeemed cTokens to prevent "dust" griefing.
    (, uint256 underlyingEquivalent) = _fromUnderlyingAndBack(
      underlyingToReceive, cTokenExchangeRate, true, true // rounding up both
    );

    // Determine the dTokens to redeem using the exchange rate, rounding up.
    dTokensBurned = _fromUnderlying(
      underlyingEquivalent, dTokenExchangeRate, true
    );

    // Burn the dTokens.
    _burn(msg.sender, underlyingToReceive, dTokensBurned);

    // Send the redeemed underlying tokens to the caller.
    require(
      underlying.transfer(msg.sender, underlyingToReceive),
      _getTransferFailureMessage()
    );
  }

  /**
   * @notice Redeem the dToken equivalent value of the underlying tokens of
   * amount `underlyingToReceive` from `msg.sender` and transfer the
   * corresponding amount of cTokens to `msg.sender`.
   * @param underlyingToReceive uint256 The amount, denominated in the
   * underlying token, of cTokens to receive.
   * @return The amount of dTokens burned in exchange for the returned cTokens.
   */
  function redeemUnderlyingToCToken(
    uint256 underlyingToReceive
  ) external returns (uint256 dTokensBurned) {
    // Instantiate the interface for the backing cToken.
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Accrue interest and retrieve current cToken and dToken exchange rates.
    (uint256 dTokenExchangeRate, uint256 cTokenExchangeRate) = _accrue(true);

    // Get received cTokens and underlying equivalent (prevent "dust" griefing).
    (
      uint256 cTokensToReceive, uint256 underlyingEquivalent
    ) = _fromUnderlyingAndBack(
      underlyingToReceive, cTokenExchangeRate, false, true // round down cTokens
    );

    // Determine redeemed dTokens using equivalent underlying value, rounded up.
    dTokensBurned = _fromUnderlying(
      underlyingEquivalent, dTokenExchangeRate, true
    );

    // Burn the dTokens.
    _burn(msg.sender, underlyingToReceive, dTokensBurned);

    // Transfer cTokens to the caller and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.transfer.selector, msg.sender, cTokensToReceive
    ));
    _checkCompoundInteraction(cToken.transfer.selector, ok, data);
  }

  /**
   * @notice Transfer cTokens with underlying value in excess of the total
   * underlying dToken value to a dedicated "vault" account. A "hard" accrual
   * will first be performed, triggering an accrual on both the cToken and the
   * dToken.
   * @return The amount of cTokens transferred to the vault account.
   */
  function pullSurplus() external returns (uint256 cTokenSurplus) {
    // Instantiate the interface for the backing cToken.
    CTokenInterface cToken = CTokenInterface(_getCToken());

    // Accrue interest on the cToken and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(cToken).call(abi.encodeWithSelector(
      cToken.accrueInterest.selector
    ));
    _checkCompoundInteraction(cToken.accrueInterest.selector, ok, data);

    // Accrue interest on the dToken, reusing the stored cToken exchange rate.
    _accrue(false);

    // Determine cToken surplus in underlying (cToken value - dToken value).
    uint256 underlyingSurplus;
    (underlyingSurplus, cTokenSurplus) = _getSurplus();

    // Transfer cToken surplus to vault and ensure that the operation succeeds.
    (ok, data) = address(cToken).call(abi.encodeWithSelector(
      cToken.transfer.selector, _getVault(), cTokenSurplus
    ));
    _checkCompoundInteraction(cToken.transfer.selector, ok, data);

    emit CollectSurplus(underlyingSurplus, cTokenSurplus);
  }

  /**
   * @notice Manually advance the dToken exchange rate and cToken exchange rate
   * to that of the current block. Note that dToken accrual does not trigger
   * cToken accrual - instead, the updated exchange rate will be calculated
   * internally.
   */
  function accrueInterest() external {
    // Accrue interest on the dToken.
    _accrue(true);
  }

  /**
   * @notice Transfer `amount` dTokens from `msg.sender` to `recipient`.
   * @param recipient address The account to transfer the dTokens to.
   * @param amount uint256 The amount of dTokens to transfer.
   * @return A boolean indicating whether the transfer was successful.
   */
  function transfer(
    address recipient, uint256 amount
  ) external returns (bool success) {
    _transfer(msg.sender, recipient, amount);
    success = true;
  }

  /**
   * @notice Transfer dTokens equivalent to `underlyingEquivalentAmount`
   * underlying from `msg.sender` to `recipient`.
   * @param recipient address The account to transfer the dTokens to.
   * @param underlyingEquivalentAmount uint256 The underlying equivalent amount
   * of dTokens to transfer.
   * @return A boolean indicating whether the transfer was successful.
   */
  function transferUnderlying(
    address recipient, uint256 underlyingEquivalentAmount
  ) external returns (bool success) {
    // Accrue interest and retrieve the current dToken exchange rate.
    (uint256 dTokenExchangeRate, ) = _accrue(true);

    // Determine dToken amount to transfer using the exchange rate, rounded up.
    uint256 dTokenAmount = _fromUnderlying(
      underlyingEquivalentAmount, dTokenExchangeRate, true
    );

    // Transfer the dTokens.
    _transfer(msg.sender, recipient, dTokenAmount);
    success = true;
  }

  /**
   * @notice Approve `spender` to transfer up to `value` dTokens on behalf of
   * `msg.sender`.
   * @param spender address The account to grant the allowance.
   * @param value uint256 The size of the allowance to grant.
   * @return A boolean indicating whether the approval was successful.
   */
  function approve(
    address spender, uint256 value
  ) external returns (bool success) {
    _approve(msg.sender, spender, value);
    success = true;
  }

  /**
   * @notice Transfer `amount` dTokens from `sender` to `recipient` as long as
   * `msg.sender` has sufficient allowance.
   * @param sender address The account to transfer the dTokens from.
   * @param recipient address The account to transfer the dTokens to.
   * @param amount uint256 The amount of dTokens to transfer.
   * @return A boolean indicating whether the transfer was successful.
   */
  function transferFrom(
    address sender, address recipient, uint256 amount
  ) external returns (bool success) {
    _transferFrom(sender, recipient, amount);
    success = true;
  }

  /**
   * @notice Transfer dTokens eqivalent to `underlyingEquivalentAmount`
   * underlying from `sender` to `recipient` as long as `msg.sender` has
   * sufficient allowance.
   * @param sender address The account to transfer the dTokens from.
   * @param recipient address The account to transfer the dTokens to.
   * @param underlyingEquivalentAmount uint256 The underlying equivalent amount
   * of dTokens to transfer.
   * @return A boolean indicating whether the transfer was successful.
   */
  function transferUnderlyingFrom(
    address sender, address recipient, uint256 underlyingEquivalentAmount
  ) external returns (bool success) {
    // Accrue interest and retrieve the current dToken exchange rate.
    (uint256 dTokenExchangeRate, ) = _accrue(true);

    // Determine dToken amount to transfer using the exchange rate, rounded up.
    uint256 dTokenAmount = _fromUnderlying(
      underlyingEquivalentAmount, dTokenExchangeRate, true
    );

    // Transfer the dTokens and adjust allowance accordingly.
    _transferFrom(sender, recipient, dTokenAmount);
    success = true;
  }

  /**
   * @notice Increase the current allowance of `spender` by `value` dTokens.
   * @param spender address The account to grant the additional allowance.
   * @param addedValue uint256 The amount to increase the allowance by.
   * @return A boolean indicating whether the modification was successful.
   */
  function increaseAllowance(
    address spender, uint256 addedValue
  ) external returns (bool success) {
    _approve(
      msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)
    );
    success = true;
  }

  /**
   * @notice Decrease the current allowance of `spender` by `value` dTokens.
   * @param spender address The account to decrease the allowance for.
   * @param subtractedValue uint256 The amount to subtract from the allowance.
   * @return A boolean indicating whether the modification was successful.
   */
  function decreaseAllowance(
    address spender, uint256 subtractedValue
  ) external returns (bool success) {
    _approve(
      msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue)
    );
    success = true;
  }

  /**
   * @notice Modify the current allowance of `spender` for `owner` by `value`
   * dTokens, increasing it if `increase` is true otherwise decreasing it, via a
   * meta-transaction that expires at `expiration` (or does not expire if the
   * value is zero) and uses `salt` as an additional input, validated using
   * `signatures`.
   * @param owner address The account granting the modified allowance.
   * @param spender address The account to modify the allowance for.
   * @param value uint256 The amount to modify the allowance by.
   * @param increase bool A flag that indicates whether the allowance will be
   * increased by the specified value (if true) or decreased by it (if false).
   * @param expiration uint256 A timestamp indicating how long the modification
   * meta-transaction is valid for - a value of zero will signify no expiration.
   * @param salt bytes32 An arbitrary salt to be provided as an additional input
   * to the hash digest used to validate the signatures.
   * @param signatures bytes A signature, or collection of signatures, that the
   * owner must provide in order to authorize the meta-transaction. If the
   * account of the owner does not have any runtime code deployed to it, the
   * signature will be verified using ecrecover; otherwise, it will be supplied
   * to the owner along with the message digest and context via ERC-1271 for
   * validation.
   * @return A boolean indicating whether the modification was successful.
   */
  function modifyAllowanceViaMetaTransaction(
    address owner,
    address spender,
    uint256 value,
    bool increase,
    uint256 expiration,
    bytes32 salt,
    bytes calldata signatures
  ) external returns (bool success) {
    require(expiration == 0 || now <= expiration, "Meta-transaction expired.");

    // Construct the meta-transaction's message hash based on relevant context.
    bytes memory context = abi.encodePacked(
      address(this),
      // _DTOKEN_VERSION,
      this.modifyAllowanceViaMetaTransaction.selector,
      expiration,
      salt,
      abi.encode(owner, spender, value, increase)
    );
    bytes32 messageHash = keccak256(context);

    // Ensure message hash has never been used before and register it as used.
    require(!_executedMetaTxs[messageHash], "Meta-transaction already used.");
    _executedMetaTxs[messageHash] = true;

    // Construct the digest to compare signatures against using EIP-191 0x45.
    bytes32 digest = keccak256(
      abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
    );

    // Calculate new allowance by applying modification to current allowance.
    uint256 currentAllowance = _allowances[owner][spender];
    uint256 newAllowance = (
      increase ? currentAllowance.add(value) : currentAllowance.sub(value)
    );

    // Use EIP-1271 if owner is a contract - otherwise, use ecrecover.
    if (_isContract(owner)) {
      // Validate via ERC-1271 against the owner account.
      bytes memory data = abi.encode(digest, context);
      bytes4 magic = ERC1271Interface(owner).isValidSignature(data, signatures);
      require(magic == bytes4(0x20c13b0b), "Invalid signatures.");
    } else {
      // Validate via ecrecover against the owner account.
      _verifyRecover(owner, digest, signatures);
    }

    // Modify the allowance.
    _approve(owner, spender, newAllowance);
    success = true;
  }

  /**
   * @notice View function to determine a meta-transaction message hash, and to
   * determine if it is still valid (i.e. it has not yet been used and is not
   * expired). The returned message hash will need to be prefixed using EIP-191
   * 0x45 and hashed again in order to generate a final digest for the required
   * signature - in other words, the same procedure utilized by `eth_Sign`.
   * @param functionSelector bytes4 The function selector for the given
   * meta-transaction. There is only one function selector available for V1:
   * `0x2d657fa5` (the selector for `modifyAllowanceViaMetaTransaction`).
   * @param arguments bytes The abi-encoded function arguments (aside from the
   * `expiration`, `salt`, and `signatures` arguments) that should be supplied
   * to the given function.
   * @param expiration uint256 A timestamp indicating how long the given
   * meta-transaction is valid for - a value of zero will signify no expiration.
   * @param salt bytes32 An arbitrary salt to be provided as an additional input
   * to the hash digest used to validate the signatures.
   * @return The total supply.
   */
  function getMetaTransactionMessageHash(
    bytes4 functionSelector,
    bytes calldata arguments,
    uint256 expiration,
    bytes32 salt
  ) external view returns (bytes32 messageHash, bool valid) {
    // Construct the meta-transaction's message hash based on relevant context.
    messageHash = keccak256(
      abi.encodePacked(
        address(this), functionSelector, expiration, salt, arguments
      )
    );

    // The meta-transaction is valid if it has not been used and is not expired.
    valid = (
      !_executedMetaTxs[messageHash] && (expiration == 0 || now <= expiration)
    );
  }

  /**
   * @notice View function to get the total dToken supply.
   * @return The total supply.
   */
  function totalSupply() external view returns (uint256 dTokenTotalSupply) {
    dTokenTotalSupply = _totalSupply;
  }

  /**
   * @notice View function to get the total dToken supply, denominated in the
   * underlying token.
   * @return The total supply.
   */
  function totalSupplyUnderlying() external view returns (
    uint256 dTokenTotalSupplyInUnderlying
  ) {
    (uint256 dTokenExchangeRate, ,) = _getExchangeRates(true);

    // Determine total value of all issued dTokens, denominated as underlying.
    dTokenTotalSupplyInUnderlying = _toUnderlying(
      _totalSupply, dTokenExchangeRate, false
    );
  }

  /**
   * @notice View function to get the total dToken balance of an account.
   * @param account address The account to check the dToken balance for.
   * @return The balance of the given account.
   */
  function balanceOf(address account) external view returns (uint256 dTokens) {
    dTokens = _balances[account];
  }

  /**
   * @notice View function to get the dToken balance of an account, denominated
   * in the underlying equivalent value.
   * @param account address The account to check the balance for.
   * @return The total underlying-equivalent dToken balance.
   */
  function balanceOfUnderlying(
    address account
  ) external view returns (uint256 underlyingBalance) {
    // Get most recent dToken exchange rate by determining accrued interest.
    (uint256 dTokenExchangeRate, ,) = _getExchangeRates(true);

    // Convert account balance to underlying equivalent using the exchange rate.
    underlyingBalance = _toUnderlying(
      _balances[account], dTokenExchangeRate, false
    );
  }

  /**
   * @notice View function to get the total allowance that `spender` has to
   * transfer dTokens from the `owner` account using `transferFrom`.
   * @param owner address The account that is granting the allowance.
   * @param spender address The account that has been granted the allowance.
   * @return The allowance of the given spender for the given owner.
   */
  function allowance(
    address owner, address spender
  ) external view returns (uint256 dTokenAllowance) {
    dTokenAllowance = _allowances[owner][spender];
  }

  /**
   * @notice View function to get the current dToken exchange rate (multiplied
   * by 10^18).
   * @return The current exchange rate.
   */
  function exchangeRateCurrent() external view returns (
    uint256 dTokenExchangeRate
  ) {
    // Get most recent dToken exchange rate by determining accrued interest.
    (dTokenExchangeRate, ,) = _getExchangeRates(true);
  }

  /**
   * @notice View function to get the current dToken interest earned per block
   * (multiplied by 10^18).
   * @return The current interest rate.
   */
  function supplyRatePerBlock() external view returns (
    uint256 dTokenInterestRate
  ) {
    (dTokenInterestRate,) = _getRatePerBlock();
  }

  /**
   * @notice View function to get the block number where accrual was last
   * performed.
   * @return The block number where accrual was last performed.
   */
  function accrualBlockNumber() external view returns (uint256 blockNumber) {
    blockNumber = _accrualIndex.block;
  }

  /**
   * @notice View function to get the total surplus, or the cToken balance that
   * exceeds the aggregate underlying value of the total dToken supply.
   * @return The total surplus in cTokens.
   */
  function getSurplus() external view returns (uint256 cTokenSurplus) {
    // Determine the cToken (cToken underlying value - dToken underlying value).
    (, cTokenSurplus) = _getSurplus();
  }

  /**
   * @notice View function to get the total surplus in the underlying, or the
   * underlying equivalent of the cToken balance that exceeds the aggregate
   * underlying value of the total dToken supply.
   * @return The total surplus, denominated in the underlying.
   */
  function getSurplusUnderlying() external view returns (
    uint256 underlyingSurplus
  ) {
    // Determine cToken surplus in underlying (cToken value - dToken value).
    (underlyingSurplus, ) = _getSurplus();
  }

  /**
   * @notice View function to get the interest rate spread taken by the dToken
   * from the current cToken supply rate per block (multiplied by 10^18).
   * @return The current interest rate spread.
   */
  function getSpreadPerBlock() external view returns (uint256 rateSpread) {
    (
      uint256 dTokenInterestRate, uint256 cTokenInterestRate
    ) = _getRatePerBlock();
    rateSpread = cTokenInterestRate.sub(dTokenInterestRate);
  }

  /**
   * @notice Pure function to get the name of the dToken.
   * @return The name of the dToken.
   */
  function name() external pure returns (string memory dTokenName) {
    dTokenName = _getDTokenName();
  }

  /**
   * @notice Pure function to get the symbol of the dToken.
   * @return The symbol of the dToken.
   */
  function symbol() external pure returns (string memory dTokenSymbol) {
    dTokenSymbol = _getDTokenSymbol();
  }

  /**
   * @notice Pure function to get the number of decimals of the dToken.
   * @return The number of decimals of the dToken.
   */
  function decimals() external pure returns (uint8 dTokenDecimals) {
    dTokenDecimals = _DECIMALS;
  }

  /**
   * @notice Pure function to get the dToken version.
   * @return The version of the dToken.
   */
  function getVersion() external pure returns (uint256 version) {
    version = _DTOKEN_VERSION;
  }

  /**
   * @notice Pure function to get the address of the cToken backing this dToken.
   * @return The address of the cToken backing this dToken.
   */
  function getCToken() external pure returns (address cToken) {
    cToken = _getCToken();
  }

  /**
   * @notice Pure function to get the address of the underlying token of this
   * dToken.
   * @return The address of the underlying token for this dToken.
   */
  function getUnderlying() external pure returns (address underlying) {
    underlying = _getUnderlying();
  }

  /**
   * @notice Private function to trigger accrual and to update the dToken and
   * cToken exchange rates in storage if necessary. The `compute` argument can
   * be set to false if an accrual has already taken place on the cToken before
   * calling this function.
   * @param compute bool A flag to indicate whether the cToken exchange rate
   * needs to be computed - if false, it will simply be read from storage on the
   * cToken in question.
   * @return The current dToken and cToken exchange rates.
   */
  function _accrue(bool compute) private returns (
    uint256 dTokenExchangeRate, uint256 cTokenExchangeRate
  ) {
    bool alreadyAccrued;
    (
      dTokenExchangeRate, cTokenExchangeRate, alreadyAccrued
    ) = _getExchangeRates(compute);

    if (!alreadyAccrued) {
      // Update storage with dToken + cToken exchange rates as of current block.
      AccrualIndex storage accrualIndex = _accrualIndex;
      accrualIndex.dTokenExchangeRate = _safeUint112(dTokenExchangeRate);
      accrualIndex.cTokenExchangeRate = _safeUint112(cTokenExchangeRate);
      accrualIndex.block = uint32(block.number);
      emit Accrue(dTokenExchangeRate, cTokenExchangeRate);
    }
  }

  /**
   * @notice Private function to mint `amount` tokens by exchanging `exchanged`
   * tokens to `account` and emit corresponding `Mint` & `Transfer` events.
   * @param account address The account to mint tokens to.
   * @param exchanged uint256 The amount of underlying tokens used to mint.
   * @param amount uint256 The amount of tokens to mint.
   */
  function _mint(address account, uint256 exchanged, uint256 amount) private {
    require(
      exchanged > 0 && amount > 0, "Mint failed: insufficient funds supplied."
    );
    _totalSupply = _totalSupply.add(amount);
    _balances[account] = _balances[account].add(amount);

    emit Mint(account, exchanged, amount);
    emit Transfer(address(0), account, amount);
  }

  /**
   * @notice Private function to burn `amount` tokens by exchanging `exchanged`
   * tokens from `account` and emit corresponding `Redeeem` & `Transfer` events.
   * @param account address The account to burn tokens from.
   * @param exchanged uint256 The amount of underlying tokens given for burning.
   * @param amount uint256 The amount of tokens to burn.
   */
  function _burn(address account, uint256 exchanged, uint256 amount) private {
    require(
      exchanged > 0 && amount > 0, "Redeem failed: insufficient funds supplied."
    );

    uint256 balancePriorToBurn = _balances[account];
    require(
      balancePriorToBurn >= amount, "Supplied amount exceeds account balance."
    );

    _totalSupply = _totalSupply.sub(amount);
    _balances[account] = balancePriorToBurn - amount; // overflow checked above

    emit Transfer(account, address(0), amount);
    emit Redeem(account, exchanged, amount);
  }

  /**
   * @notice Private function to move `amount` tokens from `sender` to
   * `recipient` and emit a corresponding `Transfer` event.
   * @param sender address The account to transfer tokens from.
   * @param recipient address The account to transfer tokens to.
   * @param amount uint256 The amount of tokens to transfer.
   */
  function _transfer(
    address sender, address recipient, uint256 amount
  ) private {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    uint256 senderBalance = _balances[sender];
    require(senderBalance >= amount, "Insufficient funds.");

    _balances[sender] = senderBalance - amount; // overflow checked above.
    _balances[recipient] = _balances[recipient].add(amount);

    emit Transfer(sender, recipient, amount);
  }

  /**
   * @notice Private function to transfer `amount` tokens from `sender` to
   * `recipient` and to deduct the transferred amount from the allowance of the
   * caller unless the allowance is set to the maximum amount.
   * @param sender address The account to transfer tokens from.
   * @param recipient address The account to transfer tokens to.
   * @param amount uint256 The amount of tokens to transfer.
   */
  function _transferFrom(
    address sender, address recipient, uint256 amount
  ) private {
    _transfer(sender, recipient, amount);
    uint256 callerAllowance = _allowances[sender][msg.sender];
    if (callerAllowance != uint256(-1)) {
      require(callerAllowance >= amount, "Insufficient allowance.");
      _approve(sender, msg.sender, callerAllowance - amount); // overflow safe.
    }
  }

  /**
   * @notice Private function to set the allowance for `spender` to transfer up
   * to `value` tokens on behalf of `owner`.
   * @param owner address The account that has granted the allowance.
   * @param spender address The account to grant the allowance.
   * @param value uint256 The size of the allowance to grant.
   */
  function _approve(address owner, address spender, uint256 value) private {
    require(owner != address(0), "ERC20: approve for the zero address");
    require(spender != address(0), "ERC20: approve to the zero address");

    _allowances[owner][spender] = value;
    emit Approval(owner, spender, value);
  }

  /**
   * @notice Private view function to get the latest dToken and cToken exchange
   * rates and provide the value for each. The `compute` argument can be set to
   * false if an accrual has already taken place on the cToken before calling
   * this function.
   * @param compute bool A flag to indicate whether the cToken exchange rate
   * needs to be computed - if false, it will simply be read from storage on the
   * cToken in question.
   * @return The dToken and cToken exchange rate, as well as a boolean
   * indicating if interest accrual has been processed already or needs to be
   * calculated and placed in storage.
   */
  function _getExchangeRates(bool compute) private view returns (
    uint256 dTokenExchangeRate, uint256 cTokenExchangeRate, bool fullyAccrued
  ) {
    // Get the stored accrual block and dToken + cToken exhange rates.
    AccrualIndex memory accrualIndex = _accrualIndex;
    uint256 storedDTokenExchangeRate = uint256(accrualIndex.dTokenExchangeRate);
    uint256 storedCTokenExchangeRate = uint256(accrualIndex.cTokenExchangeRate);
    uint256 accrualBlock = uint256(accrualIndex.block);

    // Use stored exchange rates if an accrual has already occurred this block.
    fullyAccrued = (accrualBlock == block.number);
    if (fullyAccrued) {
      dTokenExchangeRate = storedDTokenExchangeRate;
      cTokenExchangeRate = storedCTokenExchangeRate;
    } else {
      // Only compute cToken exchange rate if it has not accrued this block.
      if (compute) {
        // Get current cToken exchange rate; inheriting contract overrides this.
        (cTokenExchangeRate,) = _getCurrentCTokenRates();
      } else {
        // Otherwise, get the stored cToken exchange rate.
        cTokenExchangeRate = CTokenInterface(_getCToken()).exchangeRateStored();
      }

      // Determine the cToken interest earned during the period.
      uint256 cTokenInterest = (
        (cTokenExchangeRate.mul(_SCALING_FACTOR)).div(storedCTokenExchangeRate)
      ).sub(_SCALING_FACTOR);

      // Calculate dToken exchange rate by applying 90% of the cToken interest.
      dTokenExchangeRate = storedDTokenExchangeRate.mul(
        _SCALING_FACTOR.add(cTokenInterest.mul(9) / 10)
      ) / _SCALING_FACTOR;
    }
  }

  /**
   * @notice Private view function to get the total surplus, or cToken
   * balance that exceeds the total dToken balance.
   * @return The total surplus, denominated in both the underlying and in the
   * cToken.
   */
  function _getSurplus() private view returns (
    uint256 underlyingSurplus, uint256 cTokenSurplus
  ) {
    // Instantiate the interface for the backing cToken.
    CTokenInterface cToken = CTokenInterface(_getCToken());

    (
      uint256 dTokenExchangeRate, uint256 cTokenExchangeRate,
    ) = _getExchangeRates(true);

    // Determine value of all issued dTokens in the underlying, rounded up.
    uint256 dTokenUnderlying = _toUnderlying(
      _totalSupply, dTokenExchangeRate, true
    );

    // Determine value of all retained cTokens in the underlying, rounded down.
    uint256 cTokenUnderlying = _toUnderlying(
      cToken.balanceOf(address(this)), cTokenExchangeRate, false
    );

    // Determine the size of the surplus in terms of underlying amount.
    underlyingSurplus = cTokenUnderlying > dTokenUnderlying
      ? cTokenUnderlying - dTokenUnderlying // overflow checked above
      : 0;

    // Determine the cToken equivalent of this surplus amount.
    cTokenSurplus = underlyingSurplus == 0
      ? 0
      : _fromUnderlying(underlyingSurplus, cTokenExchangeRate, false);
  }

  /**
   * @notice Private view function to get the current dToken and cToken interest
   * supply rate per block (multiplied by 10^18).
   * @return The current dToken and cToken interest rates.
   */
  function _getRatePerBlock() private view returns (
    uint256 dTokenSupplyRate, uint256 cTokenSupplyRate
  ) {
    (, cTokenSupplyRate) = _getCurrentCTokenRates();
    dTokenSupplyRate = cTokenSupplyRate.mul(9) / 10;
  }

  /**
   * @notice Private view function to determine if a given account has runtime
   * code or not - in other words, whether or not a contract is deployed to the
   * account in question. Note that contracts that are in the process of being
   * deployed will return false on this check.
   * @param account address The account to check for contract runtime code.
   * @return Whether or not there is contract runtime code at the account.
   */
  function _isContract(address account) private view returns (bool isContract) {
    uint256 size;
    assembly { size := extcodesize(account) }
    isContract = size > 0;
  }

  /**
   * @notice Private pure function to verify that a given signature of a digest
   * resolves to the supplied account. Any error, including incorrect length,
   * malleable signature types, or unsupported `v` values, will cause a revert.
   * @param account address The account to validate against.
   * @param digest bytes32 The digest to use.
   * @param signature bytes The signature to verify.
   */
  function _verifyRecover(
    address account, bytes32 digest, bytes memory signature
  ) private pure {
    // Ensure the signature length is correct.
    require(
      signature.length == 65,
      "Must supply a single 65-byte signature when owner is not a contract."
    );

    // Divide the signature in r, s and v variables.
    bytes32 r;
    bytes32 s;
    uint8 v;
    assembly {
      r := mload(add(signature, 0x20))
      s := mload(add(signature, 0x40))
      v := byte(0, mload(add(signature, 0x60)))
    }

    require(
      uint256(s) <= _MAX_UNMALLEABLE_S,
      "Signature `s` value cannot be potentially malleable."
    );

    require(v == 27 || v == 28, "Signature `v` value not permitted.");

    require(account == ecrecover(digest, v, r, s), "Invalid signature.");
  }
}


/**
 * @title DharmaDaiImplementationV1
 * @author 0age (dToken mechanics derived from Compound cTokens, ERC20 methods
 * derived from Open Zeppelin's ERC20 contract)
 * @notice This contract provides the V1 implementation of Dharma Dai (or dDai),
 * an upgradeable, interest-bearing ERC20 token with cDai as the backing token
 * and Dai as the underlying token. The V1 dDai exchange rate will grow at 90%
 * the rate of the backing cDai exchange rate. Dharma Dai also supports
 * meta-transactions originating from externally-owned accounts, as well as from
 * contract accounts via ERC-1271.
 */
contract DharmaDaiImplementationV1 is DharmaTokenV1 {
  string internal constant _NAME = "Dharma Dai";
  string internal constant _SYMBOL = "dDai";
  string internal constant _UNDERLYING_NAME = "Dai";
  string internal constant _CTOKEN_SYMBOL = "cDai";

  CTokenInterface internal constant _CDAI = CTokenInterface(
    0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643 // mainnet
  );

  ERC20Interface internal constant _DAI = ERC20Interface(
    0x6B175474E89094C44Da98b954EedeAC495271d0F // mainnet
  );

  PotInterface internal constant _POT = PotInterface(
    0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7 // mainnet
  );

  address internal constant _VAULT = 0x7e4A8391C728fEd9069B2962699AB416628B19Fa;

  /**
   * @notice Internal view function to get the current cDai exchange rate and
   * supply rate per block.
   * @return The current cDai exchange rate, or amount of Dai that is redeemable
   * for each cDai, and the cDai supply rate per block (with 18 decimal places
   * added to each returned rate).
   */
  function _getCurrentCTokenRates() internal view returns (
    uint256 exchangeRate, uint256 supplyRate
  ) {
    // Determine the number of blocks that have elapsed since last cDai accrual.
    uint256 blockDelta = block.number.sub(_CDAI.accrualBlockNumber());

    // Return stored values if accrual has already been performed this block.
    if (blockDelta == 0) return (
      _CDAI.exchangeRateStored(), _CDAI.supplyRatePerBlock()
    );
    
    // Determine total "cash" held by cDai contract by calculating DSR interest.
    uint256 cash = ( // solhint-disable-next-line not-rely-on-time
      _rpow(_POT.dsr(), now.sub(_POT.rho()), 1e27).mul(_POT.chi()) / 1e27 // chi
    ).mul(_POT.pie(address(_CDAI))) / 1e27;

    // Get the latest interest rate model from the cDai contract.
    CDaiInterestRateModelInterface interestRateModel = (
      CDaiInterestRateModelInterface(_CDAI.interestRateModel())
    );

    // Get the current stored total borrows, reserves, and reserve factor.
    uint256 borrows = _CDAI.totalBorrows();
    uint256 reserves = _CDAI.totalReserves();
    uint256 reserveFactor = _CDAI.reserveFactorMantissa();

    // Get accumulated borrow interest via interest rate model and block delta.
    uint256 interest = interestRateModel.getBorrowRate(
      cash, borrows, reserves
    ).mul(blockDelta).mul(borrows) / _SCALING_FACTOR;

    // Update total borrows and reserves using calculated accumulated interest.
    borrows = borrows.add(interest);
    reserves = reserves.add(reserveFactor.mul(interest) / _SCALING_FACTOR);

    // Determine cDai exchange rate: (cash + borrows - reserves) / total supply
    exchangeRate = (
      ((cash.add(borrows)).sub(reserves)).mul(_SCALING_FACTOR)
    ).div(_CDAI.totalSupply());

    // Get supply rate via interest rate model and calculated parameters.
    supplyRate = interestRateModel.getSupplyRate(
      cash, borrows, reserves, reserveFactor
    );
  }

  /**
   * @notice Internal pure function to supply the name of the underlying token.
   * @return The name of the underlying token.
   */
  function _getUnderlyingName() internal pure returns (string memory underlyingName) {
    underlyingName = _UNDERLYING_NAME;
  }

  /**
   * @notice Internal pure function to supply the address of the underlying
   * token.
   * @return The address of the underlying token.
   */
  function _getUnderlying() internal pure returns (address underlying) {
    underlying = address(_DAI);
  }

  /**
   * @notice Internal pure function to supply the symbol of the backing cToken.
   * @return The symbol of the backing cToken.
   */
  function _getCTokenSymbol() internal pure returns (string memory cTokenSymbol) {
    cTokenSymbol = _CTOKEN_SYMBOL;
  }

  /**
   * @notice Internal pure function to supply the address of the backing cToken.
   * @return The address of the backing cToken.
   */
  function _getCToken() internal pure returns (address cToken) {
    cToken = address(_CDAI);
  }

  /**
   * @notice Internal pure function to supply the name of the dToken.
   * @return The name of the dToken.
   */
  function _getDTokenName() internal pure returns (string memory dTokenName) {
    dTokenName = _NAME;
  }

  /**
   * @notice Internal pure function to supply the symbol of the dToken.
   * @return The symbol of the dToken.
   */
  function _getDTokenSymbol() internal pure returns (string memory dTokenSymbol) {
    dTokenSymbol = _SYMBOL;
  }

  /**
   * @notice Internal pure function to supply the address of the vault that
   * receives surplus cTokens whenever the surplus is pulled.
   * @return The address of the vault.
   */
  function _getVault() internal pure returns (address vault) {
    vault = _VAULT;
  }

  /**
   * @notice Internal pure function to directly emulate exponentiation performed
   * by the Dai Savings Rate contract.
   * @param x uint256 The number that will be raised to the given power.
   * @param n uint256 The power to raise that number by.
   * @param base uint256 The scaling factor that will be applied to n and z.
   * @return The number raised to the given power.
   */
  function _rpow(
    uint256 x, uint256 n, uint256 base
  ) internal pure returns (uint256 z) {
    assembly {
      switch x case 0 {switch n case 0 {z := base} default {z := 0}}
      default {
        switch mod(n, 2) case 0 { z := base } default { z := x }
        let half := div(base, 2)  // for rounding.
        for { n := div(n, 2) } n { n := div(n, 2) } {
          let xx := mul(x, x)
          if iszero(eq(div(xx, x), x)) { revert(0, 0) }
          let xxRound := add(xx, half)
          if lt(xxRound, xx) { revert(0, 0) }
          x := div(xxRound, base)
          if mod(n, 2) {
            let zx := mul(z, x)
            if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
            let zxRound := add(zx, half)
            if lt(zxRound, zx) { revert(0, 0) }
            z := div(zxRound, base)
          }
        }
      }
    }
  }
}
        

Compiler Settings

{"remappings":[],"optimizer":{"runs":200,"enabled":true},"libraries":{},"evmVersion":"petersburg","compilationTarget":{"DharmaDaiImplementationV1.sol":"DharmaDaiImplementationV1"}}
              

Contract ABI

[{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"string","name":"dTokenName","internalType":"string"}],"name":"name","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"uint256","name":"version","internalType":"uint256"}],"name":"getVersion","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"dTokensBurned","internalType":"uint256"}],"name":"redeemUnderlyingToCToken","inputs":[{"type":"uint256","name":"underlyingToReceive","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokenTotalSupply","internalType":"uint256"}],"name":"totalSupply","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"cTokenSurplus","internalType":"uint256"}],"name":"pullSurplus","inputs":[],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"cTokenSurplus","internalType":"uint256"}],"name":"getSurplus","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"cTokensReceived","internalType":"uint256"}],"name":"redeemToCToken","inputs":[{"type":"uint256","name":"dTokensToBurn","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"modifyAllowanceViaMetaTransaction","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"bool","name":"increase","internalType":"bool"},{"type":"uint256","name":"expiration","internalType":"uint256"},{"type":"bytes32","name":"salt","internalType":"bytes32"},{"type":"bytes","name":"signatures","internalType":"bytes"}],"constant":false},{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"uint8","name":"dTokenDecimals","internalType":"uint8"}],"name":"decimals","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"transferUnderlying","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"underlyingEquivalentAmount","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"increaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"addedValue","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"underlyingBalance","internalType":"uint256"}],"name":"balanceOfUnderlying","inputs":[{"type":"address","name":"account","internalType":"address"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"rateSpread","internalType":"uint256"}],"name":"getSpreadPerBlock","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"underlyingSurplus","internalType":"uint256"}],"name":"getSurplusUnderlying","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"blockNumber","internalType":"uint256"}],"name":"accrualBlockNumber","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bytes32","name":"messageHash","internalType":"bytes32"},{"type":"bool","name":"valid","internalType":"bool"}],"name":"getMetaTransactionMessageHash","inputs":[{"type":"bytes4","name":"functionSelector","internalType":"bytes4"},{"type":"bytes","name":"arguments","internalType":"bytes"},{"type":"uint256","name":"expiration","internalType":"uint256"},{"type":"bytes32","name":"salt","internalType":"bytes32"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokens","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"transferUnderlyingFrom","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"underlyingEquivalentAmount","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"dTokensBurned","internalType":"uint256"}],"name":"redeemUnderlying","inputs":[{"type":"uint256","name":"underlyingToReceive","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"string","name":"dTokenSymbol","internalType":"string"}],"name":"symbol","inputs":[],"constant":true},{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"address","name":"underlying","internalType":"address"}],"name":"getUnderlying","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"dTokensMinted","internalType":"uint256"}],"name":"mint","inputs":[{"type":"uint256","name":"underlyingToSupply","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"decreaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"subtractedValue","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"accrueInterest","inputs":[],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":"success","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokenInterestRate","internalType":"uint256"}],"name":"supplyRatePerBlock","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokenExchangeRate","internalType":"uint256"}],"name":"exchangeRateCurrent","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"dTokensMinted","internalType":"uint256"}],"name":"mintViaCToken","inputs":[{"type":"uint256","name":"cTokensToSupply","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"underlyingReceived","internalType":"uint256"}],"name":"redeem","inputs":[{"type":"uint256","name":"dTokensToBurn","internalType":"uint256"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokenAllowance","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}],"constant":true},{"type":"function","stateMutability":"pure","payable":false,"outputs":[{"type":"address","name":"cToken","internalType":"address"}],"name":"getCToken","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"dTokenTotalSupplyInUnderlying","internalType":"uint256"}],"name":"totalSupplyUnderlying","inputs":[],"constant":true},{"type":"event","name":"Mint","inputs":[{"type":"address","name":"minter","internalType":"address","indexed":false},{"type":"uint256","name":"mintAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"mintDTokens","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Redeem","inputs":[{"type":"address","name":"redeemer","internalType":"address","indexed":false},{"type":"uint256","name":"redeemAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"redeemDTokens","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Accrue","inputs":[{"type":"uint256","name":"dTokenExchangeRate","internalType":"uint256","indexed":false},{"type":"uint256","name":"cTokenExchangeRate","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CollectSurplus","inputs":[{"type":"uint256","name":"surplusAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"surplusCTokens","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false}]
              

Contract Creation Code

Verify & Publish
0x608060405234801561001057600080fd5b50613f99806100206000396000f3fe608060405234801561001057600080fd5b50600436106102065760003560e01c80636e519d101161011a578063a6afed95116100ad578063d8da64f31161007c578063d8da64f314610695578063db006a75146106b2578063dd62ed3e146106cf578063ec6c350c146106fd578063fea61faa1461070557610206565b8063a6afed951461064f578063a9059cbb14610659578063ae9d70b014610685578063bd6d894d1461068d57610206565b806395d89b41116100e957806395d89b41146105da5780639816f473146105e2578063a0712d6814610606578063a457c2d71461062357610206565b80636e519d10146104c157806370a08231146105615780637d33ef7a14610587578063852a12e3146105bd57610206565b806327c0f03b1161019d578063395093511161016c57806339509351146104575780633af9e6691461048357806344b5a802146104a95780634fdafdc7146104b15780636c540baf146104b957610206565b806327c0f03b1461034d5780632d657fa51461036a578063313ce5671461040d578063336d26921461042b57610206565b806318160ddd116101d957806318160ddd146102ff57806319376532146103075780632383b0741461030f57806323b872dd1461031757610206565b806306fdde031461020b578063095ea7b3146102885780630d8e6e2c146102c8578063166d588f146102e2575b600080fd5b61021361070d565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561024d578181015183820152602001610235565b50505050905090810190601f16801561027a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102b46004803603604081101561029e57600080fd5b506001600160a01b03813516906020013561071c565b604080519115158252519081900360200190f35b6102d0610732565b60408051918252519081900360200190f35b6102d0600480360360208110156102f857600080fd5b5035610737565b6102d0610889565b6102d061088f565b6102d0610af1565b6102b46004803603606081101561032d57600080fd5b506001600160a01b03813581169160208101359091169060400135610b01565b6102d06004803603602081101561036357600080fd5b5035610b18565b6102b4600480360360e081101561038057600080fd5b6001600160a01b038235811692602081013590911691604082013591606081013515159160808201359160a08101359181019060e0810160c08201356401000000008111156103ce57600080fd5b8201836020820111156103e057600080fd5b8035906020019184600183028401116401000000008311171561040257600080fd5b509092509050610c64565b61041561116c565b6040805160ff9092168252519081900360200190f35b6102b46004803603604081101561044157600080fd5b506001600160a01b038135169060200135611171565b6102b46004803603604081101561046d57600080fd5b506001600160a01b0381351690602001356111a7565b6102d06004803603602081101561049957600080fd5b50356001600160a01b03166111e8565b6102d0611226565b6102d061124f565b6102d061125f565b610548600480360360808110156104d757600080fd5b6001600160e01b0319823516919081019060408101602082013564010000000081111561050357600080fd5b82018360208201111561051557600080fd5b8035906020019184600183028401116401000000008311171561053757600080fd5b919350915080359060200135611272565b6040805192835290151560208301528051918290030190f35b6102d06004803603602081101561057757600080fd5b50356001600160a01b0316611324565b6102b46004803603606081101561059d57600080fd5b506001600160a01b0381358116916020810135909116906040013561133f565b6102d0600480360360208110156105d357600080fd5b5035611376565b6102136115d8565b6105ea6115e2565b604080516001600160a01b039092168252519081900360200190f35b6102d06004803603602081101561061c57600080fd5b50356115ec565b6102b46004803603604081101561063957600080fd5b506001600160a01b03813516906020013561180d565b610657611849565b005b6102b46004803603604081101561066f57600080fd5b506001600160a01b038135169060200135611857565b6102d0611864565b6102d061186e565b6102d0600480360360208110156106ab57600080fd5b5035611882565b6102d0600480360360208110156106c857600080fd5b50356119c9565b6102d0600480360360408110156106e557600080fd5b506001600160a01b0381358116916020013516611bf2565b6105ea611c1d565b6102d0611c27565b6060610717611c4c565b905090565b6000610729338484611c70565b50600192915050565b600190565b600080610742611d5c565b90506000806107516001611d74565b91509150600080610766878460006001611e4c565b9150915061077681856001611e72565b9550610783338888611eed565b60408051336024820152604480820185905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b178152915181516000936060936001600160a01b038b16939092909182918083835b602083106107fe5780518252601f1990920191602091820191016107df565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610860576040519150601f19603f3d011682016040523d82523d6000602084013e610865565b606091505b50909250905061087d63a9059cbb60e01b838361204a565b50505050505050919050565b60015490565b60008061089a611d5c565b60408051600481526024810182526020810180516001600160e01b031663a6afed9560e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106109025780518252601f1990920191602091820191016108e3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610964576040519150601f19603f3d011682016040523d82523d6000602084013e610969565b606091505b50909250905061098163a6afed9560e01b838361204a565b61098b6000611d74565b5050600061099761252a565b955090506001600160a01b03841663a9059cbb60e01b6109b5612627565b604080516001600160a01b03909216602483015260448083018a905281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310610a305780518252601f199092019160209182019101610a11565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610a92576040519150601f19603f3d011682016040523d82523d6000602084013e610a97565b606091505b509093509150610aaf63a9059cbb60e01b848461204a565b604080518281526020810187905281517f045364289025ecf402644016c21b92009a98af8b33ac874d970730e8cb2af062929181900390910190a15050505090565b6000610afb61252a565b92915050565b6000610b0e84848461263f565b5060019392505050565b600080610b23611d5c565b9050600080610b326001611d74565b915091506000610b44868460006126d9565b9050610b5281836000611e72565b9450610b5f338288611eed565b60408051336024820152604480820188905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b178152915181516000936060936001600160a01b038a16939092909182918083835b60208310610bda5780518252601f199092019160209182019101610bbb565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610c3c576040519150601f19603f3d011682016040523d82523d6000602084013e610c41565b606091505b509092509050610c5963a9059cbb60e01b838361204a565b505050505050919050565b6000841580610c735750844211155b610cc4576040805162461bcd60e51b815260206004820152601960248201527f4d6574612d7472616e73616374696f6e20657870697265642e00000000000000604482015290519081900360640190fd5b604080516001600160a01b03808c16602080840191909152908b168284015260608281018b90528915156080808501919091528451808503909101815260a084019094523080821b60c08501908152632d657fa560e01b60d4860181905260d886018c905260f886018b905286519396929590948c948c94939261011801918401908083835b60208310610d695780518252601f199092019160209182019101610d4a565b51815160209384036101000a60001901801990921691161790526040805192909401828103601f19018352845281518282012060008181526004909252939020549099509197505060ff16159450610e0d9350505050576040805162461bcd60e51b815260206004820152601e60248201527f4d6574612d7472616e73616374696f6e20616c726561647920757365642e0000604482015290519081900360640190fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060008160405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012090506000600360008e6001600160a01b03166001600160a01b0316815260200190815260200160002060008d6001600160a01b03166001600160a01b0316815260200190815260200160002054905060008a610ef557610ef0828d63ffffffff61273e16565b610f05565b610f05828d63ffffffff61279b16565b9050610f108e6127f5565b1561110c57606083866040516020018083815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610f5f578181015183820152602001610f47565b50505050905090810190601f168015610f8c5780820380516001836020036101000a031916815260200191505b509350505050604051602081830303815290604052905060008f6001600160a01b03166320c13b0b838c8c6040518463ffffffff1660e01b8152600401808060200180602001838103835286818151815260200191508051906020019080838360005b83811015611007578181015183820152602001610fef565b50505050905090810190601f1680156110345780820380516001836020036101000a031916815260200191505b508381038252848152602001858580828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b15801561107c57600080fd5b505afa158015611090573d6000803e3d6000fd5b505050506040513d60208110156110a657600080fd5b505190506001600160e01b031981166320c13b0b60e01b14611105576040805162461bcd60e51b815260206004820152601360248201527224b73b30b634b21039b4b3b730ba3ab932b99760691b604482015290519081900360640190fd5b505061114d565b61114d8e848a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506127fb92505050565b6111588e8e83611c70565b5060019d9c50505050505050505050505050565b600890565b60008061117e6001611d74565b509050600061118f84836001611e72565b905061119c3386836129bf565b506001949350505050565b3360008181526003602090815260408083206001600160a01b038716845290915281205490916107299185906111e3908663ffffffff61279b16565b611c70565b6000806111f56001612b40565b50506001600160a01b03841660009081526002602052604081205491925061121f919083906126d9565b9392505050565b6000806000611233612cc0565b9092509050611248818363ffffffff61273e16565b9250505090565b600061125961252a565b50919050565b600054600160e01b900463ffffffff1690565b60008030878585898960405160200180876001600160a01b03166001600160a01b031660601b8152601401866001600160e01b0319166001600160e01b03191681526004018581526020018481526020018383808284376040805191909301818103601f19018252835280516020918201206000818152600490925292902054919a505060ff161596505085159450611318935050505057508315806113185750834211155b90509550959350505050565b6001600160a01b031660009081526002602052604090205490565b60008061134c6001611d74565b509050600061135d84836001611e72565b905061136a86868361263f565b50600195945050505050565b600080611381612cf0565b9050600061138d611d5c565b60408051602480820188905282518083039091018152604490910182526020810180516001600160e01b031663852a12e360e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106114045780518252601f1990920191602091820191016113e5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611466576040519150601f19603f3d011682016040523d82523d6000602084013e61146b565b606091505b50909250905061148363852a12e360e01b838361204a565b6000806114906000611d74565b9150915060006114a38983600180611e4c565b9150506114b281846001611e72565b97506114bf338a8a611eed565b6040805163a9059cbb60e01b8152336004820152602481018b905290516001600160a01b0389169163a9059cbb9160448083019260209291908290030181600087803b15801561150e57600080fd5b505af1158015611522573d6000803e3d6000fd5b505050506040513d602081101561153857600080fd5b5051611542612d08565b906115cb5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611590578181015183820152602001611578565b50505050905090810190601f1680156115bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5050505050505050919050565b6060610717612d99565b6000610717612cf0565b6000806115f7612cf0565b90506000611603611d5c565b604080516323b872dd60e01b81523360048201523060248201526044810187905290519192506001600160a01b038416916323b872dd916064808201926020929091908290030181600087803b15801561165c57600080fd5b505af1158015611670573d6000803e3d6000fd5b505050506040513d602081101561168657600080fd5b5051611690612d08565b906116dc5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b5060408051602480820187905282518083039091018152604490910182526020810180516001600160e01b031663140e25ad60e31b178152915181516000936060936001600160a01b038716939092909182918083835b602083106117525780518252601f199092019160209182019101611733565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146117b4576040519150601f19603f3d011682016040523d82523d6000602084013e6117b9565b606091505b5090925090506117d163140e25ad60e31b838361204a565b6000806117de6000611d74565b9150915060006117f18983600080611e4c565b91505061180081846000611e72565b975061087d338a8a612db7565b3360008181526003602090815260408083206001600160a01b038716845290915281205490916107299185906111e3908663ffffffff61273e16565b6118536001611d74565b5050565b60006107293384846129bf565b6000611259612cc0565b600061187a6001612b40565b509092915050565b60008061188d611d5c565b60408051336024820152306044820152606480820187905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106119105780518252601f1990920191602091820191016118f1565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611972576040519150601f19603f3d011682016040523d82523d6000602084013e611977565b606091505b50909250905061198f6323b872dd60e01b838361204a565b60008061199c6001611d74565b9150915060006119ae888360006126d9565b90506119bc81846000611e72565b9650610c59338289612db7565b6000806119d4612cf0565b905060006119e0611d5c565b90506000806119ef6001611d74565b915091506000611a01878460006126d9565b90506000611a128284600080611e4c565b97509050611a2133888a611eed565b60408051602480820184905282518083039091018152604490910182526020810180516001600160e01b031663db006a7560e01b178152915181516000936060936001600160a01b038b16939092909182918083835b60208310611a965780518252601f199092019160209182019101611a77565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611af8576040519150601f19603f3d011682016040523d82523d6000602084013e611afd565b606091505b509092509050611b1563db006a7560e01b838361204a565b6040805163a9059cbb60e01b8152336004820152602481018b905290516001600160a01b038a169163a9059cbb9160448083019260209291908290030181600087803b158015611b6457600080fd5b505af1158015611b78573d6000803e3d6000fd5b505050506040513d6020811015611b8e57600080fd5b5051611b98612d08565b90611be45760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b505050505050505050919050565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b6000610717611d5c565b600080611c346001612b40565b50509050611c466001548260006126d9565b91505090565b60408051808201909152600a815269446861726d612044616960b01b602082015290565b6001600160a01b038316611cb55760405162461bcd60e51b8152600401808060200182810382526023815260200180613de66023913960400191505060405180910390fd5b6001600160a01b038216611cfa5760405162461bcd60e51b8152600401808060200182810382526022815260200180613d766022913960400191505060405180910390fd5b6001600160a01b03808416600081815260036020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b735d3a536e4d6dbd6114cc1ead35777bab948e364390565b6000806000611d8284612b40565b9194509250905080611e46576000611d9984612ee4565b81546dffffffffffffffffffffffffffff19166001600160701b0391909116178155611dc483612ee4565b81546dffffffffffffffffffffffffffff60701b1916600160701b6001600160701b039290921691909102176001600160e01b0316600160e01b4363ffffffff1602178155604080518581526020810185905281517f2335845cb850fcf8fbf00f6231fabd745068faf756278e33bb45716d4cf934f3929181900390910190a1505b50915091565b600080611e5a868686611e72565b9150611e678286856126d9565b905094509492505050565b60008115611ec957611ec283611eb6611e9282600163ffffffff61273e16565b611eaa88670de0b6b3a764000063ffffffff612f3016565b9063ffffffff61279b16565b9063ffffffff612f8916565b905061121f565b611ee583611eb686670de0b6b3a764000063ffffffff612f3016565b949350505050565b600082118015611efd5750600081115b611f385760405162461bcd60e51b815260040180806020018281038252602b815260200180613ece602b913960400191505060405180910390fd5b6001600160a01b03831660009081526002602052604090205481811015611f905760405162461bcd60e51b8152600401808060200182810382526028815260200180613ef96028913960400191505060405180910390fd5b600154611fa3908363ffffffff61273e16565b6001556001600160a01b03841660008181526002602090815260408083208686039055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3604080516001600160a01b03861681526020810185905280820184905290517fe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a9299181900360600190a150505050565b6000821561239e576001600160e01b0319841663a9059cbb60e01b148061208157506001600160e01b031984166323b872dd60e01b145b156121df5781806020019051602081101561209b57600080fd5b50516120a5612fea565b6120ae86613008565b604051602001808068021b7b6b837bab732160bd1b81525060090183805190602001908083835b602083106120f45780518252601f1990920191602091820191016120d5565b6001836020036101000a03801982511681845116808217855250505050505090500180613d526024913960240182805190602001908083835b6020831061214c5780518252601f19909201916020918201910161212d565b6001836020036101000a03801982511681845116808217855250505050505090500180601760f91b81525060010192505050604051602081830303815290604052906121d95760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b50612399565b60008280602001905160208110156121f657600080fd5b50519050801561239757612208612fea565b600a8204603001600a838161221957fe5b0660300161222688613008565b604051602001808068021b7b6b837bab732160bd1b81525060090185805190602001908083835b6020831061226c5780518252601f19909201916020918201910161224d565b51815160209384036101000a60001901801990921691161790527f20636f6e74726163742072657475726e6564206572726f7220636f64652000009190930190815260f887811b6001600160f81b0319908116601e8401529087901b16601f8201526b01037b71031b0b63634b733960a51b818401528451602c90910192850191508083835b602083106123115780518252601f1990920191602091820191016122f2565b51815160209384036101000a6000190180199092169116179052601760f91b9190930190815260408051808303601e19018152600183019182905262461bcd60e51b909152600582018481528151602584015281519199509750879650604590910194509187019250819050838360008315611590578181015183820152602001611578565b505b612524565b6123a6612fea565b6123af85613008565b6123b8846131cf565b604051602001808068021b7b6b837bab732160bd1b81525060090184805190602001908083835b602083106123fe5780518252601f1990920191602091820191016123df565b6001836020036101000a03801982511681845116808217855250505050505090500180613dba602c9139602c0183805190602001908083835b602083106124565780518252601f199092019160209182019101612437565b51815160209384036101000a60001901801990921691161790526101d160f51b919093019081528451600290910192850191508083835b602083106124ac5780518252601f19909201916020918201910161248d565b51815160209384036101000a60001901801990921691161790526040805192909401828103601f190183529384905262461bcd60e51b84526004840181815282516024860152825192985096508695506044909301935091860191905080838360008315611590578181015183820152602001611578565b50505050565b6000806000612537611d5c565b90506000806125466001612b40565b5091509150600061255b6001548460016126d9565b905060006125ec856001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156125b857600080fd5b505afa1580156125cc573d6000803e3d6000fd5b505050506040513d60208110156125e257600080fd5b50518460006126d9565b90508181116125fc576000612600565b8181035b965086156126195761261487846000611e72565b61261c565b60005b955050505050509091565b737e4a8391c728fed9069b2962699ab416628b19fa90565b61264a8383836129bf565b6001600160a01b0383166000908152600360209081526040808320338452909152902054600019811461252457818110156126cc576040805162461bcd60e51b815260206004820152601760248201527f496e73756666696369656e7420616c6c6f77616e63652e000000000000000000604482015290519081900360640190fd5b6125248433848403611c70565b6000811561271557670de0b6b3a7640000612706670de0b6b3a763ffff611eaa878763ffffffff612f3016565b8161270d57fe5b04905061121f565b670de0b6b3a764000061272e858563ffffffff612f3016565b8161273557fe5b04949350505050565b600082821115612795576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b60008282018381101561121f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b3b151590565b805160411461283b5760405162461bcd60e51b8152600401808060200182810382526044815260200180613f216044913960600191505060405180910390fd5b60208101516040820151606083015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156128ac5760405162461bcd60e51b8152600401808060200182810382526034815260200180613e756034913960400191505060405180910390fd5b8060ff16601b14806128c157508060ff16601c145b6128fc5760405162461bcd60e51b8152600401808060200182810382526022815260200180613d986022913960400191505060405180910390fd5b6040805160008152602080820180845288905260ff8416828401526060820186905260808201859052915160019260a0808401939192601f1981019281900390910190855afa158015612953573d6000803e3d6000fd5b505050602060405103516001600160a01b0316866001600160a01b0316146129b7576040805162461bcd60e51b815260206004820152601260248201527124b73b30b634b21039b4b3b730ba3ab9329760711b604482015290519081900360640190fd5b505050505050565b6001600160a01b038316612a045760405162461bcd60e51b8152600401808060200182810382526025815260200180613ea96025913960400191505060405180910390fd5b6001600160a01b038216612a495760405162461bcd60e51b8152600401808060200182810382526023815260200180613d2f6023913960400191505060405180910390fd5b6001600160a01b03831660009081526002602052604090205481811015612aad576040805162461bcd60e51b815260206004820152601360248201527224b739bab33334b1b4b2b73a10333ab732399760691b604482015290519081900360640190fd5b6001600160a01b03808516600090815260026020526040808220858503905591851681522054612ae3908363ffffffff61279b16565b6001600160a01b0380851660008181526002602090815260409182902094909455805186815290519193928816927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a350505050565b6000806000612b4d613d0e565b5050604080516060810182526000546001600160701b03808216808452600160701b830490911660208401819052600160e01b90920463ffffffff16938301849052438414939091908415612ba757829650819550612cb5565b8715612bbd57612bb5613410565b509550612c2c565b612bc5611d5c565b6001600160a01b031663182df0f56040518163ffffffff1660e01b815260040160206040518083038186803b158015612bfd57600080fd5b505afa158015612c11573d6000803e3d6000fd5b505050506040513d6020811015612c2757600080fd5b505195505b6000612c5a670de0b6b3a7640000612c4e85611eb68b8463ffffffff612f3016565b9063ffffffff61273e16565b9050670de0b6b3a7640000612ca9612c9c600a612c7e85600963ffffffff612f3016565b81612c8557fe5b670de0b6b3a764000091900463ffffffff61279b16565b869063ffffffff612f3016565b81612cb057fe5b049750505b505050509193909250565b600080612ccb613410565b9150600a9050612ce282600963ffffffff612f3016565b81612ce957fe5b0491509091565b736b175474e89094c44da98b954eedeac495271d0f90565b6060612d12613c32565b6040516020018082805190602001908083835b60208310612d445780518252601f199092019160209182019101612d25565b6001836020036101000a0380198251168184511680821785525050505050509050018070103a3930b739b332b9103330b4b632b21760791b815250601101915050604051602081830303815290604052905090565b6040805180820190915260048152636444616960e01b602082015290565b600082118015612dc75750600081115b612e025760405162461bcd60e51b8152600401808060200182810382526029815260200180613e4c6029913960400191505060405180910390fd5b600154612e15908263ffffffff61279b16565b6001556001600160a01b038316600090815260026020526040902054612e41908263ffffffff61279b16565b6001600160a01b03841660008181526002602090815260409182902093909355805191825291810184905280820183905290517f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f9181900360600190a16040805182815290516001600160a01b038516916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60006001600160701b03821115612f2c5760405162461bcd60e51b8152600401808060200182810382526022815260200180613e2a6022913960400191505060405180910390fd5b5090565b600082612f3f57506000610afb565b82820282848281612f4c57fe5b041461121f5760405162461bcd60e51b8152600401808060200182810382526021815260200180613e096021913960400191505060405180910390fd5b6000808211612fdf576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b600082848161273557fe5b6040805180820190915260048152636344616960e01b602082015290565b606060006001600160e01b0319831663140e25ad60e31b141561304957604051806040016040528060048152602001631b5a5b9d60e21b8152509150611259565b6001600160e01b0319831663db006a7560e01b1415613088576040518060400160405280600681526020016572656465656d60d01b8152509150611259565b6001600160e01b0319831663852a12e360e01b14156130d1576040518060400160405280601081526020016f72656465656d556e6465726c79696e6760801b8152509150611259565b6001600160e01b031983166323b872dd60e01b1415613116576040518060400160405280600c81526020016b7472616e7366657246726f6d60a01b8152509150611259565b6001600160e01b0319831663a9059cbb60e01b141561315757604051806040016040528060088152602001673a3930b739b332b960c11b8152509150611259565b6001600160e01b0319831663a6afed9560e01b141561319e576040518060400160405280600e81526020016d1858d8dc9d59525b9d195c995cdd60921b8152509150611259565b505060408051808201909152601381527230b7103ab735b737bbb710333ab731ba34b7b760691b6020820152919050565b60606044825111801561320157508151600160fb1b9083906000906131f057fe5b01602001516001600160f81b031916145b801561322e5750815160c360f81b908390600190811061321d57fe5b01602001516001600160f81b031916145b801561325b57508151607960f81b908390600290811061324a57fe5b01602001516001600160f81b031916145b801561328857508151600560fd1b908390600390811061327757fe5b01602001516001600160f81b031916145b156133e057606060048351036040519080825280601f01601f1916602001820160405280156132be576020820181803883390190505b50905060045b8351811015613312578381815181106132d957fe5b602001015160f81c60f81b8260048303815181106132f357fe5b60200101906001600160f81b031916908160001a9053506001016132c4565b5080806020019051602081101561332857600080fd5b810190808051604051939291908464010000000082111561334857600080fd5b90830190602082018581111561335d57600080fd5b825164010000000081118282018810171561337757600080fd5b82525081516020918201929091019080838360005b838110156133a457818101518382015260200161338c565b50505050905090810190601f1680156133d15780820380516001836020036101000a031916815260200191505b5060405250505091505061340b565b50604080518082019091526012815271286e6f2072657665727420726561736f6e2960701b60208201525b919050565b600080600061349e735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b0316636c540baf6040518163ffffffff1660e01b815260040160206040518083038186803b15801561346557600080fd5b505afa158015613479573d6000803e3d6000fd5b505050506040513d602081101561348f57600080fd5b5051439063ffffffff61273e16565b9050806135a157735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b031663182df0f56040518163ffffffff1660e01b815260040160206040518083038186803b1580156134f257600080fd5b505afa158015613506573d6000803e3d6000fd5b505050506040513d602081101561351c57600080fd5b505160408051630ae9d70b60e41b81529051735d3a536e4d6dbd6114cc1ead35777bab948e36439163ae9d70b0916004808301926020929190829003018186803b15801561356957600080fd5b505afa15801561357d573d6000803e3d6000fd5b505050506040513d602081101561359357600080fd5b50519093509150613c2e9050565b604080516305f5d64360e11b8152735d3a536e4d6dbd6114cc1ead35777bab948e3643600482015290516000916b033b2e3c9fd0803ce8000000916138089173197e90f9fad81970ba7976f33cbd77088e5d7cf791630bebac8691602480820192602092909190829003018186803b15801561361c57600080fd5b505afa158015613630573d6000803e3d6000fd5b505050506040513d602081101561364657600080fd5b50516040805163324abb3160e21b815290516b033b2e3c9fd0803ce8000000916137f49173197e90f9fad81970ba7976f33cbd77088e5d7cf79163c92aecc4916004808301926020929190829003018186803b1580156136a557600080fd5b505afa1580156136b9573d6000803e3d6000fd5b505050506040513d60208110156136cf57600080fd5b50516040805163243df84160e11b815290516137e89173197e90f9fad81970ba7976f33cbd77088e5d7cf79163487bf08291600480820192602092909190829003018186803b15801561372157600080fd5b505afa158015613735573d6000803e3d6000fd5b505050506040513d602081101561374b57600080fd5b5051604080516320aba08b60e01b815290516137d69173197e90f9fad81970ba7976f33cbd77088e5d7cf7916320aba08b91600480820192602092909190829003018186803b15801561379d57600080fd5b505afa1580156137b1573d6000803e3d6000fd5b505050506040513d60208110156137c757600080fd5b5051429063ffffffff61273e16565b6b033b2e3c9fd0803ce8000000613c4f565b9063ffffffff612f3016565b816137fb57fe5b049063ffffffff612f3016565b8161380f57fe5b0490506000735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b031663f3fdb15a6040518163ffffffff1660e01b815260040160206040518083038186803b15801561386157600080fd5b505afa158015613875573d6000803e3d6000fd5b505050506040513d602081101561388b57600080fd5b5051604080516308f7a6e360e31b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e3643916347bd3718916004808301926020929190829003018186803b1580156138de57600080fd5b505afa1580156138f2573d6000803e3d6000fd5b505050506040513d602081101561390857600080fd5b505160408051638f840ddd60e01b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e364391638f840ddd916004808301926020929190829003018186803b15801561395b57600080fd5b505afa15801561396f573d6000803e3d6000fd5b505050506040513d602081101561398557600080fd5b5051604080516305cee64160e21b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e36439163173b9904916004808301926020929190829003018186803b1580156139d857600080fd5b505afa1580156139ec573d6000803e3d6000fd5b505050506040513d6020811015613a0257600080fd5b5051604080516315f2405360e01b81526004810188905260248101869052604481018590529051919250600091670de0b6b3a764000091613aae9187916137e8918c916001600160a01b038c16916315f2405391606480820192602092909190829003018186803b158015613a7657600080fd5b505afa158015613a8a573d6000803e3d6000fd5b505050506040513d6020811015613aa057600080fd5b50519063ffffffff612f3016565b81613ab557fe5b049050613ac8848263ffffffff61279b16565b9350613afc670de0b6b3a7640000613ae6848463ffffffff612f3016565b81613aed57fe5b8591900463ffffffff61279b16565b9250613b9a735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b158015613b4e57600080fd5b505afa158015613b62573d6000803e3d6000fd5b505050506040513d6020811015613b7857600080fd5b5051611eb6670de0b6b3a76400006137e887612c4e8c8b63ffffffff61279b16565b9850846001600160a01b031663b8168816878686866040518563ffffffff1660e01b81526004018085815260200184815260200183815260200182815260200194505050505060206040518083038186803b158015613bf857600080fd5b505afa158015613c0c573d6000803e3d6000fd5b505050506040513d6020811015613c2257600080fd5b50519750505050505050505b9091565b60408051808201909152600381526244616960e81b602082015290565b6000838015613cf057600184168015613c6a57859250613c6e565b8392505b50600283046002850494505b8415613cea578586028687820414613c9157600080fd5b81810181811015613ca157600080fd5b85810497506002870615613cdd578785028589820414158915151615613cc657600080fd5b83810181811015613cd657600080fd5b8790049550505b5050600285049450613c7a565b50613d06565b838015613d005760009250613d04565b8392505b505b509392505050565b60408051606081018252600080825260208201819052918101919091529056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737320636f6e74726163742072657475726e65642066616c7365206f6e2063616c6c696e672045524332303a20617070726f766520746f20746865207a65726f20616464726573735369676e6174757265206076602076616c7565206e6f74207065726d69747465642e20636f6e7472616374207265766572746564207768696c6520617474656d7074696e6720746f2063616c6c2045524332303a20617070726f766520666f7220746865207a65726f2061646472657373536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774f766572666c6f77206f6e20636f6e76657273696f6e20746f2075696e743131322e4d696e74206661696c65643a20696e73756666696369656e742066756e647320737570706c6965642e5369676e6174757265206073602076616c75652063616e6e6f7420626520706f74656e7469616c6c79206d616c6c6561626c652e45524332303a207472616e736665722066726f6d20746865207a65726f206164647265737352656465656d206661696c65643a20696e73756666696369656e742066756e647320737570706c6965642e537570706c69656420616d6f756e742065786365656473206163636f756e742062616c616e63652e4d75737420737570706c7920612073696e676c652036352d62797465207369676e6174757265207768656e206f776e6572206973206e6f74206120636f6e74726163742ea265627a7a723158202020446861726d612044616920496d706c656d656e746174696f6e205631202064736f6c634300050b0032

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106102065760003560e01c80636e519d101161011a578063a6afed95116100ad578063d8da64f31161007c578063d8da64f314610695578063db006a75146106b2578063dd62ed3e146106cf578063ec6c350c146106fd578063fea61faa1461070557610206565b8063a6afed951461064f578063a9059cbb14610659578063ae9d70b014610685578063bd6d894d1461068d57610206565b806395d89b41116100e957806395d89b41146105da5780639816f473146105e2578063a0712d6814610606578063a457c2d71461062357610206565b80636e519d10146104c157806370a08231146105615780637d33ef7a14610587578063852a12e3146105bd57610206565b806327c0f03b1161019d578063395093511161016c57806339509351146104575780633af9e6691461048357806344b5a802146104a95780634fdafdc7146104b15780636c540baf146104b957610206565b806327c0f03b1461034d5780632d657fa51461036a578063313ce5671461040d578063336d26921461042b57610206565b806318160ddd116101d957806318160ddd146102ff57806319376532146103075780632383b0741461030f57806323b872dd1461031757610206565b806306fdde031461020b578063095ea7b3146102885780630d8e6e2c146102c8578063166d588f146102e2575b600080fd5b61021361070d565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561024d578181015183820152602001610235565b50505050905090810190601f16801561027a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102b46004803603604081101561029e57600080fd5b506001600160a01b03813516906020013561071c565b604080519115158252519081900360200190f35b6102d0610732565b60408051918252519081900360200190f35b6102d0600480360360208110156102f857600080fd5b5035610737565b6102d0610889565b6102d061088f565b6102d0610af1565b6102b46004803603606081101561032d57600080fd5b506001600160a01b03813581169160208101359091169060400135610b01565b6102d06004803603602081101561036357600080fd5b5035610b18565b6102b4600480360360e081101561038057600080fd5b6001600160a01b038235811692602081013590911691604082013591606081013515159160808201359160a08101359181019060e0810160c08201356401000000008111156103ce57600080fd5b8201836020820111156103e057600080fd5b8035906020019184600183028401116401000000008311171561040257600080fd5b509092509050610c64565b61041561116c565b6040805160ff9092168252519081900360200190f35b6102b46004803603604081101561044157600080fd5b506001600160a01b038135169060200135611171565b6102b46004803603604081101561046d57600080fd5b506001600160a01b0381351690602001356111a7565b6102d06004803603602081101561049957600080fd5b50356001600160a01b03166111e8565b6102d0611226565b6102d061124f565b6102d061125f565b610548600480360360808110156104d757600080fd5b6001600160e01b0319823516919081019060408101602082013564010000000081111561050357600080fd5b82018360208201111561051557600080fd5b8035906020019184600183028401116401000000008311171561053757600080fd5b919350915080359060200135611272565b6040805192835290151560208301528051918290030190f35b6102d06004803603602081101561057757600080fd5b50356001600160a01b0316611324565b6102b46004803603606081101561059d57600080fd5b506001600160a01b0381358116916020810135909116906040013561133f565b6102d0600480360360208110156105d357600080fd5b5035611376565b6102136115d8565b6105ea6115e2565b604080516001600160a01b039092168252519081900360200190f35b6102d06004803603602081101561061c57600080fd5b50356115ec565b6102b46004803603604081101561063957600080fd5b506001600160a01b03813516906020013561180d565b610657611849565b005b6102b46004803603604081101561066f57600080fd5b506001600160a01b038135169060200135611857565b6102d0611864565b6102d061186e565b6102d0600480360360208110156106ab57600080fd5b5035611882565b6102d0600480360360208110156106c857600080fd5b50356119c9565b6102d0600480360360408110156106e557600080fd5b506001600160a01b0381358116916020013516611bf2565b6105ea611c1d565b6102d0611c27565b6060610717611c4c565b905090565b6000610729338484611c70565b50600192915050565b600190565b600080610742611d5c565b90506000806107516001611d74565b91509150600080610766878460006001611e4c565b9150915061077681856001611e72565b9550610783338888611eed565b60408051336024820152604480820185905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b178152915181516000936060936001600160a01b038b16939092909182918083835b602083106107fe5780518252601f1990920191602091820191016107df565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610860576040519150601f19603f3d011682016040523d82523d6000602084013e610865565b606091505b50909250905061087d63a9059cbb60e01b838361204a565b50505050505050919050565b60015490565b60008061089a611d5c565b60408051600481526024810182526020810180516001600160e01b031663a6afed9560e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106109025780518252601f1990920191602091820191016108e3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610964576040519150601f19603f3d011682016040523d82523d6000602084013e610969565b606091505b50909250905061098163a6afed9560e01b838361204a565b61098b6000611d74565b5050600061099761252a565b955090506001600160a01b03841663a9059cbb60e01b6109b5612627565b604080516001600160a01b03909216602483015260448083018a905281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310610a305780518252601f199092019160209182019101610a11565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610a92576040519150601f19603f3d011682016040523d82523d6000602084013e610a97565b606091505b509093509150610aaf63a9059cbb60e01b848461204a565b604080518281526020810187905281517f045364289025ecf402644016c21b92009a98af8b33ac874d970730e8cb2af062929181900390910190a15050505090565b6000610afb61252a565b92915050565b6000610b0e84848461263f565b5060019392505050565b600080610b23611d5c565b9050600080610b326001611d74565b915091506000610b44868460006126d9565b9050610b5281836000611e72565b9450610b5f338288611eed565b60408051336024820152604480820188905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b178152915181516000936060936001600160a01b038a16939092909182918083835b60208310610bda5780518252601f199092019160209182019101610bbb565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610c3c576040519150601f19603f3d011682016040523d82523d6000602084013e610c41565b606091505b509092509050610c5963a9059cbb60e01b838361204a565b505050505050919050565b6000841580610c735750844211155b610cc4576040805162461bcd60e51b815260206004820152601960248201527f4d6574612d7472616e73616374696f6e20657870697265642e00000000000000604482015290519081900360640190fd5b604080516001600160a01b03808c16602080840191909152908b168284015260608281018b90528915156080808501919091528451808503909101815260a084019094523080821b60c08501908152632d657fa560e01b60d4860181905260d886018c905260f886018b905286519396929590948c948c94939261011801918401908083835b60208310610d695780518252601f199092019160209182019101610d4a565b51815160209384036101000a60001901801990921691161790526040805192909401828103601f19018352845281518282012060008181526004909252939020549099509197505060ff16159450610e0d9350505050576040805162461bcd60e51b815260206004820152601e60248201527f4d6574612d7472616e73616374696f6e20616c726561647920757365642e0000604482015290519081900360640190fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060008160405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012090506000600360008e6001600160a01b03166001600160a01b0316815260200190815260200160002060008d6001600160a01b03166001600160a01b0316815260200190815260200160002054905060008a610ef557610ef0828d63ffffffff61273e16565b610f05565b610f05828d63ffffffff61279b16565b9050610f108e6127f5565b1561110c57606083866040516020018083815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610f5f578181015183820152602001610f47565b50505050905090810190601f168015610f8c5780820380516001836020036101000a031916815260200191505b509350505050604051602081830303815290604052905060008f6001600160a01b03166320c13b0b838c8c6040518463ffffffff1660e01b8152600401808060200180602001838103835286818151815260200191508051906020019080838360005b83811015611007578181015183820152602001610fef565b50505050905090810190601f1680156110345780820380516001836020036101000a031916815260200191505b508381038252848152602001858580828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b15801561107c57600080fd5b505afa158015611090573d6000803e3d6000fd5b505050506040513d60208110156110a657600080fd5b505190506001600160e01b031981166320c13b0b60e01b14611105576040805162461bcd60e51b815260206004820152601360248201527224b73b30b634b21039b4b3b730ba3ab932b99760691b604482015290519081900360640190fd5b505061114d565b61114d8e848a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506127fb92505050565b6111588e8e83611c70565b5060019d9c50505050505050505050505050565b600890565b60008061117e6001611d74565b509050600061118f84836001611e72565b905061119c3386836129bf565b506001949350505050565b3360008181526003602090815260408083206001600160a01b038716845290915281205490916107299185906111e3908663ffffffff61279b16565b611c70565b6000806111f56001612b40565b50506001600160a01b03841660009081526002602052604081205491925061121f919083906126d9565b9392505050565b6000806000611233612cc0565b9092509050611248818363ffffffff61273e16565b9250505090565b600061125961252a565b50919050565b600054600160e01b900463ffffffff1690565b60008030878585898960405160200180876001600160a01b03166001600160a01b031660601b8152601401866001600160e01b0319166001600160e01b03191681526004018581526020018481526020018383808284376040805191909301818103601f19018252835280516020918201206000818152600490925292902054919a505060ff161596505085159450611318935050505057508315806113185750834211155b90509550959350505050565b6001600160a01b031660009081526002602052604090205490565b60008061134c6001611d74565b509050600061135d84836001611e72565b905061136a86868361263f565b50600195945050505050565b600080611381612cf0565b9050600061138d611d5c565b60408051602480820188905282518083039091018152604490910182526020810180516001600160e01b031663852a12e360e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106114045780518252601f1990920191602091820191016113e5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611466576040519150601f19603f3d011682016040523d82523d6000602084013e61146b565b606091505b50909250905061148363852a12e360e01b838361204a565b6000806114906000611d74565b9150915060006114a38983600180611e4c565b9150506114b281846001611e72565b97506114bf338a8a611eed565b6040805163a9059cbb60e01b8152336004820152602481018b905290516001600160a01b0389169163a9059cbb9160448083019260209291908290030181600087803b15801561150e57600080fd5b505af1158015611522573d6000803e3d6000fd5b505050506040513d602081101561153857600080fd5b5051611542612d08565b906115cb5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611590578181015183820152602001611578565b50505050905090810190601f1680156115bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5050505050505050919050565b6060610717612d99565b6000610717612cf0565b6000806115f7612cf0565b90506000611603611d5c565b604080516323b872dd60e01b81523360048201523060248201526044810187905290519192506001600160a01b038416916323b872dd916064808201926020929091908290030181600087803b15801561165c57600080fd5b505af1158015611670573d6000803e3d6000fd5b505050506040513d602081101561168657600080fd5b5051611690612d08565b906116dc5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b5060408051602480820187905282518083039091018152604490910182526020810180516001600160e01b031663140e25ad60e31b178152915181516000936060936001600160a01b038716939092909182918083835b602083106117525780518252601f199092019160209182019101611733565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146117b4576040519150601f19603f3d011682016040523d82523d6000602084013e6117b9565b606091505b5090925090506117d163140e25ad60e31b838361204a565b6000806117de6000611d74565b9150915060006117f18983600080611e4c565b91505061180081846000611e72565b975061087d338a8a612db7565b3360008181526003602090815260408083206001600160a01b038716845290915281205490916107299185906111e3908663ffffffff61273e16565b6118536001611d74565b5050565b60006107293384846129bf565b6000611259612cc0565b600061187a6001612b40565b509092915050565b60008061188d611d5c565b60408051336024820152306044820152606480820187905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b178152915181519394506000936060936001600160a01b0387169392918291908083835b602083106119105780518252601f1990920191602091820191016118f1565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611972576040519150601f19603f3d011682016040523d82523d6000602084013e611977565b606091505b50909250905061198f6323b872dd60e01b838361204a565b60008061199c6001611d74565b9150915060006119ae888360006126d9565b90506119bc81846000611e72565b9650610c59338289612db7565b6000806119d4612cf0565b905060006119e0611d5c565b90506000806119ef6001611d74565b915091506000611a01878460006126d9565b90506000611a128284600080611e4c565b97509050611a2133888a611eed565b60408051602480820184905282518083039091018152604490910182526020810180516001600160e01b031663db006a7560e01b178152915181516000936060936001600160a01b038b16939092909182918083835b60208310611a965780518252601f199092019160209182019101611a77565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611af8576040519150601f19603f3d011682016040523d82523d6000602084013e611afd565b606091505b509092509050611b1563db006a7560e01b838361204a565b6040805163a9059cbb60e01b8152336004820152602481018b905290516001600160a01b038a169163a9059cbb9160448083019260209291908290030181600087803b158015611b6457600080fd5b505af1158015611b78573d6000803e3d6000fd5b505050506040513d6020811015611b8e57600080fd5b5051611b98612d08565b90611be45760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b505050505050505050919050565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b6000610717611d5c565b600080611c346001612b40565b50509050611c466001548260006126d9565b91505090565b60408051808201909152600a815269446861726d612044616960b01b602082015290565b6001600160a01b038316611cb55760405162461bcd60e51b8152600401808060200182810382526023815260200180613de66023913960400191505060405180910390fd5b6001600160a01b038216611cfa5760405162461bcd60e51b8152600401808060200182810382526022815260200180613d766022913960400191505060405180910390fd5b6001600160a01b03808416600081815260036020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b735d3a536e4d6dbd6114cc1ead35777bab948e364390565b6000806000611d8284612b40565b9194509250905080611e46576000611d9984612ee4565b81546dffffffffffffffffffffffffffff19166001600160701b0391909116178155611dc483612ee4565b81546dffffffffffffffffffffffffffff60701b1916600160701b6001600160701b039290921691909102176001600160e01b0316600160e01b4363ffffffff1602178155604080518581526020810185905281517f2335845cb850fcf8fbf00f6231fabd745068faf756278e33bb45716d4cf934f3929181900390910190a1505b50915091565b600080611e5a868686611e72565b9150611e678286856126d9565b905094509492505050565b60008115611ec957611ec283611eb6611e9282600163ffffffff61273e16565b611eaa88670de0b6b3a764000063ffffffff612f3016565b9063ffffffff61279b16565b9063ffffffff612f8916565b905061121f565b611ee583611eb686670de0b6b3a764000063ffffffff612f3016565b949350505050565b600082118015611efd5750600081115b611f385760405162461bcd60e51b815260040180806020018281038252602b815260200180613ece602b913960400191505060405180910390fd5b6001600160a01b03831660009081526002602052604090205481811015611f905760405162461bcd60e51b8152600401808060200182810382526028815260200180613ef96028913960400191505060405180910390fd5b600154611fa3908363ffffffff61273e16565b6001556001600160a01b03841660008181526002602090815260408083208686039055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3604080516001600160a01b03861681526020810185905280820184905290517fe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a9299181900360600190a150505050565b6000821561239e576001600160e01b0319841663a9059cbb60e01b148061208157506001600160e01b031984166323b872dd60e01b145b156121df5781806020019051602081101561209b57600080fd5b50516120a5612fea565b6120ae86613008565b604051602001808068021b7b6b837bab732160bd1b81525060090183805190602001908083835b602083106120f45780518252601f1990920191602091820191016120d5565b6001836020036101000a03801982511681845116808217855250505050505090500180613d526024913960240182805190602001908083835b6020831061214c5780518252601f19909201916020918201910161212d565b6001836020036101000a03801982511681845116808217855250505050505090500180601760f91b81525060010192505050604051602081830303815290604052906121d95760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315611590578181015183820152602001611578565b50612399565b60008280602001905160208110156121f657600080fd5b50519050801561239757612208612fea565b600a8204603001600a838161221957fe5b0660300161222688613008565b604051602001808068021b7b6b837bab732160bd1b81525060090185805190602001908083835b6020831061226c5780518252601f19909201916020918201910161224d565b51815160209384036101000a60001901801990921691161790527f20636f6e74726163742072657475726e6564206572726f7220636f64652000009190930190815260f887811b6001600160f81b0319908116601e8401529087901b16601f8201526b01037b71031b0b63634b733960a51b818401528451602c90910192850191508083835b602083106123115780518252601f1990920191602091820191016122f2565b51815160209384036101000a6000190180199092169116179052601760f91b9190930190815260408051808303601e19018152600183019182905262461bcd60e51b909152600582018481528151602584015281519199509750879650604590910194509187019250819050838360008315611590578181015183820152602001611578565b505b612524565b6123a6612fea565b6123af85613008565b6123b8846131cf565b604051602001808068021b7b6b837bab732160bd1b81525060090184805190602001908083835b602083106123fe5780518252601f1990920191602091820191016123df565b6001836020036101000a03801982511681845116808217855250505050505090500180613dba602c9139602c0183805190602001908083835b602083106124565780518252601f199092019160209182019101612437565b51815160209384036101000a60001901801990921691161790526101d160f51b919093019081528451600290910192850191508083835b602083106124ac5780518252601f19909201916020918201910161248d565b51815160209384036101000a60001901801990921691161790526040805192909401828103601f190183529384905262461bcd60e51b84526004840181815282516024860152825192985096508695506044909301935091860191905080838360008315611590578181015183820152602001611578565b50505050565b6000806000612537611d5c565b90506000806125466001612b40565b5091509150600061255b6001548460016126d9565b905060006125ec856001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156125b857600080fd5b505afa1580156125cc573d6000803e3d6000fd5b505050506040513d60208110156125e257600080fd5b50518460006126d9565b90508181116125fc576000612600565b8181035b965086156126195761261487846000611e72565b61261c565b60005b955050505050509091565b737e4a8391c728fed9069b2962699ab416628b19fa90565b61264a8383836129bf565b6001600160a01b0383166000908152600360209081526040808320338452909152902054600019811461252457818110156126cc576040805162461bcd60e51b815260206004820152601760248201527f496e73756666696369656e7420616c6c6f77616e63652e000000000000000000604482015290519081900360640190fd5b6125248433848403611c70565b6000811561271557670de0b6b3a7640000612706670de0b6b3a763ffff611eaa878763ffffffff612f3016565b8161270d57fe5b04905061121f565b670de0b6b3a764000061272e858563ffffffff612f3016565b8161273557fe5b04949350505050565b600082821115612795576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b60008282018381101561121f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b3b151590565b805160411461283b5760405162461bcd60e51b8152600401808060200182810382526044815260200180613f216044913960600191505060405180910390fd5b60208101516040820151606083015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156128ac5760405162461bcd60e51b8152600401808060200182810382526034815260200180613e756034913960400191505060405180910390fd5b8060ff16601b14806128c157508060ff16601c145b6128fc5760405162461bcd60e51b8152600401808060200182810382526022815260200180613d986022913960400191505060405180910390fd5b6040805160008152602080820180845288905260ff8416828401526060820186905260808201859052915160019260a0808401939192601f1981019281900390910190855afa158015612953573d6000803e3d6000fd5b505050602060405103516001600160a01b0316866001600160a01b0316146129b7576040805162461bcd60e51b815260206004820152601260248201527124b73b30b634b21039b4b3b730ba3ab9329760711b604482015290519081900360640190fd5b505050505050565b6001600160a01b038316612a045760405162461bcd60e51b8152600401808060200182810382526025815260200180613ea96025913960400191505060405180910390fd5b6001600160a01b038216612a495760405162461bcd60e51b8152600401808060200182810382526023815260200180613d2f6023913960400191505060405180910390fd5b6001600160a01b03831660009081526002602052604090205481811015612aad576040805162461bcd60e51b815260206004820152601360248201527224b739bab33334b1b4b2b73a10333ab732399760691b604482015290519081900360640190fd5b6001600160a01b03808516600090815260026020526040808220858503905591851681522054612ae3908363ffffffff61279b16565b6001600160a01b0380851660008181526002602090815260409182902094909455805186815290519193928816927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a350505050565b6000806000612b4d613d0e565b5050604080516060810182526000546001600160701b03808216808452600160701b830490911660208401819052600160e01b90920463ffffffff16938301849052438414939091908415612ba757829650819550612cb5565b8715612bbd57612bb5613410565b509550612c2c565b612bc5611d5c565b6001600160a01b031663182df0f56040518163ffffffff1660e01b815260040160206040518083038186803b158015612bfd57600080fd5b505afa158015612c11573d6000803e3d6000fd5b505050506040513d6020811015612c2757600080fd5b505195505b6000612c5a670de0b6b3a7640000612c4e85611eb68b8463ffffffff612f3016565b9063ffffffff61273e16565b9050670de0b6b3a7640000612ca9612c9c600a612c7e85600963ffffffff612f3016565b81612c8557fe5b670de0b6b3a764000091900463ffffffff61279b16565b869063ffffffff612f3016565b81612cb057fe5b049750505b505050509193909250565b600080612ccb613410565b9150600a9050612ce282600963ffffffff612f3016565b81612ce957fe5b0491509091565b736b175474e89094c44da98b954eedeac495271d0f90565b6060612d12613c32565b6040516020018082805190602001908083835b60208310612d445780518252601f199092019160209182019101612d25565b6001836020036101000a0380198251168184511680821785525050505050509050018070103a3930b739b332b9103330b4b632b21760791b815250601101915050604051602081830303815290604052905090565b6040805180820190915260048152636444616960e01b602082015290565b600082118015612dc75750600081115b612e025760405162461bcd60e51b8152600401808060200182810382526029815260200180613e4c6029913960400191505060405180910390fd5b600154612e15908263ffffffff61279b16565b6001556001600160a01b038316600090815260026020526040902054612e41908263ffffffff61279b16565b6001600160a01b03841660008181526002602090815260409182902093909355805191825291810184905280820183905290517f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f9181900360600190a16040805182815290516001600160a01b038516916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60006001600160701b03821115612f2c5760405162461bcd60e51b8152600401808060200182810382526022815260200180613e2a6022913960400191505060405180910390fd5b5090565b600082612f3f57506000610afb565b82820282848281612f4c57fe5b041461121f5760405162461bcd60e51b8152600401808060200182810382526021815260200180613e096021913960400191505060405180910390fd5b6000808211612fdf576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b600082848161273557fe5b6040805180820190915260048152636344616960e01b602082015290565b606060006001600160e01b0319831663140e25ad60e31b141561304957604051806040016040528060048152602001631b5a5b9d60e21b8152509150611259565b6001600160e01b0319831663db006a7560e01b1415613088576040518060400160405280600681526020016572656465656d60d01b8152509150611259565b6001600160e01b0319831663852a12e360e01b14156130d1576040518060400160405280601081526020016f72656465656d556e6465726c79696e6760801b8152509150611259565b6001600160e01b031983166323b872dd60e01b1415613116576040518060400160405280600c81526020016b7472616e7366657246726f6d60a01b8152509150611259565b6001600160e01b0319831663a9059cbb60e01b141561315757604051806040016040528060088152602001673a3930b739b332b960c11b8152509150611259565b6001600160e01b0319831663a6afed9560e01b141561319e576040518060400160405280600e81526020016d1858d8dc9d59525b9d195c995cdd60921b8152509150611259565b505060408051808201909152601381527230b7103ab735b737bbb710333ab731ba34b7b760691b6020820152919050565b60606044825111801561320157508151600160fb1b9083906000906131f057fe5b01602001516001600160f81b031916145b801561322e5750815160c360f81b908390600190811061321d57fe5b01602001516001600160f81b031916145b801561325b57508151607960f81b908390600290811061324a57fe5b01602001516001600160f81b031916145b801561328857508151600560fd1b908390600390811061327757fe5b01602001516001600160f81b031916145b156133e057606060048351036040519080825280601f01601f1916602001820160405280156132be576020820181803883390190505b50905060045b8351811015613312578381815181106132d957fe5b602001015160f81c60f81b8260048303815181106132f357fe5b60200101906001600160f81b031916908160001a9053506001016132c4565b5080806020019051602081101561332857600080fd5b810190808051604051939291908464010000000082111561334857600080fd5b90830190602082018581111561335d57600080fd5b825164010000000081118282018810171561337757600080fd5b82525081516020918201929091019080838360005b838110156133a457818101518382015260200161338c565b50505050905090810190601f1680156133d15780820380516001836020036101000a031916815260200191505b5060405250505091505061340b565b50604080518082019091526012815271286e6f2072657665727420726561736f6e2960701b60208201525b919050565b600080600061349e735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b0316636c540baf6040518163ffffffff1660e01b815260040160206040518083038186803b15801561346557600080fd5b505afa158015613479573d6000803e3d6000fd5b505050506040513d602081101561348f57600080fd5b5051439063ffffffff61273e16565b9050806135a157735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b031663182df0f56040518163ffffffff1660e01b815260040160206040518083038186803b1580156134f257600080fd5b505afa158015613506573d6000803e3d6000fd5b505050506040513d602081101561351c57600080fd5b505160408051630ae9d70b60e41b81529051735d3a536e4d6dbd6114cc1ead35777bab948e36439163ae9d70b0916004808301926020929190829003018186803b15801561356957600080fd5b505afa15801561357d573d6000803e3d6000fd5b505050506040513d602081101561359357600080fd5b50519093509150613c2e9050565b604080516305f5d64360e11b8152735d3a536e4d6dbd6114cc1ead35777bab948e3643600482015290516000916b033b2e3c9fd0803ce8000000916138089173197e90f9fad81970ba7976f33cbd77088e5d7cf791630bebac8691602480820192602092909190829003018186803b15801561361c57600080fd5b505afa158015613630573d6000803e3d6000fd5b505050506040513d602081101561364657600080fd5b50516040805163324abb3160e21b815290516b033b2e3c9fd0803ce8000000916137f49173197e90f9fad81970ba7976f33cbd77088e5d7cf79163c92aecc4916004808301926020929190829003018186803b1580156136a557600080fd5b505afa1580156136b9573d6000803e3d6000fd5b505050506040513d60208110156136cf57600080fd5b50516040805163243df84160e11b815290516137e89173197e90f9fad81970ba7976f33cbd77088e5d7cf79163487bf08291600480820192602092909190829003018186803b15801561372157600080fd5b505afa158015613735573d6000803e3d6000fd5b505050506040513d602081101561374b57600080fd5b5051604080516320aba08b60e01b815290516137d69173197e90f9fad81970ba7976f33cbd77088e5d7cf7916320aba08b91600480820192602092909190829003018186803b15801561379d57600080fd5b505afa1580156137b1573d6000803e3d6000fd5b505050506040513d60208110156137c757600080fd5b5051429063ffffffff61273e16565b6b033b2e3c9fd0803ce8000000613c4f565b9063ffffffff612f3016565b816137fb57fe5b049063ffffffff612f3016565b8161380f57fe5b0490506000735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b031663f3fdb15a6040518163ffffffff1660e01b815260040160206040518083038186803b15801561386157600080fd5b505afa158015613875573d6000803e3d6000fd5b505050506040513d602081101561388b57600080fd5b5051604080516308f7a6e360e31b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e3643916347bd3718916004808301926020929190829003018186803b1580156138de57600080fd5b505afa1580156138f2573d6000803e3d6000fd5b505050506040513d602081101561390857600080fd5b505160408051638f840ddd60e01b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e364391638f840ddd916004808301926020929190829003018186803b15801561395b57600080fd5b505afa15801561396f573d6000803e3d6000fd5b505050506040513d602081101561398557600080fd5b5051604080516305cee64160e21b81529051919250600091735d3a536e4d6dbd6114cc1ead35777bab948e36439163173b9904916004808301926020929190829003018186803b1580156139d857600080fd5b505afa1580156139ec573d6000803e3d6000fd5b505050506040513d6020811015613a0257600080fd5b5051604080516315f2405360e01b81526004810188905260248101869052604481018590529051919250600091670de0b6b3a764000091613aae9187916137e8918c916001600160a01b038c16916315f2405391606480820192602092909190829003018186803b158015613a7657600080fd5b505afa158015613a8a573d6000803e3d6000fd5b505050506040513d6020811015613aa057600080fd5b50519063ffffffff612f3016565b81613ab557fe5b049050613ac8848263ffffffff61279b16565b9350613afc670de0b6b3a7640000613ae6848463ffffffff612f3016565b81613aed57fe5b8591900463ffffffff61279b16565b9250613b9a735d3a536e4d6dbd6114cc1ead35777bab948e36436001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b158015613b4e57600080fd5b505afa158015613b62573d6000803e3d6000fd5b505050506040513d6020811015613b7857600080fd5b5051611eb6670de0b6b3a76400006137e887612c4e8c8b63ffffffff61279b16565b9850846001600160a01b031663b8168816878686866040518563ffffffff1660e01b81526004018085815260200184815260200183815260200182815260200194505050505060206040518083038186803b158015613bf857600080fd5b505afa158015613c0c573d6000803e3d6000fd5b505050506040513d6020811015613c2257600080fd5b50519750505050505050505b9091565b60408051808201909152600381526244616960e81b602082015290565b6000838015613cf057600184168015613c6a57859250613c6e565b8392505b50600283046002850494505b8415613cea578586028687820414613c9157600080fd5b81810181811015613ca157600080fd5b85810497506002870615613cdd578785028589820414158915151615613cc657600080fd5b83810181811015613cd657600080fd5b8790049550505b5050600285049450613c7a565b50613d06565b838015613d005760009250613d04565b8392505b505b509392505050565b60408051606081018252600080825260208201819052918101919091529056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737320636f6e74726163742072657475726e65642066616c7365206f6e2063616c6c696e672045524332303a20617070726f766520746f20746865207a65726f20616464726573735369676e6174757265206076602076616c7565206e6f74207065726d69747465642e20636f6e7472616374207265766572746564207768696c6520617474656d7074696e6720746f2063616c6c2045524332303a20617070726f766520666f7220746865207a65726f2061646472657373536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774f766572666c6f77206f6e20636f6e76657273696f6e20746f2075696e743131322e4d696e74206661696c65643a20696e73756666696369656e742066756e647320737570706c6965642e5369676e6174757265206073602076616c75652063616e6e6f7420626520706f74656e7469616c6c79206d616c6c6561626c652e45524332303a207472616e736665722066726f6d20746865207a65726f206164647265737352656465656d206661696c65643a20696e73756666696369656e742066756e647320737570706c6965642e537570706c69656420616d6f756e742065786365656473206163636f756e742062616c616e63652e4d75737420737570706c7920612073696e676c652036352d62797465207369676e6174757265207768656e206f776e6572206973206e6f74206120636f6e74726163742ea265627a7a723158202020446861726d612044616920496d706c656d656e746174696f6e205631202064736f6c634300050b0032