Contract 0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2

 

Contract Overview

Balance:
0.00000006 MATIC

MATIC Value:
Less Than $0.01 (@ $0.89/MATIC)
 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0x295c9200b729aa52d98bc6fccc32d2245ea05607d0344b4d2da04ce3c41bd34c0x611a7761212557412021-11-11 18:35:02271 days 17 hrs ago0x8af97264482b59c7aa11010907710dee6d8d8c6c IN  Create: Withdrawing0.00000006 MATIC0.0454938930
[ Download CSV Export 
Parent Txn Hash Block From To Value
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Withdrawing

Compiler Version
v0.8.4+commit.c7e474f2

Optimization Enabled:
Yes with 1 runs

Other Settings:
default evmVersion, GNU LGPLv3 license

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 16 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @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 on 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");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        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 2 of 16 : AssetRegistry.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { Address } from './Address.sol';

import { Asset } from './Structs.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { IERC20 } from './Interfaces.sol';

/**
 * @notice Library helper functions for reading from a registry of asset descriptors indexed by address and symbol
 */
library AssetRegistry {
  struct Storage {
    mapping(address => Asset) assetsByAddress;
    // Mapping value is array since the same symbol can be re-used for a different address
    // (usually as a result of a token swap or upgrade)
    mapping(string => Asset[]) assetsBySymbol;
    // Blockchain-specific native asset symbol
    string nativeAssetSymbol;
  }

  // Admin //

  function registerToken(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external {
    require(decimals <= 32, 'Token cannot have more than 32 decimals');
    require(
      tokenAddress != IERC20(address(0x0)) &&
        Address.isContract(address(tokenAddress)),
      'Invalid token address'
    );
    // The string type does not have a length property so cast to bytes to check for empty string
    require(bytes(symbol).length > 0, 'Invalid token symbol');
    require(
      !self.assetsByAddress[address(tokenAddress)].isConfirmed,
      'Token already finalized'
    );

    self.assetsByAddress[address(tokenAddress)] = Asset({
      exists: true,
      assetAddress: address(tokenAddress),
      symbol: symbol,
      decimals: decimals,
      isConfirmed: false,
      confirmedTimestampInMs: 0
    });
  }

  function confirmTokenRegistration(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external {
    Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(asset.exists, 'Unknown token');
    require(!asset.isConfirmed, 'Token already finalized');
    require(isStringEqual(asset.symbol, symbol), 'Symbols do not match');
    require(asset.decimals == decimals, 'Decimals do not match');

    asset.isConfirmed = true;
    asset.confirmedTimestampInMs = uint64(block.timestamp * 1000); // Block timestamp is in seconds, store ms
    self.assetsByAddress[address(tokenAddress)] = asset;
    self.assetsBySymbol[symbol].push(asset);
  }

  function addTokenSymbol(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol
  ) external {
    Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(
      asset.exists && asset.isConfirmed,
      'Registration of token not finalized'
    );
    require(
      !isStringEqual(symbol, self.nativeAssetSymbol),
      'Symbol reserved for native asset'
    );

    // This will prevent swapping assets for previously existing orders
    uint64 msInOneSecond = 1000;
    asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond);

    self.assetsBySymbol[symbol].push(asset);
  }

  function skim(address tokenAddress, address feeWallet) external {
    require(Address.isContract(tokenAddress), 'Invalid token address');

    uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
    AssetTransfers.transferTo(payable(feeWallet), tokenAddress, balance);
  }

  // Accessors //

  function loadBalanceInAssetUnitsByAddress(
    AssetRegistry.Storage storage self,
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid wallet address');

    Asset memory asset = loadAssetByAddress(self, assetAddress);
    return
      AssetUnitConversions.pipsToAssetUnits(
        loadBalanceInPipsFromMigrationSourceIfNeeded(
          wallet,
          assetAddress,
          balanceTracking
        ),
        asset.decimals
      );
  }

  function loadBalanceInAssetUnitsBySymbol(
    AssetRegistry.Storage storage self,
    address wallet,
    string calldata assetSymbol,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid wallet address');

    Asset memory asset =
      loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs());
    return
      AssetUnitConversions.pipsToAssetUnits(
        loadBalanceInPipsFromMigrationSourceIfNeeded(
          wallet,
          asset.assetAddress,
          balanceTracking
        ),
        asset.decimals
      );
  }

  function loadBalanceInPipsByAddress(
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint64) {
    require(wallet != address(0x0), 'Invalid wallet address');

    return
      loadBalanceInPipsFromMigrationSourceIfNeeded(
        wallet,
        assetAddress,
        balanceTracking
      );
  }

  function loadBalanceInPipsBySymbol(
    Storage storage self,
    address wallet,
    string calldata assetSymbol,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint64) {
    require(wallet != address(0x0), 'Invalid wallet address');

    address assetAddress =
      loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs())
        .assetAddress;
    return
      loadBalanceInPipsFromMigrationSourceIfNeeded(
        wallet,
        assetAddress,
        balanceTracking
      );
  }

  function loadBalanceInPipsFromMigrationSourceIfNeeded(
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) private view returns (uint64) {
    BalanceTracking.Balance memory balance =
      balanceTracking.balancesByWalletAssetPair[wallet][assetAddress];

    if (
      !balance.isMigrated &&
      address(balanceTracking.migrationSource) != address(0x0)
    ) {
      return
        balanceTracking.migrationSource.loadBalanceInPipsByAddress(
          wallet,
          assetAddress
        );
    }

    return balance.balanceInPips;
  }

  /**
   * @dev Resolves an asset address into corresponding Asset struct
   *
   * @param assetAddress Ethereum address of asset
   */
  function loadAssetByAddress(Storage storage self, address assetAddress)
    internal
    view
    returns (Asset memory)
  {
    if (assetAddress == address(0x0)) {
      return getEthAsset(self.nativeAssetSymbol);
    }

    Asset memory asset = self.assetsByAddress[assetAddress];
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found for address'
    );

    return asset;
  }

  /**
   * @dev Resolves a asset symbol into corresponding Asset struct
   *
   * @param symbol Asset symbol, e.g. 'IDEX'
   * @param timestampInMs Milliseconds since Unix epoch, usually parsed from a UUID v1 order nonce.
   * Constrains symbol resolution to the asset most recently confirmed prior to timestampInMs. Reverts
   * if no such asset exists
   */
  function loadAssetBySymbol(
    Storage storage self,
    string memory symbol,
    uint64 timestampInMs
  ) internal view returns (Asset memory) {
    if (isStringEqual(self.nativeAssetSymbol, symbol)) {
      return getEthAsset(self.nativeAssetSymbol);
    }

    Asset memory asset;
    if (self.assetsBySymbol[symbol].length > 0) {
      for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) {
        if (
          self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs
        ) {
          asset = self.assetsBySymbol[symbol][i];
        }
      }
    }
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found for symbol'
    );

    return asset;
  }

  // Util //

  function getCurrentTimestampInMs() internal view returns (uint64) {
    uint64 msInOneSecond = 1000;

    return uint64(block.timestamp) * msInOneSecond;
  }

  /**
   * @dev ETH is modeled as an always-confirmed Asset struct for programmatic consistency
   */
  function getEthAsset(string memory nativeAssetSymbol)
    private
    pure
    returns (Asset memory)
  {
    return Asset(true, address(0x0), nativeAssetSymbol, 18, true, 0);
  }

  // See https://solidity.readthedocs.io/en/latest/types.html#bytes-and-strings-as-arrays
  function isStringEqual(string memory a, string memory b)
    private
    pure
    returns (bool)
  {
    return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
  }
}

File 3 of 16 : AssetTransfers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { IERC20 } from './Interfaces.sol';

/**
 * @notice This library provides helper utilities for transfering assets in and out of contracts.
 * It further validates ERC-20 compliant balance updates in the case of token assets
 */
library AssetTransfers {
  /**
   * @dev Transfers tokens from a wallet into a contract during deposits. `wallet` must already
   * have called `approve` on the token contract for at least `tokenQuantity`. Note this only
   * applies to tokens since ETH is sent in the deposit transaction via `msg.value`
   */
  function transferFrom(
    address wallet,
    IERC20 tokenAddress,
    address to,
    uint256 quantityInAssetUnits
  ) internal {
    uint256 balanceBefore = tokenAddress.balanceOf(to);

    // Because we check for the expected balance change we can safely ignore the return value of transferFrom
    tokenAddress.transferFrom(wallet, to, quantityInAssetUnits);

    uint256 balanceAfter = tokenAddress.balanceOf(to);
    require(
      balanceAfter - balanceBefore == quantityInAssetUnits,
      'Token contract returned transferFrom success without expected balance change'
    );
  }

  /**
   * @dev Transfers ETH or token assets from a contract to a wallet when withdrawing or removing liquidity
   */
  function transferTo(
    address payable walletOrContract,
    address asset,
    uint256 quantityInAssetUnits
  ) internal {
    if (asset == address(0x0)) {
      require(
        walletOrContract.send(quantityInAssetUnits),
        'ETH transfer failed'
      );
    } else {
      uint256 balanceBefore = IERC20(asset).balanceOf(walletOrContract);

      // Because we check for the expected balance change we can safely ignore the return value of transfer
      IERC20(asset).transfer(walletOrContract, quantityInAssetUnits);

      uint256 balanceAfter = IERC20(asset).balanceOf(walletOrContract);
      require(
        balanceAfter - balanceBefore == quantityInAssetUnits,
        'Token contract returned transfer success without expected balance change'
      );
    }
  }
}

File 4 of 16 : AssetUnitConversions.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

/**
 * @notice Library helpers for converting asset quantities between asset units and pips
 */
library AssetUnitConversions {
  function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals)
    internal
    pure
    returns (uint256)
  {
    require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');

    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      return uint256(quantityInPips) * (uint256(10)**(assetDecimals - 8));
    }
    return uint256(quantityInPips) / (uint256(10)**(8 - assetDecimals));
  }

  function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals)
    internal
    pure
    returns (uint64)
  {
    require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');

    uint256 quantityInPips;
    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      quantityInPips =
        quantityInAssetUnits /
        (uint256(10)**(assetDecimals - 8));
    } else {
      quantityInPips =
        quantityInAssetUnits *
        (uint256(10)**(8 - assetDecimals));
    }
    require(quantityInPips < 2**64, 'Pip quantity overflows uint64');

    return uint64(quantityInPips);
  }
}

File 5 of 16 : BalanceTracking.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { OrderSide } from './Enums.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import {
  Asset,
  HybridTrade,
  LiquidityAddition,
  LiquidityChangeExecution,
  LiquidityRemoval,
  Order,
  OrderBookTrade,
  PoolTrade,
  Withdrawal
} from './Structs.sol';
import { IExchange, ILiquidityProviderToken } from './Interfaces.sol';

library BalanceTracking {
  using AssetRegistry for AssetRegistry.Storage;
  using PoolTradeHelpers for PoolTrade;

  struct Balance {
    bool isMigrated;
    uint64 balanceInPips;
  }

  struct Storage {
    mapping(address => mapping(address => Balance)) balancesByWalletAssetPair;
    // Predecessor Exchange contract from which to lazily migrate balances
    IExchange migrationSource;
  }

  // Depositing //

  function updateForDeposit(
    Storage storage self,
    address wallet,
    address assetAddress,
    uint64 quantityInPips
  ) internal returns (uint64 newBalanceInPips) {
    Balance storage balance =
      loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress);
    balance.balanceInPips += quantityInPips;

    return balance.balanceInPips;
  }

  // Trading //

  /**
   * @dev Updates buyer, seller, and fee wallet balances for both assets in trade pair according to
   * trade parameters
   */
  function updateForOrderBookTrade(
    Storage storage self,
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade,
    address feeWallet
  ) internal {
    Balance storage balance;

    // Seller gives base asset including fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      sell.walletAddress,
      trade.baseAssetAddress
    );
    balance.balanceInPips -= trade.grossBaseQuantityInPips;
    // Buyer receives base asset minus fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      buy.walletAddress,
      trade.baseAssetAddress
    );
    balance.balanceInPips += trade.netBaseQuantityInPips;

    // Buyer gives quote asset including fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      buy.walletAddress,
      trade.quoteAssetAddress
    );
    balance.balanceInPips -= trade.grossQuoteQuantityInPips;
    // Seller receives quote asset minus fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      sell.walletAddress,
      trade.quoteAssetAddress
    );
    balance.balanceInPips += trade.netQuoteQuantityInPips;

    // Maker fee to fee wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      trade.makerFeeAssetAddress
    );
    balance.balanceInPips += trade.makerFeeQuantityInPips;
    // Taker fee to fee wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      trade.takerFeeAssetAddress
    );
    balance.balanceInPips += trade.takerFeeQuantityInPips;
  }

  function updateForPoolTrade(
    Storage storage self,
    Order memory order,
    PoolTrade memory poolTrade,
    address feeWallet
  ) internal {
    Balance storage balance;

    // Debit from order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      order.walletAddress,
      poolTrade.getOrderDebitAssetAddress(order.side)
    );
    balance.balanceInPips -= poolTrade.getOrderDebitQuantityInPips(order.side);
    // Credit to order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      order.walletAddress,
      poolTrade.getOrderCreditAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.calculateOrderCreditQuantityInPips(
      order.side
    );

    // Fee wallet receives protocol fee from asset debited from order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      poolTrade.getOrderDebitAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.takerProtocolFeeQuantityInPips;
    // Fee wallet receives gas fee from asset credited to order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      poolTrade.getOrderCreditAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.takerGasFeeQuantityInPips;

    // Liquidity pool reserves are updated in LiquidityPoolRegistry
  }

  function updateForHybridTradeFees(
    Storage storage self,
    HybridTrade memory hybridTrade,
    address takerWallet,
    address feeWallet
  ) internal {
    Balance storage balance;
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      hybridTrade.orderBookTrade.takerFeeAssetAddress
    );
    balance.balanceInPips += hybridTrade.takerGasFeeQuantityInPips;

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      takerWallet,
      hybridTrade.orderBookTrade.takerFeeAssetAddress
    );
    balance.balanceInPips -=
      hybridTrade.takerGasFeeQuantityInPips +
      hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips;

    // Liquidity pool reserves are updated in LiquidityPoolRegistry
  }

  // Withdrawing //

  function updateForWithdrawal(
    Storage storage self,
    Withdrawal memory withdrawal,
    address assetAddress,
    address feeWallet
  ) internal returns (uint64 newExchangeBalanceInPips) {
    Balance storage balance;

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      withdrawal.walletAddress,
      assetAddress
    );
    // Reverts if balance is overdrawn
    balance.balanceInPips -= withdrawal.grossQuantityInPips;
    newExchangeBalanceInPips = balance.balanceInPips;

    if (withdrawal.gasFeeInPips > 0) {
      balance = loadBalanceAndMigrateIfNeeded(self, feeWallet, assetAddress);

      balance.balanceInPips += withdrawal.gasFeeInPips;
    }
  }

  // Wallet exits //

  function updateForExit(
    Storage storage self,
    address wallet,
    address assetAddress
  ) internal returns (uint64 previousExchangeBalanceInPips) {
    Balance storage balance;

    balance = loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress);
    previousExchangeBalanceInPips = balance.balanceInPips;

    require(previousExchangeBalanceInPips > 0, 'No balance for asset');

    balance.balanceInPips = 0;
  }

  // Liquidity pools //

  function updateForAddLiquidity(
    Storage storage self,
    LiquidityAddition memory addition,
    LiquidityChangeExecution memory execution,
    address feeWallet,
    address custodianAddress,
    ILiquidityProviderToken liquidityProviderToken
  ) internal returns (uint64 outputLiquidityInPips) {
    // Base gross debit
    Balance storage balance =
      loadBalanceAndMigrateIfNeeded(
        self,
        addition.wallet,
        execution.baseAssetAddress
      );
    balance.balanceInPips -= execution.grossBaseQuantityInPips;

    // Base fee credit
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      execution.baseAssetAddress
    );
    balance.balanceInPips +=
      execution.grossBaseQuantityInPips -
      execution.netBaseQuantityInPips;

    // Quote gross debit

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      addition.wallet,
      execution.quoteAssetAddress
    );
    balance.balanceInPips -= execution.grossQuoteQuantityInPips;

    // Quote fee credit
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      execution.quoteAssetAddress
    );
    balance.balanceInPips +=
      execution.grossQuoteQuantityInPips -
      execution.netQuoteQuantityInPips;

    // Only add output assets to wallet's balances in the Exchange if Custodian is target
    if (addition.to == custodianAddress) {
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        addition.wallet,
        address(liquidityProviderToken)
      );
      balance.balanceInPips += execution.liquidityInPips;
    } else {
      outputLiquidityInPips = execution.liquidityInPips;
    }
  }

  function updateForRemoveLiquidity(
    Storage storage self,
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    address feeWallet,
    address custodianAddress,
    ILiquidityProviderToken liquidityProviderToken
  )
    internal
    returns (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    )
  {
    Balance storage balance;

    // Base asset updates
    {
      // Only add output assets to wallet's balances in the Exchange if Custodian is target
      if (removal.to == custodianAddress) {
        // Base net credit
        balance = loadBalanceAndMigrateIfNeeded(
          self,
          removal.wallet,
          execution.baseAssetAddress
        );
        balance.balanceInPips += execution.netBaseQuantityInPips;
      } else {
        outputBaseAssetQuantityInPips = execution.netBaseQuantityInPips;
      }

      // Base fee credit
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        feeWallet,
        execution.baseAssetAddress
      );
      balance.balanceInPips +=
        execution.grossBaseQuantityInPips -
        execution.netBaseQuantityInPips;
    }

    // Quote asset updates
    {
      // Only add output assets to wallet's balances in the Exchange if Custodian is target
      if (removal.to == custodianAddress) {
        // Quote net credit
        balance = loadBalanceAndMigrateIfNeeded(
          self,
          removal.wallet,
          execution.quoteAssetAddress
        );
        balance.balanceInPips += execution.netQuoteQuantityInPips;
      } else {
        outputQuoteAssetQuantityInPips = execution.netQuoteQuantityInPips;
      }

      // Quote fee credit
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        feeWallet,
        execution.quoteAssetAddress
      );
      balance.balanceInPips +=
        execution.grossQuoteQuantityInPips -
        execution.netQuoteQuantityInPips;
    }

    // Pair token burn
    {
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        removal.wallet,
        address(liquidityProviderToken)
      );
      balance.balanceInPips -= execution.liquidityInPips;
    }
  }

  // Helpers //

  function loadBalanceAndMigrateIfNeeded(
    Storage storage self,
    address wallet,
    address assetAddress
  ) private returns (Balance storage) {
    Balance storage balance =
      self.balancesByWalletAssetPair[wallet][assetAddress];

    if (!balance.isMigrated && address(self.migrationSource) != address(0x0)) {
      balance.balanceInPips = self.migrationSource.loadBalanceInPipsByAddress(
        wallet,
        assetAddress
      );
      balance.isMigrated = true;
    }

    return balance;
  }
}

File 6 of 16 : Constants.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

/**
 * @dev See GOVERNANCE.md for descriptions of fixed parameters and fees
 */

library Constants {
  // 100 basis points/percent * 100 percent/total
  uint64 public constant basisPointsInTotal = 100 * 100;

  uint64 public constant depositIndexNotSet = 2**64 - 1;

  uint8 public constant liquidityProviderTokenDecimals = 18;

  // 1 week at 3s/block
  uint256 public constant maxChainPropagationPeriod = (7 * 24 * 60 * 60) / 3;

  // 20%
  uint64 public constant maxFeeBasisPoints = 20 * 100;

  // Pool reserve balance ratio above which price dips below 1 pip and can no longer be represented
  uint64 public constant maxLiquidityPoolReserveRatio = 10**8;

  // Pool reserve balance below which prices can no longer be represented with full pip precision
  uint64 public constant minLiquidityPoolReserveInPips = 10**8;

  // 2%
  uint64 public constant maxPoolInputFeeBasisPoints = 2 * 100;

  // 5%
  uint64 public constant maxPoolOutputAdjustmentBasisPoints = 5 * 100;

  // 1%
  uint64 public constant maxPoolPriceCorrectionBasisPoints = 1 * 100;

  // To convert integer pips to a fractional price shift decimal left by the pip precision of 8
  // decimals places
  uint64 public constant pipPriceMultiplier = 10**8;

  uint8 public constant signatureHashVersion = 3;
}

File 7 of 16 : ECDSA.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
        } else if (signature.length == 64) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                let vs := mload(add(signature, 0x40))
                r := mload(add(signature, 0x20))
                s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
                v := add(shr(255, vs), 27)
            }
        } else {
            revert("ECDSA: invalid signature length");
        }

        return recover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
        require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

File 8 of 16 : Enums.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

/**
 * @notice Enums definitions
 */

// Liquidity pools //

enum LiquidityChangeOrigination { OnChain, OffChain }

enum LiquidityChangeType { Addition, Removal }

enum LiquidityChangeState { NotInitiated, Initiated, Executed }

// Order book //

enum OrderSelfTradePrevention {
  // Decrement and cancel
  dc,
  // Cancel oldest
  co,
  // Cancel newest
  cn,
  // Cancel both
  cb
}

enum OrderSide { Buy, Sell }

enum OrderTimeInForce {
  // Good until cancelled
  gtc,
  // Good until time
  gtt,
  // Immediate or cancel
  ioc,
  // Fill or kill
  fok
}

enum OrderType {
  Market,
  Limit,
  LimitMaker,
  StopLoss,
  StopLossLimit,
  TakeProfit,
  TakeProfitLimit
}

// Withdrawals //

enum WithdrawalType { BySymbol, ByAddress }

File 9 of 16 : Hashing.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { ECDSA } from './ECDSA.sol';

import { Constants } from './Constants.sol';
import { LiquidityChangeType, WithdrawalType } from './Enums.sol';
import {
  LiquidityAddition,
  LiquidityRemoval,
  Order,
  Withdrawal
} from './Structs.sol';

/**
 * @notice Library helpers for building hashes and verifying wallet signatures
 */
library Hashing {
  function isSignatureValid(
    bytes32 hash,
    bytes memory signature,
    address signer
  ) internal pure returns (bool) {
    return
      ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer;
  }

  // Hash construction //

  function getLiquidityAdditionHash(LiquidityAddition memory addition)
    internal
    pure
    returns (bytes32)
  {
    require(
      addition.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );

    return
      keccak256(
        abi.encodePacked(
          addition.signatureHashVersion,
          uint8(LiquidityChangeType.Addition),
          uint8(addition.origination),
          addition.nonce,
          addition.wallet,
          addition.assetA,
          addition.assetB,
          addition.amountADesired,
          addition.amountBDesired,
          addition.amountAMin,
          addition.amountBMin,
          addition.to,
          addition.deadline
        )
      );
  }

  function getLiquidityRemovalHash(LiquidityRemoval memory removal)
    internal
    pure
    returns (bytes32)
  {
    require(
      removal.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );

    return
      keccak256(
        abi.encodePacked(
          removal.signatureHashVersion,
          uint8(LiquidityChangeType.Removal),
          uint8(removal.origination),
          removal.nonce,
          removal.wallet,
          removal.assetA,
          removal.assetB,
          removal.liquidity,
          removal.amountAMin,
          removal.amountBMin,
          removal.to,
          removal.deadline
        )
      );
  }

  /**
   * @dev As a gas optimization, base and quote symbols are passed in separately and combined to
   * verify the wallet hash, since this is cheaper than splitting the market symbol into its two
   * constituent asset symbols
   */
  function getOrderHash(
    Order memory order,
    string memory baseSymbol,
    string memory quoteSymbol
  ) internal pure returns (bytes32) {
    require(
      order.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );
    // Placing all the fields in a single `abi.encodePacked` call causes a `stack too deep` error
    return
      keccak256(
        abi.encodePacked(
          abi.encodePacked(
            order.signatureHashVersion,
            order.nonce,
            order.walletAddress,
            string(abi.encodePacked(baseSymbol, '-', quoteSymbol)),
            uint8(order.orderType),
            uint8(order.side),
            // Ledger qtys and prices are in pip, but order was signed by wallet owner with decimal
            // values
            pipToDecimal(order.quantityInPips)
          ),
          abi.encodePacked(
            order.isQuantityInQuote,
            order.limitPriceInPips > 0
              ? pipToDecimal(order.limitPriceInPips)
              : '',
            order.stopPriceInPips > 0
              ? pipToDecimal(order.stopPriceInPips)
              : '',
            order.clientOrderId,
            uint8(order.timeInForce),
            uint8(order.selfTradePrevention),
            order.cancelAfter
          )
        )
      );
  }

  function getWithdrawalHash(Withdrawal memory withdrawal)
    internal
    pure
    returns (bytes32)
  {
    return
      keccak256(
        abi.encodePacked(
          withdrawal.nonce,
          withdrawal.walletAddress,
          // Ternary branches must resolve to the same type, so wrap in idempotent encodePacked
          withdrawal.withdrawalType == WithdrawalType.BySymbol
            ? abi.encodePacked(withdrawal.assetSymbol)
            : abi.encodePacked(withdrawal.assetAddress),
          pipToDecimal(withdrawal.grossQuantityInPips),
          withdrawal.autoDispatchEnabled
        )
      );
  }

  /**
   * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string
   * originally signed by the wallet. For example, 1234567890 becomes '12.34567890'
   */
  function pipToDecimal(uint256 pips) private pure returns (string memory) {
    // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062
    uint256 copy = pips;
    uint256 length;
    while (copy != 0) {
      length++;
      copy /= 10;
    }
    if (length < 9) {
      length = 9; // a zero before the decimal point plus 8 decimals
    }
    length++; // for the decimal point

    bytes memory decimal = new bytes(length);
    for (uint256 i = length; i > 0; i--) {
      if (length - i == 8) {
        decimal[i - 1] = bytes1(uint8(46)); // period
      } else {
        decimal[i - 1] = bytes1(uint8(48 + (pips % 10)));
        pips /= 10;
      }
    }
    return string(decimal);
  }
}

File 10 of 16 : Interfaces.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { Order, OrderBookTrade, Withdrawal } from './Structs.sol';

/**
 * @notice Interface of the ERC20 standard as defined in the EIP, but with no return values for
 * transfer and transferFrom. By asserting expected balance changes when calling these two methods
 * we can safely ignore their return values. This allows support of non-compliant tokens that do not
 * return a boolean. See https://github.com/ethereum/solidity/issues/4116
 */
interface IERC20 {
  /**
   * @notice Returns the amount of tokens in existence.
   */
  function totalSupply() external view returns (uint256);

  /**
   * @notice Returns the amount of tokens owned by `account`.
   */
  function balanceOf(address account) external view returns (uint256);

  /**
   * @notice Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Most implementing contracts return a boolean value indicating whether the operation succeeded, but
   * we ignore this and rely on asserting balance changes instead
   *
   * Emits a {Transfer} event.
   */
  function transfer(address recipient, uint256 amount) external;

  /**
   * @notice 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);

  /**
   * @notice 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);

  /**
   * @notice Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Most implementing contracts return a boolean value indicating whether the operation succeeded, but
   * we ignore this and rely on asserting balance changes instead
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) external;

  /**
   * @notice 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);

  /**
   * @notice 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);
}

/**
 * @notice Interface to Custodian contract. Used by Exchange and Governance contracts for internal
 * delegate calls
 */
interface ICustodian {
  /**
   * @notice ETH can only be sent by the Exchange
   */
  receive() external payable;

  /**
   * @notice Withdraw any asset and amount to a target wallet
   *
   * @dev No balance checking performed
   *
   * @param wallet The wallet to which assets will be returned
   * @param asset The address of the asset to withdraw (native asset or ERC-20 contract)
   * @param quantityInAssetUnits The quantity in asset units to withdraw
   */
  function withdraw(
    address payable wallet,
    address asset,
    uint256 quantityInAssetUnits
  ) external;

  /**
   * @notice Load address of the currently whitelisted Exchange contract
   *
   * @return The address of the currently whitelisted Exchange contract
   */
  function loadExchange() external view returns (address);

  /**
   * @notice Sets a new Exchange contract address
   *
   * @param newExchange The address of the new whitelisted Exchange contract
   */
  function setExchange(address newExchange) external;

  /**
   * @notice Load address of the currently whitelisted Governance contract
   *
   * @return The address of the currently whitelisted Governance contract
   */
  function loadGovernance() external view returns (address);

  /**
   * @notice Sets a new Governance contract address
   *
   * @param newGovernance The address of the new whitelisted Governance contract
   */
  function setGovernance(address newGovernance) external;
}

/**
 * @notice Interface to Whistler Exchange contract
 *
 * @dev Used for lazy balance migrations from old to new Exchange after upgrade
 */
interface IExchange {
  /**
   * @notice Load a wallet's balance by asset address, in pips
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetAddress The asset address to load the wallet's balance for
   *
   * @return The quantity denominated in pips of asset at `assetAddress` currently deposited by `wallet`
   */
  function loadBalanceInPipsByAddress(address wallet, address assetAddress)
    external
    view
    returns (uint64);

  /**
   * @notice Load the address of the Custodian contract
   *
   * @return The address of the Custodian contract
   */
  function loadCustodian() external view returns (ICustodian);
}

interface ILiquidityProviderToken {
  function custodian() external returns (ICustodian);

  function baseAssetAddress() external returns (address);

  function quoteAssetAddress() external returns (address);

  function baseAssetSymbol() external returns (string memory);

  function quoteAssetSymbol() external returns (string memory);

  function token0() external returns (address);

  function token1() external returns (address);

  function burn(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external;

  function mint(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external;

  function reverseAssets() external;
}

interface IWETH9 is IERC20 {
  receive() external payable;

  function deposit() external payable;

  function withdraw(uint256 wad) external;
}

File 11 of 16 : Math.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

library Math {
  function multiplyPipsByFraction(
    uint64 multiplicand,
    uint64 fractionDividend,
    uint64 fractionDivisor
  ) internal pure returns (uint64) {
    return
      multiplyPipsByFraction(
        multiplicand,
        fractionDividend,
        fractionDivisor,
        false
      );
  }

  function multiplyPipsByFraction(
    uint64 multiplicand,
    uint64 fractionDividend,
    uint64 fractionDivisor,
    bool roundUp
  ) internal pure returns (uint64) {
    uint256 dividend = uint256(multiplicand) * fractionDividend;
    uint256 result = dividend / fractionDivisor;
    if (roundUp && dividend % fractionDivisor > 0) {
      result += 1;
    }
    require(result < 2**64, 'Pip quantity overflows uint64');

    return uint64(result);
  }

  function min(uint64 x, uint64 y) internal pure returns (uint64 z) {
    z = x < y ? x : y;
  }

  function sqrt(uint256 y) internal pure returns (uint256 z) {
    if (y > 3) {
      z = y;
      uint256 x = y / 2 + 1;
      while (x < z) {
        z = x;
        x = (y / x + x) / 2;
      }
    } else if (y != 0) {
      z = 1;
    }
  }
}

File 12 of 16 : PoolTradeHelpers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { OrderSide } from './Enums.sol';
import { PoolTrade } from './Structs.sol';

library PoolTradeHelpers {
  /**
   * @dev Address of asset order wallet is receiving from pool
   */
  function getOrderCreditAssetAddress(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (address) {
    return
      orderSide == OrderSide.Buy
        ? self.baseAssetAddress
        : self.quoteAssetAddress;
  }

  /**
   * @dev Address of asset order wallet is giving to pool
   */
  function getOrderDebitAssetAddress(PoolTrade memory self, OrderSide orderSide)
    internal
    pure
    returns (address)
  {
    return
      orderSide == OrderSide.Buy
        ? self.quoteAssetAddress
        : self.baseAssetAddress;
  }

  /**
   * @dev Quantity in pips of asset that order wallet is receiving from pool
   */
  function calculateOrderCreditQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      (
        orderSide == OrderSide.Buy
          ? self.netBaseQuantityInPips
          : self.netQuoteQuantityInPips
      ) - self.takerGasFeeQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that order wallet is giving to pool
   */
  function getOrderDebitQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      orderSide == OrderSide.Buy
        ? self.grossQuoteQuantityInPips
        : self.grossBaseQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that pool receives from order wallet
   */
  function calculatePoolCreditQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      (
        orderSide == OrderSide.Buy
          ? self.netQuoteQuantityInPips
          : self.netBaseQuantityInPips
      ) + self.takerPoolFeeQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that leaves pool as output
   */
  function getPoolDebitQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return (
      orderSide == OrderSide.Buy
        ? self.netBaseQuantityInPips // Pool gives net base asset plus taker gas fee
        : self.netQuoteQuantityInPips // Pool gives net quote asset plus taker gas fee
    );
  }

  /**
   * @dev Gross quantity received by order wallet
   */
  function getOrderGrossReceivedQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      orderSide == OrderSide.Buy
        ? self.grossBaseQuantityInPips
        : self.grossQuoteQuantityInPips;
  }

  function calculatePoolOutputAdjustment(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      getOrderGrossReceivedQuantityInPips(self, orderSide) -
      getPoolDebitQuantityInPips(self, orderSide);
  }
}

File 13 of 16 : Structs.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { ILiquidityProviderToken, IWETH9 } from './Interfaces.sol';
import {
  LiquidityChangeOrigination,
  OrderSelfTradePrevention,
  OrderSide,
  OrderTimeInForce,
  OrderType,
  WithdrawalType
} from './Enums.sol';

/**
 * @notice Struct definitions
 */

/**
 * @notice State tracking for a hybrid liquidity pool
 *
 * @dev Base and quote asset decimals are denormalized here to avoid extra loads from
 * `AssetRegistry.Storage`
 */
struct LiquidityPool {
  // Flag to distinguish from empty struct
  bool exists;
  uint64 baseAssetReserveInPips;
  uint8 baseAssetDecimals;
  uint64 quoteAssetReserveInPips;
  uint8 quoteAssetDecimals;
  ILiquidityProviderToken liquidityProviderToken;
}

/**
 * @dev Internal struct capturing user-initiated liquidity addition request parameters
 */
struct LiquidityAddition {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // Distinguishes between liquidity additions initated on- or off- chain
  LiquidityChangeOrigination origination;
  // UUIDv1 unique to wallet
  uint128 nonce;
  address wallet;
  address assetA;
  address assetB;
  uint256 amountADesired;
  uint256 amountBDesired;
  uint256 amountAMin;
  uint256 amountBMin;
  address to;
  uint256 deadline;
  bytes signature;
}

/**
 * @notice Internally used struct, return type from `LiquidityPools.addLiquidity`
 */
struct LiquidityAdditionDepositResult {
  string assetASymbol;
  uint64 assetAQuantityInPips;
  uint64 assetANewExchangeBalanceInPips;
  uint256 assetANewExchangeBalanceInAssetUnits;
  string assetBSymbol;
  uint64 assetBQuantityInPips;
  uint64 assetBNewExchangeBalanceInPips;
  uint256 assetBNewExchangeBalanceInAssetUnits;
}

/**
 * @notice Internally used struct, return type from `LiquidityPools.removeLiquidity`
 */
struct LiquidityRemovalDepositResult {
  address assetAddress;
  string assetSymbol;
  uint64 assetQuantityInPips;
  uint64 assetNewExchangeBalanceInPips;
  uint256 assetNewExchangeBalanceInAssetUnits;
}

/**
 * @dev Internal struct capturing user-initiated liquidity removal request parameters
 */
struct LiquidityRemoval {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // Distinguishes between liquidity additions initated on- or off- chain
  LiquidityChangeOrigination origination;
  uint128 nonce;
  address wallet;
  address assetA;
  address assetB;
  uint256 liquidity;
  uint256 amountAMin;
  uint256 amountBMin;
  address payable to;
  uint256 deadline;
  bytes signature;
}

/**
 * @notice Argument type to `Exchange.executeAddLiquidity` and `Exchange.executeRemoveLiquidity`
 */
struct LiquidityChangeExecution {
  address baseAssetAddress;
  address quoteAssetAddress;
  uint64 liquidityInPips;
  // Gross amount including fees of base asset executed
  uint64 grossBaseQuantityInPips;
  // Gross amount including fees of quote asset executed
  uint64 grossQuoteQuantityInPips;
  // Net amount of base asset sent to pool for additions or received by wallet for removals
  uint64 netBaseQuantityInPips;
  // Net amount of quote asset sent to pool for additions or received by wallet for removals
  uint64 netQuoteQuantityInPips;
}

/**
 * @notice Internally used struct, argument type to `LiquidityPoolAdmin.migrateLiquidityPool`
 */
struct LiquidityMigration {
  address token0;
  address token1;
  bool isToken1Quote;
  uint256 desiredLiquidity;
  address to;
  IWETH9 WETH;
}

/**
 * @notice Internally used struct capturing wallet order nonce invalidations created via `invalidateOrderNonce`
 */
struct NonceInvalidation {
  bool exists;
  uint64 timestampInMs;
  uint256 effectiveBlockNumber;
}

/**
 * @notice Return type for `Exchange.loadAssetBySymbol`, and `Exchange.loadAssetByAddress`; also
 * used internally by `AssetRegistry`
 */
struct Asset {
  // Flag to distinguish from empty struct
  bool exists;
  // The asset's address
  address assetAddress;
  // The asset's symbol
  string symbol;
  // The asset's decimal precision
  uint8 decimals;
  // Flag set when asset registration confirmed. Asset deposits, trades, or withdrawals only
  // allowed if true
  bool isConfirmed;
  // Timestamp as ms since Unix epoch when isConfirmed was asserted
  uint64 confirmedTimestampInMs;
}

/**
 * @notice Argument type for `Exchange.executeOrderBookTrade` and `Hashing.getOrderWalletHash`
 */
struct Order {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Wallet address that placed order and signed hash
  address walletAddress;
  // Type of order
  OrderType orderType;
  // Order side wallet is on
  OrderSide side;
  // Order quantity in base or quote asset terms depending on isQuantityInQuote flag
  uint64 quantityInPips;
  // Is quantityInPips in quote terms
  bool isQuantityInQuote;
  // For limit orders, price in decimal pips * 10^8 in quote terms
  uint64 limitPriceInPips;
  // For stop orders, stop loss or take profit price in decimal pips * 10^8 in quote terms
  uint64 stopPriceInPips;
  // Optional custom client order ID
  string clientOrderId;
  // TIF option specified by wallet for order
  OrderTimeInForce timeInForce;
  // STP behavior specified by wallet for order
  OrderSelfTradePrevention selfTradePrevention;
  // Cancellation time specified by wallet for GTT TIF order
  uint64 cancelAfter;
  // The ECDSA signature of the order hash as produced by Hashing.getOrderWalletHash
  bytes walletSignature;
}

/**
 * @notice Argument type for `Exchange.executeOrderBookTrade` specifying execution parameters for matching orders
 */
struct OrderBookTrade {
  // Base asset symbol
  string baseAssetSymbol;
  // Quote asset symbol
  string quoteAssetSymbol;
  // Base asset address
  address baseAssetAddress;
  // Quote asset address
  address quoteAssetAddress;
  // Gross amount including fees of base asset executed
  uint64 grossBaseQuantityInPips;
  // Gross amount including fees of quote asset executed
  uint64 grossQuoteQuantityInPips;
  // Net amount of base asset received by buy side wallet after fees
  uint64 netBaseQuantityInPips;
  // Net amount of quote asset received by sell side wallet after fees
  uint64 netQuoteQuantityInPips;
  // Asset address for liquidity maker's fee
  address makerFeeAssetAddress;
  // Asset address for liquidity taker's fee
  address takerFeeAssetAddress;
  // Fee paid by liquidity maker
  uint64 makerFeeQuantityInPips;
  // Fee paid by liquidity taker, inclusive of gas fees
  uint64 takerFeeQuantityInPips;
  // Execution price of trade in decimal pips * 10^8 in quote terms
  uint64 priceInPips;
  // Which side of the order (buy or sell) the liquidity maker was on
  OrderSide makerSide;
}

/**
 * @notice Argument type for `Exchange.executePoolTrade` specifying execution parameters for an
 * order against pool liquidity
 */
struct PoolTrade {
  // Base asset symbol
  string baseAssetSymbol;
  // Quote asset symbol
  string quoteAssetSymbol;
  // Base asset address
  address baseAssetAddress;
  // Quote asset address
  address quoteAssetAddress;
  // Gross amount including fees of base asset executed
  uint64 grossBaseQuantityInPips;
  // Gross amount including fees of quote asset executed
  uint64 grossQuoteQuantityInPips;
  // If wallet is buy side, net amount of quote input to pool used to calculate output; otherwise,
  // net amount of base asset leaving pool
  uint64 netBaseQuantityInPips;
  // If wallet is buy side, net amount of base input to pool used to calculate output; otherwise,
  // net amount of quote asset leaving pool
  uint64 netQuoteQuantityInPips;
  // Fee paid by liquidity taker to pool from sent asset
  uint64 takerPoolFeeQuantityInPips;
  // Fee paid by liquidity taker to fee wallet from sent asset
  uint64 takerProtocolFeeQuantityInPips;
  // Fee paid by liquidity taker to fee wallet from received asset
  uint64 takerGasFeeQuantityInPips;
  // Fee paid by liquidity taker sell to pool taken from pool's quote asset output
  uint64 takerPriceCorrectionFeeQuantityInPips;
}

struct HybridTrade {
  OrderBookTrade orderBookTrade;
  PoolTrade poolTrade;
  // Fee paid by liquidity taker to fee wallet from received asset
  uint64 takerGasFeeQuantityInPips;
}

/**
 * @notice Argument type for `Exchange.withdraw` and `Hashing.getWithdrawalWalletHash`
 */
struct Withdrawal {
  // Distinguishes between withdrawals by asset symbol or address
  WithdrawalType withdrawalType;
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Address of wallet to which funds will be returned
  address payable walletAddress;
  // Asset symbol
  string assetSymbol;
  // Asset address
  address assetAddress; // Used when assetSymbol not specified
  // Withdrawal quantity
  uint64 grossQuantityInPips;
  // Gas fee deducted from withdrawn quantity to cover dispatcher tx costs
  uint64 gasFeeInPips;
  // Not currently used but reserved for future use. Must be true
  bool autoDispatchEnabled;
  // The ECDSA signature of the withdrawal hash as produced by Hashing.getWithdrawalWalletHash
  bytes walletSignature;
}

File 14 of 16 : UUID.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

/**
 * Library helper for extracting timestamp component of Version 1 UUIDs
 */
library UUID {
  /**
   * Extracts the timestamp component of a Version 1 UUID. Used to make time-based assertions
   * against a wallet-privided nonce
   */
  function getTimestampInMsFromUuidV1(uint128 uuid)
    internal
    pure
    returns (uint64 msSinceUnixEpoch)
  {
    // https://tools.ietf.org/html/rfc4122#section-4.1.2
    uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F;
    require(version == 1, 'Must be v1 UUID');

    // Time components are in reverse order so shift+mask each to reassemble
    uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000;
    uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000;
    uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF;
    uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow);
    // Gregorian offset given in seconds by https://www.wolframalpha.com/input/?i=convert+1582-10-15+UTC+to+unix+time
    msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000) - 12219292800000;

    return msSinceUnixEpoch;
  }
}

File 15 of 16 : Validations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { Hashing } from './Hashing.sol';
import { IERC20 } from './Interfaces.sol';
import { Math } from './Math.sol';
import { UUID } from './UUID.sol';
import { OrderSide, OrderType } from './Enums.sol';
import {
  Asset,
  LiquidityPool,
  Order,
  OrderBookTrade,
  NonceInvalidation,
  PoolTrade,
  Withdrawal
} from './Structs.sol';

library Validations {
  using AssetRegistry for AssetRegistry.Storage;

  /**
   * @dev Perform fee validations common to both orderbook-only and hybrid trades. Does not
   * validate if fees are excessive as taker fee structure differs between these trade types
   *
   */
  function validateOrderBookTradeFees(OrderBookTrade memory trade)
    internal
    pure
  {
    require(
      trade.netBaseQuantityInPips +
        (
          trade.makerFeeAssetAddress == trade.baseAssetAddress
            ? trade.makerFeeQuantityInPips
            : trade.takerFeeQuantityInPips
        ) ==
        trade.grossBaseQuantityInPips,
      'Orderbook base fees unbalanced'
    );
    require(
      trade.netQuoteQuantityInPips +
        (
          trade.makerFeeAssetAddress == trade.quoteAssetAddress
            ? trade.makerFeeQuantityInPips
            : trade.takerFeeQuantityInPips
        ) ==
        trade.grossQuoteQuantityInPips,
      'Orderbook quote fees unbalanced'
    );
  }

  function validateOrderNonce(
    Order memory order,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view {
    require(
      UUID.getTimestampInMsFromUuidV1(order.nonce) >
        loadLastInvalidatedTimestamp(order.walletAddress, nonceInvalidations),
      'Order nonce timestamp too low'
    );
  }

  function validateOrderNonces(
    Order memory buy,
    Order memory sell,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view {
    require(
      UUID.getTimestampInMsFromUuidV1(buy.nonce) >
        loadLastInvalidatedTimestamp(buy.walletAddress, nonceInvalidations),
      'Buy order nonce timestamp too low'
    );
    require(
      UUID.getTimestampInMsFromUuidV1(sell.nonce) >
        loadLastInvalidatedTimestamp(sell.walletAddress, nonceInvalidations),
      'Sell order nonce timestamp too low'
    );
  }

  function validateOrderSignature(
    Order memory order,
    string memory baseAssetSymbol,
    string memory quoteAssetSymbol
  ) internal pure returns (bytes32) {
    bytes32 orderHash =
      Hashing.getOrderHash(order, baseAssetSymbol, quoteAssetSymbol);

    require(
      Hashing.isSignatureValid(
        orderHash,
        order.walletSignature,
        order.walletAddress
      ),
      order.side == OrderSide.Buy
        ? 'Invalid wallet signature for buy order'
        : 'Invalid wallet signature for sell order'
    );

    return orderHash;
  }

  function validatePoolReserveRatio(LiquidityPool memory pool) internal pure {
    (uint64 sortedReserve0, uint64 sortedReserve1) =
      pool.baseAssetReserveInPips <= pool.quoteAssetReserveInPips
        ? (pool.baseAssetReserveInPips, pool.quoteAssetReserveInPips)
        : (pool.quoteAssetReserveInPips, pool.baseAssetReserveInPips);
    require(
      uint256(sortedReserve0) * Constants.maxLiquidityPoolReserveRatio >=
        sortedReserve1,
      'Exceeded max reserve ratio'
    );
  }

  /**
   * @dev Perform fee validations common to both pool-only and hybrid trades
   */
  function validatePoolTradeInputFees(
    OrderSide orderSide,
    PoolTrade memory poolTrade
  ) internal pure {
    // Buy order sends quote as pool input, receives base as pool output; sell order sends base as
    // pool input, receives quote as pool output
    (uint64 netInputQuantityInPips, uint64 grossInputQuantityInPips) =
      orderSide == OrderSide.Buy
        ? (poolTrade.netQuoteQuantityInPips, poolTrade.grossQuoteQuantityInPips)
        : (poolTrade.netBaseQuantityInPips, poolTrade.grossBaseQuantityInPips);

    require(
      netInputQuantityInPips +
        poolTrade.takerPoolFeeQuantityInPips +
        poolTrade.takerProtocolFeeQuantityInPips ==
        grossInputQuantityInPips,
      'Pool input fees unbalanced'
    );
    require(
      Validations.isFeeQuantityValid(
        grossInputQuantityInPips - netInputQuantityInPips,
        grossInputQuantityInPips,
        Constants.maxPoolInputFeeBasisPoints
      ),
      'Excessive pool input fee'
    );
  }

  function validateWithdrawalSignature(Withdrawal memory withdrawal)
    internal
    pure
    returns (bytes32)
  {
    bytes32 withdrawalHash = Hashing.getWithdrawalHash(withdrawal);

    require(
      Hashing.isSignatureValid(
        withdrawalHash,
        withdrawal.walletSignature,
        withdrawal.walletAddress
      ),
      'Invalid wallet signature'
    );

    return withdrawalHash;
  }

  // Utils //

  function calculateImpliedQuoteQuantityInPips(
    uint64 baseQuantityInPips,
    uint64 limitPriceInPips
  ) internal pure returns (uint64) {
    return
      Math.multiplyPipsByFraction(
        baseQuantityInPips,
        limitPriceInPips,
        Constants.pipPriceMultiplier
      );
  }

  function loadLastInvalidatedTimestamp(
    address walletAddress,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) private view returns (uint64) {
    if (
      nonceInvalidations[walletAddress].exists &&
      nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number
    ) {
      return nonceInvalidations[walletAddress].timestampInMs;
    }

    return 0;
  }

  function isFeeQuantityValid(
    uint64 fee,
    uint64 total,
    uint64 max
  ) internal pure returns (bool) {
    uint64 feeBasisPoints = (fee * Constants.basisPointsInTotal) / total;
    return feeBasisPoints <= max;
  }

  function isLimitOrderType(OrderType orderType) internal pure returns (bool) {
    return
      orderType == OrderType.Limit ||
      orderType == OrderType.LimitMaker ||
      orderType == OrderType.StopLossLimit ||
      orderType == OrderType.TakeProfitLimit;
  }
}

File 16 of 16 : Withdrawing.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.4;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { Constants } from './Constants.sol';
import { UUID } from './UUID.sol';
import { Validations } from './Validations.sol';
import { WithdrawalType } from './Enums.sol';
import {
  Asset,
  LiquidityChangeExecution,
  LiquidityRemoval,
  Withdrawal
} from './Structs.sol';
import { ICustodian, ILiquidityProviderToken } from './Interfaces.sol';

library Withdrawing {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;

  function withdraw(
    Withdrawal memory withdrawal,
    ICustodian custodian,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedWithdrawalHashes
  )
    public
    returns (
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits,
      address assetAddress,
      string memory assetSymbol
    )
  {
    // Validations
    require(
      Validations.isFeeQuantityValid(
        withdrawal.gasFeeInPips,
        withdrawal.grossQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive withdrawal fee'
    );
    bytes32 withdrawalHash =
      Validations.validateWithdrawalSignature(withdrawal);
    require(
      !completedWithdrawalHashes[withdrawalHash],
      'Hash already withdrawn'
    );

    // If withdrawal is by asset symbol (most common) then resolve to asset address
    Asset memory asset =
      withdrawal.withdrawalType == WithdrawalType.BySymbol
        ? assetRegistry.loadAssetBySymbol(
          withdrawal.assetSymbol,
          UUID.getTimestampInMsFromUuidV1(withdrawal.nonce)
        )
        : assetRegistry.loadAssetByAddress(withdrawal.assetAddress);

    assetSymbol = asset.symbol;
    assetAddress = asset.assetAddress;

    // Update wallet balances
    newExchangeBalanceInPips = balanceTracking.updateForWithdrawal(
      withdrawal,
      asset.assetAddress,
      feeWallet
    );
    newExchangeBalanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      newExchangeBalanceInPips,
      asset.decimals
    );

    // Transfer funds from Custodian to wallet
    uint256 netAssetQuantityInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        withdrawal.grossQuantityInPips - withdrawal.gasFeeInPips,
        asset.decimals
      );
    custodian.withdraw(
      withdrawal.walletAddress,
      asset.assetAddress,
      netAssetQuantityInAssetUnits
    );

    // Replay prevention
    completedWithdrawalHashes[withdrawalHash] = true;
  }

  function withdrawExit(
    address assetAddress,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) external returns (uint64 previousExchangeBalanceInPips) {
    // Update wallet balance
    previousExchangeBalanceInPips = balanceTracking.updateForExit(
      msg.sender,
      assetAddress
    );

    // Transfer asset from Custodian to wallet
    Asset memory asset = assetRegistry.loadAssetByAddress(assetAddress);
    uint256 balanceInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        previousExchangeBalanceInPips,
        asset.decimals
      );
    ICustodian(custodian).withdraw(
      payable(msg.sender),
      assetAddress,
      balanceInAssetUnits
    );
  }

  function withdrawLiquidity(
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    ICustodian custodian,
    address feeWallet,
    ILiquidityProviderToken liquidityProviderToken,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) internal {
    (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    ) =
      balanceTracking.updateForRemoveLiquidity(
        removal,
        execution,
        feeWallet,
        address(custodian),
        liquidityProviderToken
      );

    Asset memory asset;
    if (outputBaseAssetQuantityInPips > 0) {
      asset = assetRegistry.loadAssetByAddress(execution.baseAssetAddress);
      custodian.withdraw(
        removal.to,
        execution.baseAssetAddress,
        AssetUnitConversions.pipsToAssetUnits(
          outputBaseAssetQuantityInPips,
          asset.decimals
        )
      );
    }
    if (outputQuoteAssetQuantityInPips > 0) {
      asset = assetRegistry.loadAssetByAddress(execution.quoteAssetAddress);
      custodian.withdraw(
        removal.to,
        execution.quoteAssetAddress,
        AssetUnitConversions.pipsToAssetUnits(
          outputQuoteAssetQuantityInPips,
          asset.decimals
        )
      );
    }
  }
}

Settings
{
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "evmVersion": "berlin",
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "libraries": {
    "Exchange.sol": {
      "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB",
      "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac",
      "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc",
      "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962",
      "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad",
      "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1",
      "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2"
    },
    "AssetRegistry.sol": {
      "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB"
    },
    "Depositing.sol": {
      "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac"
    },
    "LiquidityPoolAdmin.sol": {
      "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc"
    },
    "LiquidityPools.sol": {
      "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962"
    },
    "NonceInvalidations.sol": {
      "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad"
    },
    "Trading.sol": {
      "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1"
    },
    "Withdrawing.sol": {
      "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2"
    }
  }
}

Contract Security Audit

Contract ABI

[]

611a7761003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100405760003560e01c8063b0f2e4a614610045578063c42cc91f1461007e575b600080fd5b81801561005157600080fd5b5061006561006036600461143c565b6100b6565b604051610075949392919061164b565b60405180910390f35b81801561008a57600080fd5b5061009e6100993660046113f7565b6102c9565b6040516001600160401b039091168152602001610075565b600080600060606100d28a60c001518b60a001516107d0610365565b61011e5760405162461bcd60e51b8152602060048201526018602482015277457863657373697665207769746864726177616c2066656560401b60448201526064015b60405180910390fd5b60006101298b610399565b60008181526020889052604090205490915060ff16156101845760405162461bcd60e51b81526020600482015260166024820152752430b9b41030b63932b0b23c903bb4ba34323930bbb760511b6044820152606401610115565b6000808c5160018111156101a857634e487b7160e01b600052602160045260246000fd5b146101c25760808c01516101bd908a90610408565b6101df565b6101df8c606001516101d78e60200151610629565b8b91906106c5565b604081015160208201519550935090506101fb888d868d610a25565b955061020b868260600151610af9565b945060006102308d60c001518e60a0015161022691906118b6565b8360600151610af9565b6040808f015160208501519151636ce5768960e11b81529293506001600160a01b038f169263d9caed129261026a92918690600401611627565b600060405180830381600087803b15801561028457600080fd5b505af1158015610298573d6000803e3d6000fd5b50505060009384525050506020959095526040909420805460ff191660011790559198909750909550909350915050565b60006102d6823387610bbd565b905060006102e48487610408565b905060006102f6838360600151610af9565b604051636ce5768960e11b81529091506001600160a01b0387169063d9caed12906103299033908b908690600401611627565b600060405180830381600087803b15801561034357600080fd5b505af1158015610357573d6000803e3d6000fd5b505050505050949350505050565b6000808361037561271087611870565b61037f9190611749565b6001600160401b03808516911611159150505b9392505050565b6000806103a583610c39565b90506103bb818461010001518560400151610d1b565b6104025760405162461bcd60e51b8152602060048201526018602482015277496e76616c69642077616c6c6574207369676e617475726560401b6044820152606401610115565b92915050565b6104106112ea565b6001600160a01b0382166104ba576104b383600201805461043090611948565b80601f016020809104026020016040519081016040528092919081815260200182805461045c90611948565b80156104a95780601f1061047e576101008083540402835291602001916104a9565b820191906000526020600020905b81548152906001019060200180831161048c57829003601f168201915b5050505050610d4b565b9050610402565b6001600160a01b03808316600090815260208581526040808320815160c081018352815460ff8116151582526101009004909516928501929092526001820180549394939184019161050b90611948565b80601f016020809104026020016040519081016040528092919081815260200182805461053790611948565b80156105845780601f1061055957610100808354040283529160200191610584565b820191906000526020600020905b81548152906001019060200180831161056757829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b0316606090910152805190915080156105d1575080608001515b6103925760405162461bcd60e51b8152602060048201526024808201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f72206164646044820152637265737360e01b6064820152608401610115565b6000600f604c83901c16600181146106755760405162461bcd60e51b815260206004820152600f60248201526e135d5cdd081899481d8c4815555251608a1b6044820152606401610115565b610fff60301b601084901c1661ffff60201b603085901c1663ffffffff606086901c168183178117650b1d069b54006106b06127108361170f565b6106ba91906118b6565b979650505050505050565b6106cd6112ea565b6107638460020180546106df90611948565b80601f016020809104026020016040519081016040528092919081815260200182805461070b90611948565b80156107585780601f1061072d57610100808354040283529160200191610758565b820191906000526020600020905b81548152906001019060200180831161073b57829003601f168201915b505050505084610d8a565b156107815761077a84600201805461043090611948565b9050610392565b6107896112ea565b6000856001018560405161079d919061159c565b9081526040519081900360200190205411156109b65760005b85600101856040516107c8919061159c565b9081526040519081900360200190205460ff821610156109b457836001600160401b031686600101866040516107fe919061159c565b90815260200160405180910390208260ff168154811061082e57634e487b7160e01b600052603260045260246000fd5b60009182526020909120600390910201600201546201000090046001600160401b0316116109a2578560010185604051610868919061159c565b90815260200160405180910390208160ff168154811061089857634e487b7160e01b600052603260045260246000fd5b60009182526020918290206040805160c0810182526003909302909101805460ff8116151584526001600160a01b036101009091041693830193909352600183018054929392918401916108eb90611948565b80601f016020809104026020016040519081016040528092919081815260200182805461091790611948565b80156109645780601f1061093957610100808354040283529160200191610964565b820191906000526020600020905b81548152906001019060200180831161094757829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015291505b806109ac8161199e565b9150506107b6565b505b805180156109c5575080608001515b610a1d5760405162461bcd60e51b815260206004820152602360248201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f722073796d604482015262189bdb60ea1b6064820152608401610115565b949350505050565b600080610a3786866040015186610de3565b60a08601518154919250908290600190610a6090849061010090046001600160401b03166118b6565b82546001600160401b0391821661010093840a9081029083021990911617909255835460c08901519190048216945016159050610af057610aa2868486610de3565b60c08601518154919250908290600190610acb90849061010090046001600160401b03166116e4565b92506101000a8154816001600160401b0302191690836001600160401b031602179055505b50949350505050565b600060208260ff161115610b5f5760405162461bcd60e51b815260206004820152602760248201527f41737365742063616e6e6f742068617665206d6f7265207468616e20333220646044820152666563696d616c7360c81b6064820152608401610115565b60088260ff161115610b9457610b766008836118de565b610b8190600a6117a6565b6104b3906001600160401b038516611851565b610b9f8260086118de565b610baa90600a6117a6565b610392906001600160401b038516611735565b600080610bcb858585610de3565b805461010090046001600160401b03169250905081610c235760405162461bcd60e51b8152602060048201526014602482015273139bc818985b185b98d948199bdc88185cdcd95d60621b6044820152606401610115565b8054610100600160481b03191690559392505050565b60208101516040820151600091908284516001811115610c6957634e487b7160e01b600052602160045260246000fd5b14610caa578360800151604051602001610c96919060609190911b6001600160601b031916815260140190565b604051602081830303815290604052610ccf565b8360600151604051602001610cbf919061159c565b6040516020818303038152906040525b610ce58560a001516001600160401b0316610ee6565b8560e00151604051602001610cfe9594939291906115b8565b604051602081830303815290604052805190602001209050919050565b6000816001600160a01b0316610d39610d3386611060565b8561109a565b6001600160a01b031614949350505050565b610d536112ea565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b600081604051602001610d9d919061159c565b6040516020818303038152906040528051906020012083604051602001610dc4919061159c565b6040516020818303038152906040528051906020012014905092915050565b6001600160a01b038083166000908152602085815260408083209385168352929052908120805460ff16158015610e26575060018501546001600160a01b031615155b15610a1d57600185015460405163dbb3653560e01b81526001600160a01b03868116600483015285811660248301529091169063dbb365359060440160206040518083038186803b158015610e7a57600080fd5b505afa158015610e8e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eb29190611580565b815460ff196001600160401b039290921661010002919091166001600160481b031990911617600117815590509392505050565b60608160005b8115610f125780610efc81611983565b9150610f0b9050600a83611735565b9150610eec565b6009811015610f1f575060095b80610f2981611983565b9150506000816001600160401b03811115610f5457634e487b7160e01b600052604160045260246000fd5b6040519080825280601f01601f191660200182016040528015610f7e576020820181803683370190505b509050815b8015610af057610f93818461189f565b60081415610fe657601760f91b82610fac60018461189f565b81518110610fca57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061104e565b610ff1600a876119be565b610ffc9060306116cc565b60f81b8261100b60018461189f565b8151811061102957634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535061104b600a87611735565b95505b8061105881611931565b915050610f83565b6040517b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6020820152603c8101829052600090605c01610cfe565b6000806000808451604114156110c45750505060208201516040830151606084015160001a61113a565b8451604014156110f25750505060408201516020830151906001600160ff1b0381169060ff1c601b0161113a565b60405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610115565b61114686828585611150565b9695505050505050565b60006fa2a8918ca85bafe22016d0b997e4df60600160ff1b038211156111c35760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610115565b8360ff16601b14806111d857508360ff16601c145b61122f5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610115565b6040805160008082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa158015611283573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166112e15760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606401610115565b95945050505050565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b803561132b81611a14565b919050565b8035801515811461132b57600080fd5b600082601f830112611350578081fd5b81356001600160401b038082111561136a5761136a6119fe565b604051601f8301601f19908116603f01168101908282118183101715611392576113926119fe565b816040528381528660208588010111156113aa578485fd5b8360208701602083013792830160200193909352509392505050565b80356002811061132b57600080fd5b80356001600160801b038116811461132b57600080fd5b803561132b81611a2c565b6000806000806080858703121561140c578384fd5b843561141781611a14565b9350602085013561142781611a14565b93969395505050506040820135916060013590565b60008060008060008060c08789031215611454578182fd5b86356001600160401b038082111561146a578384fd5b90880190610120828b03121561147e578384fd5b6114866116a3565b61148f836113c6565b815261149d602084016113d5565b60208201526114ae60408401611320565b60408201526060830135828111156114c4578586fd5b6114d08c828601611340565b6060830152506114e260808401611320565b60808201526114f360a084016113ec565b60a082015261150460c084016113ec565b60c082015261151560e08401611330565b60e0820152610100808401358381111561152d578687fd5b6115398d828701611340565b82840152505080985050505061155160208801611320565b945061155f60408801611320565b9350606087013592506080870135915060a087013590509295509295509295565b600060208284031215611591578081fd5b815161039281611a2c565b600082516115ae818460208701611901565b9190910192915050565b608086901b6001600160801b0319168152606085901b6001600160601b031916601082015283516000906115f3816024850160208901611901565b84519083019061160a816024840160208901611901565b93151560f81b930160248101939093525050602501949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60018060401b038516815283602082015260018060a01b0383166040820152608060608201526000825180608084015261168c8160a0850160208701611901565b601f01601f19169190910160a00195945050505050565b60405161012081016001600160401b03811182821017156116c6576116c66119fe565b60405290565b600082198211156116df576116df6119d2565b500190565b60006001600160401b03828116848216808303821115611706576117066119d2565b01949350505050565b60006001600160801b0383811680611729576117296119e8565b92169190910492915050565b600082611744576117446119e8565b500490565b60006001600160401b0383811680611729576117296119e8565b600181815b8085111561179e578160001904821115611784576117846119d2565b8085161561179157918102915b93841c9390800290611768565b509250929050565b600061039260ff8416836000826117bf57506001610402565b816117cc57506000610402565b81600181146117e257600281146117ec57611808565b6001915050610402565b60ff8411156117fd576117fd6119d2565b50506001821b610402565b5060208310610133831016604e8410600b841016171561182b575081810a610402565b6118358383611763565b8060001904821115611849576118496119d2565b029392505050565b600081600019048311821515161561186b5761186b6119d2565b500290565b60006001600160401b0382811684821681151582840482111615611896576118966119d2565b02949350505050565b6000828210156118b1576118b16119d2565b500390565b60006001600160401b03838116908316818110156118d6576118d66119d2565b039392505050565b600060ff821660ff8416808210156118f8576118f86119d2565b90039392505050565b60005b8381101561191c578181015183820152602001611904565b8381111561192b576000848401525b50505050565b600081611940576119406119d2565b506000190190565b600181811c9082168061195c57607f821691505b6020821081141561197d57634e487b7160e01b600052602260045260246000fd5b50919050565b6000600019821415611997576119976119d2565b5060010190565b600060ff821660ff8114156119b5576119b56119d2565b60010192915050565b6000826119cd576119cd6119e8565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114611a2957600080fd5b50565b6001600160401b0381168114611a2957600080fdfea26469706673582212207fac73239b67f9206f701f29abd0bc658c1c450db9dafc60f6127dc32b06b03c64736f6c63430008040033

Deployed ByteCode Sourcemap

595:4289:15:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;722:2068;;;;;;;;;;-1:-1:-1;722:2068:15;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;2794:768;;;;;;;;;;-1:-1:-1;2794:768:15;;;;;:::i;:::-;;:::i;:::-;;;-1:-1:-1;;;;;11647:31:16;;;11629:50;;11617:2;11602:18;2794:768:15;11584:101:16;722:2068:15;1024:31;1063:38;1109:20;1137:25;1211:148;1251:10;:23;;;1284:10;:30;;;553:8:5;1211:30:15;:148::i;:::-;1196:203;;;;-1:-1:-1;;;1196:203:15;;9813:2:16;1196:203:15;;;9795:21:16;9852:2;9832:18;;;9825:30;-1:-1:-1;;;9871:18:16;;;9864:54;9935:18;;1196:203:15;;;;;;;;;1405:22;1436:51;1476:10;1436:39;:51::i;:::-;1509:41;;;;;;;;;;;;1405:82;;-1:-1:-1;1509:41:15;;1508:42;1493:95;;;;-1:-1:-1;;;1493:95:15;;10166:2:16;1493:95:15;;;10148:21:16;10205:2;10185:18;;;10178:30;-1:-1:-1;;;10224:18:16;;;10217:52;10286:18;;1493:95:15;10138:172:16;1493:95:15;1679:18;;1706:25;;:52;;;;;;-1:-1:-1;;;1706:52:15;;;;;;;;;;:267;;1949:23;;;;1916:57;;:13;;:32;:57::i;:::-;1706:267;;;1769:136;1812:10;:22;;;1846:49;1878:10;:16;;;1846:31;:49::i;:::-;1769:13;;:136;:31;:136::i;:::-;1994:12;;;;2027:18;;;;;-1:-1:-1;1994:12:15;-1:-1:-1;1679:294:15;-1:-1:-1;2109:102:15;:15;2152:10;2027:18;2196:9;2109:35;:102::i;:::-;2082:129;;2250:97;2295:24;2327:5;:14;;;2250:37;:97::i;:::-;2217:130;;2401:36;2446:135;2526:10;:23;;;2493:10;:30;;;:56;;;;:::i;:::-;2559:5;:14;;;2446:37;:135::i;:::-;2613:24;;;;;2645:18;;;;2587:118;;-1:-1:-1;;;2587:118:15;;2401:180;;-1:-1:-1;;;;;;2587:18:15;;;;;:118;;2613:24;2401:180;;2587:118;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;2737:41:15;;;;-1:-1:-1;;;2737:41:15;;;;;;;;;:48;;-1:-1:-1;;2737:48:15;2781:4;2737:48;;;722:2068;;;;-1:-1:-1;722:2068:15;;-1:-1:-1;722:2068:15;;-1:-1:-1;722:2068:15;-1:-1:-1;;722:2068:15:o;2794:768::-;2992:36;3097:73;:15;3134:10;3152:12;3097:29;:73::i;:::-;3065:105;-1:-1:-1;3224:18:15;3245:46;:13;3278:12;3245:32;:46::i;:::-;3224:67;;3297:27;3333:108;3380:29;3419:5;:14;;;3333:37;:108::i;:::-;3447:110;;-1:-1:-1;;;3447:110:15;;3297:144;;-1:-1:-1;;;;;;3447:30:15;;;;;:110;;3493:10;;3512:12;;3297:144;;3447:110;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2794:768;;;;;;;;:::o;5719:224:14:-;5824:4;;5899:5;5861:34;264:9:5;5861:3:14;:34;:::i;:::-;5860:44;;;;:::i;:::-;-1:-1:-1;;;;;5917:21:14;;;;;;;;-1:-1:-1;;5719:224:14;;;;;;:::o;4598:402::-;4700:7;4717:22;4742:37;4768:10;4742:25;:37::i;:::-;4717:62;;4801:126;4835:14;4859:10;:26;;;4895:10;:24;;;4801;:126::i;:::-;4786:181;;;;-1:-1:-1;;;4786:181:14;;7546:2:16;4786:181:14;;;7528:21:16;7585:2;7565:18;;;7558:30;-1:-1:-1;;;7604:18:16;;;7597:54;7668:18;;4786:181:14;7518:174:16;4786:181:14;4981:14;4598:402;-1:-1:-1;;4598:402:14:o;6291:411:1:-;6398:12;;:::i;:::-;-1:-1:-1;;;;;6424:28:1;;6420:91;;6469:35;6481:4;:22;;6469:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;:35::i;:::-;6462:42;;;;6420:91;-1:-1:-1;;;;;6538:34:1;;;6517:18;6538:34;;;;;;;;;;;6517:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:18;;:55;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;6517:55:1;;;-1:-1:-1;;6517:55:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;6517:55:1;;;;;;6593:12;;;;-1:-1:-1;6593:33:1;;;;;6609:5;:17;;;6593:33;6578:100;;;;-1:-1:-1;;;6578:100:1;;10517:2:16;6578:100:1;;;10499:21:16;10556:2;10536:18;;;10529:30;10595:34;10575:18;;;10568:62;-1:-1:-1;;;10646:18:16;;;10639:34;10690:19;;6578:100:1;10489:226:16;307:886:13;392:23;515:34;509:2;501:10;;;500:49;574:1;563:12;;555:40;;;;-1:-1:-1;;;555:40:13;;8259:2:16;555:40:13;;;8241:21:16;8298:2;8278:18;;;8271:30;-1:-1:-1;;;8317:18:16;;;8310:45;8372:18;;555:40:13;8231:165:16;555:40:13;-1:-1:-1;;;707:2:13;699:10;;;698:49;-1:-1:-1;;;780:2:13;772:10;;;771:49;845:10;853:2;845:10;;;;932:18;;;:28;;1144:14;1111:29;1135:5;932:28;1111:29;:::i;:::-;1104:54;;;;:::i;:::-;1085:73;307:886;-1:-1:-1;;;;;;;307:886:13:o;7066:714:1:-;7198:12;;:::i;:::-;7222:45;7236:4;:22;;7222:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7260:6;7222:13;:45::i;:::-;7218:108;;;7284:35;7296:4;:22;;7284:35;;;;;:::i;:::-;7277:42;;;;7218:108;7332:18;;:::i;:::-;7397:1;7360:4;:19;;7380:6;7360:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;:38;7356:296;;;7413:7;7408:238;7430:4;:19;;7450:6;7430:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;7426:38;;;;7408:238;;;7553:13;-1:-1:-1;;;;;7496:70:1;:4;:19;;7516:6;7496:27;;;;;;:::i;:::-;;;;;;;;;;;;;7524:1;7496:30;;;;;;;;-1:-1:-1;;;7496:30:1;;;;;;;;;;;;;;;;;;;;;;:53;;;;;;-1:-1:-1;;;;;7496:53:1;:70;7481:157;;7597:4;:19;;7617:6;7597:27;;;;;;:::i;:::-;;;;;;;;;;;;;7625:1;7597:30;;;;;;;;-1:-1:-1;;;7597:30:1;;;;;;;;;;;;;;;;;;7589:38;;;;;;;;7597:30;;;;;;;7589:38;;;;;;;;;-1:-1:-1;;;;;7589:38:1;;;;;;;;;;;;;;;;;;;7597:30;7589:38;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;7589:38:1;;;-1:-1:-1;;7589:38:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7589:38:1;;;;;;;-1:-1:-1;7481:157:1;7466:3;;;;:::i;:::-;;;;7408:238;;;;7356:296;7672:12;;:33;;;;;7688:5;:17;;;7672:33;7657:99;;;;-1:-1:-1;;;7657:99:1;;9409:2:16;7657:99:1;;;9391:21:16;9448:2;9428:18;;;9421:30;9487:34;9467:18;;;9460:62;-1:-1:-1;;;9538:18:16;;;9531:33;9581:19;;7657:99:1;9381:225:16;7657:99:1;7770:5;7066:714;-1:-1:-1;;;;7066:714:1:o;5136:678:4:-;5296:31;5335:23;5375:99;5412:4;5424:10;:24;;;5456:12;5375:29;:99::i;:::-;5544:30;;;;5519:55;;;;-1:-1:-1;5544:30:4;5519:55;;:21;;:55;;5544:30;;5519:55;;;-1:-1:-1;;;;;5519:55:4;;:::i;:::-;;;-1:-1:-1;;;;;5519:55:4;;;;;;;;;;;;;;;;;;;;;5607:21;;5639:23;;;;5607:21;;;;;;-1:-1:-1;5639:27:4;;;-1:-1:-1;5635:175:4;;5686:60;5716:4;5722:9;5733:12;5686:29;:60::i;:::-;5780:23;;;;5755:48;;;;-1:-1:-1;5780:23:4;5755:48;;:21;;:48;;5780:23;;5755:48;;;-1:-1:-1;;;;;5755:48:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;5755:48:4;;;;;-1:-1:-1;;;;;5755:48:4;;;;;;5635:175;5136:678;;;;;;;:::o;196:470:3:-;301:7;343:2;326:13;:19;;;;318:71;;;;-1:-1:-1;;;318:71:3;;10922:2:16;318:71:3;;;10904:21:16;10961:2;10941:18;;;10934:30;11000:34;10980:18;;;10973:62;-1:-1:-1;;;11051:18:16;;;11044:37;11098:19;;318:71:3;10894:229:16;318:71:3;504:1;488:13;:17;;;484:105;;;563:17;579:1;563:13;:17;:::i;:::-;549:32;;557:2;549:32;:::i;:::-;522:60;;-1:-1:-1;;;;;522:23:3;;:60;:::i;484:105::-;642:17;646:13;642:1;:17;:::i;:::-;628:32;;636:2;628:32;:::i;:::-;601:60;;-1:-1:-1;;;;;601:23:3;;:60;:::i;5840:427:4:-;5957:36;6001:23;6041:57;6071:4;6077:6;6085:12;6041:29;:57::i;:::-;6136:21;;;;;-1:-1:-1;;;;;6136:21:4;;-1:-1:-1;6136:21:4;-1:-1:-1;6136:21:4;6164:66;;;;-1:-1:-1;;;6164:66:4;;11330:2:16;6164:66:4;;;11312:21:16;11369:2;11349:18;;;11342:30;-1:-1:-1;;;11388:18:16;;;11381:50;11448:18;;6164:66:4;11302:170:16;6164:66:4;6237:25;;-1:-1:-1;;;;;;6237:25:4;;;5840:427;;-1:-1:-1;;;5840:427:4:o;3681:613:8:-;3850:16;;;;3878:24;;;;3773:7;;3850:16;3773:7;4010:25;;:52;;;;;;-1:-1:-1;;;4010:52:8;;;;;;;;;;:163;;4149:10;:23;;;4132:41;;;;;;;4351:2:16;4322:15;;;;-1:-1:-1;;;;;;4318:45:16;4306:58;;4389:2;4380:12;;4296:102;4132:41:8;;;;;;;;;;;;;4010:163;;;4094:10;:22;;;4077:40;;;;;;;;:::i;:::-;;;;;;;;;;;;;4010:163;4185:44;4198:10;:30;;;-1:-1:-1;;;;;4185:44:8;:12;:44::i;:::-;4241:10;:30;;;3822:459;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;3803:486;;;;;;3790:499;;3681:613;;;:::o;421:219::-;540:4;629:6;-1:-1:-1;;;;;565:70:8;:60;579:34;608:4;579:28;:34::i;:::-;615:9;565:13;:60::i;:::-;-1:-1:-1;;;;;565:70:8;;;421:219;-1:-1:-1;;;;421:219:8:o;8061:179:1:-;8149:12;;:::i;:::-;-1:-1:-1;8178:57:1;;;;;;;;8184:4;8178:57;;;-1:-1:-1;8178:57:1;;;;;;;;;;;;;8223:2;8178:57;;;;;;;;;;;;;;;;8061:179::o;8334:181::-;8425:4;8507:1;8490:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8480:30;;;;;;8473:1;8456:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8446:30;;;;;;:64;8439:71;;8334:181;;;;:::o;10166:510:4:-;-1:-1:-1;;;;;10353:38:4;;;10298:15;10353:38;;;;;;;;;;;:52;;;;;;;;;;;10417:18;;;;10416:19;:68;;;;-1:-1:-1;10447:20:4;;;;-1:-1:-1;;;;;10447:20:4;10439:45;;10416:68;10412:239;;;10518:20;;;;:93;;-1:-1:-1;;;10518:93:4;;-1:-1:-1;;;;;6509:15:16;;;10518:93:4;;;6491:34:16;6561:15;;;6541:18;;;6534:43;10518:20:4;;;;:47;;6426:18:16;;10518:93:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10494:117;;-1:-1:-1;;;;;;;10494:117:4;;;;;;10619:25;;;;-1:-1:-1;;;;;;10619:25:4;;;;10494:21;10619:25;;;10664:7;-1:-1:-1;10166:510:4;;;;;:::o;4486:776:8:-;4544:13;4725:4;4710:12;4755:59;4762:9;;4755:59;;4781:8;;;;:::i;:::-;;-1:-1:-1;4797:10:8;;-1:-1:-1;4805:2:8;4797:10;;:::i;:::-;;;4755:59;;;4832:1;4823:6;:10;4819:92;;;-1:-1:-1;4852:1:8;4819:92;4916:8;;;;:::i;:::-;;;;4956:20;4989:6;-1:-1:-1;;;;;4979:17:8;;;;;-1:-1:-1;;;4979:17:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;4979:17:8;-1:-1:-1;4956:40:8;-1:-1:-1;5019:6:8;5002:228;5027:5;;5002:228;;5051:10;5060:1;5051:6;:10;:::i;:::-;5065:1;5051:15;5047:177;;;-1:-1:-1;;;5078:7:8;5086:5;5090:1;5086;:5;:::i;:::-;5078:14;;;;;;-1:-1:-1;;;5078:14:8;;;;;;;;;;;;:34;-1:-1:-1;;;;;5078:34:8;;;;;;;;;5047:177;;;5183:9;5190:2;5183:4;:9;:::i;:::-;5177:16;;:2;:16;:::i;:::-;5164:31;;5147:7;5155:5;5159:1;5155;:5;:::i;:::-;5147:14;;;;;;-1:-1:-1;;;5147:14:8;;;;;;;;;;;;:48;-1:-1:-1;;;;;5147:48:8;;;;;;;;-1:-1:-1;5205:10:8;5213:2;5205:10;;:::i;:::-;;;5047:177;5034:3;;;;:::i;:::-;;;;5002:228;;4360:265:6;4559:58;;-1:-1:-1;;;4559:58:6;;;4914:80:16;5010:12;;;5003:28;;;4429:7:6;;5047:12:16;;4559:58:6;4904:161:16;1064:1459:6;1142:7;1217:9;1236;1255:7;1470:9;:16;1490:2;1470:22;1466:1011;;;-1:-1:-1;;;1752:4:6;1737:20;;1731:27;1801:4;1786:20;;1780:27;1858:4;1843:20;;1837:27;1834:1;1829:36;1708:171;;;1899:9;:16;1919:2;1899:22;1895:582;;;-1:-1:-1;;;2186:4:6;2171:20;;2165:27;2235:4;2220:20;;2214:27;;-1:-1:-1;;;;;2263:75:6;;;2368:3;2364:12;2378:2;2360:21;2137:258;;;2425:41;;-1:-1:-1;;;2425:41:6;;7899:2:16;2425:41:6;;;7881:21:16;7938:2;7918:18;;;7911:30;7977:33;7957:18;;;7950:61;8028:18;;2425:41:6;7871:181:16;1895:582:6;2494:22;2502:4;2508:1;2511;2514;2494:7;:22::i;:::-;2487:29;1064:1459;-1:-1:-1;;;;;;1064:1459:6:o;2656:1414::-;2741:7;-1:-1:-1;;;;;3642:80:6;;;3634:127;;;;-1:-1:-1;;;3634:127:6;;8603:2:16;3634:127:6;;;8585:21:16;8642:2;8622:18;;;8615:30;8681:34;8661:18;;;8654:62;-1:-1:-1;;;8732:18:16;;;8725:32;8774:19;;3634:127:6;8575:224:16;3634:127:6;3779:1;:7;;3784:2;3779:7;:18;;;;3790:1;:7;;3795:2;3790:7;3779:18;3771:65;;;;-1:-1:-1;;;3771:65:6;;9006:2:16;3771:65:6;;;8988:21:16;9045:2;9025:18;;;9018:30;9084:34;9064:18;;;9057:62;-1:-1:-1;;;9135:18:16;;;9128:32;9177:19;;3771:65:6;8978:224:16;3771:65:6;3948:24;;;3931:14;3948:24;;;;;;;;;6815:25:16;;;6888:4;6876:17;;6856:18;;;6849:45;;;;6910:18;;;6903:34;;;6953:18;;;6946:34;;;3948:24:6;;6787:19:16;;3948:24:6;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3948:24:6;;-1:-1:-1;;3948:24:6;;;-1:-1:-1;;;;;;;3990:20:6;;3982:57;;;;-1:-1:-1;;;3982:57:6;;7193:2:16;3982:57:6;;;7175:21:16;7232:2;7212:18;;;7205:30;-1:-1:-1;;;7251:18:16;;;7244:54;7315:18;;3982:57:6;7165:174:16;3982:57:6;4057:6;2656:1414;-1:-1:-1;;;;;2656:1414:6:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:134:16:-;82:20;;111:31;82:20;111:31;:::i;:::-;63:85;;;:::o;153:160::-;218:20;;274:13;;267:21;257:32;;247:2;;303:1;300;293:12;318:738;360:5;413:3;406:4;398:6;394:17;390:27;380:2;;435:5;428;421:20;380:2;462:20;;-1:-1:-1;;;;;531:10:16;;;528:2;;;544:18;;:::i;:::-;619:2;613:9;587:2;673:13;;-1:-1:-1;;669:22:16;;;693:2;665:31;661:40;649:53;;;717:18;;;737:22;;;714:46;711:2;;;763:18;;:::i;:::-;803:10;799:2;792:22;838:2;830:6;823:18;884:3;877:4;872:2;864:6;860:15;856:26;853:35;850:2;;;905:5;898;891:20;850:2;973;966:4;958:6;954:17;947:4;939:6;935:17;922:54;996:15;;;1013:4;992:26;985:41;;;;-1:-1:-1;1000:6:16;370:686;-1:-1:-1;;;370:686:16:o;1061:155::-;1141:20;;1190:1;1180:12;;1170:2;;1206:1;1203;1196:12;1221:173;1289:20;;-1:-1:-1;;;;;1338:31:16;;1328:42;;1318:2;;1384:1;1381;1374:12;1399:132;1466:20;;1495:30;1466:20;1495:30;:::i;1536:605::-;1692:6;1700;1708;1716;1769:3;1757:9;1748:7;1744:23;1740:33;1737:2;;;1791:6;1783;1776:22;1737:2;1835:9;1822:23;1854:31;1879:5;1854:31;:::i;:::-;1904:5;-1:-1:-1;1961:2:16;1946:18;;1933:32;1974:33;1933:32;1974:33;:::i;:::-;1727:414;;2026:7;;-1:-1:-1;;;;2080:2:16;2065:18;;2052:32;;2131:2;2116:18;2103:32;;1727:414::o;2146:1762::-;2370:6;2378;2386;2394;2402;2410;2463:3;2451:9;2442:7;2438:23;2434:33;2431:2;;;2485:6;2477;2470:22;2431:2;2517:23;;-1:-1:-1;;;;;2589:14:16;;;2586:2;;;2621:6;2613;2606:22;2586:2;2649:22;;;;2705:6;2687:16;;;2683:29;2680:2;;;2730:6;2722;2715:22;2680:2;2761:17;;:::i;:::-;2801:34;2832:2;2801:34;:::i;:::-;2794:5;2787:49;2868:31;2895:2;2891;2887:11;2868:31;:::i;:::-;2863:2;2856:5;2852:14;2845:55;2932:31;2959:2;2955;2951:11;2932:31;:::i;:::-;2927:2;2920:5;2916:14;2909:55;3010:2;3006;3002:11;2989:25;3039:2;3029:8;3026:16;3023:2;;;3060:6;3052;3045:22;3023:2;3101:44;3137:7;3126:8;3122:2;3118:17;3101:44;:::i;:::-;3096:2;3089:5;3085:14;3078:68;;3179:32;3206:3;3202:2;3198:12;3179:32;:::i;:::-;3173:3;3166:5;3162:15;3155:57;3245:31;3271:3;3267:2;3263:12;3245:31;:::i;:::-;3239:3;3232:5;3228:15;3221:56;3310:31;3336:3;3332:2;3328:12;3310:31;:::i;:::-;3304:3;3297:5;3293:15;3286:56;3375:29;3399:3;3395:2;3391:12;3375:29;:::i;:::-;3369:3;3362:5;3358:15;3351:54;3424:3;3473:2;3469;3465:11;3452:25;3502:2;3492:8;3489:16;3486:2;;;3523:6;3515;3508:22;3486:2;3564:44;3600:7;3589:8;3585:2;3581:17;3564:44;:::i;:::-;3559:2;3552:5;3548:14;3541:68;;;3628:5;3618:15;;;;;3652:38;3686:2;3675:9;3671:18;3652:38;:::i;:::-;3642:48;;3709:38;3743:2;3732:9;3728:18;3709:38;:::i;:::-;3699:48;;3794:2;3783:9;3779:18;3766:32;3756:42;;3845:3;3834:9;3830:19;3817:33;3807:43;;3897:3;3886:9;3882:19;3869:33;3859:43;;2421:1487;;;;;;;;:::o;3913:259::-;3982:6;4035:2;4023:9;4014:7;4010:23;4006:32;4003:2;;;4056:6;4048;4041:22;4003:2;4093:9;4087:16;4112:30;4136:5;4112:30;:::i;4403:276::-;4534:3;4572:6;4566:13;4588:53;4634:6;4629:3;4622:4;4614:6;4610:17;4588:53;:::i;:::-;4657:16;;;;;4542:137;-1:-1:-1;;4542:137:16:o;5070:808::-;5405:3;5375:16;;;-1:-1:-1;;;;;;5371:47:16;5359:60;;5482:2;5453:15;;;-1:-1:-1;;;;;;5449:45:16;5444:2;5435:12;;5428:67;5518:13;;-1:-1:-1;;5540:62:16;5518:13;5590:2;5581:12;;5574:4;5562:17;;5540:62;:::i;:::-;5662:13;;5621:16;;;;5684:63;5662:13;5733:2;5725:11;;5718:4;5706:17;;5684:63;:::i;:::-;5828:14;;5821:22;5816:3;5812:32;5766:17;;5807:2;5799:11;;5792:53;;;;-1:-1:-1;;5869:2:16;5861:11;;;-1:-1:-1;;;;5349:529:16:o;5883:391::-;-1:-1:-1;;;;;6157:15:16;;;6139:34;;6209:15;;;;6204:2;6189:18;;6182:43;6256:2;6241:18;;6234:34;;;;6089:2;6074:18;;6056:218::o;11690:657::-;11957:1;11953;11949:2;11945:10;11941:18;11933:6;11929:31;11918:9;11911:50;11997:6;11992:2;11981:9;11977:18;11970:34;12069:1;12065;12060:3;12056:11;12052:19;12044:6;12040:32;12035:2;12024:9;12020:18;12013:60;12109:3;12104:2;12093:9;12089:18;12082:31;11892:4;12142:6;12136:13;12186:6;12180:3;12169:9;12165:19;12158:35;12202:67;12262:6;12256:3;12245:9;12241:19;12236:2;12228:6;12224:15;12202:67;:::i;:::-;12330:2;12309:15;-1:-1:-1;;12305:29:16;12290:45;;;;12337:3;12286:55;;11901:446;-1:-1:-1;;;;;11901:446:16:o;12352:250::-;12419:2;12413:9;12461:6;12449:19;;-1:-1:-1;;;;;12483:34:16;;12519:22;;;12480:62;12477:2;;;12545:18;;:::i;:::-;12581:2;12574:22;12393:209;:::o;12607:128::-;12647:3;12678:1;12674:6;12671:1;12668:13;12665:2;;;12684:18;;:::i;:::-;-1:-1:-1;12720:9:16;;12655:80::o;12740:236::-;12779:3;-1:-1:-1;;;;;12845:10:16;;;12875;;;12905:12;;;12897:21;;12894:2;;;12921:18;;:::i;:::-;12957:13;;12787:189;-1:-1:-1;;;;12787:189:16:o;12981:201::-;13021:1;-1:-1:-1;;;;;13086:10:16;;;;13105:2;;13122:18;;:::i;:::-;13160:10;;13156:20;;;;;13027:155;-1:-1:-1;;13027:155:16:o;13187:120::-;13227:1;13253;13243:2;;13258:18;;:::i;:::-;-1:-1:-1;13292:9:16;;13233:74::o;13312:199::-;13351:1;-1:-1:-1;;;;;13415:10:16;;;;13434:2;;13451:18;;:::i;13516:422::-;13605:1;13648:5;13605:1;13662:270;13683:7;13673:8;13670:21;13662:270;;;13742:4;13738:1;13734:6;13730:17;13724:4;13721:27;13718:2;;;13751:18;;:::i;:::-;13801:7;13791:8;13787:22;13784:2;;;13821:16;;;;13784:2;13900:22;;;;13860:15;;;;13662:270;;;13666:3;13580:358;;;;;:::o;13943:140::-;14001:5;14030:47;14071:4;14061:8;14057:19;14051:4;14137:5;14167:8;14157:2;;-1:-1:-1;14208:1:16;14222:5;;14157:2;14256:4;14246:2;;-1:-1:-1;14293:1:16;14307:5;;14246:2;14338:4;14356:1;14351:59;;;;14424:1;14419:130;;;;14331:218;;14351:59;14381:1;14372:10;;14395:5;;;14419:130;14456:3;14446:8;14443:17;14440:2;;;14463:18;;:::i;:::-;-1:-1:-1;;14519:1:16;14505:16;;14534:5;;14331:218;;14633:2;14623:8;14620:16;14614:3;14608:4;14605:13;14601:36;14595:2;14585:8;14582:16;14577:2;14571:4;14568:12;14564:35;14561:77;14558:2;;;-1:-1:-1;14670:19:16;;;14702:5;;14558:2;14749:34;14774:8;14768:4;14749:34;:::i;:::-;14819:6;14815:1;14811:6;14807:19;14798:7;14795:32;14792:2;;;14830:18;;:::i;:::-;14868:20;;14147:747;-1:-1:-1;;;14147:747:16:o;14899:168::-;14939:7;15005:1;15001;14997:6;14993:14;14990:1;14987:21;14982:1;14975:9;14968:17;14964:45;14961:2;;;15012:18;;:::i;:::-;-1:-1:-1;15052:9:16;;14951:116::o;15072:270::-;15111:7;-1:-1:-1;;;;;15181:10:16;;;15211;;;15244:11;;15237:19;15266:12;;;15258:21;;15233:47;15230:2;;;15283:18;;:::i;:::-;15323:13;;15123:219;-1:-1:-1;;;;15123:219:16:o;15347:125::-;15387:4;15415:1;15412;15409:8;15406:2;;;15420:18;;:::i;:::-;-1:-1:-1;15457:9:16;;15396:76::o;15477:229::-;15516:4;-1:-1:-1;;;;;15613:10:16;;;;15583;;15635:12;;;15632:2;;;15650:18;;:::i;:::-;15687:13;;15525:181;-1:-1:-1;;;15525:181:16:o;15711:195::-;15749:4;15786;15783:1;15779:12;15818:4;15815:1;15811:12;15843:3;15838;15835:12;15832:2;;;15850:18;;:::i;:::-;15887:13;;;15758:148;-1:-1:-1;;;15758:148:16:o;15911:258::-;15983:1;15993:113;16007:6;16004:1;16001:13;15993:113;;;16083:11;;;16077:18;16064:11;;;16057:39;16029:2;16022:10;15993:113;;;16124:6;16121:1;16118:13;16115:2;;;16159:1;16150:6;16145:3;16141:16;16134:27;16115:2;;15964:205;;;:::o;16174:136::-;16213:3;16241:5;16231:2;;16250:18;;:::i;:::-;-1:-1:-1;;;16286:18:16;;16221:89::o;16315:380::-;16394:1;16390:12;;;;16437;;;16458:2;;16512:4;16504:6;16500:17;16490:27;;16458:2;16565;16557:6;16554:14;16534:18;16531:38;16528:2;;;16611:10;16606:3;16602:20;16599:1;16592:31;16646:4;16643:1;16636:15;16674:4;16671:1;16664:15;16528:2;;16370:325;;;:::o;16700:135::-;16739:3;-1:-1:-1;;16760:17:16;;16757:2;;;16780:18;;:::i;:::-;-1:-1:-1;16827:1:16;16816:13;;16747:88::o;16840:175::-;16877:3;16921:4;16914:5;16910:16;16950:4;16941:7;16938:17;16935:2;;;16958:18;;:::i;:::-;17007:1;16994:15;;16885:130;-1:-1:-1;;16885:130:16:o;17020:112::-;17052:1;17078;17068:2;;17083:18;;:::i;:::-;-1:-1:-1;17117:9:16;;17058:74::o;17137:127::-;17198:10;17193:3;17189:20;17186:1;17179:31;17229:4;17226:1;17219:15;17253:4;17250:1;17243:15;17269:127;17330:10;17325:3;17321:20;17318:1;17311:31;17361:4;17358:1;17351:15;17385:4;17382:1;17375:15;17401:127;17462:10;17457:3;17453:20;17450:1;17443:31;17493:4;17490:1;17483:15;17517:4;17514:1;17507:15;17533:131;-1:-1:-1;;;;;17608:31:16;;17598:42;;17588:2;;17654:1;17651;17644:12;17588:2;17578:86;:::o;17669:129::-;-1:-1:-1;;;;;17743:30:16;;17733:41;;17723:2;;17788:1;17785;17778:12

Swarm Source

ipfs://7fac73239b67f9206f701f29abd0bc658c1c450db9dafc60f6127dc32b06b03c
Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.