ETH Price: $2,443.50 (+9.92%)

Transaction Decoder

Block:
11860484 at Feb-15-2021 09:12:57 AM +UTC
Transaction Fee:
0.017505727 ETH $42.78
Gas Used:
160,603 Gas / 109 Gwei

Emitted Events:

81 HEX.Transfer( from=Vyper_contract, to=Spender, value=780139465674 )
82 Vyper_contract.TokenPurchase( buyer=0xe069cb01d06ba617bcdf789bf2ff0d5e5ca20c71, eth_sold=49562500000000000, tokens_bought=780139465674 )
83 OneInchExchange.Swapped( sender=Spender, srcToken=0xEeeeeEee...eeeeeEEeE, dstToken=HEX, dstReceiver=Spender, amount=49562500000000000, spentAmount=49562500000000000, returnAmount=780139465674, minReturnAmount=764536676360, guaranteedAmount=780139465674, referrer=0x00000000...000000000 )
84 HEX.Transfer( from=Spender, to=[Sender] 0x6cfcb2f95ef977f3cc5513f87306f18b9ad5d6e1, value=780139465674 )
85 MetaSwap.Swap( 0xbeee1e6e7fe307ddcf84b0a16137a4430ad5e2480fc4f4a8e250ab56ccd7630d, 0x5c1762e565dfd2c94d5050843f988a3702be98b423e773202fc96f258be96838, 0x0000000000000000000000006cfcb2f95ef977f3cc5513f87306f18b9ad5d6e1 )

Account State Difference:

  Address   Before After State Difference Code
0x05cDe89c...f129E7883 6.363755642289919711 Eth6.413318142289919711 Eth0.0495625
0x11eDedeb...1543ec6fB
(Metamask: Fees)
468.291327296994593588 Eth468.291764796994593588 Eth0.0004375
0x2b591e99...8c40Eeb39
0x6CFcb2f9...B9Ad5d6e1
0.1 Eth
Nonce: 0
0.032494273 Eth
Nonce: 1
0.067505727
(Ethermine)
2,318.404474259169634489 Eth2,318.421979986169634489 Eth0.017505727

Execution Trace

ETH 0.05 MetaSwap.swap( aggregatorId=oneInchFee, tokenFrom=0x0000000000000000000000000000000000000000, amount=50000000000000000, data=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000002B591E99AFE9F32EAA6214F7B7629768C40EEB3900000000000000000000000000000000000000000000000000B014D4C6AE2800000000000000000000000000000000000000000000000000000000B201EFE008000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000018DE76816D80000000000000000000000000011EDEDEBF63BEF0EA2D2D071BDF88F71543EC6FB0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000E069CB01D06BA617BCDF789BF2FF0D5E5CA20C71000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001C0000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000002B591E99AFE9F32EAA6214F7B7629768C40EEB39000000000000000000000000E069CB01D06BA617BCDF789BF2FF0D5E5CA20C7100000000000000000000000074DE5D4FCBF63E00296FD95D33236B979401663100000000000000000000000000000000000000000000000000B014D4C6AE2800000000000000000000000000000000000000000000000000000000B201EFE008000000000000000000000000000000000000000000000000000000B5A3EF8BCA00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000005CDE89CCFA0ADA8C88D5A23CAAA79EF129E7883000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B014D4C6AE280000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064AD65D76D000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000602B8C8F00000000000000000000000074DE5D4FCBF63E00296FD95D33236B979401663100000000000000000000000000000000000000000000000000000000 )
  • ETH 0.05 Spender.swap( adapter=0x61CaeCd0eb81d3Ea9c43ACC8CE7E40865951eBc3, data=0x242FB09F0000000000000000000000006CFCB2F95EF977F3CC5513F87306F18B9AD5D6E100000000000000000000000000000000000000000000000000000000000000000000000000000000000000002B591E99AFE9F32EAA6214F7B7629768C40EEB3900000000000000000000000000000000000000000000000000B014D4C6AE2800000000000000000000000000000000000000000000000000000000B201EFE008000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000018DE76816D80000000000000000000000000011EDEDEBF63BEF0EA2D2D071BDF88F71543EC6FB0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000E069CB01D06BA617BCDF789BF2FF0D5E5CA20C71000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001C0000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000002B591E99AFE9F32EAA6214F7B7629768C40EEB39000000000000000000000000E069CB01D06BA617BCDF789BF2FF0D5E5CA20C7100000000000000000000000074DE5D4FCBF63E00296FD95D33236B979401663100000000000000000000000000000000000000000000000000B014D4C6AE2800000000000000000000000000000000000000000000000000000000B201EFE008000000000000000000000000000000000000000000000000000000B5A3EF8BCA00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000005CDE89CCFA0ADA8C88D5A23CAAA79EF129E7883000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B014D4C6AE280000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064AD65D76D000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000602B8C8F00000000000000000000000074DE5D4FCBF63E00296FD95D33236B979401663100000000000000000000000000000000000000000000000000000000 )
    • ETH 0.05 0x61caecd0eb81d3ea9c43acc8ce7e40865951ebc3.242fb09f( )
      • ETH 0.0004375 Metamask: Fees.CALL( )
      • ETH 0.0495625 OneInchExchange.swap( caller=0xe069CB01D06bA617bCDf789bf2ff0D5E5ca20C71, desc=[{name:srcToken, type:address, order:1, indexed:false, value:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, valueString:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE}, {name:dstToken, type:address, order:2, indexed:false, value:0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39, valueString:0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39}, {name:srcReceiver, type:address, order:3, indexed:false, value:0xe069CB01D06bA617bCDf789bf2ff0D5E5ca20C71, valueString:0xe069CB01D06bA617bCDf789bf2ff0D5E5ca20C71}, {name:dstReceiver, type:address, order:4, indexed:false, value:0x74de5d4FCbf63E00296fd95d33236B9794016631, valueString:0x74de5d4FCbf63E00296fd95d33236B9794016631}, {name:amount, type:uint256, order:5, indexed:false, value:49562500000000000, valueString:49562500000000000}, {name:minReturnAmount, type:uint256, order:6, indexed:false, value:764536676360, valueString:764536676360}, {name:guaranteedAmount, type:uint256, order:7, indexed:false, value:780139465674, valueString:780139465674}, {name:flags, type:uint256, order:8, indexed:false, value:0, valueString:0}, {name:referrer, type:address, order:9, indexed:false, value:0x0000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000}, {name:permit, type:bytes, order:10, indexed:false, value:0x, valueString:0x}], calls= ) => ( returnAmount=780139465674 )
        • HEX.balanceOf( account=0x74de5d4FCbf63E00296fd95d33236B9794016631 ) => ( 0 )
        • ETH 0.0495625 1inch: Router 2.a8920d2b( )
          • ETH 0.0495625 Vyper_contract.ethToTokenTransferInput( min_tokens=1, deadline=1613466767, recipient=0x74de5d4FCbf63E00296fd95d33236B9794016631 ) => ( out=780139465674 )
            • ETH 0.0495625 Vyper_contract.ethToTokenTransferInput( min_tokens=1, deadline=1613466767, recipient=0x74de5d4FCbf63E00296fd95d33236B9794016631 ) => ( out=780139465674 )
            • HEX.balanceOf( account=0x74de5d4FCbf63E00296fd95d33236B9794016631 ) => ( 780139465674 )
            • HEX.balanceOf( account=0x74de5d4FCbf63E00296fd95d33236B9794016631 ) => ( 780139465674 )
            • HEX.transfer( recipient=0x6CFcb2f95EF977f3CC5513F87306F18B9Ad5d6e1, amount=780139465674 ) => ( True )
              File 1 of 6: MetaSwap
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "../Constants.sol";
              contract CommonAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  /**
                   * @dev Performs a swap
                   * @param recipient The original msg.sender performing the swap
                   * @param aggregator Address of the aggregator's contract
                   * @param spender Address to which tokens will be approved
                   * @param method Selector of the function to be called in the aggregator's contract
                   * @param tokenFrom Token to be swapped
                   * @param tokenTo Token to be received
                   * @param amountFrom Amount of tokenFrom to swap
                   * @param amountTo Minimum amount of tokenTo to receive
                   * @param data Data used for the call made to the aggregator's contract
                   */
                  function swap(
                      address payable recipient,
                      address aggregator,
                      address spender,
                      bytes4 method,
                      IERC20 tokenFrom,
                      IERC20 tokenTo,
                      uint256 amountFrom,
                      uint256 amountTo,
                      bytes calldata data
                  ) external payable {
                      require(tokenFrom != tokenTo, "TOKEN_PAIR_INVALID");
                      if (address(tokenFrom) != Constants.ETH) {
                          _approveSpender(tokenFrom, spender, amountFrom);
                      }
                      // We always forward msg.value as it may be necessary to pay fees
                      bytes memory encodedData = abi.encodePacked(method, data);
                      aggregator.functionCallWithValue(encodedData, msg.value);
                      // Transfer remaining balance of tokenFrom to sender
                      if (address(tokenFrom) != Constants.ETH) {
                          uint256 balance = tokenFrom.balanceOf(address(this));
                          _transfer(tokenFrom, balance, recipient);
                      }
                      uint256 weiBalance = address(this).balance;
                      // Transfer remaining balance of tokenTo to sender
                      if (address(tokenTo) != Constants.ETH) {
                          uint256 balance = tokenTo.balanceOf(address(this));
                          require(balance >= amountTo, "INSUFFICIENT_AMOUNT");
                          _transfer(tokenTo, balance, recipient);
                      } else {
                          // If tokenTo == ETH, then check that the remaining ETH balance >= amountTo
                          require(weiBalance >= amountTo, "INSUFFICIENT_AMOUNT");
                      }
                      // If there are unused fees or if tokenTo is ETH, transfer to sender
                      if (weiBalance > 0) {
                          recipient.sendValue(weiBalance);
                      }
                  }
                  /**
                   * @dev Transfers token to sender if amount > 0
                   * @param token IERC20 token to transfer to sender
                   * @param amount Amount of token to transfer
                   * @param recipient Address that will receive the tokens
                   */
                  function _transfer(
                      IERC20 token,
                      uint256 amount,
                      address recipient
                  ) internal {
                      if (amount > 0) {
                          token.safeTransfer(recipient, amount);
                      }
                  }
                  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol
                  /**
                   * @dev Approves max amount of token to the spender if the allowance is lower than amount
                   * @param token The ERC20 token to approve
                   * @param spender Address to which funds will be approved
                   * @param amount Amount used to compare current allowance
                   */
                  function _approveSpender(
                      IERC20 token,
                      address spender,
                      uint256 amount
                  ) internal {
                      // If allowance is not enough, approve max possible amount
                      uint256 allowance = token.allowance(address(this), spender);
                      if (allowance < amount) {
                          bytes memory returndata = address(token).functionCall(
                              abi.encodeWithSelector(
                                  token.approve.selector,
                                  spender,
                                  type(uint256).max
                              )
                          );
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "APPROVAL_FAILED");
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "./IERC20.sol";
              import "../../math/SafeMath.sol";
              import "../../utils/Address.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  using SafeMath for uint256;
                  using Address for address;
                  function safeTransfer(IERC20 token, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Deprecated. This function has issues similar to the ones found in
                   * {IERC20-approve}, and its usage is discouraged.
                   *
                   * Whenever possible, use {safeIncreaseAllowance} and
                   * {safeDecreaseAllowance} instead.
                   */
                  function safeApprove(IERC20 token, address spender, uint256 value) internal {
                      // safeApprove should only be called when setting an initial allowance,
                      // or when resetting it to zero. To increase and decrease it, use
                      // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                      // solhint-disable-next-line max-line-length
                      require((value == 0) || (token.allowance(address(this), spender) == 0),
                          "SafeERC20: approve from non-zero to non-zero allowance"
                      );
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                  }
                  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 newAllowance = token.allowance(address(this), spender).add(value);
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
                  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   */
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                      // the target address contains contract code and also asserts for success in the low-level call.
                      bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                      if (returndata.length > 0) { // Return data is optional
                          // solhint-disable-next-line max-line-length
                          require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow
               * checks.
               *
               * Arithmetic operations in Solidity wrap on overflow. This can easily result
               * in bugs, because programmers usually assume that an overflow raises an
               * error, which is the standard behavior in high level programming languages.
               * `SafeMath` restores this intuition by reverting the transaction when an
               * operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               */
              library SafeMath {
                  /**
                   * @dev Returns the addition of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `+` operator.
                   *
                   * Requirements:
                   *
                   * - Addition cannot overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      require(c >= a, "SafeMath: addition overflow");
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, "SafeMath: subtraction overflow");
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b <= a, errorMessage);
                      uint256 c = a - b;
                      return c;
                  }
                  /**
                   * @dev Returns the multiplication of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `*` operator.
                   *
                   * Requirements:
                   *
                   * - Multiplication cannot overflow.
                   */
                  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                      // benefit is lost if 'b' is also tested.
                      // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                      if (a == 0) {
                          return 0;
                      }
                      uint256 c = a * b;
                      require(c / a == b, "SafeMath: multiplication overflow");
                      return c;
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function div(uint256 a, uint256 b) internal pure returns (uint256) {
                      return div(a, b, "SafeMath: division by zero");
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                      return c;
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                      return mod(a, b, "SafeMath: modulo by zero");
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts with custom message when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.2;
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies in extcodesize, which returns 0 for contracts in
                      // construction, since the code is only stored at the end of the
                      // constructor execution.
                      uint256 size;
                      // solhint-disable-next-line no-inline-assembly
                      assembly { size := extcodesize(account) }
                      return size > 0;
                  }
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      require(address(this).balance >= amount, "Address: insufficient balance");
                      // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                      (bool success, ) = recipient.call{ value: amount }("");
                      require(success, "Address: unable to send value, recipient may have reverted");
                  }
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain`call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - `target` must be a contract.
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                    return functionCall(target, data, "Address: low-level call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                   * `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                      return _functionCallWithValue(target, data, 0, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but also transferring `value` wei to `target`.
                   *
                   * Requirements:
                   *
                   * - the calling contract must have an ETH balance of at least `value`.
                   * - the called Solidity function must be `payable`.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                   * with `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                      require(address(this).balance >= value, "Address: insufficient balance for call");
                      return _functionCallWithValue(target, data, value, errorMessage);
                  }
                  function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
                      require(isContract(target), "Address: call to non-contract");
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              // solhint-disable-next-line no-inline-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              library Constants {
                  address internal constant ETH = 0x0000000000000000000000000000000000000000;
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/math/SafeMath.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "../Constants.sol";
              contract FeeCommonAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  using SafeMath for uint256;
                  // solhint-disable-next-line var-name-mixedcase
                  address payable public immutable FEE_WALLET;
                  constructor(address payable feeWallet) public {
                      FEE_WALLET = feeWallet;
                  }
                  /**
                   * @dev Performs a swap
                   * @param recipient The original msg.sender performing the swap
                   * @param aggregator Address of the aggregator's contract
                   * @param spender Address to which tokens will be approved
                   * @param method Selector of the function to be called in the aggregator's contract
                   * @param tokenFrom Token to be swapped
                   * @param tokenTo Token to be received
                   * @param amountFrom Amount of tokenFrom to swap
                   * @param amountTo Minimum amount of tokenTo to receive
                   * @param data Data used for the call made to the aggregator's contract
                   * @param fee Amount of tokenFrom sent to the fee wallet
                   */
                  function swap(
                      address payable recipient,
                      address aggregator,
                      address spender,
                      bytes4 method,
                      IERC20 tokenFrom,
                      IERC20 tokenTo,
                      uint256 amountFrom,
                      uint256 amountTo,
                      bytes calldata data,
                      uint256 fee
                  ) external payable {
                      require(tokenFrom != tokenTo, "TOKEN_PAIR_INVALID");
                      if (address(tokenFrom) == Constants.ETH) {
                          FEE_WALLET.sendValue(fee);
                      } else {
                          _transfer(tokenFrom, fee, FEE_WALLET);
                          _approveSpender(tokenFrom, spender, amountFrom);
                      }
                      // We always forward msg.value as it may be necessary to pay fees
                      aggregator.functionCallWithValue(
                          abi.encodePacked(method, data),
                          address(this).balance
                      );
                      // Transfer remaining balance of tokenFrom to sender
                      if (address(tokenFrom) != Constants.ETH) {
                          _transfer(tokenFrom, tokenFrom.balanceOf(address(this)), recipient);
                      }
                      uint256 weiBalance = address(this).balance;
                      // Transfer remaining balance of tokenTo to sender
                      if (address(tokenTo) != Constants.ETH) {
                          uint256 balance = tokenTo.balanceOf(address(this));
                          require(balance >= amountTo, "INSUFFICIENT_AMOUNT");
                          _transfer(tokenTo, balance, recipient);
                      } else {
                          // If tokenTo == ETH, then check that the remaining ETH balance >= amountTo
                          require(weiBalance >= amountTo, "INSUFFICIENT_AMOUNT");
                      }
                      // If there are unused fees or if tokenTo is ETH, transfer to sender
                      if (weiBalance > 0) {
                          recipient.sendValue(weiBalance);
                      }
                  }
                  /**
                   * @dev Transfers token to sender if amount > 0
                   * @param token IERC20 token to transfer to sender
                   * @param amount Amount of token to transfer
                   * @param recipient Address that will receive the tokens
                   */
                  function _transfer(
                      IERC20 token,
                      uint256 amount,
                      address recipient
                  ) internal {
                      if (amount > 0) {
                          token.safeTransfer(recipient, amount);
                      }
                  }
                  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol
                  /**
                   * @dev Approves max amount of token to the spender if the allowance is lower than amount
                   * @param token The ERC20 token to approve
                   * @param spender Address to which funds will be approved
                   * @param amount Amount used to compare current allowance
                   */
                  function _approveSpender(
                      IERC20 token,
                      address spender,
                      uint256 amount
                  ) internal {
                      // If allowance is not enough, approve max possible amount
                      uint256 allowance = token.allowance(address(this), spender);
                      if (allowance < amount) {
                          bytes memory returndata = address(token).functionCall(
                              abi.encodeWithSelector(
                                  token.approve.selector,
                                  spender,
                                  type(uint256).max
                              )
                          );
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "APPROVAL_FAILED");
                          }
                      }
                  }
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/math/SafeMath.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "../Constants.sol";
              import "../IWETH.sol";
              contract FeeWethAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  using SafeMath for uint256;
                  IWETH public immutable weth;
                  // solhint-disable-next-line var-name-mixedcase
                  address payable public immutable FEE_WALLET;
                  constructor(IWETH _weth, address payable feeWallet) public {
                      weth = _weth;
                      FEE_WALLET = feeWallet;
                  }
                  /**
                   * @dev Performs a swap
                   * @param recipient The original msg.sender performing the swap
                   * @param aggregator Address of the aggregator's contract
                   * @param spender Address to which tokens will be approved
                   * @param method Selector of the function to be called in the aggregator's contract
                   * @param tokenFrom Token to be swapped
                   * @param tokenTo Token to be received
                   * @param amountFrom Amount of tokenFrom to swap
                   * @param amountTo Minimum amount of tokenTo to receive
                   * @param data Data used for the call made to the aggregator's contract
                   * @param fee Amount of tokenFrom sent to the fee wallet
                   */
                  function swap(
                      address payable recipient,
                      address aggregator,
                      address spender,
                      bytes4 method,
                      IERC20 tokenFrom,
                      IERC20 tokenTo,
                      uint256 amountFrom,
                      uint256 amountTo,
                      bytes calldata data,
                      uint256 fee
                  ) external payable {
                      require(tokenFrom != tokenTo, "TOKEN_PAIR_INVALID");
                      if (address(tokenFrom) == Constants.ETH) {
                          FEE_WALLET.sendValue(fee);
                          // If tokenFrom is ETH, msg.value = fee + amountFrom (total fee could be 0)
                          // Can't deal with ETH, convert to WETH, the remaining balance will be the fee
                          weth.deposit{value: amountFrom}();
                          _approveSpender(weth, spender, amountFrom);
                      } else {
                          _transfer(tokenFrom, fee, FEE_WALLET);
                          // Otherwise capture tokens from sender
                          _approveSpender(tokenFrom, spender, amountFrom);
                      }
                      // Perform the swap
                      aggregator.functionCallWithValue(
                          abi.encodePacked(method, data),
                          address(this).balance
                      );
                      // Transfer remaining balance of tokenFrom to sender
                      if (address(tokenFrom) != Constants.ETH) {
                          _transfer(tokenFrom, tokenFrom.balanceOf(address(this)), recipient);
                      } else {
                          // If using ETH, just unwrap any remaining WETH
                          // At the end of this function all ETH will be transferred to the sender
                          _unwrapWETH();
                      }
                      uint256 weiBalance = address(this).balance;
                      // Transfer remaining balance of tokenTo to sender
                      if (address(tokenTo) != Constants.ETH) {
                          uint256 balance = tokenTo.balanceOf(address(this));
                          require(balance >= amountTo, "INSUFFICIENT_AMOUNT");
                          _transfer(tokenTo, balance, recipient);
                      } else {
                          // If tokenTo == ETH, unwrap received WETH and add it to the wei balance,
                          // then check that the remaining ETH balance >= amountTo
                          // It is safe to not use safeMath as no one can have enough Ether to overflow
                          weiBalance += _unwrapWETH();
                          require(weiBalance >= amountTo, "INSUFFICIENT_AMOUNT");
                      }
                      // If there are unused fees or if tokenTo is ETH, transfer to sender
                      if (weiBalance > 0) {
                          recipient.sendValue(weiBalance);
                      }
                  }
                  /**
                   * @dev Unwraps all available WETH into ETH
                   */
                  function _unwrapWETH() internal returns (uint256) {
                      uint256 balance = weth.balanceOf(address(this));
                      weth.withdraw(balance);
                      return balance;
                  }
                  /**
                   * @dev Transfers token to sender if amount > 0
                   * @param token IERC20 token to transfer to sender
                   * @param amount Amount of token to transfer
                   * @param recipient Address that will receive the tokens
                   */
                  function _transfer(
                      IERC20 token,
                      uint256 amount,
                      address recipient
                  ) internal {
                      if (amount > 0) {
                          token.safeTransfer(recipient, amount);
                      }
                  }
                  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol
                  /**
                   * @dev Approves max amount of token to the spender if the allowance is lower than amount
                   * @param token The ERC20 token to approve
                   * @param spender Address to which funds will be approved
                   * @param amount Amount used to compare current allowance
                   */
                  function _approveSpender(
                      IERC20 token,
                      address spender,
                      uint256 amount
                  ) internal {
                      // If allowance is not enough, approve max possible amount
                      uint256 allowance = token.allowance(address(this), spender);
                      if (allowance < amount) {
                          bytes memory returndata = address(token).functionCall(
                              abi.encodeWithSelector(
                                  token.approve.selector,
                                  spender,
                                  type(uint256).max
                              )
                          );
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "APPROVAL_FAILED");
                          }
                      }
                  }
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              interface IWETH is IERC20 {
                  function deposit() external payable;
                  function withdraw(uint256) external;
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/math/SafeMath.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
              import "../Constants.sol";
              contract UniswapAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  using SafeMath for uint256;
                  // solhint-disable-next-line var-name-mixedcase
                  IUniswapV2Router02 public immutable UNISWAP;
                  // solhint-disable-next-line var-name-mixedcase
                  address payable public immutable FEE_WALLET;
                  constructor(address payable feeWallet, IUniswapV2Router02 uniswap) public {
                      FEE_WALLET = feeWallet;
                      UNISWAP = uniswap;
                  }
                  /**
                   * @dev Performs a swap
                   * @param recipient The original msg.sender performing the swap
                   * @param tokenFrom Token to be swapped
                   * @param tokenTo Token to be received
                   * @param amountFrom Amount of tokenFrom to swap
                   * @param amountTo Minimum amount of tokenTo to receive
                   * @param path Used by Uniswap
                   * @param deadline Timestamp at which the swap becomes invalid. Used by Uniswap
                   * @param feeOnTransfer Use `supportingFeeOnTransfer` Uniswap methods
                   * @param fee Amount of tokenFrom sent to the fee wallet
                   */
                  function swap(
                      address payable recipient,
                      IERC20 tokenFrom,
                      IERC20 tokenTo,
                      uint256 amountFrom,
                      uint256 amountTo,
                      address[] calldata path,
                      uint256 deadline,
                      bool feeOnTransfer,
                      uint256 fee
                  ) external payable {
                      require(tokenFrom != tokenTo, "TOKEN_PAIR_INVALID");
                      if (address(tokenFrom) == Constants.ETH) {
                          FEE_WALLET.sendValue(fee);
                      } else {
                          _transfer(tokenFrom, fee, FEE_WALLET);
                      }
                      if (address(tokenFrom) == Constants.ETH) {
                          if (feeOnTransfer) {
                              UNISWAP.swapExactETHForTokensSupportingFeeOnTransferTokens{
                                  value: address(this).balance
                              }(amountTo, path, address(this), deadline);
                          } else {
                              UNISWAP.swapExactETHForTokens{value: address(this).balance}(
                                  amountTo,
                                  path,
                                  address(this),
                                  deadline
                              );
                          }
                      } else {
                          _approveSpender(tokenFrom, address(UNISWAP), amountFrom);
                          if (address(tokenTo) == Constants.ETH) {
                              if (feeOnTransfer) {
                                  UNISWAP.swapExactTokensForETHSupportingFeeOnTransferTokens(
                                      amountFrom,
                                      amountTo,
                                      path,
                                      address(this),
                                      deadline
                                  );
                              } else {
                                  UNISWAP.swapExactTokensForETH(
                                      amountFrom,
                                      amountTo,
                                      path,
                                      address(this),
                                      deadline
                                  );
                              }
                          } else {
                              if (feeOnTransfer) {
                                  UNISWAP
                                      .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                                      amountFrom,
                                      amountTo,
                                      path,
                                      address(this),
                                      deadline
                                  );
                              } else {
                                  UNISWAP.swapExactTokensForTokens(
                                      amountFrom,
                                      amountTo,
                                      path,
                                      address(this),
                                      deadline
                                  );
                              }
                          }
                      }
                      // Transfer remaining balance of tokenFrom to sender
                      if (address(tokenFrom) != Constants.ETH) {
                          _transfer(tokenFrom, tokenFrom.balanceOf(address(this)), recipient);
                      }
                      uint256 weiBalance = address(this).balance;
                      // Transfer remaining balance of tokenTo to sender
                      if (address(tokenTo) != Constants.ETH) {
                          uint256 balance = tokenTo.balanceOf(address(this));
                          require(balance >= amountTo, "INSUFFICIENT_AMOUNT");
                          _transfer(tokenTo, balance, recipient);
                      } else {
                          // If tokenTo == ETH, then check that the remaining ETH balance >= amountTo
                          require(weiBalance >= amountTo, "INSUFFICIENT_AMOUNT");
                      }
                      // If there are unused fees or if tokenTo is ETH, transfer to sender
                      if (weiBalance > 0) {
                          recipient.sendValue(weiBalance);
                      }
                  }
                  /**
                   * @dev Transfers token to sender if amount > 0
                   * @param token IERC20 token to transfer to sender
                   * @param amount Amount of token to transfer
                   * @param recipient Address that will receive the tokens
                   */
                  function _transfer(
                      IERC20 token,
                      uint256 amount,
                      address recipient
                  ) internal {
                      if (amount > 0) {
                          token.safeTransfer(recipient, amount);
                      }
                  }
                  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol
                  /**
                   * @dev Approves max amount of token to the spender if the allowance is lower than amount
                   * @param token The ERC20 token to approve
                   * @param spender Address to which funds will be approved
                   * @param amount Amount used to compare current allowance
                   */
                  function _approveSpender(
                      IERC20 token,
                      address spender,
                      uint256 amount
                  ) internal {
                      // If allowance is not enough, approve max possible amount
                      uint256 allowance = token.allowance(address(this), spender);
                      if (allowance < amount) {
                          bytes memory returndata = address(token).functionCall(
                              abi.encodeWithSelector(
                                  token.approve.selector,
                                  spender,
                                  type(uint256).max
                              )
                          );
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "APPROVAL_FAILED");
                          }
                      }
                  }
              }
              pragma solidity >=0.6.2;
              import './IUniswapV2Router01.sol';
              interface IUniswapV2Router02 is IUniswapV2Router01 {
                  function removeLiquidityETHSupportingFeeOnTransferTokens(
                      address token,
                      uint liquidity,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline
                  ) external returns (uint amountETH);
                  function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
                      address token,
                      uint liquidity,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline,
                      bool approveMax, uint8 v, bytes32 r, bytes32 s
                  ) external returns (uint amountETH);
                  function swapExactTokensForTokensSupportingFeeOnTransferTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external;
                  function swapExactETHForTokensSupportingFeeOnTransferTokens(
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external payable;
                  function swapExactTokensForETHSupportingFeeOnTransferTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external;
              }
              pragma solidity >=0.6.2;
              interface IUniswapV2Router01 {
                  function factory() external pure returns (address);
                  function WETH() external pure returns (address);
                  function addLiquidity(
                      address tokenA,
                      address tokenB,
                      uint amountADesired,
                      uint amountBDesired,
                      uint amountAMin,
                      uint amountBMin,
                      address to,
                      uint deadline
                  ) external returns (uint amountA, uint amountB, uint liquidity);
                  function addLiquidityETH(
                      address token,
                      uint amountTokenDesired,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline
                  ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
                  function removeLiquidity(
                      address tokenA,
                      address tokenB,
                      uint liquidity,
                      uint amountAMin,
                      uint amountBMin,
                      address to,
                      uint deadline
                  ) external returns (uint amountA, uint amountB);
                  function removeLiquidityETH(
                      address token,
                      uint liquidity,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline
                  ) external returns (uint amountToken, uint amountETH);
                  function removeLiquidityWithPermit(
                      address tokenA,
                      address tokenB,
                      uint liquidity,
                      uint amountAMin,
                      uint amountBMin,
                      address to,
                      uint deadline,
                      bool approveMax, uint8 v, bytes32 r, bytes32 s
                  ) external returns (uint amountA, uint amountB);
                  function removeLiquidityETHWithPermit(
                      address token,
                      uint liquidity,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline,
                      bool approveMax, uint8 v, bytes32 r, bytes32 s
                  ) external returns (uint amountToken, uint amountETH);
                  function swapExactTokensForTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external returns (uint[] memory amounts);
                  function swapTokensForExactTokens(
                      uint amountOut,
                      uint amountInMax,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external returns (uint[] memory amounts);
                  function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
                      external
                      payable
                      returns (uint[] memory amounts);
                  function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
                      external
                      returns (uint[] memory amounts);
                  function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
                      external
                      returns (uint[] memory amounts);
                  function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
                      external
                      payable
                      returns (uint[] memory amounts);
                  function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
                  function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
                  function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
                  function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
                  function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "../Constants.sol";
              import "../IWETH.sol";
              contract WethAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  IWETH public immutable weth;
                  constructor(IWETH _weth) public {
                      weth = _weth;
                  }
                  /**
                   * @dev Performs a swap
                   * @param recipient The original msg.sender performing the swap
                   * @param aggregator Address of the aggregator's contract
                   * @param spender Address to which tokens will be approved
                   * @param method Selector of the function to be called in the aggregator's contract
                   * @param tokenFrom Token to be swapped
                   * @param tokenTo Token to be received
                   * @param amountFrom Amount of tokenFrom to swap
                   * @param amountTo Minimum amount of tokenTo to receive
                   * @param data Data used for the call made to the aggregator's contract
                   */
                  function swap(
                      address payable recipient,
                      address aggregator,
                      address spender,
                      bytes4 method,
                      IERC20 tokenFrom,
                      IERC20 tokenTo,
                      uint256 amountFrom,
                      uint256 amountTo,
                      bytes calldata data
                  ) external payable {
                      require(tokenFrom != tokenTo, "TOKEN_PAIR_INVALID");
                      if (address(tokenFrom) == Constants.ETH) {
                          // If tokenFrom is ETH, msg.value = fee + amountFrom (total fee could be 0)
                          // Can't deal with ETH, convert to WETH, the remaining balance will be the fee
                          weth.deposit{value: amountFrom}();
                          _approveSpender(weth, spender, amountFrom);
                      } else {
                          // Otherwise capture tokens from sender
                          _approveSpender(tokenFrom, spender, amountFrom);
                      }
                      // Perform the swap
                      aggregator.functionCallWithValue(
                          abi.encodePacked(method, data),
                          address(this).balance
                      );
                      // Transfer remaining balance of tokenFrom to sender
                      if (address(tokenFrom) != Constants.ETH) {
                          _transfer(tokenFrom, tokenFrom.balanceOf(address(this)), recipient);
                      } else {
                          // If using ETH, just unwrap any remaining WETH
                          // At the end of this function all ETH will be transferred to the sender
                          _unwrapWETH();
                      }
                      uint256 weiBalance = address(this).balance;
                      // Transfer remaining balance of tokenTo to sender
                      if (address(tokenTo) != Constants.ETH) {
                          uint256 balance = tokenTo.balanceOf(address(this));
                          require(balance >= amountTo, "INSUFFICIENT_AMOUNT");
                          _transfer(tokenTo, balance, recipient);
                      } else {
                          // If tokenTo == ETH, unwrap received WETH and add it to the wei balance,
                          // then check that the remaining ETH balance >= amountTo
                          // It is safe to not use safeMath as no one can have enough Ether to overflow
                          weiBalance += _unwrapWETH();
                          require(weiBalance >= amountTo, "INSUFFICIENT_AMOUNT");
                      }
                      // If there are unused fees or if tokenTo is ETH, transfer to sender
                      if (weiBalance > 0) {
                          recipient.sendValue(weiBalance);
                      }
                  }
                  /**
                   * @dev Unwraps all available WETH into ETH
                   */
                  function _unwrapWETH() internal returns (uint256) {
                      uint256 balance = weth.balanceOf(address(this));
                      weth.withdraw(balance);
                      return balance;
                  }
                  /**
                   * @dev Transfers token to sender if amount > 0
                   * @param token IERC20 token to transfer to sender
                   * @param amount Amount of token to transfer
                   * @param recipient Address that will receive the tokens
                   */
                  function _transfer(
                      IERC20 token,
                      uint256 amount,
                      address recipient
                  ) internal {
                      if (amount > 0) {
                          token.safeTransfer(recipient, amount);
                      }
                  }
                  // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol
                  /**
                   * @dev Approves max amount of token to the spender if the allowance is lower than amount
                   * @param token The ERC20 token to approve
                   * @param spender Address to which funds will be approved
                   * @param amount Amount used to compare current allowance
                   */
                  function _approveSpender(
                      IERC20 token,
                      address spender,
                      uint256 amount
                  ) internal {
                      // If allowance is not enough, approve max possible amount
                      uint256 allowance = token.allowance(address(this), spender);
                      if (allowance < amount) {
                          bytes memory returndata = address(token).functionCall(
                              abi.encodeWithSelector(
                                  token.approve.selector,
                                  spender,
                                  type(uint256).max
                              )
                          );
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "APPROVAL_FAILED");
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              interface ICHI is IERC20 {
                  function freeUpTo(uint256 value) external returns (uint256);
                  function freeFromUpTo(
                      address from,
                      uint256 value
                  ) external returns (uint256);
                  function mint(uint256 value) external;
              }
                
              // SPDX-License-Identifier: UNLICENSED
              pragma solidity ^0.6.0;
              // We import the contract so truffle compiles it, and we have the ABI
              // available when working from truffle console.
              import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; //helpers// SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "../../GSN/Context.sol";
              import "./IERC20.sol";
              import "../../math/SafeMath.sol";
              import "../../utils/Address.sol";
              /**
               * @dev Implementation of the {IERC20} interface.
               *
               * This implementation is agnostic to the way tokens are created. This means
               * that a supply mechanism has to be added in a derived contract using {_mint}.
               * For a generic mechanism see {ERC20PresetMinterPauser}.
               *
               * TIP: For a detailed writeup see our guide
               * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
               * to implement supply mechanisms].
               *
               * We have followed general OpenZeppelin guidelines: functions revert instead
               * of returning `false` on failure. This behavior is nonetheless conventional
               * and does not conflict with the expectations of ERC20 applications.
               *
               * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
               * This allows applications to reconstruct the allowance for all accounts just
               * by listening to said events. Other implementations of the EIP may not emit
               * these events, as it isn't required by the specification.
               *
               * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
               * functions have been added to mitigate the well-known issues around setting
               * allowances. See {IERC20-approve}.
               */
              contract ERC20 is Context, IERC20 {
                  using SafeMath for uint256;
                  using Address for address;
                  mapping (address => uint256) private _balances;
                  mapping (address => mapping (address => uint256)) private _allowances;
                  uint256 private _totalSupply;
                  string private _name;
                  string private _symbol;
                  uint8 private _decimals;
                  /**
                   * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                   * a default value of 18.
                   *
                   * To select a different value for {decimals}, use {_setupDecimals}.
                   *
                   * All three of these values are immutable: they can only be set once during
                   * construction.
                   */
                  constructor (string memory name, string memory symbol) public {
                      _name = name;
                      _symbol = symbol;
                      _decimals = 18;
                  }
                  /**
                   * @dev Returns the name of the token.
                   */
                  function name() public view returns (string memory) {
                      return _name;
                  }
                  /**
                   * @dev Returns the symbol of the token, usually a shorter version of the
                   * name.
                   */
                  function symbol() public view returns (string memory) {
                      return _symbol;
                  }
                  /**
                   * @dev Returns the number of decimals used to get its user representation.
                   * For example, if `decimals` equals `2`, a balance of `505` tokens should
                   * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                   *
                   * Tokens usually opt for a value of 18, imitating the relationship between
                   * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                   * called.
                   *
                   * NOTE: This information is only used for _display_ purposes: it in
                   * no way affects any of the arithmetic of the contract, including
                   * {IERC20-balanceOf} and {IERC20-transfer}.
                   */
                  function decimals() public view returns (uint8) {
                      return _decimals;
                  }
                  /**
                   * @dev See {IERC20-totalSupply}.
                   */
                  function totalSupply() public view override returns (uint256) {
                      return _totalSupply;
                  }
                  /**
                   * @dev See {IERC20-balanceOf}.
                   */
                  function balanceOf(address account) public view override returns (uint256) {
                      return _balances[account];
                  }
                  /**
                   * @dev See {IERC20-transfer}.
                   *
                   * Requirements:
                   *
                   * - `recipient` cannot be the zero address.
                   * - the caller must have a balance of at least `amount`.
                   */
                  function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                      _transfer(_msgSender(), recipient, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-allowance}.
                   */
                  function allowance(address owner, address spender) public view virtual override returns (uint256) {
                      return _allowances[owner][spender];
                  }
                  /**
                   * @dev See {IERC20-approve}.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function approve(address spender, uint256 amount) public virtual override returns (bool) {
                      _approve(_msgSender(), spender, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-transferFrom}.
                   *
                   * Emits an {Approval} event indicating the updated allowance. This is not
                   * required by the EIP. See the note at the beginning of {ERC20};
                   *
                   * Requirements:
                   * - `sender` and `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   * - the caller must have allowance for ``sender``'s tokens of at least
                   * `amount`.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
                      _transfer(sender, recipient, amount);
                      _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                      return true;
                  }
                  /**
                   * @dev Atomically increases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                      return true;
                  }
                  /**
                   * @dev Atomically decreases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `spender` must have allowance for the caller of at least
                   * `subtractedValue`.
                   */
                  function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                      return true;
                  }
                  /**
                   * @dev Moves tokens `amount` from `sender` to `recipient`.
                   *
                   * This is internal function is equivalent to {transfer}, and can be used to
                   * e.g. implement automatic token fees, slashing mechanisms, etc.
                   *
                   * Emits a {Transfer} event.
                   *
                   * Requirements:
                   *
                   * - `sender` cannot be the zero address.
                   * - `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   */
                  function _transfer(address sender, address recipient, uint256 amount) internal virtual {
                      require(sender != address(0), "ERC20: transfer from the zero address");
                      require(recipient != address(0), "ERC20: transfer to the zero address");
                      _beforeTokenTransfer(sender, recipient, amount);
                      _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                      _balances[recipient] = _balances[recipient].add(amount);
                      emit Transfer(sender, recipient, amount);
                  }
                  /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                   * the total supply.
                   *
                   * Emits a {Transfer} event with `from` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `to` cannot be the zero address.
                   */
                  function _mint(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: mint to the zero address");
                      _beforeTokenTransfer(address(0), account, amount);
                      _totalSupply = _totalSupply.add(amount);
                      _balances[account] = _balances[account].add(amount);
                      emit Transfer(address(0), account, amount);
                  }
                  /**
                   * @dev Destroys `amount` tokens from `account`, reducing the
                   * total supply.
                   *
                   * Emits a {Transfer} event with `to` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `account` cannot be the zero address.
                   * - `account` must have at least `amount` tokens.
                   */
                  function _burn(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: burn from the zero address");
                      _beforeTokenTransfer(account, address(0), amount);
                      _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                      _totalSupply = _totalSupply.sub(amount);
                      emit Transfer(account, address(0), amount);
                  }
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
                   *
                   * This internal function is equivalent to `approve`, and can be used to
                   * e.g. set automatic allowances for certain subsystems, etc.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `owner` cannot be the zero address.
                   * - `spender` cannot be the zero address.
                   */
                  function _approve(address owner, address spender, uint256 amount) internal virtual {
                      require(owner != address(0), "ERC20: approve from the zero address");
                      require(spender != address(0), "ERC20: approve to the zero address");
                      _allowances[owner][spender] = amount;
                      emit Approval(owner, spender, amount);
                  }
                  /**
                   * @dev Sets {decimals} to a value other than the default one of 18.
                   *
                   * WARNING: This function should only be called from the constructor. Most
                   * applications that interact with token contracts will not expect
                   * {decimals} to ever change, and may work incorrectly if it does.
                   */
                  function _setupDecimals(uint8 decimals_) internal {
                      _decimals = decimals_;
                  }
                  /**
                   * @dev Hook that is called before any transfer of tokens. This includes
                   * minting and burning.
                   *
                   * Calling conditions:
                   *
                   * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                   * will be to transferred to `to`.
                   * - when `from` is zero, `amount` tokens will be minted for `to`.
                   * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                   * - `from` and `to` are never both zero.
                   *
                   * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                   */
                  function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              /*
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with GSN meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal view virtual returns (address payable) {
                      return msg.sender;
                  }
                  function _msgData() internal view virtual returns (bytes memory) {
                      this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                      return msg.data;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/access/Ownable.sol";
              import "@openzeppelin/contracts/utils/Pausable.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "./ICHI.sol";
              import "./Spender.sol";
              /**
               * @title MetaSwap
               */
              contract MetaSwap is Ownable, Pausable, ReentrancyGuard {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  struct Adapter {
                      address addr; // adapter's address
                      bytes4 selector;
                      bytes data; // adapter's fixed data
                  }
                  ICHI public immutable chi;
                  Spender public immutable spender;
                  // Mapping of aggregatorId to aggregator
                  mapping(string => Adapter) public adapters;
                  mapping(string => bool) public adapterRemoved;
                  event AdapterSet(
                      string indexed aggregatorId,
                      address indexed addr,
                      bytes4 selector,
                      bytes data
                  );
                  event AdapterRemoved(string indexed aggregatorId);
                  event Swap(string indexed aggregatorId, address indexed sender);
                  constructor(ICHI _chi) public {
                      chi = _chi;
                      spender = new Spender();
                  }
                  /**
                   * @dev Sets the adapter for an aggregator. It can't be changed later.
                   * @param aggregatorId Aggregator's identifier
                   * @param addr Address of the contract that contains the logic for this aggregator
                   * @param selector The function selector of the swap function in the adapter
                   * @param data Fixed abi encoded data the will be passed in each delegatecall made to the adapter
                   */
                  function setAdapter(
                      string calldata aggregatorId,
                      address addr,
                      bytes4 selector,
                      bytes calldata data
                  ) external onlyOwner {
                      require(addr.isContract(), "ADAPTER_IS_NOT_A_CONTRACT");
                      require(!adapterRemoved[aggregatorId], "ADAPTER_REMOVED");
                      Adapter storage adapter = adapters[aggregatorId];
                      require(adapter.addr == address(0), "ADAPTER_EXISTS");
                      adapter.addr = addr;
                      adapter.selector = selector;
                      adapter.data = data;
                      emit AdapterSet(aggregatorId, addr, selector, data);
                  }
                  /**
                   * @dev Removes the adapter for an existing aggregator. This can't be undone.
                   * @param aggregatorId Aggregator's identifier
                   */
                  function removeAdapter(string calldata aggregatorId) external onlyOwner {
                      require(
                          adapters[aggregatorId].addr != address(0),
                          "ADAPTER_DOES_NOT_EXIST"
                      );
                      delete adapters[aggregatorId];
                      adapterRemoved[aggregatorId] = true;
                      emit AdapterRemoved(aggregatorId);
                  }
                  /**
                   * @dev Performs a swap
                   * @param aggregatorId Identifier of the aggregator to be used for the swap
                   * @param data Dynamic data which is concatenated with the fixed aggregator's
                   * data in the delecatecall made to the adapter
                   */
                  function swap(
                      string calldata aggregatorId,
                      IERC20 tokenFrom,
                      uint256 amount,
                      bytes calldata data
                  ) external payable whenNotPaused nonReentrant {
                      _swap(aggregatorId, tokenFrom, amount, data);
                  }
                  /**
                   * @dev Performs a swap
                   * @param aggregatorId Identifier of the aggregator to be used for the swap
                   * @param data Dynamic data which is concatenated with the fixed aggregator's
                   * data in the delecatecall made to the adapter
                   */
                  function swapUsingGasToken(
                      string calldata aggregatorId,
                      IERC20 tokenFrom,
                      uint256 amount,
                      bytes calldata data
                  ) external payable whenNotPaused nonReentrant {
                      uint256 gas = gasleft();
                      _swap(aggregatorId, tokenFrom, amount, data);
                      uint256 gasSpent = 21000 + gas - gasleft() + 16 * msg.data.length;
                      chi.freeFromUpTo(msg.sender, (gasSpent + 14154) / 41947);
                  }
                  function pauseSwaps() external onlyOwner {
                      _pause();
                  }
                  function unpauseSwaps() external onlyOwner {
                      _unpause();
                  }
                  function _swap(
                      string calldata aggregatorId,
                      IERC20 tokenFrom,
                      uint256 amount,
                      bytes calldata data
                  ) internal {
                      Adapter storage adapter = adapters[aggregatorId];
                      if (address(tokenFrom) != Constants.ETH) {
                          tokenFrom.safeTransferFrom(msg.sender, address(spender), amount);
                      }
                      spender.swap{value: msg.value}(
                          adapter.addr,
                          abi.encodePacked(
                              adapter.selector,
                              abi.encode(msg.sender),
                              adapter.data,
                              data
                          )
                      );
                      emit Swap(aggregatorId, msg.sender);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "../GSN/Context.sol";
              /**
               * @dev Contract module which provides a basic access control mechanism, where
               * there is an account (an owner) that can be granted exclusive access to
               * specific functions.
               *
               * By default, the owner account will be the one that deploys the contract. This
               * can later be changed with {transferOwnership}.
               *
               * This module is used through inheritance. It will make available the modifier
               * `onlyOwner`, which can be applied to your functions to restrict their use to
               * the owner.
               */
              contract Ownable is Context {
                  address private _owner;
                  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  /**
                   * @dev Initializes the contract setting the deployer as the initial owner.
                   */
                  constructor () internal {
                      address msgSender = _msgSender();
                      _owner = msgSender;
                      emit OwnershipTransferred(address(0), msgSender);
                  }
                  /**
                   * @dev Returns the address of the current owner.
                   */
                  function owner() public view returns (address) {
                      return _owner;
                  }
                  /**
                   * @dev Throws if called by any account other than the owner.
                   */
                  modifier onlyOwner() {
                      require(_owner == _msgSender(), "Ownable: caller is not the owner");
                      _;
                  }
                  /**
                   * @dev Leaves the contract without owner. It will not be possible to call
                   * `onlyOwner` functions anymore. Can only be called by the current owner.
                   *
                   * NOTE: Renouncing ownership will leave the contract without an owner,
                   * thereby removing any functionality that is only available to the owner.
                   */
                  function renounceOwnership() public virtual onlyOwner {
                      emit OwnershipTransferred(_owner, address(0));
                      _owner = address(0);
                  }
                  /**
                   * @dev Transfers ownership of the contract to a new account (`newOwner`).
                   * Can only be called by the current owner.
                   */
                  function transferOwnership(address newOwner) public virtual onlyOwner {
                      require(newOwner != address(0), "Ownable: new owner is the zero address");
                      emit OwnershipTransferred(_owner, newOwner);
                      _owner = newOwner;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "../GSN/Context.sol";
              /**
               * @dev Contract module which allows children to implement an emergency stop
               * mechanism that can be triggered by an authorized account.
               *
               * This module is used through inheritance. It will make available the
               * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
               * the functions of your contract. Note that they will not be pausable by
               * simply including this module, only once the modifiers are put in place.
               */
              contract Pausable is Context {
                  /**
                   * @dev Emitted when the pause is triggered by `account`.
                   */
                  event Paused(address account);
                  /**
                   * @dev Emitted when the pause is lifted by `account`.
                   */
                  event Unpaused(address account);
                  bool private _paused;
                  /**
                   * @dev Initializes the contract in unpaused state.
                   */
                  constructor () internal {
                      _paused = false;
                  }
                  /**
                   * @dev Returns true if the contract is paused, and false otherwise.
                   */
                  function paused() public view returns (bool) {
                      return _paused;
                  }
                  /**
                   * @dev Modifier to make a function callable only when the contract is not paused.
                   *
                   * Requirements:
                   *
                   * - The contract must not be paused.
                   */
                  modifier whenNotPaused() {
                      require(!_paused, "Pausable: paused");
                      _;
                  }
                  /**
                   * @dev Modifier to make a function callable only when the contract is paused.
                   *
                   * Requirements:
                   *
                   * - The contract must be paused.
                   */
                  modifier whenPaused() {
                      require(_paused, "Pausable: not paused");
                      _;
                  }
                  /**
                   * @dev Triggers stopped state.
                   *
                   * Requirements:
                   *
                   * - The contract must not be paused.
                   */
                  function _pause() internal virtual whenNotPaused {
                      _paused = true;
                      emit Paused(_msgSender());
                  }
                  /**
                   * @dev Returns to normal state.
                   *
                   * Requirements:
                   *
                   * - The contract must be paused.
                   */
                  function _unpause() internal virtual whenPaused {
                      _paused = false;
                      emit Unpaused(_msgSender());
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              contract ReentrancyGuard {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  constructor () internal {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and make it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      // On the first call to nonReentrant, _notEntered will be true
                      require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                      _;
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "./Constants.sol";
              contract Spender {
                  address public immutable metaswap;
                  constructor() public {
                      metaswap = msg.sender;
                  }
                  /// @dev Receives ether from swaps
                  fallback() external payable {}
                  function swap(address adapter, bytes calldata data) external payable {
                      require(msg.sender == metaswap, "FORBIDDEN");
                      require(adapter != address(0), "ADAPTER_NOT_PROVIDED");
                      _delegate(adapter, data, "ADAPTER_DELEGATECALL_FAILED");
                  }
                  /**
                   * @dev Performs a delegatecall and bubbles up the errors, adapted from
                   * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
                   * @param target Address of the contract to delegatecall
                   * @param data Data passed in the delegatecall
                   * @param errorMessage Fallback revert reason
                   */
                  function _delegate(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) private returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              // solhint-disable-next-line no-inline-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
              }
              pragma solidity ^0.6.0;
              import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
              import "@openzeppelin/contracts/utils/Address.sol";
              contract MockAdapter {
                  using SafeERC20 for IERC20;
                  using Address for address;
                  using Address for address payable;
                  event MockAdapterEvent(
                      address sender,
                      uint256 valueFixed,
                      uint256 valueDynamic
                  );
                  function test(
                      address sender,
                      uint256 valueFixed,
                      uint256 valueDynamic
                  ) external payable {
                      emit MockAdapterEvent(sender, valueFixed, valueDynamic);
                  }
                  function testRevert(
                      address,
                      uint256,
                      uint256
                  ) external payable {
                      revert("SWAP_FAILED");
                  }
                  function testRevertNoReturnData(
                      address,
                      uint256,
                      uint256
                  ) external payable {
                      revert();
                  }
              }
              pragma solidity ^0.6.0;
              // TAKEN FROM https://github.com/gnosis/mock-contract
              // TODO: use their npm package once it is published for solidity 0.6
              interface MockInterface {
                  /**
                   * @dev After calling this method, the mock will return `response` when it is called
                   * with any calldata that is not mocked more specifically below
                   * (e.g. using givenMethodReturn).
                   * @param response ABI encoded response that will be returned if method is invoked
                   */
                  function givenAnyReturn(bytes calldata response) external;
                  function givenAnyReturnBool(bool response) external;
                  function givenAnyReturnUint(uint256 response) external;
                  function givenAnyReturnAddress(address response) external;
                  function givenAnyRevert() external;
                  function givenAnyRevertWithMessage(string calldata message) external;
                  function givenAnyRunOutOfGas() external;
                  /**
                   * @dev After calling this method, the mock will return `response` when the given
                   * methodId is called regardless of arguments. If the methodId and arguments
                   * are mocked more specifically (using `givenMethodAndArguments`) the latter
                   * will take precedence.
                   * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it
                   * @param response ABI encoded response that will be returned if method is invoked
                   */
                  function givenMethodReturn(bytes calldata method, bytes calldata response)
                      external;
                  function givenMethodReturnBool(bytes calldata method, bool response)
                      external;
                  function givenMethodReturnUint(bytes calldata method, uint256 response)
                      external;
                  function givenMethodReturnAddress(bytes calldata method, address response)
                      external;
                  function givenMethodRevert(bytes calldata method) external;
                  function givenMethodRevertWithMessage(
                      bytes calldata method,
                      string calldata message
                  ) external;
                  function givenMethodRunOutOfGas(bytes calldata method) external;
                  /**
                   * @dev After calling this method, the mock will return `response` when the given
                   * methodId is called with matching arguments. These exact calldataMocks will take
                   * precedence over all other calldataMocks.
                   * @param call ABI encoded calldata (methodId and arguments)
                   * @param response ABI encoded response that will be returned if contract is invoked with calldata
                   */
                  function givenCalldataReturn(bytes calldata call, bytes calldata response)
                      external;
                  function givenCalldataReturnBool(bytes calldata call, bool response)
                      external;
                  function givenCalldataReturnUint(bytes calldata call, uint256 response)
                      external;
                  function givenCalldataReturnAddress(bytes calldata call, address response)
                      external;
                  function givenCalldataRevert(bytes calldata call) external;
                  function givenCalldataRevertWithMessage(
                      bytes calldata call,
                      string calldata message
                  ) external;
                  function givenCalldataRunOutOfGas(bytes calldata call) external;
                  /**
                   * @dev Returns the number of times anything has been called on this mock since last reset
                   */
                  function invocationCount() external returns (uint256);
                  /**
                   * @dev Returns the number of times the given method has been called on this mock since last reset
                   * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it
                   */
                  function invocationCountForMethod(bytes calldata method)
                      external
                      returns (uint256);
                  /**
                   * @dev Returns the number of times this mock has been called with the exact calldata since last reset.
                   * @param call ABI encoded calldata (methodId and arguments)
                   */
                  function invocationCountForCalldata(bytes calldata call)
                      external
                      returns (uint256);
                  /**
                   * @dev Resets all mocked methods and invocation counts.
                   */
                  function reset() external;
              }
              /**
               * Implementation of the MockInterface.
               */
              contract MockContract is MockInterface {
                  enum MockType {Return, Revert, OutOfGas}
                  bytes32 public constant MOCKS_LIST_START = hex"01";
                  bytes public constant MOCKS_LIST_END = "0xff";
                  bytes32 public constant MOCKS_LIST_END_HASH = keccak256(MOCKS_LIST_END);
                  bytes4 public constant SENTINEL_ANY_MOCKS = hex"01";
                  bytes public constant DEFAULT_FALLBACK_VALUE = abi.encode(false);
                  // A linked list allows easy iteration and inclusion checks
                  mapping(bytes32 => bytes) calldataMocks;
                  mapping(bytes => MockType) calldataMockTypes;
                  mapping(bytes => bytes) calldataExpectations;
                  mapping(bytes => string) calldataRevertMessage;
                  mapping(bytes32 => uint256) calldataInvocations;
                  mapping(bytes4 => bytes4) methodIdMocks;
                  mapping(bytes4 => MockType) methodIdMockTypes;
                  mapping(bytes4 => bytes) methodIdExpectations;
                  mapping(bytes4 => string) methodIdRevertMessages;
                  mapping(bytes32 => uint256) methodIdInvocations;
                  MockType fallbackMockType;
                  bytes fallbackExpectation = DEFAULT_FALLBACK_VALUE;
                  string fallbackRevertMessage;
                  uint256 invocations;
                  uint256 resetCount;
                  constructor() public {
                      calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END;
                      methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS;
                  }
                  function trackCalldataMock(bytes memory call) private {
                      bytes32 callHash = keccak256(call);
                      if (calldataMocks[callHash].length == 0) {
                          calldataMocks[callHash] = calldataMocks[MOCKS_LIST_START];
                          calldataMocks[MOCKS_LIST_START] = call;
                      }
                  }
                  function trackMethodIdMock(bytes4 methodId) private {
                      if (methodIdMocks[methodId] == 0x0) {
                          methodIdMocks[methodId] = methodIdMocks[SENTINEL_ANY_MOCKS];
                          methodIdMocks[SENTINEL_ANY_MOCKS] = methodId;
                      }
                  }
                  function _givenAnyReturn(bytes memory response) internal {
                      fallbackMockType = MockType.Return;
                      fallbackExpectation = response;
                  }
                  function givenAnyReturn(bytes calldata response) external override {
                      _givenAnyReturn(response);
                  }
                  function givenAnyReturnBool(bool response) external override {
                      uint256 flag = response ? 1 : 0;
                      _givenAnyReturn(uintToBytes(flag));
                  }
                  function givenAnyReturnUint(uint256 response) external override {
                      _givenAnyReturn(uintToBytes(response));
                  }
                  function givenAnyReturnAddress(address response) external override {
                      _givenAnyReturn(uintToBytes(uint256(response)));
                  }
                  function givenAnyRevert() external override {
                      fallbackMockType = MockType.Revert;
                      fallbackRevertMessage = "";
                  }
                  function givenAnyRevertWithMessage(string calldata message)
                      external
                      override
                  {
                      fallbackMockType = MockType.Revert;
                      fallbackRevertMessage = message;
                  }
                  function givenAnyRunOutOfGas() external override {
                      fallbackMockType = MockType.OutOfGas;
                  }
                  function _givenCalldataReturn(bytes memory call, bytes memory response)
                      private
                  {
                      calldataMockTypes[call] = MockType.Return;
                      calldataExpectations[call] = response;
                      trackCalldataMock(call);
                  }
                  function givenCalldataReturn(bytes calldata call, bytes calldata response)
                      external
                      override
                  {
                      _givenCalldataReturn(call, response);
                  }
                  function givenCalldataReturnBool(bytes calldata call, bool response)
                      external
                      override
                  {
                      uint256 flag = response ? 1 : 0;
                      _givenCalldataReturn(call, uintToBytes(flag));
                  }
                  function givenCalldataReturnUint(bytes calldata call, uint256 response)
                      external
                      override
                  {
                      _givenCalldataReturn(call, uintToBytes(response));
                  }
                  function givenCalldataReturnAddress(bytes calldata call, address response)
                      external
                      override
                  {
                      _givenCalldataReturn(call, uintToBytes(uint256(response)));
                  }
                  function _givenMethodReturn(bytes memory call, bytes memory response)
                      private
                  {
                      bytes4 method = bytesToBytes4(call);
                      methodIdMockTypes[method] = MockType.Return;
                      methodIdExpectations[method] = response;
                      trackMethodIdMock(method);
                  }
                  function givenMethodReturn(bytes calldata call, bytes calldata response)
                      external
                      override
                  {
                      _givenMethodReturn(call, response);
                  }
                  function givenMethodReturnBool(bytes calldata call, bool response)
                      external
                      override
                  {
                      uint256 flag = response ? 1 : 0;
                      _givenMethodReturn(call, uintToBytes(flag));
                  }
                  function givenMethodReturnUint(bytes calldata call, uint256 response)
                      external
                      override
                  {
                      _givenMethodReturn(call, uintToBytes(response));
                  }
                  function givenMethodReturnAddress(bytes calldata call, address response)
                      external
                      override
                  {
                      _givenMethodReturn(call, uintToBytes(uint256(response)));
                  }
                  function givenCalldataRevert(bytes calldata call) external override {
                      calldataMockTypes[call] = MockType.Revert;
                      calldataRevertMessage[call] = "";
                      trackCalldataMock(call);
                  }
                  function givenMethodRevert(bytes calldata call) external override {
                      bytes4 method = bytesToBytes4(call);
                      methodIdMockTypes[method] = MockType.Revert;
                      trackMethodIdMock(method);
                  }
                  function givenCalldataRevertWithMessage(
                      bytes calldata call,
                      string calldata message
                  ) external override {
                      calldataMockTypes[call] = MockType.Revert;
                      calldataRevertMessage[call] = message;
                      trackCalldataMock(call);
                  }
                  function givenMethodRevertWithMessage(
                      bytes calldata call,
                      string calldata message
                  ) external override {
                      bytes4 method = bytesToBytes4(call);
                      methodIdMockTypes[method] = MockType.Revert;
                      methodIdRevertMessages[method] = message;
                      trackMethodIdMock(method);
                  }
                  function givenCalldataRunOutOfGas(bytes calldata call) external override {
                      calldataMockTypes[call] = MockType.OutOfGas;
                      trackCalldataMock(call);
                  }
                  function givenMethodRunOutOfGas(bytes calldata call) external override {
                      bytes4 method = bytesToBytes4(call);
                      methodIdMockTypes[method] = MockType.OutOfGas;
                      trackMethodIdMock(method);
                  }
                  function invocationCount() external override returns (uint256) {
                      return invocations;
                  }
                  function invocationCountForMethod(bytes calldata call)
                      external
                      override
                      returns (uint256)
                  {
                      bytes4 method = bytesToBytes4(call);
                      return
                          methodIdInvocations[keccak256(
                              abi.encodePacked(resetCount, method)
                          )];
                  }
                  function invocationCountForCalldata(bytes calldata call)
                      external
                      override
                      returns (uint256)
                  {
                      return
                          calldataInvocations[keccak256(abi.encodePacked(resetCount, call))];
                  }
                  function reset() external override {
                      // Reset all exact calldataMocks
                      bytes memory nextMock = calldataMocks[MOCKS_LIST_START];
                      bytes32 mockHash = keccak256(nextMock);
                      // We cannot compary bytes
                      while (mockHash != MOCKS_LIST_END_HASH) {
                          // Reset all mock maps
                          calldataMockTypes[nextMock] = MockType.Return;
                          calldataExpectations[nextMock] = hex"";
                          calldataRevertMessage[nextMock] = "";
                          // Set next mock to remove
                          nextMock = calldataMocks[mockHash];
                          // Remove from linked list
                          calldataMocks[mockHash] = "";
                          // Update mock hash
                          mockHash = keccak256(nextMock);
                      }
                      // Clear list
                      calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END;
                      // Reset all any calldataMocks
                      bytes4 nextAnyMock = methodIdMocks[SENTINEL_ANY_MOCKS];
                      while (nextAnyMock != SENTINEL_ANY_MOCKS) {
                          bytes4 currentAnyMock = nextAnyMock;
                          methodIdMockTypes[currentAnyMock] = MockType.Return;
                          methodIdExpectations[currentAnyMock] = hex"";
                          methodIdRevertMessages[currentAnyMock] = "";
                          nextAnyMock = methodIdMocks[currentAnyMock];
                          // Remove from linked list
                          methodIdMocks[currentAnyMock] = 0x0;
                      }
                      // Clear list
                      methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS;
                      fallbackExpectation = DEFAULT_FALLBACK_VALUE;
                      fallbackMockType = MockType.Return;
                      invocations = 0;
                      resetCount += 1;
                  }
                  function useAllGas() private {
                      while (true) {
                          bool s;
                          assembly {
                              //expensive call to EC multiply contract
                              s := call(sub(gas(), 2000), 6, 0, 0x0, 0xc0, 0x0, 0x60)
                          }
                      }
                  }
                  function bytesToBytes4(bytes memory b) private pure returns (bytes4) {
                      bytes4 out;
                      for (uint256 i = 0; i < 4; i++) {
                          out |= bytes4(b[i] & 0xFF) >> (i * 8);
                      }
                      return out;
                  }
                  function uintToBytes(uint256 x) private pure returns (bytes memory b) {
                      b = new bytes(32);
                      assembly {
                          mstore(add(b, 32), x)
                      }
                  }
                  function updateInvocationCount(
                      bytes4 methodId,
                      bytes memory originalMsgData
                  ) public {
                      require(
                          msg.sender == address(this),
                          "Can only be called from the contract itself"
                      );
                      invocations += 1;
                      methodIdInvocations[keccak256(
                          abi.encodePacked(resetCount, methodId)
                      )] += 1;
                      calldataInvocations[keccak256(
                          abi.encodePacked(resetCount, originalMsgData)
                      )] += 1;
                  }
                  fallback() external payable {
                      bytes4 methodId;
                      assembly {
                          methodId := calldataload(0)
                      }
                      // First, check exact matching overrides
                      if (calldataMockTypes[msg.data] == MockType.Revert) {
                          revert(calldataRevertMessage[msg.data]);
                      }
                      if (calldataMockTypes[msg.data] == MockType.OutOfGas) {
                          useAllGas();
                      }
                      bytes memory result = calldataExpectations[msg.data];
                      // Then check method Id overrides
                      if (result.length == 0) {
                          if (methodIdMockTypes[methodId] == MockType.Revert) {
                              revert(methodIdRevertMessages[methodId]);
                          }
                          if (methodIdMockTypes[methodId] == MockType.OutOfGas) {
                              useAllGas();
                          }
                          result = methodIdExpectations[methodId];
                      }
                      // Last, use the fallback override
                      if (result.length == 0) {
                          if (fallbackMockType == MockType.Revert) {
                              revert(fallbackRevertMessage);
                          }
                          if (fallbackMockType == MockType.OutOfGas) {
                              useAllGas();
                          }
                          result = fallbackExpectation;
                      }
                      // Record invocation as separate call so we don't rollback in case we are called with STATICCALL
                      (, bytes memory r) = address(this).call{gas: 100000}(
                          abi.encodeWithSignature(
                              "updateInvocationCount(bytes4,bytes)",
                              methodId,
                              msg.data
                          )
                      );
                      assert(r.length == 0);
                      assembly {
                          return(add(0x20, result), mload(result))
                      }
                  }
              }
              pragma solidity ^0.6.0;
              contract MockSelfDestruct {
                  constructor() public payable {}
                  fallback() external payable {
                      selfdestruct(msg.sender);
                  }
                  function kill(address payable target) external payable {
                      selfdestruct(target);
                  }
              }
              

              File 2 of 6: Vyper_contract
              # @title Uniswap Exchange Interface V1
              # @notice Source code found at https://github.com/uniswap
              # @notice Use at your own risk
              
              contract Factory():
                  def getExchange(token_addr: address) -> address: constant
              
              contract Exchange():
                  def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei): constant
                  def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256: modifying
                  def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei): modifying
              
              TokenPurchase: event({buyer: indexed(address), eth_sold: indexed(uint256(wei)), tokens_bought: indexed(uint256)})
              EthPurchase: event({buyer: indexed(address), tokens_sold: indexed(uint256), eth_bought: indexed(uint256(wei))})
              AddLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)})
              RemoveLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)})
              Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256})
              Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256})
              
              name: public(bytes32)                             # Uniswap V1
              symbol: public(bytes32)                           # UNI-V1
              decimals: public(uint256)                         # 18
              totalSupply: public(uint256)                      # total number of UNI in existence
              balances: uint256[address]                        # UNI balance of an address
              allowances: (uint256[address])[address]           # UNI allowance of one address on another
              token: address(ERC20)                             # address of the ERC20 token traded on this contract
              factory: Factory                                  # interface for the factory that created this contract
              
              # @dev This function acts as a contract constructor which is not currently supported in contracts deployed
              #      using create_with_code_of(). It is called once by the factory during contract creation.
              @public
              def setup(token_addr: address):
                  assert (self.factory == ZERO_ADDRESS and self.token == ZERO_ADDRESS) and token_addr != ZERO_ADDRESS
                  self.factory = msg.sender
                  self.token = token_addr
                  self.name = 0x556e697377617020563100000000000000000000000000000000000000000000
                  self.symbol = 0x554e492d56310000000000000000000000000000000000000000000000000000
                  self.decimals = 18
              
              # @notice Deposit ETH and Tokens (self.token) at current ratio to mint UNI tokens.
              # @dev min_liquidity does nothing when total UNI supply is 0.
              # @param min_liquidity Minimum number of UNI sender will mint if total UNI supply is greater than 0.
              # @param max_tokens Maximum number of tokens deposited. Deposits max amount if total UNI supply is 0.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return The amount of UNI minted.
              @public
              @payable
              def addLiquidity(min_liquidity: uint256, max_tokens: uint256, deadline: timestamp) -> uint256:
                  assert deadline > block.timestamp and (max_tokens > 0 and msg.value > 0)
                  total_liquidity: uint256 = self.totalSupply
                  if total_liquidity > 0:
                      assert min_liquidity > 0
                      eth_reserve: uint256(wei) = self.balance - msg.value
                      token_reserve: uint256 = self.token.balanceOf(self)
                      token_amount: uint256 = msg.value * token_reserve / eth_reserve + 1
                      liquidity_minted: uint256 = msg.value * total_liquidity / eth_reserve
                      assert max_tokens >= token_amount and liquidity_minted >= min_liquidity
                      self.balances[msg.sender] += liquidity_minted
                      self.totalSupply = total_liquidity + liquidity_minted
                      assert self.token.transferFrom(msg.sender, self, token_amount)
                      log.AddLiquidity(msg.sender, msg.value, token_amount)
                      log.Transfer(ZERO_ADDRESS, msg.sender, liquidity_minted)
                      return liquidity_minted
                  else:
                      assert (self.factory != ZERO_ADDRESS and self.token != ZERO_ADDRESS) and msg.value >= 1000000000
                      assert self.factory.getExchange(self.token) == self
                      token_amount: uint256 = max_tokens
                      initial_liquidity: uint256 = as_unitless_number(self.balance)
                      self.totalSupply = initial_liquidity
                      self.balances[msg.sender] = initial_liquidity
                      assert self.token.transferFrom(msg.sender, self, token_amount)
                      log.AddLiquidity(msg.sender, msg.value, token_amount)
                      log.Transfer(ZERO_ADDRESS, msg.sender, initial_liquidity)
                      return initial_liquidity
              
              # @dev Burn UNI tokens to withdraw ETH and Tokens at current ratio.
              # @param amount Amount of UNI burned.
              # @param min_eth Minimum ETH withdrawn.
              # @param min_tokens Minimum Tokens withdrawn.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return The amount of ETH and Tokens withdrawn.
              @public
              def removeLiquidity(amount: uint256, min_eth: uint256(wei), min_tokens: uint256, deadline: timestamp) -> (uint256(wei), uint256):
                  assert (amount > 0 and deadline > block.timestamp) and (min_eth > 0 and min_tokens > 0)
                  total_liquidity: uint256 = self.totalSupply
                  assert total_liquidity > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_amount: uint256(wei) = amount * self.balance / total_liquidity
                  token_amount: uint256 = amount * token_reserve / total_liquidity
                  assert eth_amount >= min_eth and token_amount >= min_tokens
                  self.balances[msg.sender] -= amount
                  self.totalSupply = total_liquidity - amount
                  send(msg.sender, eth_amount)
                  assert self.token.transfer(msg.sender, token_amount)
                  log.RemoveLiquidity(msg.sender, eth_amount, token_amount)
                  log.Transfer(msg.sender, ZERO_ADDRESS, amount)
                  return eth_amount, token_amount
              
              # @dev Pricing function for converting between ETH and Tokens.
              # @param input_amount Amount of ETH or Tokens being sold.
              # @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
              # @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
              # @return Amount of ETH or Tokens bought.
              @private
              @constant
              def getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
                  assert input_reserve > 0 and output_reserve > 0
                  input_amount_with_fee: uint256 = input_amount * 997
                  numerator: uint256 = input_amount_with_fee * output_reserve
                  denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee
                  return numerator / denominator
              
              # @dev Pricing function for converting between ETH and Tokens.
              # @param output_amount Amount of ETH or Tokens being bought.
              # @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
              # @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
              # @return Amount of ETH or Tokens sold.
              @private
              @constant
              def getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
                  assert input_reserve > 0 and output_reserve > 0
                  numerator: uint256 = input_reserve * output_amount * 1000
                  denominator: uint256 = (output_reserve - output_amount) * 997
                  return numerator / denominator + 1
              
              @private
              def ethToTokenInput(eth_sold: uint256(wei), min_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
                  assert deadline >= block.timestamp and (eth_sold > 0 and min_tokens > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_bought: uint256 = self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance - eth_sold), token_reserve)
                  assert tokens_bought >= min_tokens
                  assert self.token.transfer(recipient, tokens_bought)
                  log.TokenPurchase(buyer, eth_sold, tokens_bought)
                  return tokens_bought
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies exact input (msg.value).
              # @dev User cannot specify minimum output or deadline.
              @public
              @payable
              def __default__():
                  self.ethToTokenInput(msg.value, 1, block.timestamp, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies exact input (msg.value) and minimum output.
              # @param min_tokens Minimum Tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of Tokens bought.
              @public
              @payable
              def ethToTokenSwapInput(min_tokens: uint256, deadline: timestamp) -> uint256:
                  return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens and transfers Tokens to recipient.
              # @dev User specifies exact input (msg.value) and minimum output
              # @param min_tokens Minimum Tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output Tokens.
              # @return Amount of Tokens bought.
              @public
              @payable
              def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, recipient)
              
              @private
              def ethToTokenOutput(tokens_bought: uint256, max_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
                  assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance - max_eth), token_reserve)
                  # Throws if eth_sold > max_eth
                  eth_refund: uint256(wei) = max_eth - as_wei_value(eth_sold, 'wei')
                  if eth_refund > 0:
                      send(buyer, eth_refund)
                  assert self.token.transfer(recipient, tokens_bought)
                  log.TokenPurchase(buyer, as_wei_value(eth_sold, 'wei'), tokens_bought)
                  return as_wei_value(eth_sold, 'wei')
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies maximum input (msg.value) and exact output.
              # @param tokens_bought Amount of tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of ETH sold.
              @public
              @payable
              def ethToTokenSwapOutput(tokens_bought: uint256, deadline: timestamp) -> uint256(wei):
                  return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens and transfers Tokens to recipient.
              # @dev User specifies maximum input (msg.value) and exact output.
              # @param tokens_bought Amount of tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output Tokens.
              # @return Amount of ETH sold.
              @public
              @payable
              def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei):
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, recipient)
              
              @private
              def tokenToEthInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
                  assert deadline >= block.timestamp and (tokens_sold > 0 and min_eth > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
                  assert wei_bought >= min_eth
                  send(recipient, wei_bought)
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  log.EthPurchase(buyer, tokens_sold, wei_bought)
                  return wei_bought
              
              
              # @notice Convert Tokens to ETH.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_eth Minimum ETH purchased.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of ETH bought.
              @public
              def tokenToEthSwapInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp) -> uint256(wei):
                  return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, msg.sender)
              
              # @notice Convert Tokens to ETH and transfers ETH to recipient.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_eth Minimum ETH purchased.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @return Amount of ETH bought.
              @public
              def tokenToEthTransferInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, recipient: address) -> uint256(wei):
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, recipient)
              
              @private
              def tokenToEthOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
                  assert deadline >= block.timestamp and eth_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
                  # tokens sold is always > 0
                  assert max_tokens >= tokens_sold
                  send(recipient, eth_bought)
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  log.EthPurchase(buyer, tokens_sold, eth_bought)
                  return tokens_sold
              
              # @notice Convert Tokens to ETH.
              # @dev User specifies maximum input and exact output.
              # @param eth_bought Amount of ETH purchased.
              # @param max_tokens Maximum Tokens sold.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of Tokens sold.
              @public
              def tokenToEthSwapOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp) -> uint256:
                  return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, msg.sender)
              
              # @notice Convert Tokens to ETH and transfers ETH to recipient.
              # @dev User specifies maximum input and exact output.
              # @param eth_bought Amount of ETH purchased.
              # @param max_tokens Maximum Tokens sold.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @return Amount of Tokens sold.
              @public
              def tokenToEthTransferOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, recipient)
              
              @private
              def tokenToTokenInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
                  assert (deadline >= block.timestamp and tokens_sold > 0) and (min_tokens_bought > 0 and min_eth_bought > 0)
                  assert exchange_addr != self and exchange_addr != ZERO_ADDRESS
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
                  assert wei_bought >= min_eth_bought
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  tokens_bought: uint256 = Exchange(exchange_addr).ethToTokenTransferInput(min_tokens_bought, deadline, recipient, value=wei_bought)
                  log.EthPurchase(buyer, tokens_sold, wei_bought)
                  return tokens_bought
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr).
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (token_addr) bought.
              @public
              def tokenToTokenSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr) and transfers
              #         Tokens (token_addr) to recipient.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (token_addr) bought.
              @public
              def tokenToTokenTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)
              
              @private
              def tokenToTokenOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
                  assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth_sold > 0)
                  assert exchange_addr != self and exchange_addr != ZERO_ADDRESS
                  eth_bought: uint256(wei) = Exchange(exchange_addr).getEthToTokenOutputPrice(tokens_bought)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
                  # tokens sold is always > 0
                  assert max_tokens_sold >= tokens_sold and max_eth_sold >= eth_bought
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  eth_sold: uint256(wei) = Exchange(exchange_addr).ethToTokenTransferOutput(tokens_bought, deadline, recipient, value=eth_bought)
                  log.EthPurchase(buyer, tokens_sold, eth_bought)
                  return tokens_sold
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr).
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToTokenSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr) and transfers
              #         Tokens (token_addr) to recipient.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToTokenTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token).
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (exchange_addr.token) bought.
              @public
              def tokenToExchangeSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token) and transfers
              #         Tokens (exchange_addr.token) to recipient.
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (exchange_addr.token) bought.
              @public
              def tokenToExchangeTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
                  assert recipient != self
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token).
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToExchangeSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token) and transfers
              #         Tokens (exchange_addr.token) to recipient.
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToExchangeTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
                  assert recipient != self
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Public price function for ETH to Token trades with an exact input.
              # @param eth_sold Amount of ETH sold.
              # @return Amount of Tokens that can be bought with input ETH.
              @public
              @constant
              def getEthToTokenInputPrice(eth_sold: uint256(wei)) -> uint256:
                  assert eth_sold > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  return self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance), token_reserve)
              
              # @notice Public price function for ETH to Token trades with an exact output.
              # @param tokens_bought Amount of Tokens bought.
              # @return Amount of ETH needed to buy output Tokens.
              @public
              @constant
              def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei):
                  assert tokens_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance), token_reserve)
                  return as_wei_value(eth_sold, 'wei')
              
              # @notice Public price function for Token to ETH trades with an exact input.
              # @param tokens_sold Amount of Tokens sold.
              # @return Amount of ETH that can be bought with input Tokens.
              @public
              @constant
              def getTokenToEthInputPrice(tokens_sold: uint256) -> uint256(wei):
                  assert tokens_sold > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  return as_wei_value(eth_bought, 'wei')
              
              # @notice Public price function for Token to ETH trades with an exact output.
              # @param eth_bought Amount of output ETH.
              # @return Amount of Tokens needed to buy output ETH.
              @public
              @constant
              def getTokenToEthOutputPrice(eth_bought: uint256(wei)) -> uint256:
                  assert eth_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  return self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
              
              # @return Address of Token that is sold on this exchange.
              @public
              @constant
              def tokenAddress() -> address:
                  return self.token
              
              # @return Address of factory that created this exchange.
              @public
              @constant
              def factoryAddress() -> address(Factory):
                  return self.factory
              
              # ERC20 compatibility for exchange liquidity modified from
              # https://github.com/ethereum/vyper/blob/master/examples/tokens/ERC20.vy
              @public
              @constant
              def balanceOf(_owner : address) -> uint256:
                  return self.balances[_owner]
              
              @public
              def transfer(_to : address, _value : uint256) -> bool:
                  self.balances[msg.sender] -= _value
                  self.balances[_to] += _value
                  log.Transfer(msg.sender, _to, _value)
                  return True
              
              @public
              def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
                  self.balances[_from] -= _value
                  self.balances[_to] += _value
                  self.allowances[_from][msg.sender] -= _value
                  log.Transfer(_from, _to, _value)
                  return True
              
              @public
              def approve(_spender : address, _value : uint256) -> bool:
                  self.allowances[msg.sender][_spender] = _value
                  log.Approval(msg.sender, _spender, _value)
                  return True
              
              @public
              @constant
              def allowance(_owner : address, _spender : address) -> uint256:
                  return self.allowances[_owner][_spender]

              File 3 of 6: Spender
              {"Constants.84ef19f8.sol":{"content":"// SPDX-License-Identifier: MIT\r\n\r\npragma solidity ^0.6.0;\r\n\r\nlibrary Constants {\r\n    address internal constant ETH = 0x0000000000000000000000000000000000000000;\r\n}\r\n"},"Spender.3372a096.sol":{"content":"// SPDX-License-Identifier: MIT\r\n\r\npragma solidity ^0.6.0;\r\n\r\nimport \"./Constants.84ef19f8.sol\";\r\n\r\ncontract Spender {\r\n    address public immutable metaswap;\r\n\r\n    constructor() public {\r\n        metaswap = msg.sender;\r\n    }\r\n\r\n    /// @dev Receives ether from swaps\r\n    fallback() external payable {}\r\n\r\n    function swap(address adapter, bytes calldata data) external payable {\r\n        require(msg.sender == metaswap, \"FORBIDDEN\");\r\n        require(adapter != address(0), \"ADAPTER_NOT_PROVIDED\");\r\n        _delegate(adapter, data, \"ADAPTER_DELEGATECALL_FAILED\");\r\n    }\r\n\r\n    /**\r\n     * @dev Performs a delegatecall and bubbles up the errors, adapted from\r\n     * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol\r\n     * @param target Address of the contract to delegatecall\r\n     * @param data Data passed in the delegatecall\r\n     * @param errorMessage Fallback revert reason\r\n     */\r\n    function _delegate(\r\n        address target,\r\n        bytes memory data,\r\n        string memory errorMessage\r\n    ) private returns (bytes memory) {\r\n        // solhint-disable-next-line avoid-low-level-calls\r\n        (bool success, bytes memory returndata) = target.delegatecall(data);\r\n        if (success) {\r\n            return returndata;\r\n        } else {\r\n            // Look for revert reason and bubble it up if present\r\n            if (returndata.length \u003e 0) {\r\n                // The easiest way to bubble the revert reason is using memory via assembly\r\n\r\n                // solhint-disable-next-line no-inline-assembly\r\n                assembly {\r\n                    let returndata_size := mload(returndata)\r\n                    revert(add(32, returndata), returndata_size)\r\n                }\r\n            } else {\r\n                revert(errorMessage);\r\n            }\r\n        }\r\n    }\r\n}\r\n"}}

              File 4 of 6: HEX
              pragma solidity 0.5.13;
              
              /*
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with GSN meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              contract Context {
                  // Empty internal constructor, to prevent people from mistakenly deploying
                  // an instance of this contract, which should be used via inheritance.
                  constructor () internal { }
                  // solhint-disable-previous-line no-empty-blocks
              
                  function _msgSender() internal view returns (address payable) {
                      return msg.sender;
                  }
              
                  function _msgData() internal view returns (bytes memory) {
                      this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                      return msg.data;
                  }
              }
              
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
               * the optional functions; to access them see {ERC20Detailed}.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
              
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
              
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
              
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
              
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow
               * checks.
               *
               * Arithmetic operations in Solidity wrap on overflow. This can easily result
               * in bugs, because programmers usually assume that an overflow raises an
               * error, which is the standard behavior in high level programming languages.
               * `SafeMath` restores this intuition by reverting the transaction when an
               * operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               */
              library SafeMath {
                  /**
                   * @dev Returns the addition of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `+` operator.
                   *
                   * Requirements:
                   * - Addition cannot overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      require(c >= a, "SafeMath: addition overflow");
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, "SafeMath: subtraction overflow");
                  }
              
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   * - Subtraction cannot overflow.
                   *
                   * _Available since v2.4.0._
                   */
                  function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b <= a, errorMessage);
                      uint256 c = a - b;
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the multiplication of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `*` operator.
                   *
                   * Requirements:
                   * - Multiplication cannot overflow.
                   */
                  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                      // benefit is lost if 'b' is also tested.
                      // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                      if (a == 0) {
                          return 0;
                      }
              
                      uint256 c = a * b;
                      require(c / a == b, "SafeMath: multiplication overflow");
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   * - The divisor cannot be zero.
                   */
                  function div(uint256 a, uint256 b) internal pure returns (uint256) {
                      return div(a, b, "SafeMath: division by zero");
                  }
              
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   * - The divisor cannot be zero.
                   *
                   * _Available since v2.4.0._
                   */
                  function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      // Solidity only automatically asserts when dividing by 0
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   * - The divisor cannot be zero.
                   */
                  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                      return mod(a, b, "SafeMath: modulo by zero");
                  }
              
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts with custom message when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   * - The divisor cannot be zero.
                   *
                   * _Available since v2.4.0._
                   */
                  function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }
              
              /**
               * @dev Implementation of the {IERC20} interface.
               *
               * This implementation is agnostic to the way tokens are created. This means
               * that a supply mechanism has to be added in a derived contract using {_mint}.
               * For a generic mechanism see {ERC20Mintable}.
               *
               * TIP: For a detailed writeup see our guide
               * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
               * to implement supply mechanisms].
               *
               * We have followed general OpenZeppelin guidelines: functions revert instead
               * of returning `false` on failure. This behavior is nonetheless conventional
               * and does not conflict with the expectations of ERC20 applications.
               *
               * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
               * This allows applications to reconstruct the allowance for all accounts just
               * by listening to said events. Other implementations of the EIP may not emit
               * these events, as it isn't required by the specification.
               *
               * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
               * functions have been added to mitigate the well-known issues around setting
               * allowances. See {IERC20-approve}.
               */
              contract ERC20 is Context, IERC20 {
                  using SafeMath for uint256;
              
                  mapping (address => uint256) private _balances;
              
                  mapping (address => mapping (address => uint256)) private _allowances;
              
                  uint256 private _totalSupply;
              
                  /**
                   * @dev See {IERC20-totalSupply}.
                   */
                  function totalSupply() public view returns (uint256) {
                      return _totalSupply;
                  }
              
                  /**
                   * @dev See {IERC20-balanceOf}.
                   */
                  function balanceOf(address account) public view returns (uint256) {
                      return _balances[account];
                  }
              
                  /**
                   * @dev See {IERC20-transfer}.
                   *
                   * Requirements:
                   *
                   * - `recipient` cannot be the zero address.
                   * - the caller must have a balance of at least `amount`.
                   */
                  function transfer(address recipient, uint256 amount) public returns (bool) {
                      _transfer(_msgSender(), recipient, amount);
                      return true;
                  }
              
                  /**
                   * @dev See {IERC20-allowance}.
                   */
                  function allowance(address owner, address spender) public view returns (uint256) {
                      return _allowances[owner][spender];
                  }
              
                  /**
                   * @dev See {IERC20-approve}.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function approve(address spender, uint256 amount) public returns (bool) {
                      _approve(_msgSender(), spender, amount);
                      return true;
                  }
              
                  /**
                   * @dev See {IERC20-transferFrom}.
                   *
                   * Emits an {Approval} event indicating the updated allowance. This is not
                   * required by the EIP. See the note at the beginning of {ERC20};
                   *
                   * Requirements:
                   * - `sender` and `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   * - the caller must have allowance for `sender`'s tokens of at least
                   * `amount`.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
                      _transfer(sender, recipient, amount);
                      _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                      return true;
                  }
              
                  /**
                   * @dev Atomically increases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                      return true;
                  }
              
                  /**
                   * @dev Atomically decreases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `spender` must have allowance for the caller of at least
                   * `subtractedValue`.
                   */
                  function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                      return true;
                  }
              
                  /**
                   * @dev Moves tokens `amount` from `sender` to `recipient`.
                   *
                   * This is internal function is equivalent to {transfer}, and can be used to
                   * e.g. implement automatic token fees, slashing mechanisms, etc.
                   *
                   * Emits a {Transfer} event.
                   *
                   * Requirements:
                   *
                   * - `sender` cannot be the zero address.
                   * - `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   */
                  function _transfer(address sender, address recipient, uint256 amount) internal {
                      require(sender != address(0), "ERC20: transfer from the zero address");
                      require(recipient != address(0), "ERC20: transfer to the zero address");
              
                      _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                      _balances[recipient] = _balances[recipient].add(amount);
                      emit Transfer(sender, recipient, amount);
                  }
              
                  /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                   * the total supply.
                   *
                   * Emits a {Transfer} event with `from` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `to` cannot be the zero address.
                   */
                  function _mint(address account, uint256 amount) internal {
                      require(account != address(0), "ERC20: mint to the zero address");
              
                      _totalSupply = _totalSupply.add(amount);
                      _balances[account] = _balances[account].add(amount);
                      emit Transfer(address(0), account, amount);
                  }
              
                   /**
                   * @dev Destroys `amount` tokens from `account`, reducing the
                   * total supply.
                   *
                   * Emits a {Transfer} event with `to` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `account` cannot be the zero address.
                   * - `account` must have at least `amount` tokens.
                   */
                  function _burn(address account, uint256 amount) internal {
                      require(account != address(0), "ERC20: burn from the zero address");
              
                      _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                      _totalSupply = _totalSupply.sub(amount);
                      emit Transfer(account, address(0), amount);
                  }
              
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                   *
                   * This is internal function is equivalent to `approve`, and can be used to
                   * e.g. set automatic allowances for certain subsystems, etc.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `owner` cannot be the zero address.
                   * - `spender` cannot be the zero address.
                   */
                  function _approve(address owner, address spender, uint256 amount) internal {
                      require(owner != address(0), "ERC20: approve from the zero address");
                      require(spender != address(0), "ERC20: approve to the zero address");
              
                      _allowances[owner][spender] = amount;
                      emit Approval(owner, spender, amount);
                  }
              
                  /**
                   * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
                   * from the caller's allowance.
                   *
                   * See {_burn} and {_approve}.
                   */
                  function _burnFrom(address account, uint256 amount) internal {
                      _burn(account, amount);
                      _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
                  }
              }
              
              contract GlobalsAndUtility is ERC20 {
                  /*  XfLobbyEnter      (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      address  indexed  memberAddr
                      uint256  indexed  entryId
                      uint96            rawAmount       -->  data0 [135: 40]
                      address  indexed  referrerAddr
                  */
                  event XfLobbyEnter(
                      uint256 data0,
                      address indexed memberAddr,
                      uint256 indexed entryId,
                      address indexed referrerAddr
                  );
              
                  /*  XfLobbyExit       (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      address  indexed  memberAddr
                      uint256  indexed  entryId
                      uint72            xfAmount        -->  data0 [111: 40]
                      address  indexed  referrerAddr
                  */
                  event XfLobbyExit(
                      uint256 data0,
                      address indexed memberAddr,
                      uint256 indexed entryId,
                      address indexed referrerAddr
                  );
              
                  /*  DailyDataUpdate   (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      uint16            beginDay        -->  data0 [ 55: 40]
                      uint16            endDay          -->  data0 [ 71: 56]
                      bool              isAutoUpdate    -->  data0 [ 79: 72]
                      address  indexed  updaterAddr
                  */
                  event DailyDataUpdate(
                      uint256 data0,
                      address indexed updaterAddr
                  );
              
                  /*  Claim             (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      bytes20  indexed  btcAddr
                      uint56            rawSatoshis     -->  data0 [ 95: 40]
                      uint56            adjSatoshis     -->  data0 [151: 96]
                      address  indexed  claimToAddr
                      uint8             claimFlags      -->  data0 [159:152]
                      uint72            claimedHearts   -->  data0 [231:160]
                      address  indexed  referrerAddr
                      address           senderAddr      -->  data1 [159:  0]
                  */
                  event Claim(
                      uint256 data0,
                      uint256 data1,
                      bytes20 indexed btcAddr,
                      address indexed claimToAddr,
                      address indexed referrerAddr
                  );
              
                  /*  ClaimAssist       (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      bytes20           btcAddr         -->  data0 [199: 40]
                      uint56            rawSatoshis     -->  data0 [255:200]
                      uint56            adjSatoshis     -->  data1 [ 55:  0]
                      address           claimToAddr     -->  data1 [215: 56]
                      uint8             claimFlags      -->  data1 [223:216]
                      uint72            claimedHearts   -->  data2 [ 71:  0]
                      address           referrerAddr    -->  data2 [231: 72]
                      address  indexed  senderAddr
                  */
                  event ClaimAssist(
                      uint256 data0,
                      uint256 data1,
                      uint256 data2,
                      address indexed senderAddr
                  );
              
                  /*  StakeStart        (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      address  indexed  stakerAddr
                      uint40   indexed  stakeId
                      uint72            stakedHearts    -->  data0 [111: 40]
                      uint72            stakeShares     -->  data0 [183:112]
                      uint16            stakedDays      -->  data0 [199:184]
                      bool              isAutoStake     -->  data0 [207:200]
                  */
                  event StakeStart(
                      uint256 data0,
                      address indexed stakerAddr,
                      uint40 indexed stakeId
                  );
              
                  /*  StakeGoodAccounting(auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      address  indexed  stakerAddr
                      uint40   indexed  stakeId
                      uint72            stakedHearts    -->  data0 [111: 40]
                      uint72            stakeShares     -->  data0 [183:112]
                      uint72            payout          -->  data0 [255:184]
                      uint72            penalty         -->  data1 [ 71:  0]
                      address  indexed  senderAddr
                  */
                  event StakeGoodAccounting(
                      uint256 data0,
                      uint256 data1,
                      address indexed stakerAddr,
                      uint40 indexed stakeId,
                      address indexed senderAddr
                  );
              
                  /*  StakeEnd          (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      address  indexed  stakerAddr
                      uint40   indexed  stakeId
                      uint72            stakedHearts    -->  data0 [111: 40]
                      uint72            stakeShares     -->  data0 [183:112]
                      uint72            payout          -->  data0 [255:184]
                      uint72            penalty         -->  data1 [ 71:  0]
                      uint16            servedDays      -->  data1 [ 87: 72]
                      bool              prevUnlocked    -->  data1 [ 95: 88]
                  */
                  event StakeEnd(
                      uint256 data0,
                      uint256 data1,
                      address indexed stakerAddr,
                      uint40 indexed stakeId
                  );
              
                  /*  ShareRateChange   (auto-generated event)
              
                      uint40            timestamp       -->  data0 [ 39:  0]
                      uint40            shareRate       -->  data0 [ 79: 40]
                      uint40   indexed  stakeId
                  */
                  event ShareRateChange(
                      uint256 data0,
                      uint40 indexed stakeId
                  );
              
                  /* Origin address */
                  address internal constant ORIGIN_ADDR = 0x9A6a414D6F3497c05E3b1De90520765fA1E07c03;
              
                  /* Flush address */
                  address payable internal constant FLUSH_ADDR = 0xDEC9f2793e3c17cd26eeFb21C4762fA5128E0399;
              
                  /* ERC20 constants */
                  string public constant name = "HEX";
                  string public constant symbol = "HEX";
                  uint8 public constant decimals = 8;
              
                  /* Hearts per Satoshi = 10,000 * 1e8 / 1e8 = 1e4 */
                  uint256 private constant HEARTS_PER_HEX = 10 ** uint256(decimals); // 1e8
                  uint256 private constant HEX_PER_BTC = 1e4;
                  uint256 private constant SATOSHIS_PER_BTC = 1e8;
                  uint256 internal constant HEARTS_PER_SATOSHI = HEARTS_PER_HEX / SATOSHIS_PER_BTC * HEX_PER_BTC;
              
                  /* Time of contract launch (2019-12-03T00:00:00Z) */
                  uint256 internal constant LAUNCH_TIME = 1575331200;
              
                  /* Size of a Hearts or Shares uint */
                  uint256 internal constant HEART_UINT_SIZE = 72;
              
                  /* Size of a transform lobby entry index uint */
                  uint256 internal constant XF_LOBBY_ENTRY_INDEX_SIZE = 40;
                  uint256 internal constant XF_LOBBY_ENTRY_INDEX_MASK = (1 << XF_LOBBY_ENTRY_INDEX_SIZE) - 1;
              
                  /* Seed for WAAS Lobby */
                  uint256 internal constant WAAS_LOBBY_SEED_HEX = 1e9;
                  uint256 internal constant WAAS_LOBBY_SEED_HEARTS = WAAS_LOBBY_SEED_HEX * HEARTS_PER_HEX;
              
                  /* Start of claim phase */
                  uint256 internal constant PRE_CLAIM_DAYS = 1;
                  uint256 internal constant CLAIM_PHASE_START_DAY = PRE_CLAIM_DAYS;
              
                  /* Length of claim phase */
                  uint256 private constant CLAIM_PHASE_WEEKS = 50;
                  uint256 internal constant CLAIM_PHASE_DAYS = CLAIM_PHASE_WEEKS * 7;
              
                  /* End of claim phase */
                  uint256 internal constant CLAIM_PHASE_END_DAY = CLAIM_PHASE_START_DAY + CLAIM_PHASE_DAYS;
              
                  /* Number of words to hold 1 bit for each transform lobby day */
                  uint256 internal constant XF_LOBBY_DAY_WORDS = (CLAIM_PHASE_END_DAY + 255) >> 8;
              
                  /* BigPayDay */
                  uint256 internal constant BIG_PAY_DAY = CLAIM_PHASE_END_DAY + 1;
              
                  /* Root hash of the UTXO Merkle tree */
                  bytes32 internal constant MERKLE_TREE_ROOT = 0x4e831acb4223b66de3b3d2e54a2edeefb0de3d7916e2886a4b134d9764d41bec;
              
                  /* Size of a Satoshi claim uint in a Merkle leaf */
                  uint256 internal constant MERKLE_LEAF_SATOSHI_SIZE = 45;
              
                  /* Zero-fill between BTC address and Satoshis in a Merkle leaf */
                  uint256 internal constant MERKLE_LEAF_FILL_SIZE = 256 - 160 - MERKLE_LEAF_SATOSHI_SIZE;
                  uint256 internal constant MERKLE_LEAF_FILL_BASE = (1 << MERKLE_LEAF_FILL_SIZE) - 1;
                  uint256 internal constant MERKLE_LEAF_FILL_MASK = MERKLE_LEAF_FILL_BASE << MERKLE_LEAF_SATOSHI_SIZE;
              
                  /* Size of a Satoshi total uint */
                  uint256 internal constant SATOSHI_UINT_SIZE = 51;
                  uint256 internal constant SATOSHI_UINT_MASK = (1 << SATOSHI_UINT_SIZE) - 1;
              
                  /* Total Satoshis from all BTC addresses in UTXO snapshot */
                  uint256 internal constant FULL_SATOSHIS_TOTAL = 1807766732160668;
              
                  /* Total Satoshis from supported BTC addresses in UTXO snapshot after applying Silly Whale */
                  uint256 internal constant CLAIMABLE_SATOSHIS_TOTAL = 910087996911001;
              
                  /* Number of claimable BTC addresses in UTXO snapshot */
                  uint256 internal constant CLAIMABLE_BTC_ADDR_COUNT = 27997742;
              
                  /* Largest BTC address Satoshis balance in UTXO snapshot (sanity check) */
                  uint256 internal constant MAX_BTC_ADDR_BALANCE_SATOSHIS = 25550214098481;
              
                  /* Percentage of total claimed Hearts that will be auto-staked from a claim */
                  uint256 internal constant AUTO_STAKE_CLAIM_PERCENT = 90;
              
                  /* Stake timing parameters */
                  uint256 internal constant MIN_STAKE_DAYS = 1;
                  uint256 internal constant MIN_AUTO_STAKE_DAYS = 350;
              
                  uint256 internal constant MAX_STAKE_DAYS = 5555; // Approx 15 years
              
                  uint256 internal constant EARLY_PENALTY_MIN_DAYS = 90;
              
                  uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
                  uint256 internal constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7;
              
                  uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
                  uint256 internal constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7;
              
                  /* Stake shares Longer Pays Better bonus constants used by _stakeStartBonusHearts() */
                  uint256 private constant LPB_BONUS_PERCENT = 20;
                  uint256 private constant LPB_BONUS_MAX_PERCENT = 200;
                  uint256 internal constant LPB = 364 * 100 / LPB_BONUS_PERCENT;
                  uint256 internal constant LPB_MAX_DAYS = LPB * LPB_BONUS_MAX_PERCENT / 100;
              
                  /* Stake shares Bigger Pays Better bonus constants used by _stakeStartBonusHearts() */
                  uint256 private constant BPB_BONUS_PERCENT = 10;
                  uint256 private constant BPB_MAX_HEX = 150 * 1e6;
                  uint256 internal constant BPB_MAX_HEARTS = BPB_MAX_HEX * HEARTS_PER_HEX;
                  uint256 internal constant BPB = BPB_MAX_HEARTS * 100 / BPB_BONUS_PERCENT;
              
                  /* Share rate is scaled to increase precision */
                  uint256 internal constant SHARE_RATE_SCALE = 1e5;
              
                  /* Share rate max (after scaling) */
                  uint256 internal constant SHARE_RATE_UINT_SIZE = 40;
                  uint256 internal constant SHARE_RATE_MAX = (1 << SHARE_RATE_UINT_SIZE) - 1;
              
                  /* Constants for preparing the claim message text */
                  uint8 internal constant ETH_ADDRESS_BYTE_LEN = 20;
                  uint8 internal constant ETH_ADDRESS_HEX_LEN = ETH_ADDRESS_BYTE_LEN * 2;
              
                  uint8 internal constant CLAIM_PARAM_HASH_BYTE_LEN = 12;
                  uint8 internal constant CLAIM_PARAM_HASH_HEX_LEN = CLAIM_PARAM_HASH_BYTE_LEN * 2;
              
                  uint8 internal constant BITCOIN_SIG_PREFIX_LEN = 24;
                  bytes24 internal constant BITCOIN_SIG_PREFIX_STR = "Bitcoin Signed Message:\n";
              
                  bytes internal constant STD_CLAIM_PREFIX_STR = "Claim_HEX_to_0x";
                  bytes internal constant OLD_CLAIM_PREFIX_STR = "Claim_BitcoinHEX_to_0x";
              
                  bytes16 internal constant HEX_DIGITS = "0123456789abcdef";
              
                  /* Claim flags passed to btcAddressClaim()  */
                  uint8 internal constant CLAIM_FLAG_MSG_PREFIX_OLD = 1 << 0;
                  uint8 internal constant CLAIM_FLAG_BTC_ADDR_COMPRESSED = 1 << 1;
                  uint8 internal constant CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH = 1 << 2;
                  uint8 internal constant CLAIM_FLAG_BTC_ADDR_BECH32 = 1 << 3;
                  uint8 internal constant CLAIM_FLAG_ETH_ADDR_LOWERCASE = 1 << 4;
              
                  /* Globals expanded for memory (except _latestStakeId) and compact for storage */
                  struct GlobalsCache {
                      // 1
                      uint256 _lockedHeartsTotal;
                      uint256 _nextStakeSharesTotal;
                      uint256 _shareRate;
                      uint256 _stakePenaltyTotal;
                      // 2
                      uint256 _dailyDataCount;
                      uint256 _stakeSharesTotal;
                      uint40 _latestStakeId;
                      uint256 _unclaimedSatoshisTotal;
                      uint256 _claimedSatoshisTotal;
                      uint256 _claimedBtcAddrCount;
                      //
                      uint256 _currentDay;
                  }
              
                  struct GlobalsStore {
                      // 1
                      uint72 lockedHeartsTotal;
                      uint72 nextStakeSharesTotal;
                      uint40 shareRate;
                      uint72 stakePenaltyTotal;
                      // 2
                      uint16 dailyDataCount;
                      uint72 stakeSharesTotal;
                      uint40 latestStakeId;
                      uint128 claimStats;
                  }
              
                  GlobalsStore public globals;
              
                  /* Claimed BTC addresses */
                  mapping(bytes20 => bool) public btcAddressClaims;
              
                  /* Daily data */
                  struct DailyDataStore {
                      uint72 dayPayoutTotal;
                      uint72 dayStakeSharesTotal;
                      uint56 dayUnclaimedSatoshisTotal;
                  }
              
                  mapping(uint256 => DailyDataStore) public dailyData;
              
                  /* Stake expanded for memory (except _stakeId) and compact for storage */
                  struct StakeCache {
                      uint40 _stakeId;
                      uint256 _stakedHearts;
                      uint256 _stakeShares;
                      uint256 _lockedDay;
                      uint256 _stakedDays;
                      uint256 _unlockedDay;
                      bool _isAutoStake;
                  }
              
                  struct StakeStore {
                      uint40 stakeId;
                      uint72 stakedHearts;
                      uint72 stakeShares;
                      uint16 lockedDay;
                      uint16 stakedDays;
                      uint16 unlockedDay;
                      bool isAutoStake;
                  }
              
                  mapping(address => StakeStore[]) public stakeLists;
              
                  /* Temporary state for calculating daily rounds */
                  struct DailyRoundState {
                      uint256 _allocSupplyCached;
                      uint256 _mintOriginBatch;
                      uint256 _payoutTotal;
                  }
              
                  struct XfLobbyEntryStore {
                      uint96 rawAmount;
                      address referrerAddr;
                  }
              
                  struct XfLobbyQueueStore {
                      uint40 headIndex;
                      uint40 tailIndex;
                      mapping(uint256 => XfLobbyEntryStore) entries;
                  }
              
                  mapping(uint256 => uint256) public xfLobby;
                  mapping(uint256 => mapping(address => XfLobbyQueueStore)) public xfLobbyMembers;
              
                  /**
                   * @dev PUBLIC FACING: Optionally update daily data for a smaller
                   * range to reduce gas cost for a subsequent operation
                   * @param beforeDay Only update days before this day number (optional; 0 for current day)
                   */
                  function dailyDataUpdate(uint256 beforeDay)
                      external
                  {
                      GlobalsCache memory g;
                      GlobalsCache memory gSnapshot;
                      _globalsLoad(g, gSnapshot);
              
                      /* Skip pre-claim period */
                      require(g._currentDay > CLAIM_PHASE_START_DAY, "HEX: Too early");
              
                      if (beforeDay != 0) {
                          require(beforeDay <= g._currentDay, "HEX: beforeDay cannot be in the future");
              
                          _dailyDataUpdate(g, beforeDay, false);
                      } else {
                          /* Default to updating before current day */
                          _dailyDataUpdate(g, g._currentDay, false);
                      }
              
                      _globalsSync(g, gSnapshot);
                  }
              
                  /**
                   * @dev PUBLIC FACING: External helper to return multiple values of daily data with
                   * a single call. Ugly implementation due to limitations of the standard ABI encoder.
                   * @param beginDay First day of data range
                   * @param endDay Last day (non-inclusive) of data range
                   * @return Fixed array of packed values
                   */
                  function dailyDataRange(uint256 beginDay, uint256 endDay)
                      external
                      view
                      returns (uint256[] memory list)
                  {
                      require(beginDay < endDay && endDay <= globals.dailyDataCount, "HEX: range invalid");
              
                      list = new uint256[](endDay - beginDay);
              
                      uint256 src = beginDay;
                      uint256 dst = 0;
                      uint256 v;
                      do {
                          v = uint256(dailyData[src].dayUnclaimedSatoshisTotal) << (HEART_UINT_SIZE * 2);
                          v |= uint256(dailyData[src].dayStakeSharesTotal) << HEART_UINT_SIZE;
                          v |= uint256(dailyData[src].dayPayoutTotal);
              
                          list[dst++] = v;
                      } while (++src < endDay);
              
                      return list;
                  }
              
                  /**
                   * @dev PUBLIC FACING: External helper to return most global info with a single call.
                   * Ugly implementation due to limitations of the standard ABI encoder.
                   * @return Fixed array of values
                   */
                  function globalInfo()
                      external
                      view
                      returns (uint256[13] memory)
                  {
                      uint256 _claimedBtcAddrCount;
                      uint256 _claimedSatoshisTotal;
                      uint256 _unclaimedSatoshisTotal;
              
                      (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal) = _claimStatsDecode(
                          globals.claimStats
                      );
              
                      return [
                          // 1
                          globals.lockedHeartsTotal,
                          globals.nextStakeSharesTotal,
                          globals.shareRate,
                          globals.stakePenaltyTotal,
                          // 2
                          globals.dailyDataCount,
                          globals.stakeSharesTotal,
                          globals.latestStakeId,
                          _unclaimedSatoshisTotal,
                          _claimedSatoshisTotal,
                          _claimedBtcAddrCount,
                          //
                          block.timestamp,
                          totalSupply(),
                          xfLobby[_currentDay()]
                      ];
                  }
              
                  /**
                   * @dev PUBLIC FACING: ERC20 totalSupply() is the circulating supply and does not include any
                   * staked Hearts. allocatedSupply() includes both.
                   * @return Allocated Supply in Hearts
                   */
                  function allocatedSupply()
                      external
                      view
                      returns (uint256)
                  {
                      return totalSupply() + globals.lockedHeartsTotal;
                  }
              
                  /**
                   * @dev PUBLIC FACING: External helper for the current day number since launch time
                   * @return Current day number (zero-based)
                   */
                  function currentDay()
                      external
                      view
                      returns (uint256)
                  {
                      return _currentDay();
                  }
              
                  function _currentDay()
                      internal
                      view
                      returns (uint256)
                  {
                      return (block.timestamp - LAUNCH_TIME) / 1 days;
                  }
              
                  function _dailyDataUpdateAuto(GlobalsCache memory g)
                      internal
                  {
                      _dailyDataUpdate(g, g._currentDay, true);
                  }
              
                  function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot)
                      internal
                      view
                  {
                      // 1
                      g._lockedHeartsTotal = globals.lockedHeartsTotal;
                      g._nextStakeSharesTotal = globals.nextStakeSharesTotal;
                      g._shareRate = globals.shareRate;
                      g._stakePenaltyTotal = globals.stakePenaltyTotal;
                      // 2
                      g._dailyDataCount = globals.dailyDataCount;
                      g._stakeSharesTotal = globals.stakeSharesTotal;
                      g._latestStakeId = globals.latestStakeId;
                      (g._claimedBtcAddrCount, g._claimedSatoshisTotal, g._unclaimedSatoshisTotal) = _claimStatsDecode(
                          globals.claimStats
                      );
                      //
                      g._currentDay = _currentDay();
              
                      _globalsCacheSnapshot(g, gSnapshot);
                  }
              
                  function _globalsCacheSnapshot(GlobalsCache memory g, GlobalsCache memory gSnapshot)
                      internal
                      pure
                  {
                      // 1
                      gSnapshot._lockedHeartsTotal = g._lockedHeartsTotal;
                      gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal;
                      gSnapshot._shareRate = g._shareRate;
                      gSnapshot._stakePenaltyTotal = g._stakePenaltyTotal;
                      // 2
                      gSnapshot._dailyDataCount = g._dailyDataCount;
                      gSnapshot._stakeSharesTotal = g._stakeSharesTotal;
                      gSnapshot._latestStakeId = g._latestStakeId;
                      gSnapshot._unclaimedSatoshisTotal = g._unclaimedSatoshisTotal;
                      gSnapshot._claimedSatoshisTotal = g._claimedSatoshisTotal;
                      gSnapshot._claimedBtcAddrCount = g._claimedBtcAddrCount;
                  }
              
                  function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot)
                      internal
                  {
                      if (g._lockedHeartsTotal != gSnapshot._lockedHeartsTotal
                          || g._nextStakeSharesTotal != gSnapshot._nextStakeSharesTotal
                          || g._shareRate != gSnapshot._shareRate
                          || g._stakePenaltyTotal != gSnapshot._stakePenaltyTotal) {
                          // 1
                          globals.lockedHeartsTotal = uint72(g._lockedHeartsTotal);
                          globals.nextStakeSharesTotal = uint72(g._nextStakeSharesTotal);
                          globals.shareRate = uint40(g._shareRate);
                          globals.stakePenaltyTotal = uint72(g._stakePenaltyTotal);
                      }
                      if (g._dailyDataCount != gSnapshot._dailyDataCount
                          || g._stakeSharesTotal != gSnapshot._stakeSharesTotal
                          || g._latestStakeId != gSnapshot._latestStakeId
                          || g._unclaimedSatoshisTotal != gSnapshot._unclaimedSatoshisTotal
                          || g._claimedSatoshisTotal != gSnapshot._claimedSatoshisTotal
                          || g._claimedBtcAddrCount != gSnapshot._claimedBtcAddrCount) {
                          // 2
                          globals.dailyDataCount = uint16(g._dailyDataCount);
                          globals.stakeSharesTotal = uint72(g._stakeSharesTotal);
                          globals.latestStakeId = g._latestStakeId;
                          globals.claimStats = _claimStatsEncode(
                              g._claimedBtcAddrCount,
                              g._claimedSatoshisTotal,
                              g._unclaimedSatoshisTotal
                          );
                      }
                  }
              
                  function _stakeLoad(StakeStore storage stRef, uint40 stakeIdParam, StakeCache memory st)
                      internal
                      view
                  {
                      /* Ensure caller's stakeIndex is still current */
                      require(stakeIdParam == stRef.stakeId, "HEX: stakeIdParam not in stake");
              
                      st._stakeId = stRef.stakeId;
                      st._stakedHearts = stRef.stakedHearts;
                      st._stakeShares = stRef.stakeShares;
                      st._lockedDay = stRef.lockedDay;
                      st._stakedDays = stRef.stakedDays;
                      st._unlockedDay = stRef.unlockedDay;
                      st._isAutoStake = stRef.isAutoStake;
                  }
              
                  function _stakeUpdate(StakeStore storage stRef, StakeCache memory st)
                      internal
                  {
                      stRef.stakeId = st._stakeId;
                      stRef.stakedHearts = uint72(st._stakedHearts);
                      stRef.stakeShares = uint72(st._stakeShares);
                      stRef.lockedDay = uint16(st._lockedDay);
                      stRef.stakedDays = uint16(st._stakedDays);
                      stRef.unlockedDay = uint16(st._unlockedDay);
                      stRef.isAutoStake = st._isAutoStake;
                  }
              
                  function _stakeAdd(
                      StakeStore[] storage stakeListRef,
                      uint40 newStakeId,
                      uint256 newStakedHearts,
                      uint256 newStakeShares,
                      uint256 newLockedDay,
                      uint256 newStakedDays,
                      bool newAutoStake
                  )
                      internal
                  {
                      stakeListRef.push(
                          StakeStore(
                              newStakeId,
                              uint72(newStakedHearts),
                              uint72(newStakeShares),
                              uint16(newLockedDay),
                              uint16(newStakedDays),
                              uint16(0), // unlockedDay
                              newAutoStake
                          )
                      );
                  }
              
                  /**
                   * @dev Efficiently delete from an unordered array by moving the last element
                   * to the "hole" and reducing the array length. Can change the order of the list
                   * and invalidate previously held indexes.
                   * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd()
                   * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage
                   * @param stakeIndex Index of the element to delete
                   */
                  function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex)
                      internal
                  {
                      uint256 lastIndex = stakeListRef.length - 1;
              
                      /* Skip the copy if element to be removed is already the last element */
                      if (stakeIndex != lastIndex) {
                          /* Copy last element to the requested element's "hole" */
                          stakeListRef[stakeIndex] = stakeListRef[lastIndex];
                      }
              
                      /*
                          Reduce the array length now that the array is contiguous.
                          Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex'
                      */
                      stakeListRef.pop();
                  }
              
                  function _claimStatsEncode(
                      uint256 _claimedBtcAddrCount,
                      uint256 _claimedSatoshisTotal,
                      uint256 _unclaimedSatoshisTotal
                  )
                      internal
                      pure
                      returns (uint128)
                  {
                      uint256 v = _claimedBtcAddrCount << (SATOSHI_UINT_SIZE * 2);
                      v |= _claimedSatoshisTotal << SATOSHI_UINT_SIZE;
                      v |= _unclaimedSatoshisTotal;
              
                      return uint128(v);
                  }
              
                  function _claimStatsDecode(uint128 v)
                      internal
                      pure
                      returns (uint256 _claimedBtcAddrCount, uint256 _claimedSatoshisTotal, uint256 _unclaimedSatoshisTotal)
                  {
                      _claimedBtcAddrCount = v >> (SATOSHI_UINT_SIZE * 2);
                      _claimedSatoshisTotal = (v >> SATOSHI_UINT_SIZE) & SATOSHI_UINT_MASK;
                      _unclaimedSatoshisTotal = v & SATOSHI_UINT_MASK;
              
                      return (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal);
                  }
              
                  /**
                   * @dev Estimate the stake payout for an incomplete day
                   * @param g Cache of stored globals
                   * @param stakeSharesParam Param from stake to calculate bonuses for
                   * @param day Day to calculate bonuses for
                   * @return Payout in Hearts
                   */
                  function _estimatePayoutRewardsDay(GlobalsCache memory g, uint256 stakeSharesParam, uint256 day)
                      internal
                      view
                      returns (uint256 payout)
                  {
                      /* Prevent updating state for this estimation */
                      GlobalsCache memory gTmp;
                      _globalsCacheSnapshot(g, gTmp);
              
                      DailyRoundState memory rs;
                      rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal;
              
                      _dailyRoundCalc(gTmp, rs, day);
              
                      /* Stake is no longer locked so it must be added to total as if it were */
                      gTmp._stakeSharesTotal += stakeSharesParam;
              
                      payout = rs._payoutTotal * stakeSharesParam / gTmp._stakeSharesTotal;
              
                      if (day == BIG_PAY_DAY) {
                          uint256 bigPaySlice = gTmp._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam
                              / gTmp._stakeSharesTotal;
                          payout += bigPaySlice + _calcAdoptionBonus(gTmp, bigPaySlice);
                      }
              
                      return payout;
                  }
              
                  function _calcAdoptionBonus(GlobalsCache memory g, uint256 payout)
                      internal
                      pure
                      returns (uint256)
                  {
                      /*
                          VIRAL REWARDS: Add adoption percentage bonus to payout
              
                          viral = payout * (claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT)
                      */
                      uint256 viral = payout * g._claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT;
              
                      /*
                          CRIT MASS REWARDS: Add adoption percentage bonus to payout
              
                          crit  = payout * (claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL)
                      */
                      uint256 crit = payout * g._claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL;
              
                      return viral + crit;
                  }
              
                  function _dailyRoundCalc(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)
                      private
                      pure
                  {
                      /*
                          Calculate payout round
              
                          Inflation of 3.69% inflation per 364 days             (approx 1 year)
                          dailyInterestRate   = exp(log(1 + 3.69%)  / 364) - 1
                                              = exp(log(1 + 0.0369) / 364) - 1
                                              = exp(log(1.0369) / 364) - 1
                                              = 0.000099553011616349            (approx)
              
                          payout  = allocSupply * dailyInterestRate
                                  = allocSupply / (1 / dailyInterestRate)
                                  = allocSupply / (1 / 0.000099553011616349)
                                  = allocSupply / 10044.899534066692            (approx)
                                  = allocSupply * 10000 / 100448995             (* 10000/10000 for int precision)
                      */
                      rs._payoutTotal = rs._allocSupplyCached * 10000 / 100448995;
              
                      if (day < CLAIM_PHASE_END_DAY) {
                          uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS;
              
                          uint256 originBonus = bigPaySlice + _calcAdoptionBonus(g, rs._payoutTotal + bigPaySlice);
                          rs._mintOriginBatch += originBonus;
                          rs._allocSupplyCached += originBonus;
              
                          rs._payoutTotal += _calcAdoptionBonus(g, rs._payoutTotal);
                      }
              
                      if (g._stakePenaltyTotal != 0) {
                          rs._payoutTotal += g._stakePenaltyTotal;
                          g._stakePenaltyTotal = 0;
                      }
                  }
              
                  function _dailyRoundCalcAndStore(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)
                      private
                  {
                      _dailyRoundCalc(g, rs, day);
              
                      dailyData[day].dayPayoutTotal = uint72(rs._payoutTotal);
                      dailyData[day].dayStakeSharesTotal = uint72(g._stakeSharesTotal);
                      dailyData[day].dayUnclaimedSatoshisTotal = uint56(g._unclaimedSatoshisTotal);
                  }
              
                  function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay, bool isAutoUpdate)
                      private
                  {
                      if (g._dailyDataCount >= beforeDay) {
                          /* Already up-to-date */
                          return;
                      }
              
                      DailyRoundState memory rs;
                      rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal;
              
                      uint256 day = g._dailyDataCount;
              
                      _dailyRoundCalcAndStore(g, rs, day);
              
                      /* Stakes started during this day are added to the total the next day */
                      if (g._nextStakeSharesTotal != 0) {
                          g._stakeSharesTotal += g._nextStakeSharesTotal;
                          g._nextStakeSharesTotal = 0;
                      }
              
                      while (++day < beforeDay) {
                          _dailyRoundCalcAndStore(g, rs, day);
                      }
              
                      _emitDailyDataUpdate(g._dailyDataCount, day, isAutoUpdate);
                      g._dailyDataCount = day;
              
                      if (rs._mintOriginBatch != 0) {
                          _mint(ORIGIN_ADDR, rs._mintOriginBatch);
                      }
                  }
              
                  function _emitDailyDataUpdate(uint256 beginDay, uint256 endDay, bool isAutoUpdate)
                      private
                  {
                      emit DailyDataUpdate( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint16(beginDay)) << 40)
                              | (uint256(uint16(endDay)) << 56)
                              | (isAutoUpdate ? (1 << 72) : 0),
                          msg.sender
                      );
                  }
              }
              
              contract StakeableToken is GlobalsAndUtility {
                  /**
                   * @dev PUBLIC FACING: Open a stake.
                   * @param newStakedHearts Number of Hearts to stake
                   * @param newStakedDays Number of days to stake
                   */
                  function stakeStart(uint256 newStakedHearts, uint256 newStakedDays)
                      external
                  {
                      GlobalsCache memory g;
                      GlobalsCache memory gSnapshot;
                      _globalsLoad(g, gSnapshot);
              
                      /* Enforce the minimum stake time */
                      require(newStakedDays >= MIN_STAKE_DAYS, "HEX: newStakedDays lower than minimum");
              
                      /* Check if log data needs to be updated */
                      _dailyDataUpdateAuto(g);
              
                      _stakeStart(g, newStakedHearts, newStakedDays, false);
              
                      /* Remove staked Hearts from balance of staker */
                      _burn(msg.sender, newStakedHearts);
              
                      _globalsSync(g, gSnapshot);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Unlocks a completed stake, distributing the proceeds of any penalty
                   * immediately. The staker must still call stakeEnd() to retrieve their stake return (if any).
                   * @param stakerAddr Address of staker
                   * @param stakeIndex Index of stake within stake list
                   * @param stakeIdParam The stake's id
                   */
                  function stakeGoodAccounting(address stakerAddr, uint256 stakeIndex, uint40 stakeIdParam)
                      external
                  {
                      GlobalsCache memory g;
                      GlobalsCache memory gSnapshot;
                      _globalsLoad(g, gSnapshot);
              
                      /* require() is more informative than the default assert() */
                      require(stakeLists[stakerAddr].length != 0, "HEX: Empty stake list");
                      require(stakeIndex < stakeLists[stakerAddr].length, "HEX: stakeIndex invalid");
              
                      StakeStore storage stRef = stakeLists[stakerAddr][stakeIndex];
              
                      /* Get stake copy */
                      StakeCache memory st;
                      _stakeLoad(stRef, stakeIdParam, st);
              
                      /* Stake must have served full term */
                      require(g._currentDay >= st._lockedDay + st._stakedDays, "HEX: Stake not fully served");
              
                      /* Stake must still be locked */
                      require(st._unlockedDay == 0, "HEX: Stake already unlocked");
              
                      /* Check if log data needs to be updated */
                      _dailyDataUpdateAuto(g);
              
                      /* Unlock the completed stake */
                      _stakeUnlock(g, st);
              
                      /* stakeReturn value is unused here */
                      (, uint256 payout, uint256 penalty, uint256 cappedPenalty) = _stakePerformance(
                          g,
                          st,
                          st._stakedDays
                      );
              
                      _emitStakeGoodAccounting(
                          stakerAddr,
                          stakeIdParam,
                          st._stakedHearts,
                          st._stakeShares,
                          payout,
                          penalty
                      );
              
                      if (cappedPenalty != 0) {
                          _splitPenaltyProceeds(g, cappedPenalty);
                      }
              
                      /* st._unlockedDay has changed */
                      _stakeUpdate(stRef, st);
              
                      _globalsSync(g, gSnapshot);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Closes a stake. The order of the stake list can change so
                   * a stake id is used to reject stale indexes.
                   * @param stakeIndex Index of stake within stake list
                   * @param stakeIdParam The stake's id
                   */
                  function stakeEnd(uint256 stakeIndex, uint40 stakeIdParam)
                      external
                  {
                      GlobalsCache memory g;
                      GlobalsCache memory gSnapshot;
                      _globalsLoad(g, gSnapshot);
              
                      StakeStore[] storage stakeListRef = stakeLists[msg.sender];
              
                      /* require() is more informative than the default assert() */
                      require(stakeListRef.length != 0, "HEX: Empty stake list");
                      require(stakeIndex < stakeListRef.length, "HEX: stakeIndex invalid");
              
                      /* Get stake copy */
                      StakeCache memory st;
                      _stakeLoad(stakeListRef[stakeIndex], stakeIdParam, st);
              
                      /* Check if log data needs to be updated */
                      _dailyDataUpdateAuto(g);
              
                      uint256 servedDays = 0;
              
                      bool prevUnlocked = (st._unlockedDay != 0);
                      uint256 stakeReturn;
                      uint256 payout = 0;
                      uint256 penalty = 0;
                      uint256 cappedPenalty = 0;
              
                      if (g._currentDay >= st._lockedDay) {
                          if (prevUnlocked) {
                              /* Previously unlocked in stakeGoodAccounting(), so must have served full term */
                              servedDays = st._stakedDays;
                          } else {
                              _stakeUnlock(g, st);
              
                              servedDays = g._currentDay - st._lockedDay;
                              if (servedDays > st._stakedDays) {
                                  servedDays = st._stakedDays;
                              } else {
                                  /* Deny early-unstake before an auto-stake minimum has been served */
                                  if (servedDays < MIN_AUTO_STAKE_DAYS) {
                                      require(!st._isAutoStake, "HEX: Auto-stake still locked");
                                  }
                              }
                          }
              
                          (stakeReturn, payout, penalty, cappedPenalty) = _stakePerformance(g, st, servedDays);
                      } else {
                          /* Deny early-unstake before an auto-stake minimum has been served */
                          require(!st._isAutoStake, "HEX: Auto-stake still locked");
              
                          /* Stake hasn't been added to the total yet, so no penalties or rewards apply */
                          g._nextStakeSharesTotal -= st._stakeShares;
              
                          stakeReturn = st._stakedHearts;
                      }
              
                      _emitStakeEnd(
                          stakeIdParam,
                          st._stakedHearts,
                          st._stakeShares,
                          payout,
                          penalty,
                          servedDays,
                          prevUnlocked
                      );
              
                      if (cappedPenalty != 0 && !prevUnlocked) {
                          /* Split penalty proceeds only if not previously unlocked by stakeGoodAccounting() */
                          _splitPenaltyProceeds(g, cappedPenalty);
                      }
              
                      /* Pay the stake return, if any, to the staker */
                      if (stakeReturn != 0) {
                          _mint(msg.sender, stakeReturn);
              
                          /* Update the share rate if necessary */
                          _shareRateUpdate(g, st, stakeReturn);
                      }
                      g._lockedHeartsTotal -= st._stakedHearts;
              
                      _stakeRemove(stakeListRef, stakeIndex);
              
                      _globalsSync(g, gSnapshot);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Return the current stake count for a staker address
                   * @param stakerAddr Address of staker
                   */
                  function stakeCount(address stakerAddr)
                      external
                      view
                      returns (uint256)
                  {
                      return stakeLists[stakerAddr].length;
                  }
              
                  /**
                   * @dev Open a stake.
                   * @param g Cache of stored globals
                   * @param newStakedHearts Number of Hearts to stake
                   * @param newStakedDays Number of days to stake
                   * @param newAutoStake Stake is automatic directly from a new claim
                   */
                  function _stakeStart(
                      GlobalsCache memory g,
                      uint256 newStakedHearts,
                      uint256 newStakedDays,
                      bool newAutoStake
                  )
                      internal
                  {
                      /* Enforce the maximum stake time */
                      require(newStakedDays <= MAX_STAKE_DAYS, "HEX: newStakedDays higher than maximum");
              
                      uint256 bonusHearts = _stakeStartBonusHearts(newStakedHearts, newStakedDays);
                      uint256 newStakeShares = (newStakedHearts + bonusHearts) * SHARE_RATE_SCALE / g._shareRate;
              
                      /* Ensure newStakedHearts is enough for at least one stake share */
                      require(newStakeShares != 0, "HEX: newStakedHearts must be at least minimum shareRate");
              
                      /*
                          The stakeStart timestamp will always be part-way through the current
                          day, so it needs to be rounded-up to the next day to ensure all
                          stakes align with the same fixed calendar days. The current day is
                          already rounded-down, so rounded-up is current day + 1.
                      */
                      uint256 newLockedDay = g._currentDay < CLAIM_PHASE_START_DAY
                          ? CLAIM_PHASE_START_DAY + 1
                          : g._currentDay + 1;
              
                      /* Create Stake */
                      uint40 newStakeId = ++g._latestStakeId;
                      _stakeAdd(
                          stakeLists[msg.sender],
                          newStakeId,
                          newStakedHearts,
                          newStakeShares,
                          newLockedDay,
                          newStakedDays,
                          newAutoStake
                      );
              
                      _emitStakeStart(newStakeId, newStakedHearts, newStakeShares, newStakedDays, newAutoStake);
              
                      /* Stake is added to total in the next round, not the current round */
                      g._nextStakeSharesTotal += newStakeShares;
              
                      /* Track total staked Hearts for inflation calculations */
                      g._lockedHeartsTotal += newStakedHearts;
                  }
              
                  /**
                   * @dev Calculates total stake payout including rewards for a multi-day range
                   * @param g Cache of stored globals
                   * @param stakeSharesParam Param from stake to calculate bonuses for
                   * @param beginDay First day to calculate bonuses for
                   * @param endDay Last day (non-inclusive) of range to calculate bonuses for
                   * @return Payout in Hearts
                   */
                  function _calcPayoutRewards(
                      GlobalsCache memory g,
                      uint256 stakeSharesParam,
                      uint256 beginDay,
                      uint256 endDay
                  )
                      private
                      view
                      returns (uint256 payout)
                  {
                      for (uint256 day = beginDay; day < endDay; day++) {
                          payout += dailyData[day].dayPayoutTotal * stakeSharesParam
                              / dailyData[day].dayStakeSharesTotal;
                      }
              
                      /* Less expensive to re-read storage than to have the condition inside the loop */
                      if (beginDay <= BIG_PAY_DAY && endDay > BIG_PAY_DAY) {
                          uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam
                              / dailyData[BIG_PAY_DAY].dayStakeSharesTotal;
              
                          payout += bigPaySlice + _calcAdoptionBonus(g, bigPaySlice);
                      }
                      return payout;
                  }
              
                  /**
                   * @dev Calculate bonus Hearts for a new stake, if any
                   * @param newStakedHearts Number of Hearts to stake
                   * @param newStakedDays Number of days to stake
                   */
                  function _stakeStartBonusHearts(uint256 newStakedHearts, uint256 newStakedDays)
                      private
                      pure
                      returns (uint256 bonusHearts)
                  {
                      /*
                          LONGER PAYS BETTER:
              
                          If longer than 1 day stake is committed to, each extra day
                          gives bonus shares of approximately 0.0548%, which is approximately 20%
                          extra per year of increased stake length committed to, but capped to a
                          maximum of 200% extra.
              
                          extraDays       =  stakedDays - 1
              
                          longerBonus%    = (extraDays / 364) * 20%
                                          = (extraDays / 364) / 5
                                          =  extraDays / 1820
                                          =  extraDays / LPB
              
                          extraDays       =  longerBonus% * 1820
                          extraDaysMax    =  longerBonusMax% * 1820
                                          =  200% * 1820
                                          =  3640
                                          =  LPB_MAX_DAYS
              
                          BIGGER PAYS BETTER:
              
                          Bonus percentage scaled 0% to 10% for the first 150M HEX of stake.
              
                          biggerBonus%    = (cappedHearts /  BPB_MAX_HEARTS) * 10%
                                          = (cappedHearts /  BPB_MAX_HEARTS) / 10
                                          =  cappedHearts / (BPB_MAX_HEARTS * 10)
                                          =  cappedHearts /  BPB
              
                          COMBINED:
              
                          combinedBonus%  =            longerBonus%  +  biggerBonus%
              
                                                    cappedExtraDays     cappedHearts
                                          =         ---------------  +  ------------
                                                          LPB               BPB
              
                                              cappedExtraDays * BPB     cappedHearts * LPB
                                          =   ---------------------  +  ------------------
                                                    LPB * BPB               LPB * BPB
              
                                              cappedExtraDays * BPB  +  cappedHearts * LPB
                                          =   --------------------------------------------
                                                                LPB  *  BPB
              
                          bonusHearts     = hearts * combinedBonus%
                                          = hearts * (cappedExtraDays * BPB  +  cappedHearts * LPB) / (LPB * BPB)
                      */
                      uint256 cappedExtraDays = 0;
              
                      /* Must be more than 1 day for Longer-Pays-Better */
                      if (newStakedDays > 1) {
                          cappedExtraDays = newStakedDays <= LPB_MAX_DAYS ? newStakedDays - 1 : LPB_MAX_DAYS;
                      }
              
                      uint256 cappedStakedHearts = newStakedHearts <= BPB_MAX_HEARTS
                          ? newStakedHearts
                          : BPB_MAX_HEARTS;
              
                      bonusHearts = cappedExtraDays * BPB + cappedStakedHearts * LPB;
                      bonusHearts = newStakedHearts * bonusHearts / (LPB * BPB);
              
                      return bonusHearts;
                  }
              
                  function _stakeUnlock(GlobalsCache memory g, StakeCache memory st)
                      private
                      pure
                  {
                      g._stakeSharesTotal -= st._stakeShares;
                      st._unlockedDay = g._currentDay;
                  }
              
                  function _stakePerformance(GlobalsCache memory g, StakeCache memory st, uint256 servedDays)
                      private
                      view
                      returns (uint256 stakeReturn, uint256 payout, uint256 penalty, uint256 cappedPenalty)
                  {
                      if (servedDays < st._stakedDays) {
                          (payout, penalty) = _calcPayoutAndEarlyPenalty(
                              g,
                              st._lockedDay,
                              st._stakedDays,
                              servedDays,
                              st._stakeShares
                          );
                          stakeReturn = st._stakedHearts + payout;
                      } else {
                          // servedDays must == stakedDays here
                          payout = _calcPayoutRewards(
                              g,
                              st._stakeShares,
                              st._lockedDay,
                              st._lockedDay + servedDays
                          );
                          stakeReturn = st._stakedHearts + payout;
              
                          penalty = _calcLatePenalty(st._lockedDay, st._stakedDays, st._unlockedDay, stakeReturn);
                      }
                      if (penalty != 0) {
                          if (penalty > stakeReturn) {
                              /* Cannot have a negative stake return */
                              cappedPenalty = stakeReturn;
                              stakeReturn = 0;
                          } else {
                              /* Remove penalty from the stake return */
                              cappedPenalty = penalty;
                              stakeReturn -= cappedPenalty;
                          }
                      }
                      return (stakeReturn, payout, penalty, cappedPenalty);
                  }
              
                  function _calcPayoutAndEarlyPenalty(
                      GlobalsCache memory g,
                      uint256 lockedDayParam,
                      uint256 stakedDaysParam,
                      uint256 servedDays,
                      uint256 stakeSharesParam
                  )
                      private
                      view
                      returns (uint256 payout, uint256 penalty)
                  {
                      uint256 servedEndDay = lockedDayParam + servedDays;
              
                      /* 50% of stakedDays (rounded up) with a minimum applied */
                      uint256 penaltyDays = (stakedDaysParam + 1) / 2;
                      if (penaltyDays < EARLY_PENALTY_MIN_DAYS) {
                          penaltyDays = EARLY_PENALTY_MIN_DAYS;
                      }
              
                      if (servedDays == 0) {
                          /* Fill penalty days with the estimated average payout */
                          uint256 expected = _estimatePayoutRewardsDay(g, stakeSharesParam, lockedDayParam);
                          penalty = expected * penaltyDays;
                          return (payout, penalty); // Actual payout was 0
                      }
              
                      if (penaltyDays < servedDays) {
                          /*
                              Simplified explanation of intervals where end-day is non-inclusive:
              
                              penalty:    [lockedDay  ...  penaltyEndDay)
                              delta:                      [penaltyEndDay  ...  servedEndDay)
                              payout:     [lockedDay  .......................  servedEndDay)
                          */
                          uint256 penaltyEndDay = lockedDayParam + penaltyDays;
                          penalty = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, penaltyEndDay);
              
                          uint256 delta = _calcPayoutRewards(g, stakeSharesParam, penaltyEndDay, servedEndDay);
                          payout = penalty + delta;
                          return (payout, penalty);
                      }
              
                      /* penaltyDays >= servedDays  */
                      payout = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, servedEndDay);
              
                      if (penaltyDays == servedDays) {
                          penalty = payout;
                      } else {
                          /*
                              (penaltyDays > servedDays) means not enough days served, so fill the
                              penalty days with the average payout from only the days that were served.
                          */
                          penalty = payout * penaltyDays / servedDays;
                      }
                      return (payout, penalty);
                  }
              
                  function _calcLatePenalty(
                      uint256 lockedDayParam,
                      uint256 stakedDaysParam,
                      uint256 unlockedDayParam,
                      uint256 rawStakeReturn
                  )
                      private
                      pure
                      returns (uint256)
                  {
                      /* Allow grace time before penalties accrue */
                      uint256 maxUnlockedDay = lockedDayParam + stakedDaysParam + LATE_PENALTY_GRACE_DAYS;
                      if (unlockedDayParam <= maxUnlockedDay) {
                          return 0;
                      }
              
                      /* Calculate penalty as a percentage of stake return based on time */
                      return rawStakeReturn * (unlockedDayParam - maxUnlockedDay) / LATE_PENALTY_SCALE_DAYS;
                  }
              
                  function _splitPenaltyProceeds(GlobalsCache memory g, uint256 penalty)
                      private
                  {
                      /* Split a penalty 50:50 between Origin and stakePenaltyTotal */
                      uint256 splitPenalty = penalty / 2;
              
                      if (splitPenalty != 0) {
                          _mint(ORIGIN_ADDR, splitPenalty);
                      }
              
                      /* Use the other half of the penalty to account for an odd-numbered penalty */
                      splitPenalty = penalty - splitPenalty;
                      g._stakePenaltyTotal += splitPenalty;
                  }
              
                  function _shareRateUpdate(GlobalsCache memory g, StakeCache memory st, uint256 stakeReturn)
                      private
                  {
                      if (stakeReturn > st._stakedHearts) {
                          /*
                              Calculate the new shareRate that would yield the same number of shares if
                              the user re-staked this stakeReturn, factoring in any bonuses they would
                              receive in stakeStart().
                          */
                          uint256 bonusHearts = _stakeStartBonusHearts(stakeReturn, st._stakedDays);
                          uint256 newShareRate = (stakeReturn + bonusHearts) * SHARE_RATE_SCALE / st._stakeShares;
              
                          if (newShareRate > SHARE_RATE_MAX) {
                              /*
                                  Realistically this can't happen, but there are contrived theoretical
                                  scenarios that can lead to extreme values of newShareRate, so it is
                                  capped to prevent them anyway.
                              */
                              newShareRate = SHARE_RATE_MAX;
                          }
              
                          if (newShareRate > g._shareRate) {
                              g._shareRate = newShareRate;
              
                              _emitShareRateChange(newShareRate, st._stakeId);
                          }
                      }
                  }
              
                  function _emitStakeStart(
                      uint40 stakeId,
                      uint256 stakedHearts,
                      uint256 stakeShares,
                      uint256 stakedDays,
                      bool isAutoStake
                  )
                      private
                  {
                      emit StakeStart( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint72(stakedHearts)) << 40)
                              | (uint256(uint72(stakeShares)) << 112)
                              | (uint256(uint16(stakedDays)) << 184)
                              | (isAutoStake ? (1 << 200) : 0),
                          msg.sender,
                          stakeId
                      );
                  }
              
                  function _emitStakeGoodAccounting(
                      address stakerAddr,
                      uint40 stakeId,
                      uint256 stakedHearts,
                      uint256 stakeShares,
                      uint256 payout,
                      uint256 penalty
                  )
                      private
                  {
                      emit StakeGoodAccounting( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint72(stakedHearts)) << 40)
                              | (uint256(uint72(stakeShares)) << 112)
                              | (uint256(uint72(payout)) << 184),
                          uint256(uint72(penalty)),
                          stakerAddr,
                          stakeId,
                          msg.sender
                      );
                  }
              
                  function _emitStakeEnd(
                      uint40 stakeId,
                      uint256 stakedHearts,
                      uint256 stakeShares,
                      uint256 payout,
                      uint256 penalty,
                      uint256 servedDays,
                      bool prevUnlocked
                  )
                      private
                  {
                      emit StakeEnd( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint72(stakedHearts)) << 40)
                              | (uint256(uint72(stakeShares)) << 112)
                              | (uint256(uint72(payout)) << 184),
                          uint256(uint72(penalty))
                              | (uint256(uint16(servedDays)) << 72)
                              | (prevUnlocked ? (1 << 88) : 0),
                          msg.sender,
                          stakeId
                      );
                  }
              
                  function _emitShareRateChange(uint256 shareRate, uint40 stakeId)
                      private
                  {
                      emit ShareRateChange( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint40(shareRate)) << 40),
                          stakeId
                      );
                  }
              }
              
              /**
               * @dev These functions deal with verification of Merkle trees (hash trees),
               */
              library MerkleProof {
                  /**
                   * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
                   * defined by `root`. For this, a `proof` must be provided, containing
                   * sibling hashes on the branch from the leaf to the root of the tree. Each
                   * pair of leaves and each pair of pre-images are assumed to be sorted.
                   */
                  function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
                      bytes32 computedHash = leaf;
              
                      for (uint256 i = 0; i < proof.length; i++) {
                          bytes32 proofElement = proof[i];
              
                          if (computedHash < proofElement) {
                              // Hash(current computed hash + current element of the proof)
                              computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
                          } else {
                              // Hash(current element of the proof + current computed hash)
                              computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
                          }
                      }
              
                      // Check if the computed hash (root) is equal to the provided root
                      return computedHash == root;
                  }
              }
              
              contract UTXOClaimValidation is StakeableToken {
                  /**
                   * @dev PUBLIC FACING: Verify a BTC address and balance are unclaimed and part of the Merkle tree
                   * @param btcAddr Bitcoin address (binary; no base58-check encoding)
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @param proof Merkle tree proof
                   * @return True if can be claimed
                   */
                  function btcAddressIsClaimable(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
                      external
                      view
                      returns (bool)
                  {
                      uint256 day = _currentDay();
              
                      require(day >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started");
                      require(day < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended");
              
                      /* Don't need to check Merkle proof if UTXO BTC address has already been claimed    */
                      if (btcAddressClaims[btcAddr]) {
                          return false;
                      }
              
                      /* Verify the Merkle tree proof */
                      return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Verify a BTC address and balance are part of the Merkle tree
                   * @param btcAddr Bitcoin address (binary; no base58-check encoding)
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @param proof Merkle tree proof
                   * @return True if valid
                   */
                  function btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
                      external
                      pure
                      returns (bool)
                  {
                      return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Verify a Merkle proof using the UTXO Merkle tree
                   * @param merkleLeaf Leaf asserted to be present in the Merkle tree
                   * @param proof Generated Merkle tree proof
                   * @return True if valid
                   */
                  function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] calldata proof)
                      external
                      pure
                      returns (bool)
                  {
                      return _merkleProofIsValid(merkleLeaf, proof);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Verify that a Bitcoin signature matches the claim message containing
                   * the Ethereum address and claim param hash
                   * @param claimToAddr Eth address within the signed claim message
                   * @param claimParamHash Param hash within the signed claim message
                   * @param pubKeyX First  half of uncompressed ECDSA public key
                   * @param pubKeyY Second half of uncompressed ECDSA public key
                   * @param claimFlags Claim flags specifying address and message formats
                   * @param v v parameter of ECDSA signature
                   * @param r r parameter of ECDSA signature
                   * @param s s parameter of ECDSA signature
                   * @return True if matching
                   */
                  function claimMessageMatchesSignature(
                      address claimToAddr,
                      bytes32 claimParamHash,
                      bytes32 pubKeyX,
                      bytes32 pubKeyY,
                      uint8 claimFlags,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  )
                      public
                      pure
                      returns (bool)
                  {
                      require(v >= 27 && v <= 30, "HEX: v invalid");
              
                      /*
                          ecrecover() returns an Eth address rather than a public key, so
                          we must do the same to compare.
                      */
                      address pubKeyEthAddr = pubKeyToEthAddress(pubKeyX, pubKeyY);
              
                      /* Create and hash the claim message text */
                      bytes32 messageHash = _hash256(
                          _claimMessageCreate(claimToAddr, claimParamHash, claimFlags)
                      );
              
                      /* Verify the public key */
                      return ecrecover(messageHash, v, r, s) == pubKeyEthAddr;
                  }
              
                  /**
                   * @dev PUBLIC FACING: Derive an Ethereum address from an ECDSA public key
                   * @param pubKeyX First  half of uncompressed ECDSA public key
                   * @param pubKeyY Second half of uncompressed ECDSA public key
                   * @return Derived Eth address
                   */
                  function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY)
                      public
                      pure
                      returns (address)
                  {
                      return address(uint160(uint256(keccak256(abi.encodePacked(pubKeyX, pubKeyY)))));
                  }
              
                  /**
                   * @dev PUBLIC FACING: Derive a Bitcoin address from an ECDSA public key
                   * @param pubKeyX First  half of uncompressed ECDSA public key
                   * @param pubKeyY Second half of uncompressed ECDSA public key
                   * @param claimFlags Claim flags specifying address and message formats
                   * @return Derived Bitcoin address (binary; no base58-check encoding)
                   */
                  function pubKeyToBtcAddress(bytes32 pubKeyX, bytes32 pubKeyY, uint8 claimFlags)
                      public
                      pure
                      returns (bytes20)
                  {
                      /*
                          Helpful references:
                           - https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
                           - https://github.com/cryptocoinjs/ecurve/blob/master/lib/point.js
                      */
                      uint8 startingByte;
                      bytes memory pubKey;
                      bool compressed = (claimFlags & CLAIM_FLAG_BTC_ADDR_COMPRESSED) != 0;
                      bool nested = (claimFlags & CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH) != 0;
                      bool bech32 = (claimFlags & CLAIM_FLAG_BTC_ADDR_BECH32) != 0;
              
                      if (compressed) {
                          /* Compressed public key format */
                          require(!(nested && bech32), "HEX: claimFlags invalid");
              
                          startingByte = (pubKeyY[31] & 0x01) == 0 ? 0x02 : 0x03;
                          pubKey = abi.encodePacked(startingByte, pubKeyX);
                      } else {
                          /* Uncompressed public key format */
                          require(!nested && !bech32, "HEX: claimFlags invalid");
              
                          startingByte = 0x04;
                          pubKey = abi.encodePacked(startingByte, pubKeyX, pubKeyY);
                      }
              
                      bytes20 pubKeyHash = _hash160(pubKey);
                      if (nested) {
                          return _hash160(abi.encodePacked(hex"0014", pubKeyHash));
                      }
                      return pubKeyHash;
                  }
              
                  /**
                   * @dev Verify a BTC address and balance are part of the Merkle tree
                   * @param btcAddr Bitcoin address (binary; no base58-check encoding)
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @param proof Merkle tree proof
                   * @return True if valid
                   */
                  function _btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] memory proof)
                      internal
                      pure
                      returns (bool)
                  {
                      /*
                          Ensure the proof does not attempt to treat a Merkle leaf as if it were an
                          internal Merkle tree node. A leaf will always have the zero-fill. An
                          internal node will never have the zero-fill, as guaranteed by HEX's Merkle
                          tree construction.
              
                          The first element, proof[0], will always be a leaf because it is the pair
                          of the leaf being validated. The rest of the elements, proof[1..length-1],
                          must be internal nodes.
              
                          The number of leaves (CLAIMABLE_BTC_ADDR_COUNT) is even, as guaranteed by
                          HEX's Merkle tree construction, which eliminates the only edge-case where
                          this validation would not apply.
                      */
                      require((uint256(proof[0]) & MERKLE_LEAF_FILL_MASK) == 0, "HEX: proof invalid");
                      for (uint256 i = 1; i < proof.length; i++) {
                          require((uint256(proof[i]) & MERKLE_LEAF_FILL_MASK) != 0, "HEX: proof invalid");
                      }
              
                      /*
                          Calculate the 32 byte Merkle leaf associated with this BTC address and balance
                              160 bits: BTC address
                               52 bits: Zero-fill
                               45 bits: Satoshis (limited by MAX_BTC_ADDR_BALANCE_SATOSHIS)
                      */
                      bytes32 merkleLeaf = bytes32(btcAddr) | bytes32(rawSatoshis);
              
                      /* Verify the Merkle tree proof */
                      return _merkleProofIsValid(merkleLeaf, proof);
                  }
              
                  /**
                   * @dev Verify a Merkle proof using the UTXO Merkle tree
                   * @param merkleLeaf Leaf asserted to be present in the Merkle tree
                   * @param proof Generated Merkle tree proof
                   * @return True if valid
                   */
                  function _merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof)
                      private
                      pure
                      returns (bool)
                  {
                      return MerkleProof.verify(proof, MERKLE_TREE_ROOT, merkleLeaf);
                  }
              
                  function _claimMessageCreate(address claimToAddr, bytes32 claimParamHash, uint8 claimFlags)
                      private
                      pure
                      returns (bytes memory)
                  {
                      bytes memory prefixStr = (claimFlags & CLAIM_FLAG_MSG_PREFIX_OLD) != 0
                          ? OLD_CLAIM_PREFIX_STR
                          : STD_CLAIM_PREFIX_STR;
              
                      bool includeAddrChecksum = (claimFlags & CLAIM_FLAG_ETH_ADDR_LOWERCASE) == 0;
              
                      bytes memory addrStr = _addressStringCreate(claimToAddr, includeAddrChecksum);
              
                      if (claimParamHash == 0) {
                          return abi.encodePacked(
                              BITCOIN_SIG_PREFIX_LEN,
                              BITCOIN_SIG_PREFIX_STR,
                              uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN,
                              prefixStr,
                              addrStr
                          );
                      }
              
                      bytes memory claimParamHashStr = new bytes(CLAIM_PARAM_HASH_HEX_LEN);
              
                      _hexStringFromData(claimParamHashStr, claimParamHash, CLAIM_PARAM_HASH_BYTE_LEN);
              
                      return abi.encodePacked(
                          BITCOIN_SIG_PREFIX_LEN,
                          BITCOIN_SIG_PREFIX_STR,
                          uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN + 1 + CLAIM_PARAM_HASH_HEX_LEN,
                          prefixStr,
                          addrStr,
                          "_",
                          claimParamHashStr
                      );
                  }
              
                  function _addressStringCreate(address addr, bool includeAddrChecksum)
                      private
                      pure
                      returns (bytes memory addrStr)
                  {
                      addrStr = new bytes(ETH_ADDRESS_HEX_LEN);
                      _hexStringFromData(addrStr, bytes32(bytes20(addr)), ETH_ADDRESS_BYTE_LEN);
              
                      if (includeAddrChecksum) {
                          bytes32 addrStrHash = keccak256(addrStr);
              
                          uint256 offset = 0;
              
                          for (uint256 i = 0; i < ETH_ADDRESS_BYTE_LEN; i++) {
                              uint8 b = uint8(addrStrHash[i]);
              
                              _addressStringChecksumChar(addrStr, offset++, b >> 4);
                              _addressStringChecksumChar(addrStr, offset++, b & 0x0f);
                          }
                      }
              
                      return addrStr;
                  }
              
                  function _addressStringChecksumChar(bytes memory addrStr, uint256 offset, uint8 hashNybble)
                      private
                      pure
                  {
                      bytes1 ch = addrStr[offset];
              
                      if (ch >= "a" && hashNybble >= 8) {
                          addrStr[offset] = ch ^ 0x20;
                      }
                  }
              
                  function _hexStringFromData(bytes memory hexStr, bytes32 data, uint256 dataLen)
                      private
                      pure
                  {
                      uint256 offset = 0;
              
                      for (uint256 i = 0; i < dataLen; i++) {
                          uint8 b = uint8(data[i]);
              
                          hexStr[offset++] = HEX_DIGITS[b >> 4];
                          hexStr[offset++] = HEX_DIGITS[b & 0x0f];
                      }
                  }
              
                  /**
                   * @dev sha256(sha256(data))
                   * @param data Data to be hashed
                   * @return 32-byte hash
                   */
                  function _hash256(bytes memory data)
                      private
                      pure
                      returns (bytes32)
                  {
                      return sha256(abi.encodePacked(sha256(data)));
                  }
              
                  /**
                   * @dev ripemd160(sha256(data))
                   * @param data Data to be hashed
                   * @return 20-byte hash
                   */
                  function _hash160(bytes memory data)
                      private
                      pure
                      returns (bytes20)
                  {
                      return ripemd160(abi.encodePacked(sha256(data)));
                  }
              }
              
              contract UTXORedeemableToken is UTXOClaimValidation {
                  /**
                   * @dev PUBLIC FACING: Claim a BTC address and its Satoshi balance in Hearts
                   * crediting the appropriate amount to a specified Eth address. Bitcoin ECDSA
                   * signature must be from that BTC address and must match the claim message
                   * for the Eth address.
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @param proof Merkle tree proof
                   * @param claimToAddr Destination Eth address to credit Hearts to
                   * @param pubKeyX First  half of uncompressed ECDSA public key for the BTC address
                   * @param pubKeyY Second half of uncompressed ECDSA public key for the BTC address
                   * @param claimFlags Claim flags specifying address and message formats
                   * @param v v parameter of ECDSA signature
                   * @param r r parameter of ECDSA signature
                   * @param s s parameter of ECDSA signature
                   * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days
                   * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
                   * @return Total number of Hearts credited, if successful
                   */
                  function btcAddressClaim(
                      uint256 rawSatoshis,
                      bytes32[] calldata proof,
                      address claimToAddr,
                      bytes32 pubKeyX,
                      bytes32 pubKeyY,
                      uint8 claimFlags,
                      uint8 v,
                      bytes32 r,
                      bytes32 s,
                      uint256 autoStakeDays,
                      address referrerAddr
                  )
                      external
                      returns (uint256)
                  {
                      /* Sanity check */
                      require(rawSatoshis <= MAX_BTC_ADDR_BALANCE_SATOSHIS, "HEX: CHK: rawSatoshis");
              
                      /* Enforce the minimum stake time for the auto-stake from this claim */
                      require(autoStakeDays >= MIN_AUTO_STAKE_DAYS, "HEX: autoStakeDays lower than minimum");
              
                      /* Ensure signature matches the claim message containing the Eth address and claimParamHash */
                      {
                          bytes32 claimParamHash = 0;
              
                          if (claimToAddr != msg.sender) {
                              /* Claimer did not send this, so claim params must be signed */
                              claimParamHash = keccak256(
                                  abi.encodePacked(MERKLE_TREE_ROOT, autoStakeDays, referrerAddr)
                              );
                          }
              
                          require(
                              claimMessageMatchesSignature(
                                  claimToAddr,
                                  claimParamHash,
                                  pubKeyX,
                                  pubKeyY,
                                  claimFlags,
                                  v,
                                  r,
                                  s
                              ),
                              "HEX: Signature mismatch"
                          );
                      }
              
                      /* Derive BTC address from public key */
                      bytes20 btcAddr = pubKeyToBtcAddress(pubKeyX, pubKeyY, claimFlags);
              
                      /* Ensure BTC address has not yet been claimed */
                      require(!btcAddressClaims[btcAddr], "HEX: BTC address balance already claimed");
              
                      /* Ensure BTC address is part of the Merkle tree */
                      require(
                          _btcAddressIsValid(btcAddr, rawSatoshis, proof),
                          "HEX: BTC address or balance unknown"
                      );
              
                      /* Mark BTC address as claimed */
                      btcAddressClaims[btcAddr] = true;
              
                      return _satoshisClaimSync(
                          rawSatoshis,
                          claimToAddr,
                          btcAddr,
                          claimFlags,
                          autoStakeDays,
                          referrerAddr
                      );
                  }
              
                  function _satoshisClaimSync(
                      uint256 rawSatoshis,
                      address claimToAddr,
                      bytes20 btcAddr,
                      uint8 claimFlags,
                      uint256 autoStakeDays,
                      address referrerAddr
                  )
                      private
                      returns (uint256 totalClaimedHearts)
                  {
                      GlobalsCache memory g;
                      GlobalsCache memory gSnapshot;
                      _globalsLoad(g, gSnapshot);
              
                      totalClaimedHearts = _satoshisClaim(
                          g,
                          rawSatoshis,
                          claimToAddr,
                          btcAddr,
                          claimFlags,
                          autoStakeDays,
                          referrerAddr
                      );
              
                      _globalsSync(g, gSnapshot);
              
                      return totalClaimedHearts;
                  }
              
                  /**
                   * @dev Credit an Eth address with the Hearts value of a raw Satoshis balance
                   * @param g Cache of stored globals
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @param claimToAddr Destination Eth address for the claimed Hearts to be sent
                   * @param btcAddr Bitcoin address (binary; no base58-check encoding)
                   * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days
                   * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
                   * @return Total number of Hearts credited, if successful
                   */
                  function _satoshisClaim(
                      GlobalsCache memory g,
                      uint256 rawSatoshis,
                      address claimToAddr,
                      bytes20 btcAddr,
                      uint8 claimFlags,
                      uint256 autoStakeDays,
                      address referrerAddr
                  )
                      private
                      returns (uint256 totalClaimedHearts)
                  {
                      /* Allowed only during the claim phase */
                      require(g._currentDay >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started");
                      require(g._currentDay < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended");
              
                      /* Check if log data needs to be updated */
                      _dailyDataUpdateAuto(g);
              
                      /* Sanity check */
                      require(
                          g._claimedBtcAddrCount < CLAIMABLE_BTC_ADDR_COUNT,
                          "HEX: CHK: _claimedBtcAddrCount"
                      );
              
                      (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts) = _calcClaimValues(
                          g,
                          rawSatoshis
                      );
              
                      /* Increment claim count to track viral rewards */
                      g._claimedBtcAddrCount++;
              
                      totalClaimedHearts = _remitBonuses(
                          claimToAddr,
                          btcAddr,
                          claimFlags,
                          rawSatoshis,
                          adjSatoshis,
                          claimedHearts,
                          claimBonusHearts,
                          referrerAddr
                      );
              
                      /* Auto-stake a percentage of the successful claim */
                      uint256 autoStakeHearts = totalClaimedHearts * AUTO_STAKE_CLAIM_PERCENT / 100;
                      _stakeStart(g, autoStakeHearts, autoStakeDays, true);
              
                      /* Mint remaining claimed Hearts to claim address */
                      _mint(claimToAddr, totalClaimedHearts - autoStakeHearts);
              
                      return totalClaimedHearts;
                  }
              
                  function _remitBonuses(
                      address claimToAddr,
                      bytes20 btcAddr,
                      uint8 claimFlags,
                      uint256 rawSatoshis,
                      uint256 adjSatoshis,
                      uint256 claimedHearts,
                      uint256 claimBonusHearts,
                      address referrerAddr
                  )
                      private
                      returns (uint256 totalClaimedHearts)
                  {
                      totalClaimedHearts = claimedHearts + claimBonusHearts;
              
                      uint256 originBonusHearts = claimBonusHearts;
              
                      if (referrerAddr == address(0)) {
                          /* No referrer */
                          _emitClaim(
                              claimToAddr,
                              btcAddr,
                              claimFlags,
                              rawSatoshis,
                              adjSatoshis,
                              totalClaimedHearts,
                              referrerAddr
                          );
                      } else {
                          /* Referral bonus of 10% of total claimed Hearts to claimer */
                          uint256 referralBonusHearts = totalClaimedHearts / 10;
              
                          totalClaimedHearts += referralBonusHearts;
              
                          /* Then a cumulative referrer bonus of 20% to referrer */
                          uint256 referrerBonusHearts = totalClaimedHearts / 5;
              
                          originBonusHearts += referralBonusHearts + referrerBonusHearts;
              
                          if (referrerAddr == claimToAddr) {
                              /* Self-referred */
                              totalClaimedHearts += referrerBonusHearts;
                              _emitClaim(
                                  claimToAddr,
                                  btcAddr,
                                  claimFlags,
                                  rawSatoshis,
                                  adjSatoshis,
                                  totalClaimedHearts,
                                  referrerAddr
                              );
                          } else {
                              /* Referred by different address */
                              _emitClaim(
                                  claimToAddr,
                                  btcAddr,
                                  claimFlags,
                                  rawSatoshis,
                                  adjSatoshis,
                                  totalClaimedHearts,
                                  referrerAddr
                              );
                              _mint(referrerAddr, referrerBonusHearts);
                          }
                      }
              
                      _mint(ORIGIN_ADDR, originBonusHearts);
              
                      return totalClaimedHearts;
                  }
              
                  function _emitClaim(
                      address claimToAddr,
                      bytes20 btcAddr,
                      uint8 claimFlags,
                      uint256 rawSatoshis,
                      uint256 adjSatoshis,
                      uint256 claimedHearts,
                      address referrerAddr
                  )
                      private
                  {
                      emit Claim( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint56(rawSatoshis)) << 40)
                              | (uint256(uint56(adjSatoshis)) << 96)
                              | (uint256(claimFlags) << 152)
                              | (uint256(uint72(claimedHearts)) << 160),
                          uint256(uint160(msg.sender)),
                          btcAddr,
                          claimToAddr,
                          referrerAddr
                      );
              
                      if (claimToAddr == msg.sender) {
                          return;
                      }
              
                      emit ClaimAssist( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint160(btcAddr)) << 40)
                              | (uint256(uint56(rawSatoshis)) << 200),
                          uint256(uint56(adjSatoshis))
                              | (uint256(uint160(claimToAddr)) << 56)
                              | (uint256(claimFlags) << 216),
                          uint256(uint72(claimedHearts))
                              | (uint256(uint160(referrerAddr)) << 72),
                          msg.sender
                      );
                  }
              
                  function _calcClaimValues(GlobalsCache memory g, uint256 rawSatoshis)
                      private
                      pure
                      returns (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts)
                  {
                      /* Apply Silly Whale reduction */
                      adjSatoshis = _adjustSillyWhale(rawSatoshis);
                      require(
                          g._claimedSatoshisTotal + adjSatoshis <= CLAIMABLE_SATOSHIS_TOTAL,
                          "HEX: CHK: _claimedSatoshisTotal"
                      );
                      g._claimedSatoshisTotal += adjSatoshis;
              
                      uint256 daysRemaining = CLAIM_PHASE_END_DAY - g._currentDay;
              
                      /* Apply late-claim reduction */
                      adjSatoshis = _adjustLateClaim(adjSatoshis, daysRemaining);
                      g._unclaimedSatoshisTotal -= adjSatoshis;
              
                      /* Convert to Hearts and calculate speed bonus */
                      claimedHearts = adjSatoshis * HEARTS_PER_SATOSHI;
                      claimBonusHearts = _calcSpeedBonus(claimedHearts, daysRemaining);
              
                      return (adjSatoshis, claimedHearts, claimBonusHearts);
                  }
              
                  /**
                   * @dev Apply Silly Whale adjustment
                   * @param rawSatoshis Raw BTC address balance in Satoshis
                   * @return Adjusted BTC address balance in Satoshis
                   */
                  function _adjustSillyWhale(uint256 rawSatoshis)
                      private
                      pure
                      returns (uint256)
                  {
                      if (rawSatoshis < 1000e8) {
                          /* For < 1,000 BTC: no penalty */
                          return rawSatoshis;
                      }
                      if (rawSatoshis >= 10000e8) {
                          /* For >= 10,000 BTC: penalty is 75%, leaving 25% */
                          return rawSatoshis / 4;
                      }
                      /*
                          For 1,000 <= BTC < 10,000: penalty scales linearly from 50% to 75%
              
                          penaltyPercent  = (btc - 1000) / (10000 - 1000) * (75 - 50) + 50
                                          = (btc - 1000) / 9000 * 25 + 50
                                          = (btc - 1000) / 360 + 50
              
                          appliedPercent  = 100 - penaltyPercent
                                          = 100 - ((btc - 1000) / 360 + 50)
                                          = 100 - (btc - 1000) / 360 - 50
                                          = 50 - (btc - 1000) / 360
                                          = (18000 - (btc - 1000)) / 360
                                          = (18000 - btc + 1000) / 360
                                          = (19000 - btc) / 360
              
                          adjustedBtc     = btc * appliedPercent / 100
                                          = btc * ((19000 - btc) / 360) / 100
                                          = btc * (19000 - btc) / 36000
              
                          adjustedSat     = 1e8 * adjustedBtc
                                          = 1e8 * (btc * (19000 - btc) / 36000)
                                          = 1e8 * ((sat / 1e8) * (19000 - (sat / 1e8)) / 36000)
                                          = 1e8 * (sat / 1e8) * (19000 - (sat / 1e8)) / 36000
                                          = (sat / 1e8) * 1e8 * (19000 - (sat / 1e8)) / 36000
                                          = (sat / 1e8) * (19000e8 - sat) / 36000
                                          = sat * (19000e8 - sat) / 36000e8
                      */
                      return rawSatoshis * (19000e8 - rawSatoshis) / 36000e8;
                  }
              
                  /**
                   * @dev Apply late-claim adjustment to scale claim to zero by end of claim phase
                   * @param adjSatoshis Adjusted BTC address balance in Satoshis (after Silly Whale)
                   * @param daysRemaining Number of reward days remaining in claim phase
                   * @return Adjusted BTC address balance in Satoshis (after Silly Whale and Late-Claim)
                   */
                  function _adjustLateClaim(uint256 adjSatoshis, uint256 daysRemaining)
                      private
                      pure
                      returns (uint256)
                  {
                      /*
                          Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time.
              
                          adjustedSat = sat * (daysRemaining / CLAIM_PHASE_DAYS) * 100%
                                      = sat *  daysRemaining / CLAIM_PHASE_DAYS
                      */
                      return adjSatoshis * daysRemaining / CLAIM_PHASE_DAYS;
                  }
              
                  /**
                   * @dev Calculates speed bonus for claiming earlier in the claim phase
                   * @param claimedHearts Hearts claimed from adjusted BTC address balance Satoshis
                   * @param daysRemaining Number of claim days remaining in claim phase
                   * @return Speed bonus in Hearts
                   */
                  function _calcSpeedBonus(uint256 claimedHearts, uint256 daysRemaining)
                      private
                      pure
                      returns (uint256)
                  {
                      /*
                          Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time.
                          Speed bonus is 20% ... 0% inclusive.
              
                          bonusHearts = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) * 20%
                                      = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) * 20/100
                                      = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) / 5
                                      = claimedHearts  *  (daysRemaining - 1)  / ((CLAIM_PHASE_DAYS - 1)  * 5)
                      */
                      return claimedHearts * (daysRemaining - 1) / ((CLAIM_PHASE_DAYS - 1) * 5);
                  }
              }
              
              contract TransformableToken is UTXORedeemableToken {
                  /**
                   * @dev PUBLIC FACING: Enter the tranform lobby for the current round
                   * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
                   */
                  function xfLobbyEnter(address referrerAddr)
                      external
                      payable
                  {
                      uint256 enterDay = _currentDay();
                      require(enterDay < CLAIM_PHASE_END_DAY, "HEX: Lobbies have ended");
              
                      uint256 rawAmount = msg.value;
                      require(rawAmount != 0, "HEX: Amount required");
              
                      XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender];
              
                      uint256 entryIndex = qRef.tailIndex++;
              
                      qRef.entries[entryIndex] = XfLobbyEntryStore(uint96(rawAmount), referrerAddr);
              
                      xfLobby[enterDay] += rawAmount;
              
                      _emitXfLobbyEnter(enterDay, entryIndex, rawAmount, referrerAddr);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Leave the transform lobby after the round is complete
                   * @param enterDay Day number when the member entered
                   * @param count Number of queued-enters to exit (optional; 0 for all)
                   */
                  function xfLobbyExit(uint256 enterDay, uint256 count)
                      external
                  {
                      require(enterDay < _currentDay(), "HEX: Round is not complete");
              
                      XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender];
              
                      uint256 headIndex = qRef.headIndex;
                      uint256 endIndex;
              
                      if (count != 0) {
                          require(count <= qRef.tailIndex - headIndex, "HEX: count invalid");
                          endIndex = headIndex + count;
                      } else {
                          endIndex = qRef.tailIndex;
                          require(headIndex < endIndex, "HEX: count invalid");
                      }
              
                      uint256 waasLobby = _waasLobby(enterDay);
                      uint256 _xfLobby = xfLobby[enterDay];
                      uint256 totalXfAmount = 0;
                      uint256 originBonusHearts = 0;
              
                      do {
                          uint256 rawAmount = qRef.entries[headIndex].rawAmount;
                          address referrerAddr = qRef.entries[headIndex].referrerAddr;
              
                          delete qRef.entries[headIndex];
              
                          uint256 xfAmount = waasLobby * rawAmount / _xfLobby;
              
                          if (referrerAddr == address(0)) {
                              /* No referrer */
                              _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                          } else {
                              /* Referral bonus of 10% of xfAmount to member */
                              uint256 referralBonusHearts = xfAmount / 10;
              
                              xfAmount += referralBonusHearts;
              
                              /* Then a cumulative referrer bonus of 20% to referrer */
                              uint256 referrerBonusHearts = xfAmount / 5;
              
                              if (referrerAddr == msg.sender) {
                                  /* Self-referred */
                                  xfAmount += referrerBonusHearts;
                                  _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                              } else {
                                  /* Referred by different address */
                                  _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                                  _mint(referrerAddr, referrerBonusHearts);
                              }
                              originBonusHearts += referralBonusHearts + referrerBonusHearts;
                          }
              
                          totalXfAmount += xfAmount;
                      } while (++headIndex < endIndex);
              
                      qRef.headIndex = uint40(headIndex);
              
                      if (originBonusHearts != 0) {
                          _mint(ORIGIN_ADDR, originBonusHearts);
                      }
                      if (totalXfAmount != 0) {
                          _mint(msg.sender, totalXfAmount);
                      }
                  }
              
                  /**
                   * @dev PUBLIC FACING: Release any value that has been sent to the contract
                   */
                  function xfLobbyFlush()
                      external
                  {
                      require(address(this).balance != 0, "HEX: No value");
              
                      FLUSH_ADDR.transfer(address(this).balance);
                  }
              
                  /**
                   * @dev PUBLIC FACING: External helper to return multiple values of xfLobby[] with
                   * a single call
                   * @param beginDay First day of data range
                   * @param endDay Last day (non-inclusive) of data range
                   * @return Fixed array of values
                   */
                  function xfLobbyRange(uint256 beginDay, uint256 endDay)
                      external
                      view
                      returns (uint256[] memory list)
                  {
                      require(
                          beginDay < endDay && endDay <= CLAIM_PHASE_END_DAY && endDay <= _currentDay(),
                          "HEX: invalid range"
                      );
              
                      list = new uint256[](endDay - beginDay);
              
                      uint256 src = beginDay;
                      uint256 dst = 0;
                      do {
                          list[dst++] = uint256(xfLobby[src++]);
                      } while (src < endDay);
              
                      return list;
                  }
              
                  /**
                   * @dev PUBLIC FACING: Return a current lobby member queue entry.
                   * Only needed due to limitations of the standard ABI encoder.
                   * @param memberAddr Eth address of the lobby member
                   * @param entryId 49 bit compound value. Top 9 bits: enterDay, Bottom 40 bits: entryIndex
                   * @return 1: Raw amount that was entered with; 2: Referring Eth addr (optional; 0x0 for no referrer)
                   */
                  function xfLobbyEntry(address memberAddr, uint256 entryId)
                      external
                      view
                      returns (uint256 rawAmount, address referrerAddr)
                  {
                      uint256 enterDay = entryId >> XF_LOBBY_ENTRY_INDEX_SIZE;
                      uint256 entryIndex = entryId & XF_LOBBY_ENTRY_INDEX_MASK;
              
                      XfLobbyEntryStore storage entry = xfLobbyMembers[enterDay][memberAddr].entries[entryIndex];
              
                      require(entry.rawAmount != 0, "HEX: Param invalid");
              
                      return (entry.rawAmount, entry.referrerAddr);
                  }
              
                  /**
                   * @dev PUBLIC FACING: Return the lobby days that a user is in with a single call
                   * @param memberAddr Eth address of the user
                   * @return Bit vector of lobby day numbers
                   */
                  function xfLobbyPendingDays(address memberAddr)
                      external
                      view
                      returns (uint256[XF_LOBBY_DAY_WORDS] memory words)
                  {
                      uint256 day = _currentDay() + 1;
              
                      if (day > CLAIM_PHASE_END_DAY) {
                          day = CLAIM_PHASE_END_DAY;
                      }
              
                      while (day-- != 0) {
                          if (xfLobbyMembers[day][memberAddr].tailIndex > xfLobbyMembers[day][memberAddr].headIndex) {
                              words[day >> 8] |= 1 << (day & 255);
                          }
                      }
              
                      return words;
                  }
              
                  function _waasLobby(uint256 enterDay)
                      private
                      returns (uint256 waasLobby)
                  {
                      if (enterDay >= CLAIM_PHASE_START_DAY) {
                          GlobalsCache memory g;
                          GlobalsCache memory gSnapshot;
                          _globalsLoad(g, gSnapshot);
              
                          _dailyDataUpdateAuto(g);
              
                          uint256 unclaimed = dailyData[enterDay].dayUnclaimedSatoshisTotal;
                          waasLobby = unclaimed * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS;
              
                          _globalsSync(g, gSnapshot);
                      } else {
                          waasLobby = WAAS_LOBBY_SEED_HEARTS;
                      }
                      return waasLobby;
                  }
              
                  function _emitXfLobbyEnter(
                      uint256 enterDay,
                      uint256 entryIndex,
                      uint256 rawAmount,
                      address referrerAddr
                  )
                      private
                  {
                      emit XfLobbyEnter( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint96(rawAmount)) << 40),
                          msg.sender,
                          (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex,
                          referrerAddr
                      );
                  }
              
                  function _emitXfLobbyExit(
                      uint256 enterDay,
                      uint256 entryIndex,
                      uint256 xfAmount,
                      address referrerAddr
                  )
                      private
                  {
                      emit XfLobbyExit( // (auto-generated event)
                          uint256(uint40(block.timestamp))
                              | (uint256(uint72(xfAmount)) << 40),
                          msg.sender,
                          (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex,
                          referrerAddr
                      );
                  }
              }
              
              contract HEX is TransformableToken {
                  constructor()
                      public
                  {
                      /* Initialize global shareRate to 1 */
                      globals.shareRate = uint40(1 * SHARE_RATE_SCALE);
              
                      /* Initialize dailyDataCount to skip pre-claim period */
                      globals.dailyDataCount = uint16(PRE_CLAIM_DAYS);
              
                      /* Add all Satoshis from UTXO snapshot to contract */
                      globals.claimStats = _claimStatsEncode(
                          0, // _claimedBtcAddrCount
                          0, // _claimedSatoshisTotal
                          FULL_SATOSHIS_TOTAL // _unclaimedSatoshisTotal
                      );
                  }
              
                  function() external payable {}
              }

              File 5 of 6: OneInchExchange
              // File: @openzeppelin/contracts/GSN/Context.sol
              
              // SPDX-License-Identifier: MIT
              
              pragma solidity ^0.6.0;
              
              /*
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with GSN meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal view virtual returns (address payable) {
                      return msg.sender;
                  }
              
                  function _msgData() internal view virtual returns (bytes memory) {
                      this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                      return msg.data;
                  }
              }
              
              // File: @openzeppelin/contracts/access/Ownable.sol
              
              
              pragma solidity ^0.6.0;
              
              /**
               * @dev Contract module which provides a basic access control mechanism, where
               * there is an account (an owner) that can be granted exclusive access to
               * specific functions.
               *
               * By default, the owner account will be the one that deploys the contract. This
               * can later be changed with {transferOwnership}.
               *
               * This module is used through inheritance. It will make available the modifier
               * `onlyOwner`, which can be applied to your functions to restrict their use to
               * the owner.
               */
              contract Ownable is Context {
                  address private _owner;
              
                  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              
                  /**
                   * @dev Initializes the contract setting the deployer as the initial owner.
                   */
                  constructor () internal {
                      address msgSender = _msgSender();
                      _owner = msgSender;
                      emit OwnershipTransferred(address(0), msgSender);
                  }
              
                  /**
                   * @dev Returns the address of the current owner.
                   */
                  function owner() public view returns (address) {
                      return _owner;
                  }
              
                  /**
                   * @dev Throws if called by any account other than the owner.
                   */
                  modifier onlyOwner() {
                      require(_owner == _msgSender(), "Ownable: caller is not the owner");
                      _;
                  }
              
                  /**
                   * @dev Leaves the contract without owner. It will not be possible to call
                   * `onlyOwner` functions anymore. Can only be called by the current owner.
                   *
                   * NOTE: Renouncing ownership will leave the contract without an owner,
                   * thereby removing any functionality that is only available to the owner.
                   */
                  function renounceOwnership() public virtual onlyOwner {
                      emit OwnershipTransferred(_owner, address(0));
                      _owner = address(0);
                  }
              
                  /**
                   * @dev Transfers ownership of the contract to a new account (`newOwner`).
                   * Can only be called by the current owner.
                   */
                  function transferOwnership(address newOwner) public virtual onlyOwner {
                      require(newOwner != address(0), "Ownable: new owner is the zero address");
                      emit OwnershipTransferred(_owner, newOwner);
                      _owner = newOwner;
                  }
              }
              
              // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
              
              
              pragma solidity ^0.6.0;
              
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
              
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
              
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
              
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
              
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
              
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              
              // File: @openzeppelin/contracts/math/SafeMath.sol
              
              
              pragma solidity ^0.6.0;
              
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow
               * checks.
               *
               * Arithmetic operations in Solidity wrap on overflow. This can easily result
               * in bugs, because programmers usually assume that an overflow raises an
               * error, which is the standard behavior in high level programming languages.
               * `SafeMath` restores this intuition by reverting the transaction when an
               * operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               */
              library SafeMath {
                  /**
                   * @dev Returns the addition of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `+` operator.
                   *
                   * Requirements:
                   *
                   * - Addition cannot overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      require(c >= a, "SafeMath: addition overflow");
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, "SafeMath: subtraction overflow");
                  }
              
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b <= a, errorMessage);
                      uint256 c = a - b;
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the multiplication of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `*` operator.
                   *
                   * Requirements:
                   *
                   * - Multiplication cannot overflow.
                   */
                  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                      // benefit is lost if 'b' is also tested.
                      // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                      if (a == 0) {
                          return 0;
                      }
              
                      uint256 c = a * b;
                      require(c / a == b, "SafeMath: multiplication overflow");
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function div(uint256 a, uint256 b) internal pure returns (uint256) {
                      return div(a, b, "SafeMath: division by zero");
                  }
              
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                   * division by zero. The result is rounded towards zero.
                   *
                   * Counterpart to Solidity's `/` operator. Note: this function uses a
                   * `revert` opcode (which leaves remaining gas untouched) while Solidity
                   * uses an invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
              
                      return c;
                  }
              
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                      return mod(a, b, "SafeMath: modulo by zero");
                  }
              
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts with custom message when dividing by zero.
                   *
                   * Counterpart to Solidity's `%` operator. This function uses a `revert`
                   * opcode (which leaves remaining gas untouched) while Solidity uses an
                   * invalid opcode to revert (consuming all remaining gas).
                   *
                   * Requirements:
                   *
                   * - The divisor cannot be zero.
                   */
                  function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }
              
              // File: @openzeppelin/contracts/utils/Address.sol
              
              
              pragma solidity ^0.6.2;
              
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies in extcodesize, which returns 0 for contracts in
                      // construction, since the code is only stored at the end of the
                      // constructor execution.
              
                      uint256 size;
                      // solhint-disable-next-line no-inline-assembly
                      assembly { size := extcodesize(account) }
                      return size > 0;
                  }
              
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      require(address(this).balance >= amount, "Address: insufficient balance");
              
                      // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                      (bool success, ) = recipient.call{ value: amount }("");
                      require(success, "Address: unable to send value, recipient may have reverted");
                  }
              
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain`call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - `target` must be a contract.
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                    return functionCall(target, data, "Address: low-level call failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                   * `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                      return _functionCallWithValue(target, data, 0, errorMessage);
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but also transferring `value` wei to `target`.
                   *
                   * Requirements:
                   *
                   * - the calling contract must have an ETH balance of at least `value`.
                   * - the called Solidity function must be `payable`.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                  }
              
                  /**
                   * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                   * with `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                      require(address(this).balance >= value, "Address: insufficient balance for call");
                      return _functionCallWithValue(target, data, value, errorMessage);
                  }
              
                  function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
                      require(isContract(target), "Address: call to non-contract");
              
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
              
                              // solhint-disable-next-line no-inline-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
              }
              
              // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
              
              
              pragma solidity ^0.6.0;
              
              
              
              
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  using SafeMath for uint256;
                  using Address for address;
              
                  function safeTransfer(IERC20 token, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
              
                  function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
              
                  /**
                   * @dev Deprecated. This function has issues similar to the ones found in
                   * {IERC20-approve}, and its usage is discouraged.
                   *
                   * Whenever possible, use {safeIncreaseAllowance} and
                   * {safeDecreaseAllowance} instead.
                   */
                  function safeApprove(IERC20 token, address spender, uint256 value) internal {
                      // safeApprove should only be called when setting an initial allowance,
                      // or when resetting it to zero. To increase and decrease it, use
                      // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                      // solhint-disable-next-line max-line-length
                      require((value == 0) || (token.allowance(address(this), spender) == 0),
                          "SafeERC20: approve from non-zero to non-zero allowance"
                      );
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                  }
              
                  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 newAllowance = token.allowance(address(this), spender).add(value);
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
              
                  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
              
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   */
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                      // the target address contains contract code and also asserts for success in the low-level call.
              
                      bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                      if (returndata.length > 0) { // Return data is optional
                          // solhint-disable-next-line max-line-length
                          require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                      }
                  }
              }
              
              // File: @openzeppelin/contracts/utils/Pausable.sol
              
              
              pragma solidity ^0.6.0;
              
              
              /**
               * @dev Contract module which allows children to implement an emergency stop
               * mechanism that can be triggered by an authorized account.
               *
               * This module is used through inheritance. It will make available the
               * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
               * the functions of your contract. Note that they will not be pausable by
               * simply including this module, only once the modifiers are put in place.
               */
              contract Pausable is Context {
                  /**
                   * @dev Emitted when the pause is triggered by `account`.
                   */
                  event Paused(address account);
              
                  /**
                   * @dev Emitted when the pause is lifted by `account`.
                   */
                  event Unpaused(address account);
              
                  bool private _paused;
              
                  /**
                   * @dev Initializes the contract in unpaused state.
                   */
                  constructor () internal {
                      _paused = false;
                  }
              
                  /**
                   * @dev Returns true if the contract is paused, and false otherwise.
                   */
                  function paused() public view returns (bool) {
                      return _paused;
                  }
              
                  /**
                   * @dev Modifier to make a function callable only when the contract is not paused.
                   *
                   * Requirements:
                   *
                   * - The contract must not be paused.
                   */
                  modifier whenNotPaused() {
                      require(!_paused, "Pausable: paused");
                      _;
                  }
              
                  /**
                   * @dev Modifier to make a function callable only when the contract is paused.
                   *
                   * Requirements:
                   *
                   * - The contract must be paused.
                   */
                  modifier whenPaused() {
                      require(_paused, "Pausable: not paused");
                      _;
                  }
              
                  /**
                   * @dev Triggers stopped state.
                   *
                   * Requirements:
                   *
                   * - The contract must not be paused.
                   */
                  function _pause() internal virtual whenNotPaused {
                      _paused = true;
                      emit Paused(_msgSender());
                  }
              
                  /**
                   * @dev Returns to normal state.
                   *
                   * Requirements:
                   *
                   * - The contract must be paused.
                   */
                  function _unpause() internal virtual whenPaused {
                      _paused = false;
                      emit Unpaused(_msgSender());
                  }
              }
              
              // File: contracts/interfaces/IChi.sol
              
              
              pragma solidity ^0.6.12;
              
              
              
              interface IChi is IERC20 {
                  function mint(uint256 value) external;
                  function free(uint256 value) external returns (uint256 freed);
                  function freeFromUpTo(address from, uint256 value) external returns (uint256 freed);
              }
              
              // File: contracts/interfaces/IERC20Permit.sol
              
              
              pragma solidity ^0.6.12;
              
              
              interface IERC20Permit {
                  function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
              }
              
              // File: contracts/interfaces/IGasDiscountExtension.sol
              
              
              pragma solidity ^0.6.12;
              
              
              
              interface IGasDiscountExtension {
                  function calculateGas(uint256 gasUsed, uint256 flags, uint256 calldataLength) external pure returns (IChi, uint256);
              }
              
              // File: contracts/interfaces/ISafeERC20Extension.sol
              
              
              pragma solidity ^0.6.12;
              
              
              
              interface ISafeERC20Extension {
                  function safeApprove(IERC20 token, address spender, uint256 amount) external;
                  function safeTransfer(IERC20 token, address payable target, uint256 amount) external;
              }
              
              // File: contracts/interfaces/IOneInchCaller.sol
              
              
              pragma solidity ^0.6.12;
              pragma experimental ABIEncoderV2;
              
              
              
              
              
              interface IOneInchCaller is ISafeERC20Extension, IGasDiscountExtension {
                  struct CallDescription {
                      uint256 targetWithMandatory;
                      uint256 gasLimit;
                      uint256 value;
                      bytes data;
                  }
              
                  function makeCall(CallDescription memory desc) external;
                  function makeCalls(CallDescription[] memory desc) external payable;
              }
              
              // File: contracts/helpers/RevertReasonParser.sol
              
              
              pragma solidity ^0.6.12;
              
              
              library RevertReasonParser {
                  function parse(bytes memory data, string memory prefix) internal pure returns (string memory) {
                      // https://solidity.readthedocs.io/en/latest/control-structures.html#revert
                      // We assume that revert reason is abi-encoded as Error(string)
              
                      // 68 = 4-byte selector 0x08c379a0 + 32 bytes offset + 32 bytes length
                      if (data.length >= 68 && data[0] == "\x08" && data[1] == "\xc3" && data[2] == "\x79" && data[3] == "\xa0") {
                          string memory reason;
                          // solhint-disable no-inline-assembly
                          assembly {
                              // 68 = 32 bytes data length + 4-byte selector + 32 bytes offset
                              reason := add(data, 68)
                          }
                          /*
                              revert reason is padded up to 32 bytes with ABI encoder: Error(string)
                              also sometimes there is extra 32 bytes of zeros padded in the end:
                              https://github.com/ethereum/solidity/issues/10170
                              because of that we can't check for equality and instead check
                              that string length + extra 68 bytes is less than overall data length
                          */
                          require(data.length >= 68 + bytes(reason).length, "Invalid revert reason");
                          return string(abi.encodePacked(prefix, "Error(", reason, ")"));
                      }
                      // 36 = 4-byte selector 0x4e487b71 + 32 bytes integer
                      else if (data.length == 36 && data[0] == "\x4e" && data[1] == "\x48" && data[2] == "\x7b" && data[3] == "\x71") {
                          uint256 code;
                          // solhint-disable no-inline-assembly
                          assembly {
                              // 36 = 32 bytes data length + 4-byte selector
                              code := mload(add(data, 36))
                          }
                          return string(abi.encodePacked(prefix, "Panic(", _toHex(code), ")"));
                      }
              
                      return string(abi.encodePacked(prefix, "Unknown()"));
                  }
              
                  function _toHex(uint256 value) private pure returns(string memory) {
                      return _toHex(abi.encodePacked(value));
                  }
              
                  function _toHex(bytes memory data) private pure returns(string memory) {
                      bytes memory alphabet = "0123456789abcdef";
                      bytes memory str = new bytes(2 + data.length * 2);
                      str[0] = "0";
                      str[1] = "x";
                      for (uint256 i = 0; i < data.length; i++) {
                          str[2 * i + 2] = alphabet[uint8(data[i] >> 4)];
                          str[2 * i + 3] = alphabet[uint8(data[i] & 0x0f)];
                      }
                      return string(str);
                  }
              }
              
              // File: contracts/helpers/UniERC20.sol
              
              
              pragma solidity ^0.6.12;
              
              
              
              
              
              library UniERC20 {
                  using SafeMath for uint256;
                  using SafeERC20 for IERC20;
              
                  IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                  IERC20 private constant _ZERO_ADDRESS = IERC20(0);
              
                  function isETH(IERC20 token) internal pure returns (bool) {
                      return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS);
                  }
              
                  function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) {
                      if (isETH(token)) {
                          return account.balance;
                      } else {
                          return token.balanceOf(account);
                      }
                  }
              
                  function uniTransfer(IERC20 token, address payable to, uint256 amount) internal {
                      if (amount > 0) {
                          if (isETH(token)) {
                              to.transfer(amount);
                          } else {
                              token.safeTransfer(to, amount);
                          }
                      }
                  }
              
                  function uniApprove(IERC20 token, address to, uint256 amount) internal {
                      require(!isETH(token), "Approve called on ETH");
              
                      if (amount == 0) {
                          token.safeApprove(to, 0);
                      } else {
                          uint256 allowance = token.allowance(address(this), to);
                          if (allowance < amount) {
                              if (allowance > 0) {
                                  token.safeApprove(to, 0);
                              }
                              token.safeApprove(to, amount);
                          }
                      }
                  }
              }
              
              // File: contracts/OneInchExchange.sol
              
              
              pragma solidity ^0.6.12;
              
              
              
              
              
              
              
              
              
              
              contract OneInchExchange is Ownable, Pausable {
                  using SafeMath for uint256;
                  using SafeERC20 for IERC20;
                  using UniERC20 for IERC20;
              
                  uint256 private constant _PARTIAL_FILL = 0x01;
                  uint256 private constant _REQUIRES_EXTRA_ETH = 0x02;
                  uint256 private constant _SHOULD_CLAIM = 0x04;
                  uint256 private constant _BURN_FROM_MSG_SENDER = 0x08;
                  uint256 private constant _BURN_FROM_TX_ORIGIN = 0x10;
              
                  struct SwapDescription {
                      IERC20 srcToken;
                      IERC20 dstToken;
                      address srcReceiver;
                      address dstReceiver;
                      uint256 amount;
                      uint256 minReturnAmount;
                      uint256 guaranteedAmount;
                      uint256 flags;
                      address referrer;
                      bytes permit;
                  }
              
                  event Swapped(
                      address indexed sender,
                      IERC20 indexed srcToken,
                      IERC20 indexed dstToken,
                      address dstReceiver,
                      uint256 amount,
                      uint256 spentAmount,
                      uint256 returnAmount,
                      uint256 minReturnAmount,
                      uint256 guaranteedAmount,
                      address referrer
                  );
              
                  event Error(
                      string reason
                  );
              
                  function discountedSwap(
                      IOneInchCaller caller,
                      SwapDescription calldata desc,
                      IOneInchCaller.CallDescription[] calldata calls
                  )
                      external
                      payable
                      returns (uint256 returnAmount)
                  {
                      uint256 initialGas = gasleft();
              
                      address chiSource = address(0);
                      if (desc.flags & _BURN_FROM_MSG_SENDER != 0) {
                          chiSource = msg.sender;
                      } else if (desc.flags & _BURN_FROM_TX_ORIGIN != 0) {
                          chiSource = tx.origin; // solhint-disable-line avoid-tx-origin
                      } else {
                          revert("Incorrect CHI burn flags");
                      }
              
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory data) = address(this).delegatecall(abi.encodeWithSelector(this.swap.selector, caller, desc, calls));
                      if (success) {
                          returnAmount = abi.decode(data, (uint256));
                      } else {
                          if (msg.value > 0) {
                              msg.sender.transfer(msg.value);
                          }
                          emit Error(RevertReasonParser.parse(data, "Swap failed: "));
                      }
              
                      (IChi chi, uint256 amount) = caller.calculateGas(initialGas.sub(gasleft()), desc.flags, msg.data.length);
                      chi.freeFromUpTo(chiSource, amount);
                  }
              
                  function swap(
                      IOneInchCaller caller,
                      SwapDescription calldata desc,
                      IOneInchCaller.CallDescription[] calldata calls
                  )
                      external
                      payable
                      whenNotPaused
                      returns (uint256 returnAmount)
                  {
                      require(desc.minReturnAmount > 0, "Min return should not be 0");
                      require(calls.length > 0, "Call data should exist");
              
                      uint256 flags = desc.flags;
                      IERC20 srcToken = desc.srcToken;
                      IERC20 dstToken = desc.dstToken;
              
                      if (flags & _REQUIRES_EXTRA_ETH != 0) {
                          require(msg.value > (srcToken.isETH() ? desc.amount : 0), "Invalid msg.value");
                      } else {
                          require(msg.value == (srcToken.isETH() ? desc.amount : 0), "Invalid msg.value");
                      }
              
                      if (flags & _SHOULD_CLAIM != 0) {
                          require(!srcToken.isETH(), "Claim token is ETH");
                          _claim(srcToken, desc.srcReceiver, desc.amount, desc.permit);
                      }
              
                      address dstReceiver = (desc.dstReceiver == address(0)) ? msg.sender : desc.dstReceiver;
                      uint256 initialSrcBalance = (flags & _PARTIAL_FILL != 0) ? srcToken.uniBalanceOf(msg.sender) : 0;
                      uint256 initialDstBalance = dstToken.uniBalanceOf(dstReceiver);
              
                      caller.makeCalls{value: msg.value}(calls);
              
                      uint256 spentAmount = desc.amount;
                      returnAmount = dstToken.uniBalanceOf(dstReceiver).sub(initialDstBalance);
              
                      if (flags & _PARTIAL_FILL != 0) {
                          spentAmount = initialSrcBalance.add(desc.amount).sub(srcToken.uniBalanceOf(msg.sender));
                          require(returnAmount.mul(desc.amount) >= desc.minReturnAmount.mul(spentAmount), "Return amount is not enough");
                      } else {
                          require(returnAmount >= desc.minReturnAmount, "Return amount is not enough");
                      }
              
                      _emitSwapped(desc, srcToken, dstToken, dstReceiver, spentAmount, returnAmount);
                  }
              
                  function _emitSwapped(
                      SwapDescription calldata desc,
                      IERC20 srcToken,
                      IERC20 dstToken,
                      address dstReceiver,
                      uint256 spentAmount,
                      uint256 returnAmount
                   ) private {
                      emit Swapped(
                          msg.sender,
                          srcToken,
                          dstToken,
                          dstReceiver,
                          desc.amount,
                          spentAmount,
                          returnAmount,
                          desc.minReturnAmount,
                          desc.guaranteedAmount,
                          desc.referrer
                      );
                  }
              
                  function _claim(IERC20 token, address dst, uint256 amount, bytes calldata permit) private {
                      // TODO: Is it safe to call permit on tokens without implemented permit? Fallback will be called. Is it bad for proxies?
              
                      if (permit.length == 32 * 7) {
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory result) = address(token).call(abi.encodeWithSelector(IERC20Permit.permit.selector, permit));
                          if (!success) {
                              revert(RevertReasonParser.parse(result, "Permit call failed: "));
                          }
                      }
              
                      token.safeTransferFrom(msg.sender, dst, amount);
                  }
              
                  function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
                      token.uniTransfer(msg.sender, amount);
                  }
              
                  function pause() external onlyOwner {
                      _pause();
                  }
              }

              File 6 of 6: Vyper_contract
              # @title Uniswap Exchange Interface V1
              # @notice Source code found at https://github.com/uniswap
              # @notice Use at your own risk
              
              contract Factory():
                  def getExchange(token_addr: address) -> address: constant
              
              contract Exchange():
                  def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei): constant
                  def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256: modifying
                  def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei): modifying
              
              TokenPurchase: event({buyer: indexed(address), eth_sold: indexed(uint256(wei)), tokens_bought: indexed(uint256)})
              EthPurchase: event({buyer: indexed(address), tokens_sold: indexed(uint256), eth_bought: indexed(uint256(wei))})
              AddLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)})
              RemoveLiquidity: event({provider: indexed(address), eth_amount: indexed(uint256(wei)), token_amount: indexed(uint256)})
              Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256})
              Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256})
              
              name: public(bytes32)                             # Uniswap V1
              symbol: public(bytes32)                           # UNI-V1
              decimals: public(uint256)                         # 18
              totalSupply: public(uint256)                      # total number of UNI in existence
              balances: uint256[address]                        # UNI balance of an address
              allowances: (uint256[address])[address]           # UNI allowance of one address on another
              token: address(ERC20)                             # address of the ERC20 token traded on this contract
              factory: Factory                                  # interface for the factory that created this contract
              
              # @dev This function acts as a contract constructor which is not currently supported in contracts deployed
              #      using create_with_code_of(). It is called once by the factory during contract creation.
              @public
              def setup(token_addr: address):
                  assert (self.factory == ZERO_ADDRESS and self.token == ZERO_ADDRESS) and token_addr != ZERO_ADDRESS
                  self.factory = msg.sender
                  self.token = token_addr
                  self.name = 0x556e697377617020563100000000000000000000000000000000000000000000
                  self.symbol = 0x554e492d56310000000000000000000000000000000000000000000000000000
                  self.decimals = 18
              
              # @notice Deposit ETH and Tokens (self.token) at current ratio to mint UNI tokens.
              # @dev min_liquidity does nothing when total UNI supply is 0.
              # @param min_liquidity Minimum number of UNI sender will mint if total UNI supply is greater than 0.
              # @param max_tokens Maximum number of tokens deposited. Deposits max amount if total UNI supply is 0.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return The amount of UNI minted.
              @public
              @payable
              def addLiquidity(min_liquidity: uint256, max_tokens: uint256, deadline: timestamp) -> uint256:
                  assert deadline > block.timestamp and (max_tokens > 0 and msg.value > 0)
                  total_liquidity: uint256 = self.totalSupply
                  if total_liquidity > 0:
                      assert min_liquidity > 0
                      eth_reserve: uint256(wei) = self.balance - msg.value
                      token_reserve: uint256 = self.token.balanceOf(self)
                      token_amount: uint256 = msg.value * token_reserve / eth_reserve + 1
                      liquidity_minted: uint256 = msg.value * total_liquidity / eth_reserve
                      assert max_tokens >= token_amount and liquidity_minted >= min_liquidity
                      self.balances[msg.sender] += liquidity_minted
                      self.totalSupply = total_liquidity + liquidity_minted
                      assert self.token.transferFrom(msg.sender, self, token_amount)
                      log.AddLiquidity(msg.sender, msg.value, token_amount)
                      log.Transfer(ZERO_ADDRESS, msg.sender, liquidity_minted)
                      return liquidity_minted
                  else:
                      assert (self.factory != ZERO_ADDRESS and self.token != ZERO_ADDRESS) and msg.value >= 1000000000
                      assert self.factory.getExchange(self.token) == self
                      token_amount: uint256 = max_tokens
                      initial_liquidity: uint256 = as_unitless_number(self.balance)
                      self.totalSupply = initial_liquidity
                      self.balances[msg.sender] = initial_liquidity
                      assert self.token.transferFrom(msg.sender, self, token_amount)
                      log.AddLiquidity(msg.sender, msg.value, token_amount)
                      log.Transfer(ZERO_ADDRESS, msg.sender, initial_liquidity)
                      return initial_liquidity
              
              # @dev Burn UNI tokens to withdraw ETH and Tokens at current ratio.
              # @param amount Amount of UNI burned.
              # @param min_eth Minimum ETH withdrawn.
              # @param min_tokens Minimum Tokens withdrawn.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return The amount of ETH and Tokens withdrawn.
              @public
              def removeLiquidity(amount: uint256, min_eth: uint256(wei), min_tokens: uint256, deadline: timestamp) -> (uint256(wei), uint256):
                  assert (amount > 0 and deadline > block.timestamp) and (min_eth > 0 and min_tokens > 0)
                  total_liquidity: uint256 = self.totalSupply
                  assert total_liquidity > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_amount: uint256(wei) = amount * self.balance / total_liquidity
                  token_amount: uint256 = amount * token_reserve / total_liquidity
                  assert eth_amount >= min_eth and token_amount >= min_tokens
                  self.balances[msg.sender] -= amount
                  self.totalSupply = total_liquidity - amount
                  send(msg.sender, eth_amount)
                  assert self.token.transfer(msg.sender, token_amount)
                  log.RemoveLiquidity(msg.sender, eth_amount, token_amount)
                  log.Transfer(msg.sender, ZERO_ADDRESS, amount)
                  return eth_amount, token_amount
              
              # @dev Pricing function for converting between ETH and Tokens.
              # @param input_amount Amount of ETH or Tokens being sold.
              # @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
              # @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
              # @return Amount of ETH or Tokens bought.
              @private
              @constant
              def getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
                  assert input_reserve > 0 and output_reserve > 0
                  input_amount_with_fee: uint256 = input_amount * 997
                  numerator: uint256 = input_amount_with_fee * output_reserve
                  denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee
                  return numerator / denominator
              
              # @dev Pricing function for converting between ETH and Tokens.
              # @param output_amount Amount of ETH or Tokens being bought.
              # @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
              # @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
              # @return Amount of ETH or Tokens sold.
              @private
              @constant
              def getOutputPrice(output_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
                  assert input_reserve > 0 and output_reserve > 0
                  numerator: uint256 = input_reserve * output_amount * 1000
                  denominator: uint256 = (output_reserve - output_amount) * 997
                  return numerator / denominator + 1
              
              @private
              def ethToTokenInput(eth_sold: uint256(wei), min_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
                  assert deadline >= block.timestamp and (eth_sold > 0 and min_tokens > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_bought: uint256 = self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance - eth_sold), token_reserve)
                  assert tokens_bought >= min_tokens
                  assert self.token.transfer(recipient, tokens_bought)
                  log.TokenPurchase(buyer, eth_sold, tokens_bought)
                  return tokens_bought
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies exact input (msg.value).
              # @dev User cannot specify minimum output or deadline.
              @public
              @payable
              def __default__():
                  self.ethToTokenInput(msg.value, 1, block.timestamp, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies exact input (msg.value) and minimum output.
              # @param min_tokens Minimum Tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of Tokens bought.
              @public
              @payable
              def ethToTokenSwapInput(min_tokens: uint256, deadline: timestamp) -> uint256:
                  return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens and transfers Tokens to recipient.
              # @dev User specifies exact input (msg.value) and minimum output
              # @param min_tokens Minimum Tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output Tokens.
              # @return Amount of Tokens bought.
              @public
              @payable
              def ethToTokenTransferInput(min_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.ethToTokenInput(msg.value, min_tokens, deadline, msg.sender, recipient)
              
              @private
              def ethToTokenOutput(tokens_bought: uint256, max_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
                  assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance - max_eth), token_reserve)
                  # Throws if eth_sold > max_eth
                  eth_refund: uint256(wei) = max_eth - as_wei_value(eth_sold, 'wei')
                  if eth_refund > 0:
                      send(buyer, eth_refund)
                  assert self.token.transfer(recipient, tokens_bought)
                  log.TokenPurchase(buyer, as_wei_value(eth_sold, 'wei'), tokens_bought)
                  return as_wei_value(eth_sold, 'wei')
              
              # @notice Convert ETH to Tokens.
              # @dev User specifies maximum input (msg.value) and exact output.
              # @param tokens_bought Amount of tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of ETH sold.
              @public
              @payable
              def ethToTokenSwapOutput(tokens_bought: uint256, deadline: timestamp) -> uint256(wei):
                  return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, msg.sender)
              
              # @notice Convert ETH to Tokens and transfers Tokens to recipient.
              # @dev User specifies maximum input (msg.value) and exact output.
              # @param tokens_bought Amount of tokens bought.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output Tokens.
              # @return Amount of ETH sold.
              @public
              @payable
              def ethToTokenTransferOutput(tokens_bought: uint256, deadline: timestamp, recipient: address) -> uint256(wei):
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.ethToTokenOutput(tokens_bought, msg.value, deadline, msg.sender, recipient)
              
              @private
              def tokenToEthInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, buyer: address, recipient: address) -> uint256(wei):
                  assert deadline >= block.timestamp and (tokens_sold > 0 and min_eth > 0)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
                  assert wei_bought >= min_eth
                  send(recipient, wei_bought)
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  log.EthPurchase(buyer, tokens_sold, wei_bought)
                  return wei_bought
              
              
              # @notice Convert Tokens to ETH.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_eth Minimum ETH purchased.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of ETH bought.
              @public
              def tokenToEthSwapInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp) -> uint256(wei):
                  return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, msg.sender)
              
              # @notice Convert Tokens to ETH and transfers ETH to recipient.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_eth Minimum ETH purchased.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @return Amount of ETH bought.
              @public
              def tokenToEthTransferInput(tokens_sold: uint256, min_eth: uint256(wei), deadline: timestamp, recipient: address) -> uint256(wei):
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.tokenToEthInput(tokens_sold, min_eth, deadline, msg.sender, recipient)
              
              @private
              def tokenToEthOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, buyer: address, recipient: address) -> uint256:
                  assert deadline >= block.timestamp and eth_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
                  # tokens sold is always > 0
                  assert max_tokens >= tokens_sold
                  send(recipient, eth_bought)
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  log.EthPurchase(buyer, tokens_sold, eth_bought)
                  return tokens_sold
              
              # @notice Convert Tokens to ETH.
              # @dev User specifies maximum input and exact output.
              # @param eth_bought Amount of ETH purchased.
              # @param max_tokens Maximum Tokens sold.
              # @param deadline Time after which this transaction can no longer be executed.
              # @return Amount of Tokens sold.
              @public
              def tokenToEthSwapOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp) -> uint256:
                  return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, msg.sender)
              
              # @notice Convert Tokens to ETH and transfers ETH to recipient.
              # @dev User specifies maximum input and exact output.
              # @param eth_bought Amount of ETH purchased.
              # @param max_tokens Maximum Tokens sold.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @return Amount of Tokens sold.
              @public
              def tokenToEthTransferOutput(eth_bought: uint256(wei), max_tokens: uint256, deadline: timestamp, recipient: address) -> uint256:
                  assert recipient != self and recipient != ZERO_ADDRESS
                  return self.tokenToEthOutput(eth_bought, max_tokens, deadline, msg.sender, recipient)
              
              @private
              def tokenToTokenInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
                  assert (deadline >= block.timestamp and tokens_sold > 0) and (min_tokens_bought > 0 and min_eth_bought > 0)
                  assert exchange_addr != self and exchange_addr != ZERO_ADDRESS
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  wei_bought: uint256(wei) = as_wei_value(eth_bought, 'wei')
                  assert wei_bought >= min_eth_bought
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  tokens_bought: uint256 = Exchange(exchange_addr).ethToTokenTransferInput(min_tokens_bought, deadline, recipient, value=wei_bought)
                  log.EthPurchase(buyer, tokens_sold, wei_bought)
                  return tokens_bought
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr).
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (token_addr) bought.
              @public
              def tokenToTokenSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr) and transfers
              #         Tokens (token_addr) to recipient.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (token_addr) bought.
              @public
              def tokenToTokenTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)
              
              @private
              def tokenToTokenOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, buyer: address, recipient: address, exchange_addr: address) -> uint256:
                  assert deadline >= block.timestamp and (tokens_bought > 0 and max_eth_sold > 0)
                  assert exchange_addr != self and exchange_addr != ZERO_ADDRESS
                  eth_bought: uint256(wei) = Exchange(exchange_addr).getEthToTokenOutputPrice(tokens_bought)
                  token_reserve: uint256 = self.token.balanceOf(self)
                  tokens_sold: uint256 = self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
                  # tokens sold is always > 0
                  assert max_tokens_sold >= tokens_sold and max_eth_sold >= eth_bought
                  assert self.token.transferFrom(buyer, self, tokens_sold)
                  eth_sold: uint256(wei) = Exchange(exchange_addr).ethToTokenTransferOutput(tokens_bought, deadline, recipient, value=eth_bought)
                  log.EthPurchase(buyer, tokens_sold, eth_bought)
                  return tokens_sold
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr).
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToTokenSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (token_addr) and transfers
              #         Tokens (token_addr) to recipient.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToTokenTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, token_addr: address) -> uint256:
                  exchange_addr: address = self.factory.getExchange(token_addr)
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token).
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (exchange_addr.token) bought.
              @public
              def tokenToExchangeSwapInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token) and transfers
              #         Tokens (exchange_addr.token) to recipient.
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies exact input and minimum output.
              # @param tokens_sold Amount of Tokens sold.
              # @param min_tokens_bought Minimum Tokens (token_addr) purchased.
              # @param min_eth_bought Minimum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (exchange_addr.token) bought.
              @public
              def tokenToExchangeTransferInput(tokens_sold: uint256, min_tokens_bought: uint256, min_eth_bought: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
                  assert recipient != self
                  return self.tokenToTokenInput(tokens_sold, min_tokens_bought, min_eth_bought, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token).
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param exchange_addr The address of the exchange for the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToExchangeSwapOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, exchange_addr: address) -> uint256:
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, msg.sender, exchange_addr)
              
              # @notice Convert Tokens (self.token) to Tokens (exchange_addr.token) and transfers
              #         Tokens (exchange_addr.token) to recipient.
              # @dev Allows trades through contracts that were not deployed from the same factory.
              # @dev User specifies maximum input and exact output.
              # @param tokens_bought Amount of Tokens (token_addr) bought.
              # @param max_tokens_sold Maximum Tokens (self.token) sold.
              # @param max_eth_sold Maximum ETH purchased as intermediary.
              # @param deadline Time after which this transaction can no longer be executed.
              # @param recipient The address that receives output ETH.
              # @param token_addr The address of the token being purchased.
              # @return Amount of Tokens (self.token) sold.
              @public
              def tokenToExchangeTransferOutput(tokens_bought: uint256, max_tokens_sold: uint256, max_eth_sold: uint256(wei), deadline: timestamp, recipient: address, exchange_addr: address) -> uint256:
                  assert recipient != self
                  return self.tokenToTokenOutput(tokens_bought, max_tokens_sold, max_eth_sold, deadline, msg.sender, recipient, exchange_addr)
              
              # @notice Public price function for ETH to Token trades with an exact input.
              # @param eth_sold Amount of ETH sold.
              # @return Amount of Tokens that can be bought with input ETH.
              @public
              @constant
              def getEthToTokenInputPrice(eth_sold: uint256(wei)) -> uint256:
                  assert eth_sold > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  return self.getInputPrice(as_unitless_number(eth_sold), as_unitless_number(self.balance), token_reserve)
              
              # @notice Public price function for ETH to Token trades with an exact output.
              # @param tokens_bought Amount of Tokens bought.
              # @return Amount of ETH needed to buy output Tokens.
              @public
              @constant
              def getEthToTokenOutputPrice(tokens_bought: uint256) -> uint256(wei):
                  assert tokens_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_sold: uint256 = self.getOutputPrice(tokens_bought, as_unitless_number(self.balance), token_reserve)
                  return as_wei_value(eth_sold, 'wei')
              
              # @notice Public price function for Token to ETH trades with an exact input.
              # @param tokens_sold Amount of Tokens sold.
              # @return Amount of ETH that can be bought with input Tokens.
              @public
              @constant
              def getTokenToEthInputPrice(tokens_sold: uint256) -> uint256(wei):
                  assert tokens_sold > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  eth_bought: uint256 = self.getInputPrice(tokens_sold, token_reserve, as_unitless_number(self.balance))
                  return as_wei_value(eth_bought, 'wei')
              
              # @notice Public price function for Token to ETH trades with an exact output.
              # @param eth_bought Amount of output ETH.
              # @return Amount of Tokens needed to buy output ETH.
              @public
              @constant
              def getTokenToEthOutputPrice(eth_bought: uint256(wei)) -> uint256:
                  assert eth_bought > 0
                  token_reserve: uint256 = self.token.balanceOf(self)
                  return self.getOutputPrice(as_unitless_number(eth_bought), token_reserve, as_unitless_number(self.balance))
              
              # @return Address of Token that is sold on this exchange.
              @public
              @constant
              def tokenAddress() -> address:
                  return self.token
              
              # @return Address of factory that created this exchange.
              @public
              @constant
              def factoryAddress() -> address(Factory):
                  return self.factory
              
              # ERC20 compatibility for exchange liquidity modified from
              # https://github.com/ethereum/vyper/blob/master/examples/tokens/ERC20.vy
              @public
              @constant
              def balanceOf(_owner : address) -> uint256:
                  return self.balances[_owner]
              
              @public
              def transfer(_to : address, _value : uint256) -> bool:
                  self.balances[msg.sender] -= _value
                  self.balances[_to] += _value
                  log.Transfer(msg.sender, _to, _value)
                  return True
              
              @public
              def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
                  self.balances[_from] -= _value
                  self.balances[_to] += _value
                  self.allowances[_from][msg.sender] -= _value
                  log.Transfer(_from, _to, _value)
                  return True
              
              @public
              def approve(_spender : address, _value : uint256) -> bool:
                  self.allowances[msg.sender][_spender] = _value
                  log.Approval(msg.sender, _spender, _value)
                  return True
              
              @public
              @constant
              def allowance(_owner : address, _spender : address) -> uint256:
                  return self.allowances[_owner][_spender]