Contract Overview
My Name Tag:
Not Available, login to update
[ Download CSV Export ]
Latest 25 internal transaction
[ Download CSV Export ]
Contract Name:
Exchange_v3_1
Compiler Version
v0.8.10+commit.fc410830
Contract Source Code (Solidity Standard Json-Input format)
// 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); } } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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)); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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' ); } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; /** * @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); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; /** * @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; // To convert double integer pips to a fractional price shift decimal left by the double pip // precision of 16 decimals places uint64 public constant doublePipPriceMultiplier = 10**16; uint8 public constant signatureHashVersion = 4; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { Address } from './Address.sol'; import { ICustodian } from './Interfaces.sol'; import { Owned } from './Owned.sol'; import { AssetTransfers } from './AssetTransfers.sol'; /** * @notice The Custodian contract. Holds custody of all deposited funds for whitelisted Exchange * contract with minimal additional logic */ contract Custodian is ICustodian, Owned { // Events // /** * @notice Emitted on construction and when Governance upgrades the Exchange contract address */ event ExchangeChanged(address oldExchange, address newExchange); /** * @notice Emitted on construction and when Governance replaces itself by upgrading the Governance contract address */ event GovernanceChanged(address oldGovernance, address newGovernance); address _exchange; address _governance; /** * @notice Instantiate a new Custodian * * @dev Sets `owner` and `admin` to `msg.sender`. Sets initial values for Exchange and Governance * contract addresses, after which they can only be changed by the currently set Governance contract * itself * * @param exchange Address of deployed Exchange contract to whitelist * @param governance ddress of deployed Governance contract to whitelist */ constructor(address exchange, address governance) Owned() { require(Address.isContract(exchange), 'Invalid exchange contract address'); require( Address.isContract(governance), 'Invalid governance contract address' ); _exchange = exchange; _governance = governance; emit ExchangeChanged(address(0x0), exchange); emit GovernanceChanged(address(0x0), governance); } /** * @notice ETH can only be sent by the Exchange */ receive() external payable override onlyExchange {} /** * @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 (ETH or ERC-20 contract) * @param quantityInAssetUnits The quantity in asset units to withdraw */ function withdraw( address payable wallet, address asset, uint256 quantityInAssetUnits ) external override onlyExchange { AssetTransfers.transferTo(wallet, asset, quantityInAssetUnits); } /** * @notice Load address of the currently whitelisted Exchange contract * * @return The address of the currently whitelisted Exchange contract */ function loadExchange() external view override returns (address) { return _exchange; } /** * @notice Sets a new Exchange contract address * * @param newExchange The address of the new whitelisted Exchange contract */ function setExchange(address newExchange) external override onlyGovernance { require(Address.isContract(newExchange), 'Invalid contract address'); address oldExchange = _exchange; _exchange = newExchange; emit ExchangeChanged(oldExchange, newExchange); } /** * @notice Load address of the currently whitelisted Governance contract * * @return The address of the currently whitelisted Governance contract */ function loadGovernance() external view override returns (address) { return _governance; } /** * @notice Sets a new Governance contract address * * @param newGovernance The address of the new whitelisted Governance contract */ function setGovernance(address newGovernance) external override onlyGovernance { require(Address.isContract(newGovernance), 'Invalid contract address'); address oldGovernance = _governance; _governance = newGovernance; emit GovernanceChanged(oldGovernance, newGovernance); } // RBAC // modifier onlyExchange() { require(msg.sender == _exchange, 'Caller must be Exchange contract'); _; } modifier onlyGovernance() { require(msg.sender == _governance, 'Caller must be Governance contract'); _; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetTransfers } from './AssetTransfers.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { ICustodian, IERC20 } from './Interfaces.sol'; import { Asset, LiquidityAdditionDepositResult, LiquidityRemovalDepositResult } from './Structs.sol'; library Depositing { using AssetRegistry for AssetRegistry.Storage; using BalanceTracking for BalanceTracking.Storage; /** * @dev delegatecall entry point for `Exchange` when depositing native or token assets */ function deposit( address wallet, Asset memory asset, uint256 quantityInAssetUnits, ICustodian custodian, BalanceTracking.Storage storage balanceTracking ) public returns ( uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ) { return depositAsset( wallet, asset, quantityInAssetUnits, custodian, balanceTracking ); } function depositLiquidityReserves( address wallet, address assetA, address assetB, uint256 quantityAInAssetUnits, uint256 quantityBInAssetUnits, ICustodian custodian, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) internal returns (LiquidityAdditionDepositResult memory result) { Asset memory asset; asset = assetRegistry.loadAssetByAddress(assetA); result.assetASymbol = asset.symbol; ( result.assetAQuantityInPips, result.assetANewExchangeBalanceInPips, result.assetANewExchangeBalanceInAssetUnits ) = depositAsset( wallet, asset, quantityAInAssetUnits, custodian, balanceTracking ); asset = assetRegistry.loadAssetByAddress(assetB); result.assetBSymbol = asset.symbol; ( result.assetBQuantityInPips, result.assetBNewExchangeBalanceInPips, result.assetBNewExchangeBalanceInAssetUnits ) = depositAsset( wallet, asset, quantityBInAssetUnits, custodian, balanceTracking ); } function depositLiquidityTokens( address wallet, address liquidityProviderToken, uint256 quantityInAssetUnits, ICustodian custodian, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) internal returns (LiquidityRemovalDepositResult memory result) { Asset memory asset = assetRegistry.loadAssetByAddress(liquidityProviderToken); result.assetSymbol = asset.symbol; result.assetAddress = liquidityProviderToken; ( result.assetQuantityInPips, result.assetNewExchangeBalanceInPips, result.assetNewExchangeBalanceInAssetUnits ) = depositAsset( wallet, asset, quantityInAssetUnits, custodian, balanceTracking ); } function depositAsset( address wallet, Asset memory asset, uint256 quantityInAssetUnits, ICustodian custodian, BalanceTracking.Storage storage balanceTracking ) internal returns ( uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ) { quantityInPips = AssetUnitConversions.assetUnitsToPips( quantityInAssetUnits, asset.decimals ); require(quantityInPips > 0, 'Quantity is too low'); // Convert from pips back into asset units to remove any fractional amount that is too small // to express in pips. If the asset is ETH, this leftover fractional amount accumulates as dust // in the `Exchange` contract. If the asset is a token the `Exchange` will call `transferFrom` // without this fractional amount and there will be no dust uint256 quantityInAssetUnitsWithoutFractionalPips = AssetUnitConversions.pipsToAssetUnits(quantityInPips, asset.decimals); // Forward the funds to the `Custodian` if (asset.assetAddress == address(0x0)) { // If the asset is ETH then the funds were already assigned to the `Exchange` via msg.value. AssetTransfers.transferTo( payable(address(custodian)), asset.assetAddress, quantityInAssetUnitsWithoutFractionalPips ); } else { // If the asset is a token, call the transferFrom function on the token contract for the // pre-approved asset quantity AssetTransfers.transferFrom( wallet, IERC20(asset.assetAddress), payable(address(custodian)), quantityInAssetUnitsWithoutFractionalPips ); } // Update balance with actual transferred quantity newExchangeBalanceInPips = balanceTracking.updateForDeposit( wallet, asset.assetAddress, quantityInPips ); newExchangeBalanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits( newExchangeBalanceInPips, asset.decimals ); } }
// 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)); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IERC20.sol"; import "./IERC20Metadata.sol"; import "./Context.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin guidelines: functions revert instead * of returning `false` on failure. This behavior is nonetheless conventional * and does not conflict with the expectations of ERC20 applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20, IERC20Metadata { mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * The defaut value of {decimals} is 18. To select a different value for * {decimals} you should overload it. * * All two of these values are immutable: they can only be set once during * construction. */ constructor (string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5,05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless this function is * overridden; * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { _transfer(_msgSender(), recipient, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { _approve(_msgSender(), spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * Requirements: * * - `sender` and `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. * - the caller must have allowance for ``sender``'s tokens of at least * `amount`. */ function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { _transfer(sender, recipient, amount); uint256 currentAllowance = _allowances[sender][_msgSender()]; require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); _approve(sender, _msgSender(), currentAllowance - amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { uint256 currentAllowance = _allowances[_msgSender()][spender]; require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); _approve(_msgSender(), spender, currentAllowance - subtractedValue); return true; } /** * @dev Moves tokens `amount` from `sender` to `recipient`. * * This is internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `sender` cannot be the zero address. * - `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. */ function _transfer(address sender, address recipient, uint256 amount) internal virtual { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(sender, recipient, amount); uint256 senderBalance = _balances[sender]; require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); _balances[sender] = senderBalance - amount; _balances[recipient] += amount; emit Transfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `to` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); _balances[account] = accountBalance - amount; _totalSupply -= amount; emit Transfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be to transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; /** * @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 }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { Address } from './Address.sol'; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { Constants } from './Constants.sol'; import { Depositing } from './Depositing.sol'; import { LiquidityPools } from './LiquidityPools.sol'; import { LiquidityPoolAdmin } from './LiquidityPoolAdmin.sol'; import { NonceInvalidations } from './NonceInvalidations.sol'; import { Owned } from './Owned.sol'; import { Trading } from './Trading.sol'; import { UUID } from './UUID.sol'; import { Withdrawing } from './Withdrawing.sol'; import { LiquidityChangeOrigination, OrderSide } from './Enums.sol'; import { ICustodian, IERC20, IExchange, ILiquidityProviderToken, IWETH9 } from './Interfaces.sol'; import { Asset, HybridTrade, LiquidityAddition, LiquidityAdditionDepositResult, LiquidityChangeExecution, LiquidityMigration, LiquidityPool, LiquidityRemoval, LiquidityRemovalDepositResult, NonceInvalidation, Order, OrderBookTrade, PoolTrade, Withdrawal } from './Structs.sol'; /** * @notice The Exchange contract. Implements all deposit, trade, and withdrawal logic and * associated balance tracking * * @dev The term `asset` refers collectively to ETH and ERC-20 tokens, the term `token` refers only * to the latter */ contract Exchange_v3_1 is IExchange, Owned { using AssetRegistry for AssetRegistry.Storage; using BalanceTracking for BalanceTracking.Storage; using LiquidityPools for LiquidityPools.Storage; using LiquidityPoolAdmin for LiquidityPools.Storage; using NonceInvalidations for mapping(address => NonceInvalidation); // Events // /** * @notice Emitted when an admin changes the Chain Propagation Period tunable parameter with * `setChainPropagationPeriod` */ event ChainPropagationPeriodChanged(uint256 previousValue, uint256 newValue); /** * @notice Emitted when a user deposits ETH with `depositEther` or a token with * `depositTokenByAddress` or `depositTokenBySymbol` */ event Deposited( uint64 index, address wallet, address assetAddress, string assetSymbol, uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ); /** * @notice Emitted when the Dispatcher Wallet submits a hybrid trade for execution with * `executeHybridTrade` */ event HybridTradeExecuted( address buyWallet, address sellWallet, string baseAssetSymbol, string quoteAssetSymbol, uint64 orderBookBaseQuantityInPips, uint64 orderBookQuoteQuantityInPips, uint64 poolBaseQuantityInPips, uint64 poolQuoteQuantityInPips, uint64 totalBaseQuantityInPips, uint64 totalQuoteQuantityInPips, OrderSide takerSide ); /** * @notice Emitted when a user initiates an Add Liquidity request via `addLiquidity` or * `addLiquidityETH` */ event LiquidityAdditionInitiated( address wallet, address assetA, address assetB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ); /** * @notice Emitted when the Dispatcher Wallet submits a liquidity addition for execution with * `executeAddLiquidity` */ event LiquidityAdditionExecuted( address wallet, address baseAssetAddress, address quoteAssetAddress, uint64 baseQuantityInPips, uint64 quoteQuantityInPips, uint64 liquidityInPips ); /** * @notice Emitted when an Admin switches liquidity pool asset direction via * `reverseLiquidityPoolAssets` */ event LiquidityPoolAssetsReversed( address originalBaseAssetAddress, address originalQuoteAssetAddress ); /** * @notice Emitted when a user initiates a Remove Liquidity request via `removeLiquidity` or * `removeLiquidityETH` */ event LiquidityRemovalInitiated( address wallet, address assetA, address assetB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ); /** * @notice Emitted when the Dispatcher Wallet submits a liquidity removal for execution with * `executeRemoveLiquidity` */ event LiquidityRemovalExecuted( address wallet, address baseAssetAddress, address quoteAssetAddress, uint64 baseQuantityInPips, uint64 quoteQuantityInPips, uint64 liquidityInPips ); /** * @notice Emitted when the Dispatcher Wallet submits a trade for execution with * `executeOrderBookTrade` */ event OrderBookTradeExecuted( address buyWallet, address sellWallet, string baseAssetSymbol, string quoteAssetSymbol, uint64 baseQuantityInPips, uint64 quoteQuantityInPips, OrderSide takerSide ); /** * @notice Emitted when a user invalidates an order nonce with `invalidateOrderNonce` */ event OrderNonceInvalidated( address wallet, uint128 nonce, uint128 timestampInMs, uint256 effectiveBlockNumber ); /** * @notice Emitted when the Dispatcher Wallet submits a pool trade for execution with * `executePoolTrade` */ event PoolTradeExecuted( address wallet, string baseAssetSymbol, string quoteAssetSymbol, uint64 baseQuantityInPips, uint64 quoteQuantityInPips, OrderSide takerSide ); /** * @notice Emitted when an admin adds a symbol to a previously registered and confirmed token * via `addTokenSymbol` */ event TokenSymbolAdded(IERC20 assetAddress, string assetSymbol); /** * @notice Emitted when a user invokes the Exit Wallet mechanism with `exitWallet` */ event WalletExited(address wallet, uint256 effectiveBlockNumber); /** * @notice Emitted when a user withdraws liquidity reserve assets through the Exit Wallet * mechanism with `removeLiquidityExit` */ event WalletExitLiquidityRemoved( address wallet, address baseAssetAddress, address quoteAssetAddress, uint64 baseAssetQuantityInPips, uint64 quoteAssetQuantityInPips ); /** * @notice Emitted when a user withdraws an asset balance through the Exit Wallet mechanism with * `withdrawExit` */ event WalletExitWithdrawn( address wallet, address assetAddress, uint64 quantityInPips ); /** * @notice Emitted when a user clears the exited status of a wallet previously exited with * `exitWallet` */ event WalletExitCleared(address wallet); /** * @notice Emitted when the Dispatcher Wallet submits a withdrawal with `withdraw` */ event Withdrawn( address wallet, address assetAddress, string assetSymbol, uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ); // Internally used structs // struct WalletExit { bool exists; uint256 effectiveBlockNumber; } // Storage // // Asset registry data AssetRegistry.Storage _assetRegistry; // Balance tracking BalanceTracking.Storage _balanceTracking; // CLOB - mapping of order wallet hash => isComplete mapping(bytes32 => bool) _completedOrderHashes; // CLOB - mapping of wallet => last invalidated timestampInMs mapping(address => NonceInvalidation) _nonceInvalidations; // CLOB - mapping of order hash => filled quantity in pips mapping(bytes32 => uint64) _partiallyFilledOrderQuantitiesInPips; // Custodian ICustodian _custodian; // Deposit index uint64 public _depositIndex; // Exits mapping(address => WalletExit) public _walletExits; // Liquidity pools address _liquidityMigrator; LiquidityPools.Storage _liquidityPools; // Withdrawals - mapping of withdrawal wallet hash => isComplete mapping(bytes32 => bool) _completedWithdrawalHashes; // Tunable parameters uint256 _chainPropagationPeriod; address _dispatcherWallet; address _feeWallet; /** * @notice Instantiate a new `Exchange` contract * * @dev Sets `_balanceTracking.migrationSource` to first argument, and `_owner` and `_admin` to * `msg.sender` */ constructor( IExchange balanceMigrationSource, address feeWallet, string memory nativeAssetSymbol ) Owned() { require( address(balanceMigrationSource) == address(0x0) || Address.isContract(address(balanceMigrationSource)), 'Invalid migration source' ); _balanceTracking.migrationSource = balanceMigrationSource; setFeeWallet(feeWallet); _assetRegistry.nativeAssetSymbol = nativeAssetSymbol; // Deposits must be manually enabled via `setDepositIndex` _depositIndex = Constants.depositIndexNotSet; } /** * @notice Sets the address of the `Custodian` contract * * @dev The `Custodian` accepts `Exchange` and `Governance` addresses in its constructor, after * which they can only be changed by the `Governance` contract itself. Therefore the `Custodian` * must be deployed last and its address set here on an existing `Exchange` contract. This value * is immutable once set and cannot be changed again * * @param newCustodian The address of the `Custodian` contract deployed against this `Exchange` * contract's address */ function setCustodian(ICustodian newCustodian) external onlyAdmin { require( _custodian == ICustodian(payable(address(0x0))), 'Custodian can only be set once' ); require(Address.isContract(address(newCustodian)), 'Invalid address'); _custodian = newCustodian; } /** * @notice Enable depositing assets into the Exchange by setting the current deposit index from * the old Exchange contract's value. This function can only be called once * */ function setDepositIndex() external onlyAdmin { require( _depositIndex == Constants.depositIndexNotSet, 'Can only be set once' ); _depositIndex = address(_balanceTracking.migrationSource) == address(0x0) ? 0 : _balanceTracking.migrationSource._depositIndex(); } /*** Tunable parameters ***/ /** * @notice Sets a new Chain Propagation Period - the block delay after which order nonce * invalidations and wallet exits go into effect * * @param newChainPropagationPeriod The new Chain Propagation Period expressed as a number of * blocks. Must be less than `Constants.maxChainPropagationPeriod` */ function setChainPropagationPeriod(uint256 newChainPropagationPeriod) external onlyAdmin { require( newChainPropagationPeriod < Constants.maxChainPropagationPeriod, 'New period greater than max' ); uint256 oldChainPropagationPeriod = _chainPropagationPeriod; _chainPropagationPeriod = newChainPropagationPeriod; emit ChainPropagationPeriodChanged( oldChainPropagationPeriod, newChainPropagationPeriod ); } /** * @notice Sets the address of the Fee wallet * * @dev Trade and Withdraw fees will accrue in the `_balances` mappings for this wallet * @dev Visibility public instead of external to allow invocation from `constructor` * * @param newFeeWallet The new Fee wallet. Must be different from the current one */ function setFeeWallet(address newFeeWallet) public onlyAdmin { require(newFeeWallet != address(0x0), 'Invalid wallet address'); require(newFeeWallet != _feeWallet, 'Must be different from current'); _feeWallet = newFeeWallet; } /** * @notice Sets the address of the `Migrator` contract * * @param newMigrator The new Migrator contract. Must be different from the current one */ function setMigrator(address newMigrator) external onlyAdmin { require(Address.isContract(address(newMigrator)), 'Invalid address'); require( newMigrator != _liquidityMigrator, 'Must be different from current' ); _liquidityMigrator = newMigrator; } // Accessors // /** * @notice Load a wallet's balance by asset address, in asset units * * @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 asset units of asset at `assetAddress` currently * deposited by `wallet` */ function loadBalanceInAssetUnitsByAddress( address wallet, address assetAddress ) external view returns (uint256) { return _assetRegistry.loadBalanceInAssetUnitsByAddress( wallet, assetAddress, _balanceTracking ); } /** * @notice Load a wallet's balance by asset symbol, in asset units * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetSymbol The asset symbol to load the wallet's balance for * * @return The quantity denominated in asset units of asset `assetSymbol` currently deposited * by `wallet` */ function loadBalanceInAssetUnitsBySymbol( address wallet, string calldata assetSymbol ) external view returns (uint256) { return _assetRegistry.loadBalanceInAssetUnitsBySymbol( wallet, assetSymbol, _balanceTracking ); } /** * @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 override returns (uint64) { return AssetRegistry.loadBalanceInPipsByAddress( wallet, assetAddress, _balanceTracking ); } /** * @notice Load a wallet's balance by asset symbol, in pips * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetSymbol The asset symbol to load the wallet's balance for * * @return The quantity denominated in pips of asset with `assetSymbol` currently deposited by * `wallet` */ function loadBalanceInPipsBySymbol( address wallet, string calldata assetSymbol ) external view returns (uint64) { return _assetRegistry.loadBalanceInPipsBySymbol( wallet, assetSymbol, _balanceTracking ); } /** * @notice Load the address of the Custodian contract * * @return The address of the Custodian contract */ function loadCustodian() external view override returns (ICustodian) { return _custodian; } /** * @notice Load the address of the Fee wallet * * @return The address of the Fee wallet */ function loadFeeWallet() external view returns (address) { return _feeWallet; } /** * @notice Load the internally-tracked liquidity pool descriptor for a base-quote asset pair * * @return A `LiquidityPool` struct encapsulating the current state of the internally-tracked * liquidity pool for the given base-quote asset pair. Reverts if no such pool exists */ function loadLiquidityPoolByAssetAddresses( address baseAssetAddress, address quoteAssetAddress ) external view override returns (LiquidityPool memory) { return _liquidityPools.loadLiquidityPoolByAssetAddresses( baseAssetAddress, quoteAssetAddress ); } /** * @notice Load the address of the Migrator contract * * @return The address of the Migrator contract */ function loadLiquidityMigrator() external view returns (address) { return _liquidityMigrator; } /** * @notice Load the quantity filled so far for a partially filled orders * * @dev Invalidating an order nonce will not clear partial fill quantities for earlier orders * because * the gas cost would potentially be unbound * * @param orderHash The order hash as originally signed by placing wallet that uniquely * identifies an order * * @return For partially filled orders, the amount filled so far in pips. For orders in all other * states, 0 */ function loadPartiallyFilledOrderQuantityInPips(bytes32 orderHash) external view returns (uint64) { return _partiallyFilledOrderQuantitiesInPips[orderHash]; } // Depositing // /** * @notice DO NOT send assets directly to the `Exchange`, instead use the appropriate deposit * function * * @dev Internally used to unwrap WETH during liquidity pool migrations via * `migrateLiquidityPool`. The sender is only required to be a contract rather than locking it * to a particular WETH instance to allow for migrating from multiple pools that use different * WETH contracts */ receive() external payable { require(Address.isContract(msg.sender), 'Use depositEther'); } /** * @notice Deposit ETH */ function depositEther() external payable { deposit( msg.sender, _assetRegistry.loadAssetByAddress(address(0x0)), msg.value ); } /** * @notice Deposit `IERC20` compliant tokens * * @param tokenAddress The token contract address * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the * `approve` method on the token contract for at least this quantity first */ function depositTokenByAddress( address tokenAddress, uint256 quantityInAssetUnits ) external { Asset memory asset = _assetRegistry.loadAssetByAddress(tokenAddress); require(address(tokenAddress) != address(0x0), 'Use depositEther'); deposit(msg.sender, asset, quantityInAssetUnits); } /** * @notice Deposit `IERC20` compliant tokens * * @param assetSymbol The case-sensitive symbol string for the token * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the * `approve` method on the token contract for at least this quantity first */ function depositTokenBySymbol( string memory assetSymbol, uint256 quantityInAssetUnits ) external { Asset memory asset = _assetRegistry.loadAssetBySymbol( assetSymbol, AssetRegistry.getCurrentTimestampInMs() ); require(address(asset.assetAddress) != address(0x0), 'Use depositEther'); deposit(msg.sender, asset, quantityInAssetUnits); } function deposit( address wallet, Asset memory asset, uint256 quantityInAssetUnits ) private { // Deposits are disabled until `setDepositIndex` is called successfully require(_depositIndex != Constants.depositIndexNotSet, 'Deposits disabled'); // Calling exitWallet disables deposits immediately on mining, in contrast to withdrawals and // trades which respect the Chain Propagation Period given by `effectiveBlockNumber` via // `isWalletExitFinalized` require(!_walletExits[wallet].exists, 'Wallet exited'); ( uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ) = Depositing.deposit( wallet, asset, quantityInAssetUnits, _custodian, _balanceTracking ); _depositIndex++; emit Deposited( _depositIndex, wallet, asset.assetAddress, asset.symbol, quantityInPips, newExchangeBalanceInPips, newExchangeBalanceInAssetUnits ); } // Trades // /** * @notice Settles a trade between two orders submitted and matched off-chain * * @param buy An `Order` struct encoding the parameters of the buy-side order (receiving base, * giving quote) * @param sell An `Order` struct encoding the parameters of the sell-side order (giving base, * receiving quote) * @param orderBookTrade An `OrderBookTrade` struct encoding the parameters of this trade * execution of the two orders */ function executeOrderBookTrade( Order memory buy, Order memory sell, OrderBookTrade memory orderBookTrade ) external onlyDispatcher { require( !isWalletExitFinalized(buy.walletAddress), 'Buy wallet exit finalized' ); require( !isWalletExitFinalized(sell.walletAddress), 'Sell wallet exit finalized' ); Trading.executeOrderBookTrade( buy, sell, orderBookTrade, _feeWallet, _assetRegistry, _balanceTracking, _completedOrderHashes, _nonceInvalidations, _partiallyFilledOrderQuantitiesInPips ); emit OrderBookTradeExecuted( buy.walletAddress, sell.walletAddress, orderBookTrade.baseAssetSymbol, orderBookTrade.quoteAssetSymbol, orderBookTrade.grossBaseQuantityInPips, orderBookTrade.grossQuoteQuantityInPips, orderBookTrade.makerSide == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy ); } /** * @notice Settles a trade between pool liquidity and an order submitted and matched off-chain * * @param order An `Order` struct encoding the parameters of the taker order * @param poolTrade A `PoolTrade` struct encoding the parameters of this trade execution between * the order and pool liquidity */ function executePoolTrade(Order memory order, PoolTrade memory poolTrade) external onlyDispatcher { require( !isWalletExitFinalized(order.walletAddress), 'Order wallet exit finalized' ); Trading.executePoolTrade( order, poolTrade, _feeWallet, _assetRegistry, _liquidityPools, _balanceTracking, _completedOrderHashes, _nonceInvalidations, _partiallyFilledOrderQuantitiesInPips ); emit PoolTradeExecuted( order.walletAddress, poolTrade.baseAssetSymbol, poolTrade.quoteAssetSymbol, poolTrade.grossBaseQuantityInPips, poolTrade.grossQuoteQuantityInPips, order.side ); } /** * @notice Settles a trade between pool liquidity and two order submitted and matched off-chain. * The taker order is filled by pool liquidity up to the maker order price and the remainder of * the taker order quantity is then filled by the maker order * * @param buy An `Order` struct encoding the parameters of the buy-side order (receiving base, * giving quote) * @param sell An `Order` struct encoding the parameters of the sell-side order (giving base, * receiving quote) * @param hybridTrade A `HybridTrade` struct encoding the parameters of this trade execution * between the two orders and pool liquidity */ function executeHybridTrade( Order memory buy, Order memory sell, HybridTrade memory hybridTrade ) external onlyDispatcher { // OrderBook trade validations require( !isWalletExitFinalized(buy.walletAddress), 'Buy wallet exit finalized' ); require( !isWalletExitFinalized(sell.walletAddress), 'Sell wallet exit finalized' ); Trading.executeHybridTrade( buy, sell, hybridTrade, _feeWallet, _assetRegistry, _liquidityPools, _balanceTracking, _completedOrderHashes, _nonceInvalidations, _partiallyFilledOrderQuantitiesInPips ); emit HybridTradeExecuted( buy.walletAddress, sell.walletAddress, hybridTrade.orderBookTrade.baseAssetSymbol, hybridTrade.orderBookTrade.quoteAssetSymbol, hybridTrade.orderBookTrade.grossBaseQuantityInPips, hybridTrade.orderBookTrade.grossQuoteQuantityInPips, hybridTrade.poolTrade.grossBaseQuantityInPips, hybridTrade.poolTrade.grossQuoteQuantityInPips, hybridTrade.orderBookTrade.grossBaseQuantityInPips + hybridTrade.poolTrade.grossBaseQuantityInPips, hybridTrade.orderBookTrade.grossQuoteQuantityInPips + hybridTrade.poolTrade.grossQuoteQuantityInPips, hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy ); } // Withdrawing // /** * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently * whitelisted Dispatcher wallet * * @param withdrawal A `Withdrawal` struct encoding the parameters of the withdrawal */ function withdraw(Withdrawal memory withdrawal) public onlyDispatcher { require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited'); ( uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits, address assetAddress, string memory assetSymbol ) = Withdrawing.withdraw( withdrawal, _custodian, _feeWallet, _assetRegistry, _balanceTracking, _completedWithdrawalHashes ); emit Withdrawn( withdrawal.walletAddress, assetAddress, assetSymbol, withdrawal.grossQuantityInPips, newExchangeBalanceInPips, newExchangeBalanceInAssetUnits ); } // Liquidity pools // /** * @notice Create a new internally tracked liquidity pool copied from a previous Exchange contract * * @param baseAssetAddress The base asset address * @param quoteAssetAddress The quote asset address */ function upgradeLiquidityPool( address baseAssetAddress, address quoteAssetAddress ) external onlyAdmin { _liquidityPools.upgradeLiquidityPool( baseAssetAddress, quoteAssetAddress, _assetRegistry, _balanceTracking.migrationSource, _custodian ); } /** * @notice Create a new internally tracked liquidity pool and associated LP token * * @param baseAssetAddress The base asset address * @param quoteAssetAddress The quote asset address */ function createLiquidityPool( address baseAssetAddress, address quoteAssetAddress ) external onlyAdmin { _liquidityPools.createLiquidityPool( baseAssetAddress, quoteAssetAddress, _assetRegistry ); } /** * @notice Migrate reserve assets into an internally tracked liquidity pool and mint the * specified quantity of the associated LP token. If the pool and LP token do not already exist * then create new ones * * @dev This function should be called by a Migrator contract associated with a Farm by invoking * the `migrate` function on a Farm instance, passing in the `pid` of a pool holding tokens * compliant with the `IUniswapV2Pair` interface. The Migrator will then liquidate all tokens * held in the pool by calling the `burn` function on the Pair contract, transfer the output * reserve assets to the Exchange, and call this function. The Exchange then mints the * `desiredQuantity` of the new IDEX LP tokens back to the Migrator. This `desiredLiquidity` * should be exactly equal to the asset unit quantity of Pair tokens originally deposited in the * Farm pool * * @param token0 The address of `token0` in the Pair contract being migrated * @param token1 The address of `token1` in the Pair contract being migrated * @param isToken1Quote If true, maps `token0` to the base asset and `token1` to the quote asset * in the internally tracked pool; otherwise maps `token1` to base and `token0` to quote * @param desiredLiquidity The quantity of asset units of the new LP token to mint back to the * Migrator * @param to Recipient of the liquidity tokens * * @return liquidityProviderToken The address of the liquidity provider ERC-20 token representing * liquidity in the internally-tracked pool corresponding to the asset pair */ function migrateLiquidityPool( address token0, address token1, bool isToken1Quote, uint256 desiredLiquidity, address to, address payable WETH ) external onlyMigrator returns (address liquidityProviderToken) { liquidityProviderToken = _liquidityPools.migrateLiquidityPool( LiquidityMigration( token0, token1, isToken1Quote, desiredLiquidity, to, IWETH9(WETH) ), _custodian, _assetRegistry ); } /** * @notice Reverse the base and quote assets in an internally tracked liquidity pool * * @param baseAssetAddress The base asset address * @param quoteAssetAddress The quote asset address */ function reverseLiquidityPoolAssets( address baseAssetAddress, address quoteAssetAddress ) external onlyAdmin { _liquidityPools.reverseLiquidityPoolAssets( baseAssetAddress, quoteAssetAddress ); emit LiquidityPoolAssetsReversed(baseAssetAddress, quoteAssetAddress); } /** * @notice Adds liquidity to a ERC-20⇄ERC-20 pool * * @dev To cover all possible scenarios, `msg.sender` should have already given the Exchange an * allowance of at least `amountADesired`/`amountBDesired` on `tokenA`/`tokenB` * * @param tokenA The contract address of the desired token * @param tokenB The contract address of the desired token * @param amountADesired The amount of `tokenA` to add as liquidity if the B/A price is <= * `amountBDesired`/`amountADesired` (A depreciates) * @param amountBDesired The amount of `tokenB` to add as liquidity if the A/B price is <= * `amountADesired`/`amountBDesired` (B depreciates) * @param amountAMin Bounds the extent to which the B/A price can go up. Must be <= * `amountADesired` * @param amountBMin Bounds the extent to which the A/B price can go up. Must be <= * `amountBDesired` * @param to Recipient of the liquidity tokens * @param deadline Unix timestamp in seconds after which the transaction will revert */ function addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external { // Calling exitWallet disables on-chain add liquidity initiation immediately on mining, in // contrast to withdrawals, trades, and liquidity change executions which respect the Chain // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized` require(!_walletExits[msg.sender].exists, 'Wallet exited'); LiquidityAdditionDepositResult memory result = _liquidityPools.addLiquidity( LiquidityAddition( Constants.signatureHashVersion, LiquidityChangeOrigination.OnChain, 0, msg.sender, tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline, bytes('') ), _custodian, _assetRegistry, _balanceTracking ); emit Deposited( ++_depositIndex, msg.sender, tokenA, result.assetASymbol, result.assetAQuantityInPips, result.assetANewExchangeBalanceInPips, result.assetANewExchangeBalanceInAssetUnits ); emit Deposited( ++_depositIndex, msg.sender, tokenB, result.assetBSymbol, result.assetBQuantityInPips, result.assetBNewExchangeBalanceInPips, result.assetBNewExchangeBalanceInAssetUnits ); emit LiquidityAdditionInitiated( msg.sender, tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline ); } /** * @notice Adds liquidity to a ERC-20⇄ETH pool * * @dev To cover all possible scenarios, `msg.sender` should have already given the router an * allowance of at least `amountTokenDesired` on `token`. `msg.value` is treated as * `amountETHDesired` * * @param token The contract address of the desired token * @param amountTokenDesired The amount of token to add as liquidity if the ETH/token * price is <= `msg.value`/`amountTokenDesired` (token depreciates) * @param amountTokenMin The amount of ETH to add as liquidity if the token/ETH * price is <= `amountTokenDesired`/`msg.value` (ETH depreciates) * @param amountETHMin Bounds the extent to which the token/ETH price can go up. Must be * <= `msg.value` * @param to Recipient of the liquidity tokens * @param deadline Unix timestamp in seconds after which the transaction will revert */ function addLiquidityETH( address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline ) external payable { // Calling exitWallet disables on-chain add liquidity initiation immediately on mining, in // contrast to withdrawals, trades, and liquidity change executions which respect the Chain // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized` require(!_walletExits[msg.sender].exists, 'Wallet exited'); LiquidityAdditionDepositResult memory result = _liquidityPools.addLiquidity( LiquidityAddition( Constants.signatureHashVersion, LiquidityChangeOrigination.OnChain, 0, msg.sender, token, address(0x0), amountTokenDesired, msg.value, amountTokenMin, amountETHMin, to, deadline, bytes('') ), _custodian, _assetRegistry, _balanceTracking ); emit Deposited( ++_depositIndex, msg.sender, token, result.assetASymbol, result.assetAQuantityInPips, result.assetANewExchangeBalanceInPips, result.assetANewExchangeBalanceInAssetUnits ); emit Deposited( ++_depositIndex, msg.sender, address(0x0), result.assetBSymbol, result.assetBQuantityInPips, result.assetBNewExchangeBalanceInPips, result.assetBNewExchangeBalanceInAssetUnits ); emit LiquidityAdditionInitiated( msg.sender, token, address(0x0), amountTokenDesired, msg.value, amountTokenMin, amountETHMin, to, deadline ); } /** * @notice Settles a liquidity addition by transferring deposited assets from wallet balances to * pool reserves and minting LP tokens * * @param addition A `LiquidityAddition` struct encoding the parameters of the addition requested * by the user either on-chain via `addLiquidity` or `addLiquidityETH` or off-chain via * ECDSA-signed API request * @param execution A `LiquidityChangeExecution` struct encoding the parameters of this liquidity * addition execution that meets the terms of the request */ function executeAddLiquidity( LiquidityAddition calldata addition, LiquidityChangeExecution calldata execution ) external onlyDispatcher { require(!isWalletExitFinalized(addition.wallet), 'Wallet exit finalized'); _liquidityPools.executeAddLiquidity( addition, execution, _feeWallet, address(_custodian), _balanceTracking ); emit LiquidityAdditionExecuted( addition.wallet, execution.baseAssetAddress, execution.quoteAssetAddress, execution.grossBaseQuantityInPips, execution.grossQuoteQuantityInPips, execution.liquidityInPips ); } /** * @notice Removes liquidity from an ERC-20⇄ERC-20 pool * * @dev `msg.sender` should have already given the Exchange an allowance of at least `liquidity` * on the pool * * @param tokenA The contract address of the desired token * @param tokenB The contract address of the desired token * @param liquidity The amount of liquidity tokens to remove * @param amountAMin The minimum amount of `tokenA` that must be received * @param amountBMin The minimum amount of `tokenB` that must be received * @param to Recipient of the underlying assets * @param deadline Unix timestamp in seconds after which the transaction will revert */ function removeLiquidity( address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) public { // Calling exitWallet disables on-chain remove liquidity initiation immediately on mining, in // contrast to withdrawals, trades, and liquidity change executions which respect the Chain // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized` require(!_walletExits[msg.sender].exists, 'Wallet exited'); LiquidityRemovalDepositResult memory result = _liquidityPools.removeLiquidity( LiquidityRemoval( Constants.signatureHashVersion, LiquidityChangeOrigination.OnChain, 0, msg.sender, tokenA, tokenB, liquidity, amountAMin, amountBMin, payable(to), deadline, bytes('') ), _custodian, _assetRegistry, _balanceTracking ); emit Deposited( ++_depositIndex, msg.sender, result.assetAddress, result.assetSymbol, result.assetQuantityInPips, result.assetNewExchangeBalanceInPips, result.assetNewExchangeBalanceInAssetUnits ); emit LiquidityRemovalInitiated( msg.sender, tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline ); } /** * @notice Removes liquidity from an ERC-20⇄ETH pool and receive ETH * * @dev `msg.sender` should have already given the Exchange an allowance of at least `liquidity` * on the pool * * @param token token The contract address of the desired token * @param token liquidity The amount of liquidity tokens to remove * @param token amountTokenMin The minimum amount of token that must be received * @param token amountETHMin The minimum amount of ETH that must be received * @param to Recipient of the underlying assets * @param deadline Unix timestamp in seconds after which the transaction will revert */ function removeLiquidityETH( address token, uint256 liquidity, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline ) external { // Calling exitWallet disables on-chain remove liquidity initiation immediately on mining, in // contrast to withdrawals, trades, and liquidity change executions which respect the Chain // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized` require(!_walletExits[msg.sender].exists, 'Wallet exited'); LiquidityRemovalDepositResult memory result = _liquidityPools.removeLiquidity( LiquidityRemoval( Constants.signatureHashVersion, LiquidityChangeOrigination.OnChain, 0, msg.sender, token, address(0x0), liquidity, amountTokenMin, amountETHMin, payable(to), deadline, bytes('') ), _custodian, _assetRegistry, _balanceTracking ); emit Deposited( ++_depositIndex, msg.sender, result.assetAddress, result.assetSymbol, result.assetQuantityInPips, result.assetNewExchangeBalanceInPips, result.assetNewExchangeBalanceInAssetUnits ); emit LiquidityRemovalInitiated( msg.sender, token, address(0x0), liquidity, amountTokenMin, amountETHMin, payable(to), deadline ); } /** * @notice Settles a liquidity removal by burning deposited LP tokens and transferring reserve * assets from pool reserves to the recipient * * @param removal A `LiquidityRemoval` struct encoding the parameters of the removal requested * by the user either 1) on-chain via `removeLiquidity` or `removeLiquidityETH`, 2) off-chain via * ECDSA-signed API request, or 3) requested by the Dispatcher wallet itself in case the wallet * has exited and its liquidity positions must be liquidated automatically * @param execution A `LiquidityChangeExecution` struct encoding the parameters of this liquidity * removal execution that meets the terms of the request */ function executeRemoveLiquidity( LiquidityRemoval calldata removal, LiquidityChangeExecution calldata execution ) external onlyDispatcher { _liquidityPools.executeRemoveLiquidity( removal, execution, _walletExits[removal.wallet].exists, ICustodian(_custodian), _feeWallet, _assetRegistry, _balanceTracking ); emit LiquidityRemovalExecuted( removal.wallet, execution.baseAssetAddress, execution.quoteAssetAddress, execution.grossBaseQuantityInPips, execution.grossQuoteQuantityInPips, execution.liquidityInPips ); } /** * @notice Remove liquidity from a pool immediately without the need for Dispatcher wallet * settlement. The wallet must be exited and the Chain Propagation Period must have already * passed since calling `exitWallet`. The LP tokens must already be deposited in the Exchange * * @param baseAssetAddress The base asset address * @param quoteAssetAddress The quote asset address */ function removeLiquidityExit( address baseAssetAddress, address quoteAssetAddress ) external { require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized'); ( uint64 outputBaseAssetQuantityInPips, uint64 outputQuoteAssetQuantityInPips ) = _liquidityPools.removeLiquidityExit( baseAssetAddress, quoteAssetAddress, ICustodian(_custodian), _balanceTracking ); emit WalletExitLiquidityRemoved( msg.sender, baseAssetAddress, quoteAssetAddress, outputBaseAssetQuantityInPips, outputQuoteAssetQuantityInPips ); } // Wallet exits // /** * @notice Flags the sending wallet as exited, immediately disabling deposits and on-chain * intitiation of liquidity changes upon mining. After the Chain Propagation Period passes * trades, withdrawals, and liquidity change executions are also disabled for the wallet, * and assets may then be withdrawn one at a time via `withdrawExit` */ function exitWallet() external { require(!_walletExits[msg.sender].exists, 'Wallet already exited'); _walletExits[msg.sender] = WalletExit( true, block.number + _chainPropagationPeriod ); emit WalletExited(msg.sender, block.number + _chainPropagationPeriod); } /** * @notice Withdraw the entire balance of an asset for an exited wallet. The Chain Propagation * Period must have already passed since calling `exitWallet` * * @param assetAddress The address of the asset to withdraw */ function withdrawExit(address assetAddress) external { require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized'); // Update wallet balance uint64 previousExchangeBalanceInPips = Withdrawing.withdrawExit( assetAddress, _custodian, _assetRegistry, _balanceTracking ); emit WalletExitWithdrawn( msg.sender, assetAddress, previousExchangeBalanceInPips ); } /** * @notice Clears exited status of sending wallet. Upon mining immediately enables * deposits, trades, and withdrawals by sending wallet */ function clearWalletExit() external { require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized'); delete _walletExits[msg.sender]; emit WalletExitCleared(msg.sender); } function isWalletExitFinalized(address wallet) internal view returns (bool) { WalletExit storage exit = _walletExits[wallet]; return exit.exists && exit.effectiveBlockNumber <= block.number; } // Invalidation // /** * @notice Invalidate all order nonces with a timestampInMs lower than the one provided * * @param nonce A Version 1 UUID. After calling and once the Chain Propagation Period has * elapsed, `executeOrderBookTrade` will reject order nonces from this wallet with a * timestampInMs component lower than the one provided */ function invalidateOrderNonce(uint128 nonce) external { (uint64 timestampInMs, uint256 effectiveBlockNumber) = _nonceInvalidations.invalidateOrderNonce(nonce, _chainPropagationPeriod); emit OrderNonceInvalidated( msg.sender, nonce, timestampInMs, effectiveBlockNumber ); } // Asset registry // /** * @notice Initiate registration process for a token asset. Only `IERC20` compliant tokens can be * added - ETH is hardcoded in the registry * * @param tokenAddress The address of the `IERC20` compliant token contract to add * @param symbol The symbol identifying the token asset * @param decimals The decimal precision of the token */ function registerToken( IERC20 tokenAddress, string calldata symbol, uint8 decimals ) external onlyAdmin { _assetRegistry.registerToken(tokenAddress, symbol, decimals); } /** * @notice Finalize registration process for a token asset. All parameters must exactly match a * previous call to `registerToken` * * @param tokenAddress The address of the `IERC20` compliant token contract to add * @param symbol The symbol identifying the token asset * @param decimals The decimal precision of the token */ function confirmTokenRegistration( IERC20 tokenAddress, string calldata symbol, uint8 decimals ) external onlyAdmin { _assetRegistry.confirmTokenRegistration(tokenAddress, symbol, decimals); } /** * @notice Add a symbol to a token that has already been registered and confirmed * * @param tokenAddress The address of the `IERC20` compliant token contract the symbol will * identify * @param symbol The symbol identifying the token asset */ function addTokenSymbol(IERC20 tokenAddress, string calldata symbol) external onlyAdmin { _assetRegistry.addTokenSymbol(tokenAddress, symbol); emit TokenSymbolAdded(tokenAddress, symbol); } /** * @notice Loads an asset descriptor struct by its symbol and timestamp * * @dev Since multiple token addresses can potentially share the same symbol (in case of a token * swap/contract upgrade) the provided `timestampInMs` is compared against each asset's * `confirmedTimestampInMs` to uniquely determine the newest asset for the symbol at that point * in time * * @param assetSymbol The asset's symbol * @param timestampInMs Point in time used to disambiguate multiple tokens with same symbol * * @return A `Asset` record describing the asset */ function loadAssetBySymbol(string calldata assetSymbol, uint64 timestampInMs) external view returns (Asset memory) { return _assetRegistry.loadAssetBySymbol(assetSymbol, timestampInMs); } // Dispatcher whitelisting // /** * @notice Sets the wallet whitelisted to dispatch transactions calling the * `executeOrderBookTrade`, `executePoolTrade`, `executeHybridTrade`, `withdraw`, * `executeAddLiquidity`, and `executeRemoveLiquidity` functions * * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the * current one */ function setDispatcher(address newDispatcherWallet) external onlyAdmin { require(newDispatcherWallet != address(0x0), 'Invalid wallet address'); require( newDispatcherWallet != _dispatcherWallet, 'Must be different from current' ); _dispatcherWallet = newDispatcherWallet; } /** * @notice Clears the currently set whitelisted dispatcher wallet, effectively disabling calling * the `executeOrderBookTrade`, `executePoolTrade`, `executeHybridTrade`, `withdraw`, * `executeAddLiquidity`, and `executeRemoveLiquidity` functions until a new wallet is set with * `setDispatcher` */ function removeDispatcher() external onlyAdmin { _dispatcherWallet = address(0x0); } modifier onlyDispatcher() { require(msg.sender == _dispatcherWallet, 'Caller is not dispatcher'); _; } // Migrator whitelisting // modifier onlyMigrator() { require(msg.sender == _liquidityMigrator, 'Caller is not Migrator'); _; } // Asset skimming // /** * @notice Sends tokens mistakenly sent directly to the `Exchange` to the fee wallet (the * `receive` function rejects ETH except when wrapping/unwrapping) */ function skim(address tokenAddress) external onlyAdmin { AssetRegistry.skim(tokenAddress, _feeWallet); } // Exchange upgrades // /** * @notice Following an Exchange upgrade via the Governance contract, this function allows the * new Exchange to reclaim blockchain storage by cleanup up old balance tracking */ function cleanupWalletBalance(address wallet, address assetAddress) external { address currentExchange = ICustodian(_custodian).loadExchange(); require(msg.sender == currentExchange, 'Caller is not Exchange'); delete _balanceTracking.balancesByWalletAssetPair[wallet][assetAddress]; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { Address } from './Address.sol'; import { ICustodian } from './Interfaces.sol'; import { Owned } from './Owned.sol'; contract Governance is Owned { /** * @notice Emitted when admin initiates upgrade of `Exchange` contract address on `Custodian` via * `initiateExchangeUpgrade` */ event ExchangeUpgradeInitiated( address oldExchange, address newExchange, uint256 blockThreshold ); /** * @notice Emitted when admin cancels previously started `Exchange` upgrade with `cancelExchangeUpgrade` */ event ExchangeUpgradeCanceled(address oldExchange, address newExchange); /** * @notice Emitted when admin finalizes `Exchange` upgrade via `finalizeExchangeUpgrade` */ event ExchangeUpgradeFinalized(address oldExchange, address newExchange); /** * @notice Emitted when admin initiates upgrade of `Governance` contract address on `Custodian` via * `initiateGovernanceUpgrade` */ event GovernanceUpgradeInitiated( address oldGovernance, address newGovernance, uint256 blockThreshold ); /** * @notice Emitted when admin cancels previously started `Governance` upgrade with `cancelGovernanceUpgrade` */ event GovernanceUpgradeCanceled(address oldGovernance, address newGovernance); /** * @notice Emitted when admin finalizes `Governance` upgrade via `finalizeGovernanceUpgrade`, effectively replacing * this contract and rendering it non-functioning */ event GovernanceUpgradeFinalized( address oldGovernance, address newGovernance ); // Internally used structs // struct ContractUpgrade { bool exists; address newContract; uint256 blockThreshold; } // Storage // uint256 immutable _blockDelay; ICustodian _custodian; ContractUpgrade _currentExchangeUpgrade; ContractUpgrade _currentGovernanceUpgrade; /** * @notice Instantiate a new `Governance` contract * * @dev Sets `owner` and `admin` to `msg.sender`. Sets the values for `_blockDelay` governing `Exchange` * and `Governance` upgrades. This value is immutable, and cannot be changed after construction * * @param blockDelay The minimum number of blocks that must be mined after initiating an `Exchange` * or `Governance` upgrade before the upgrade may be finalized */ constructor(uint256 blockDelay) Owned() { _blockDelay = blockDelay; } /** * @notice Sets the address of the `Custodian` contract. The `Custodian` accepts `Exchange` and * `Governance` addresses in its constructor, after which they can only be changed by the * `Governance` contract itself. Therefore the `Custodian` must be deployed last and its address * set here on an existing `Governance` contract. This value is immutable once set and cannot be * changed again * * @param newCustodian The address of the `Custodian` contract deployed against this `Governance` * contract's address */ function setCustodian(ICustodian newCustodian) external onlyAdmin { require( _custodian == ICustodian(payable(address(0x0))), 'Custodian can only be set once' ); require(Address.isContract(address(newCustodian)), 'Invalid address'); _custodian = newCustodian; } // Exchange upgrade // /** * @notice Initiates `Exchange` contract upgrade proccess on `Custodian`. Once `blockDelay` has passed * the process can be finalized with `finalizeExchangeUpgrade` * * @param newExchange The address of the new `Exchange` contract */ function initiateExchangeUpgrade(address newExchange) external onlyAdmin { require(Address.isContract(address(newExchange)), 'Invalid address'); require( newExchange != _custodian.loadExchange(), 'Must be different from current Exchange' ); require( !_currentExchangeUpgrade.exists, 'Exchange upgrade already in progress' ); _currentExchangeUpgrade = ContractUpgrade( true, newExchange, block.number + _blockDelay ); emit ExchangeUpgradeInitiated( _custodian.loadExchange(), newExchange, _currentExchangeUpgrade.blockThreshold ); } /** * @notice Cancels an in-flight `Exchange` contract upgrade that has not yet been finalized */ function cancelExchangeUpgrade() external onlyAdmin { require(_currentExchangeUpgrade.exists, 'No Exchange upgrade in progress'); address newExchange = _currentExchangeUpgrade.newContract; delete _currentExchangeUpgrade; emit ExchangeUpgradeCanceled(_custodian.loadExchange(), newExchange); } /** * @notice Finalizes the `Exchange` contract upgrade by changing the contract address on the `Custodian` * contract with `setExchange`. The number of blocks specified by `_blockDelay` must have passed since calling * `initiateExchangeUpgrade` * * @param newExchange The address of the new `Exchange` contract. Must equal the address provided to * `initiateExchangeUpgrade` */ function finalizeExchangeUpgrade(address newExchange) external onlyAdmin { require(_currentExchangeUpgrade.exists, 'No Exchange upgrade in progress'); require( _currentExchangeUpgrade.newContract == newExchange, 'Address mismatch' ); require( block.number >= _currentExchangeUpgrade.blockThreshold, 'Block threshold not yet reached' ); address oldExchange = _custodian.loadExchange(); _custodian.setExchange(newExchange); delete _currentExchangeUpgrade; emit ExchangeUpgradeFinalized(oldExchange, newExchange); } // Governance upgrade // /** * @notice Initiates `Governance` contract upgrade proccess on `Custodian`. Once `blockDelay` has passed * the process can be finalized with `finalizeGovernanceUpgrade` * * @param newGovernance The address of the new `Governance` contract */ function initiateGovernanceUpgrade(address newGovernance) external onlyAdmin { require(Address.isContract(address(newGovernance)), 'Invalid address'); require( newGovernance != _custodian.loadGovernance(), 'Must be different from current Governance' ); require( !_currentGovernanceUpgrade.exists, 'Governance upgrade already in progress' ); _currentGovernanceUpgrade = ContractUpgrade( true, newGovernance, block.number + _blockDelay ); emit GovernanceUpgradeInitiated( _custodian.loadGovernance(), newGovernance, _currentGovernanceUpgrade.blockThreshold ); } /** * @notice Cancels an in-flight `Governance` contract upgrade that has not yet been finalized */ function cancelGovernanceUpgrade() external onlyAdmin { require( _currentGovernanceUpgrade.exists, 'No Governance upgrade in progress' ); address newGovernance = _currentGovernanceUpgrade.newContract; delete _currentGovernanceUpgrade; emit GovernanceUpgradeCanceled(_custodian.loadGovernance(), newGovernance); } /** * @notice Finalizes the `Governance` contract upgrade by changing the contract address on the `Custodian` * contract with `setGovernance`. The number of blocks specified by `_blockDelay` must have passed since calling * `initiateGovernanceUpgrade`. * * @dev After successfully calling this function, this contract will become useless since it is no * longer whitelisted in the `Custodian` * * @param newGovernance The address of the new `Governance` contract. Must equal the address provided to * `initiateGovernanceUpgrade` */ function finalizeGovernanceUpgrade(address newGovernance) external onlyAdmin { require( _currentGovernanceUpgrade.exists, 'No Governance upgrade in progress' ); require( _currentGovernanceUpgrade.newContract == newGovernance, 'Address mismatch' ); require( block.number >= _currentGovernanceUpgrade.blockThreshold, 'Block threshold not yet reached' ); address oldGovernance = _custodian.loadGovernance(); _custodian.setGovernance(newGovernance); delete _currentGovernanceUpgrade; emit GovernanceUpgradeFinalized(oldGovernance, newGovernance); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { HybridTrade } from './Structs.sol'; library HybridTradeHelpers { /** * @dev Total fees paid by taker from received asset across orderbook and pool trades. Does not * include pool input fees nor pool output adjustment */ function calculateTakerFeeQuantityInPips(HybridTrade memory self) internal pure returns (uint64) { return self.takerGasFeeQuantityInPips + self.orderBookTrade.takerFeeQuantityInPips; } /** * @dev Gross quantity received by taker */ function calculateTakerGrossReceivedQuantityInPips(HybridTrade memory self) internal pure returns (uint64) { return ( self.orderBookTrade.takerFeeAssetAddress == self.orderBookTrade.baseAssetAddress ? self.orderBookTrade.grossBaseQuantityInPips + self.poolTrade.grossBaseQuantityInPips : self.orderBookTrade.grossQuoteQuantityInPips + self.poolTrade.grossQuoteQuantityInPips ); } /** * @dev Gross quantity received by maker */ function getMakerGrossQuantityInPips(HybridTrade memory self) internal pure returns (uint64) { return self.orderBookTrade.takerFeeAssetAddress == self.orderBookTrade.baseAssetAddress ? self.orderBookTrade.grossQuoteQuantityInPips : self.orderBookTrade.grossBaseQuantityInPips; } /** * @dev Net quantity received by maker */ function getMakerNetQuantityInPips(HybridTrade memory self) internal pure returns (uint64) { return self.orderBookTrade.takerFeeAssetAddress == self.orderBookTrade.baseAssetAddress ? self.orderBookTrade.netQuoteQuantityInPips : self.orderBookTrade.netBaseQuantityInPips; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { Constants } from './Constants.sol'; import { HybridTradeHelpers } from './HybridTradeHelpers.sol'; import { OrderBookTradeValidations } from './OrderBookTradeValidations.sol'; import { OrderSide } from './Enums.sol'; import { PoolTradeHelpers } from './PoolTradeHelpers.sol'; import { PoolTradeValidations } from './PoolTradeValidations.sol'; import { Validations } from './Validations.sol'; import { Asset, HybridTrade, Order, OrderBookTrade, NonceInvalidation, PoolTrade } from './Structs.sol'; library HybridTradeValidations { using AssetRegistry for AssetRegistry.Storage; using HybridTradeHelpers for HybridTrade; using PoolTradeHelpers for PoolTrade; function validateHybridTrade( Order memory buy, Order memory sell, HybridTrade memory hybridTrade, AssetRegistry.Storage storage assetRegistry, mapping(address => NonceInvalidation) storage nonceInvalidations ) internal view returns (bytes32 buyHash, bytes32 sellHash) { require( buy.walletAddress != sell.walletAddress, 'Self-trading not allowed' ); require( hybridTrade.orderBookTrade.baseAssetAddress == hybridTrade.poolTrade.baseAssetAddress && hybridTrade.orderBookTrade.quoteAssetAddress == hybridTrade.poolTrade.quoteAssetAddress, 'Mismatched trade assets' ); validateFees(hybridTrade); // Order book trade validations Validations.validateOrderNonces(buy, sell, nonceInvalidations); (buyHash, sellHash) = OrderBookTradeValidations.validateOrderSignatures( buy, sell, hybridTrade.orderBookTrade ); OrderBookTradeValidations.validateAssetPair( buy, sell, hybridTrade.orderBookTrade, assetRegistry ); OrderBookTradeValidations.validateLimitPrices( buy, sell, hybridTrade.orderBookTrade ); // Pool trade validations Order memory takerOrder = hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? sell : buy; PoolTradeValidations.validateLimitPrice(takerOrder, hybridTrade.poolTrade); } function validatePoolPrice( Order memory makerOrder, uint64 baseAssetReserveInPips, uint64 quoteAssetReserveInPips ) internal pure { if ( makerOrder.side == OrderSide.Buy && Validations.isLimitOrderType(makerOrder.orderType) ) { // Price of pool must not be better (lower) than resting buy price require( Validations.calculateImpliedQuoteQuantityInPips( baseAssetReserveInPips, makerOrder.limitPriceInPips ) <= quoteAssetReserveInPips, 'Pool marginal buy price exceeded' ); } if ( makerOrder.side == OrderSide.Sell && Validations.isLimitOrderType(makerOrder.orderType) ) { // Price of pool must not be better (higher) than resting sell price require( Validations.calculateImpliedQuoteQuantityInPips( baseAssetReserveInPips, makerOrder.limitPriceInPips // Allow additional pip buffers for integer rounding ) + 1 >= quoteAssetReserveInPips - 1, 'Pool marginal sell price exceeded' ); } } function validateFees(HybridTrade memory hybridTrade) private pure { require( hybridTrade.poolTrade.takerGasFeeQuantityInPips == 0, 'Non-zero pool gas fee' ); // Validate maker fee on orderbook trade uint64 grossQuantityInPips = hybridTrade.getMakerGrossQuantityInPips(); require( Validations.isFeeQuantityValid( (grossQuantityInPips - hybridTrade.getMakerNetQuantityInPips()), grossQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive maker fee' ); OrderSide takerOrderSide = hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy; // Validate taker fees across orderbook and pool trades grossQuantityInPips = hybridTrade .calculateTakerGrossReceivedQuantityInPips(); require( Validations.isFeeQuantityValid( hybridTrade.poolTrade.calculatePoolOutputAdjustment(takerOrderSide), grossQuantityInPips, Constants.maxPoolOutputAdjustmentBasisPoints ), 'Excessive pool output adjustment' ); require( Validations.isFeeQuantityValid( hybridTrade.calculateTakerFeeQuantityInPips(), grossQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive taker fee' ); // Validate price correction, if present if (hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips > 0) { // Price correction only allowed for hybrid trades with a taker sell require( hybridTrade.orderBookTrade.makerSide == OrderSide.Buy, 'Price correction not allowed' ); // Do not allow quote output with a price correction as the latter is effectively a negative // net quote output require( hybridTrade.poolTrade.netQuoteQuantityInPips == 0, 'Quote out not allowed with price correction' ); grossQuantityInPips = hybridTrade .poolTrade .getOrderGrossReceivedQuantityInPips(takerOrderSide); if ( hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips > grossQuantityInPips ) { require( Validations.isFeeQuantityValid( hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips - grossQuantityInPips, grossQuantityInPips, Constants.maxPoolPriceCorrectionBasisPoints ), 'Excessive price correction' ); } } Validations.validatePoolTradeInputFees( takerOrderSide, hybridTrade.poolTrade ); Validations.validateOrderBookTradeFees(hybridTrade.orderBookTrade); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { LiquidityPool, 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); /** * @notice Load the internally-tracked liquidity pool descriptor for a base-quote asset pair * * @return A `LiquidityPool` struct encapsulating the current state of the internally-tracked * liquidity pool for the given base-quote asset pair. Reverts if no such pool exists */ function loadLiquidityPoolByAssetAddresses( address baseAssetAddress, address quoteAssetAddress ) external view returns (LiquidityPool memory); /** * @notice Load the number of deposits made to the contract, for use when upgrading to a new * Exchange via Governance * * @return The number of deposits successfully made to the Exchange */ function _depositIndex() external view returns (uint64); } 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; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { LiquidityChangeExecution } from './Structs.sol'; library LiquidityChangeExecutionHelpers { /** * @dev Quantity in pips of gross base quantity sent to fee wallet */ function calculateBaseFeeQuantityInPips(LiquidityChangeExecution memory self) internal pure returns (uint64) { return self.grossBaseQuantityInPips - self.netBaseQuantityInPips; } /** * @dev Quantity in pips of gross quote quantity sent to fee wallet */ function calculateQuoteFeeQuantityInPips(LiquidityChangeExecution memory self) internal pure returns (uint64) { return self.grossQuoteQuantityInPips - self.netQuoteQuantityInPips; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { Constants } from './Constants.sol'; import { IERC20 } from './Interfaces.sol'; import { LiquidityChangeExecutionHelpers } from './LiquidityChangeExecutionHelpers.sol'; import { LiquidityPoolHelpers } from './LiquidityPoolHelpers.sol'; import { Validations } from './Validations.sol'; import { LiquidityAddition, LiquidityChangeExecution, LiquidityPool, LiquidityRemoval } from './Structs.sol'; library LiquidityChangeExecutionValidations { using LiquidityChangeExecutionHelpers for LiquidityChangeExecution; using LiquidityPoolHelpers for LiquidityPool; function validateLiquidityAddition( LiquidityAddition memory addition, LiquidityChangeExecution memory execution, LiquidityPool memory pool ) internal view { require( ((execution.baseAssetAddress == addition.assetA && execution.quoteAssetAddress == addition.assetB) || (execution.baseAssetAddress == addition.assetB && execution.quoteAssetAddress == addition.assetA)), 'Asset address mismatch' ); ( uint256 minBaseInAssetUnits, uint256 desiredBaseInAssetUnits, uint256 minQuoteInAssetUnits, uint256 desiredQuoteInAssetUnits ) = execution.baseAssetAddress == addition.assetA ? ( addition.amountAMin, addition.amountADesired, addition.amountBMin, addition.amountBDesired ) : ( addition.amountBMin, addition.amountBDesired, addition.amountAMin, addition.amountADesired ); (uint64 minBase, uint64 maxBase, uint64 minQuote, uint64 maxQuote) = ( AssetUnitConversions.assetUnitsToPips( minBaseInAssetUnits, pool.baseAssetDecimals ), AssetUnitConversions.assetUnitsToPips( desiredBaseInAssetUnits, pool.baseAssetDecimals ), AssetUnitConversions.assetUnitsToPips( minQuoteInAssetUnits, pool.quoteAssetDecimals ), AssetUnitConversions.assetUnitsToPips( desiredQuoteInAssetUnits, pool.quoteAssetDecimals ) ); require( execution.grossBaseQuantityInPips >= minBase, 'Min base quantity not met' ); require( execution.grossBaseQuantityInPips <= maxBase, 'Desired base quantity exceeded' ); require( execution.grossQuoteQuantityInPips >= minQuote, 'Min quote quantity not met' ); require( execution.grossQuoteQuantityInPips <= maxQuote, 'Desired quote quantity exceeded' ); require( execution.liquidityInPips > 0 && execution.liquidityInPips == pool.calculateOutputLiquidityInPips( execution.netBaseQuantityInPips, execution.netQuoteQuantityInPips ), 'Invalid liquidity minted' ); validateLiquidityChangeExecutionFees(execution); } function validateLiquidityRemoval( LiquidityRemoval memory removal, LiquidityChangeExecution memory execution, LiquidityPool memory pool ) internal view { require( ((execution.baseAssetAddress == removal.assetA && execution.quoteAssetAddress == removal.assetB) || (execution.baseAssetAddress == removal.assetB && execution.quoteAssetAddress == removal.assetA)), 'Asset address mismatch' ); require( execution.grossBaseQuantityInPips > 0 && execution.grossQuoteQuantityInPips > 0, 'Gross quantities must be nonzero' ); (uint256 minBaseInAssetUnits, uint256 minQuoteInAssetUnits) = execution.baseAssetAddress == removal.assetA ? (removal.amountAMin, removal.amountBMin) : (removal.amountBMin, removal.amountAMin); (uint64 minBase, uint64 minQuote) = ( AssetUnitConversions.assetUnitsToPips( minBaseInAssetUnits, pool.baseAssetDecimals ), AssetUnitConversions.assetUnitsToPips( minQuoteInAssetUnits, pool.quoteAssetDecimals ) ); require( execution.grossBaseQuantityInPips >= minBase, 'Min base quantity not met' ); require( execution.grossQuoteQuantityInPips >= minQuote, 'Min quote quantity not met' ); require( execution.liquidityInPips == AssetUnitConversions.assetUnitsToPips( removal.liquidity, Constants.liquidityProviderTokenDecimals ), 'Invalid liquidity burned' ); (uint256 expectedBaseAssetQuantityInPips, ) = pool.calculateOutputAssetQuantitiesInPips(execution.liquidityInPips); // To allow for integer rounding precision loss, only validate exact output quantity for the // base asset, and allow the invariant price check performed in // validateAndUpdateForLiquidityRemoval to indirectly validate the output quantity of the quote // asset require( execution.grossBaseQuantityInPips == expectedBaseAssetQuantityInPips, 'Invalid base quantity' ); validateLiquidityChangeExecutionFees(execution); } function validateLiquidityChangeExecutionFees( LiquidityChangeExecution memory execution ) private pure { require( Validations.isFeeQuantityValid( execution.calculateBaseFeeQuantityInPips(), execution.grossBaseQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive base fee' ); require( Validations.isFeeQuantityValid( execution.calculateQuoteFeeQuantityInPips(), execution.grossQuoteQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive quote fee' ); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetTransfers } from './AssetTransfers.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { Constants } from './Constants.sol'; import { LiquidityPools } from './LiquidityPools.sol'; import { LiquidityProviderToken } from '../LiquidityProviderToken.sol'; import { Validations } from './Validations.sol'; import { ICustodian, IERC20, IExchange, ILiquidityProviderToken, IWETH9 } from './Interfaces.sol'; import { Asset, LiquidityMigration, LiquidityPool } from './Structs.sol'; library LiquidityPoolAdmin { using AssetRegistry for AssetRegistry.Storage; function upgradeLiquidityPool( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress, AssetRegistry.Storage storage assetRegistry, IExchange balanceMigrationSource, ICustodian custodian ) public { require( custodian.loadExchange() == address(this), 'Can only be set on Exchange whitelisted with Custodian' ); // Use bidirectional mapping to require uniqueness of pools by asset pair regardless of // base-quote positions require( address( self.liquidityProviderTokensByAddress[baseAssetAddress][ quoteAssetAddress ] ) == address(0x0), 'Pool already exists' ); // loadLiquidityPoolByAssetAddresses will revert if the pool does not exist LiquidityPool memory migrationSourcePool = balanceMigrationSource.loadLiquidityPoolByAssetAddresses( baseAssetAddress, quoteAssetAddress ); // Create internally-tracked liquidity pool self.poolsByAddresses[baseAssetAddress][ quoteAssetAddress ] = migrationSourcePool; // Store LP token address in both pair directions to allow lookup by unordered asset pairs // during on-chain initiated liquidity removals self.liquidityProviderTokensByAddress[baseAssetAddress][ quoteAssetAddress ] = ILiquidityProviderToken(migrationSourcePool.liquidityProviderToken); self.liquidityProviderTokensByAddress[quoteAssetAddress][ baseAssetAddress ] = ILiquidityProviderToken(migrationSourcePool.liquidityProviderToken); // Build an asset descriptor for the LP token and add it to the registry. Asset memory lpTokenAsset = Asset({ exists: true, assetAddress: address(migrationSourcePool.liquidityProviderToken), symbol: string( abi.encodePacked( 'ILP-', migrationSourcePool.liquidityProviderToken.baseAssetSymbol(), '-', migrationSourcePool.liquidityProviderToken.quoteAssetSymbol() ) ), decimals: Constants.liquidityProviderTokenDecimals, isConfirmed: true, confirmedTimestampInMs: uint64(block.timestamp * 1000) // Block timestamp is in seconds, store ms }); assetRegistry.assetsByAddress[lpTokenAsset.assetAddress] = lpTokenAsset; assetRegistry.assetsBySymbol[lpTokenAsset.symbol].push(lpTokenAsset); } function createLiquidityPool( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress, AssetRegistry.Storage storage assetRegistry ) public { { createLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress, assetRegistry ); } } function migrateLiquidityPool( LiquidityPools.Storage storage self, LiquidityMigration memory migration, ICustodian custodian, AssetRegistry.Storage storage assetRegistry ) public returns (address liquidityProviderToken) { require( AssetUnitConversions.assetUnitsToPips( migration.desiredLiquidity, Constants.liquidityProviderTokenDecimals ) > 0, 'Desired liquidity too low' ); { // Map Pair token reserve addresses to provided market base/quote addresses ( address baseAssetAddress, address quoteAssetAddress, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits ) = transferMigratedTokenReservesToCustodian(migration, custodian); LiquidityPool storage pool; pool = loadOrCreateLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress, assetRegistry ); liquidityProviderToken = address(pool.liquidityProviderToken); { // Convert transferred reserve amounts to pips and store pool.baseAssetReserveInPips += AssetUnitConversions.assetUnitsToPips( baseAssetQuantityInAssetUnits, pool.baseAssetDecimals ); require(pool.baseAssetReserveInPips > 0, 'Insufficient base quantity'); pool.quoteAssetReserveInPips += AssetUnitConversions.assetUnitsToPips( quoteAssetQuantityInAssetUnits, pool.quoteAssetDecimals ); require( pool.quoteAssetReserveInPips > 0, 'Insufficient quote quantity' ); Validations.validatePoolReserveRatio(pool); } // Mint desired liquidity to Farm to complete migration ILiquidityProviderToken(liquidityProviderToken).mint( address(this), migration.desiredLiquidity, baseAssetQuantityInAssetUnits, quoteAssetQuantityInAssetUnits, migration.to ); } } function reverseLiquidityPoolAssets( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress ) public { LiquidityPool memory pool = LiquidityPools.loadLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress ); delete self.poolsByAddresses[baseAssetAddress][quoteAssetAddress]; self.poolsByAddresses[quoteAssetAddress][baseAssetAddress] = pool; ( pool.baseAssetReserveInPips, pool.baseAssetDecimals, pool.quoteAssetReserveInPips, pool.quoteAssetDecimals ) = ( pool.quoteAssetReserveInPips, pool.quoteAssetDecimals, pool.baseAssetReserveInPips, pool.baseAssetDecimals ); pool.liquidityProviderToken.reverseAssets(); } // Helpers // function loadOrCreateLiquidityPoolByAssetAddresses( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress, AssetRegistry.Storage storage assetRegistry ) private returns (LiquidityPool storage pool) { pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress]; if (!pool.exists) { pool = createLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress, assetRegistry ); } } function createLiquidityPoolByAssetAddresses( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress, AssetRegistry.Storage storage assetRegistry ) private returns (LiquidityPool storage pool) { // Use bidirectional mapping to require uniqueness of pools by asset pair regardless of // base-quote positions require( address( self.liquidityProviderTokensByAddress[baseAssetAddress][ quoteAssetAddress ] ) == address(0x0), 'Pool already exists' ); // Create internally-tracked liquidity pool pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress]; pool.exists = true; // Store asset decimals to avoid redundant asset registry lookups Asset memory asset; asset = assetRegistry.loadAssetByAddress(baseAssetAddress); string memory baseAssetSymbol = asset.symbol; pool.baseAssetDecimals = asset.decimals; asset = assetRegistry.loadAssetByAddress(quoteAssetAddress); string memory quoteAssetSymbol = asset.symbol; pool.quoteAssetDecimals = asset.decimals; // Create an LP token contract tied to this market. Construct salt from byte-sorted assets to // maintain a stable address if asset direction is reversed via `reverseLiquidityPoolAssets` bytes32 salt = keccak256( baseAssetAddress < quoteAssetAddress ? abi.encodePacked(baseAssetAddress, quoteAssetAddress) : abi.encodePacked(quoteAssetAddress, baseAssetAddress) ); ILiquidityProviderToken liquidityProviderToken = new LiquidityProviderToken{ salt: salt }( baseAssetAddress, quoteAssetAddress, baseAssetSymbol, quoteAssetSymbol ); // Store LP token address in both pair directions to allow lookup by unordered asset pairs // during on-chain initiated liquidity removals self.liquidityProviderTokensByAddress[baseAssetAddress][ quoteAssetAddress ] = ILiquidityProviderToken(liquidityProviderToken); self.liquidityProviderTokensByAddress[quoteAssetAddress][ baseAssetAddress ] = ILiquidityProviderToken(liquidityProviderToken); // Associate the newly created LP token contract with the pool pool.liquidityProviderToken = ILiquidityProviderToken( liquidityProviderToken ); // Build an asset descriptor for the new LP token and add it to the registry. There is no need // to validate against it already existing as the preceeding CREATE2 will revert on duplicate // asset pairs Asset memory lpTokenAsset = Asset({ exists: true, assetAddress: address(liquidityProviderToken), symbol: string( abi.encodePacked('ILP-', baseAssetSymbol, '-', quoteAssetSymbol) ), decimals: Constants.liquidityProviderTokenDecimals, isConfirmed: true, confirmedTimestampInMs: uint64(block.timestamp * 1000) // Block timestamp is in seconds, store ms }); assetRegistry.assetsByAddress[lpTokenAsset.assetAddress] = lpTokenAsset; assetRegistry.assetsBySymbol[lpTokenAsset.symbol].push(lpTokenAsset); } function transferMigratedTokenReservesToCustodian( LiquidityMigration memory migration, ICustodian custodian ) private returns ( address baseAssetAddress, address quoteAssetAddress, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits ) { // Obtain reserve amounts sent to the Exchange uint256 reserve0 = IERC20(migration.token0).balanceOf(address(this)); uint256 reserve1 = IERC20(migration.token1).balanceOf(address(this)); // Transfer reserves to Custodian and unwrap WETH if needed transferMigratedTokenReserveToCustodian( migration.token0, reserve0, migration.WETH, custodian ); transferMigratedTokenReserveToCustodian( migration.token1, reserve1, migration.WETH, custodian ); address unwrappedToken0 = migration.token0 == address(migration.WETH) ? address(0x0) : migration.token0; address unwrappedToken1 = migration.token1 == address(migration.WETH) ? address(0x0) : migration.token1; return migration.isToken1Quote ? (unwrappedToken0, unwrappedToken1, reserve0, reserve1) : (unwrappedToken1, unwrappedToken0, reserve1, reserve0); } function transferMigratedTokenReserveToCustodian( address token, uint256 reserve, IWETH9 WETH, ICustodian custodian ) private { // Unwrap WETH if (token == address(WETH)) { WETH.withdraw(reserve); AssetTransfers.transferTo( payable(address(custodian)), address(0x0), reserve ); } else { AssetTransfers.transferTo(payable(address(custodian)), token, reserve); } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { SafeCast } from './SafeCast.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { Constants } from './Constants.sol'; import { IERC20 } from './Interfaces.sol'; import { LiquidityPool } from './Structs.sol'; import { Math } from './Math.sol'; library LiquidityPoolHelpers { function calculateCurrentPoolPriceInPips(LiquidityPool memory self) internal pure returns (uint64) { if (self.baseAssetReserveInPips == 0) { return 0; } return Math.multiplyPipsByFraction( Constants.pipPriceMultiplier, self.quoteAssetReserveInPips, self.baseAssetReserveInPips ); } /** * @dev Calculate reserve asset quantities to remove from a pool for a given liquidity amount * @dev This function will revert if the base reserve is zero */ function calculateOutputAssetQuantitiesInPips( LiquidityPool memory self, uint64 liquidityToBurnInPips ) internal view returns ( uint64 outputBaseAssetQuantityInPips, uint64 outputQuoteAssetQuantityInPips ) { uint64 totalLiquidityInPips = AssetUnitConversions.assetUnitsToPips( IERC20(address(self.liquidityProviderToken)).totalSupply(), Constants.liquidityProviderTokenDecimals ); // Use fraction of total liquidity burned to calculate proportionate base amount out outputBaseAssetQuantityInPips = Math.multiplyPipsByFraction( self.baseAssetReserveInPips, liquidityToBurnInPips, totalLiquidityInPips ); // Calculate quote amount out that maintains the current pool price given above base amount out. // Use double pip precision to avoid precision loss for very high prices uint256 poolPriceInDoublePips = (uint256(self.quoteAssetReserveInPips) * Constants.doublePipPriceMultiplier) / self.baseAssetReserveInPips; uint64 targetQuoteAssetReserveInPips = SafeCast.toUint64( (uint256(self.baseAssetReserveInPips - outputBaseAssetQuantityInPips) * poolPriceInDoublePips) / Constants.doublePipPriceMultiplier ); outputQuoteAssetQuantityInPips = self.quoteAssetReserveInPips - targetQuoteAssetReserveInPips; } /** * @dev Calculate LP token quantity to mint for given reserve asset quantities */ function calculateOutputLiquidityInPips( LiquidityPool memory self, uint64 baseQuantityInPips, uint64 quoteQuantityInPips ) internal view returns (uint64 outputLiquidityInPips) { uint256 totalSupplyInAssetUnits = IERC20(address(self.liquidityProviderToken)).totalSupply(); // For initial deposit use geometric mean of reserve quantities if (totalSupplyInAssetUnits == 0) { // There is no need to check for uint64 overflow since sqrt(max * max) = max return uint64(Math.sqrt(uint256(baseQuantityInPips) * quoteQuantityInPips)); } uint64 totalLiquidityInPips = AssetUnitConversions.assetUnitsToPips( totalSupplyInAssetUnits, Constants.liquidityProviderTokenDecimals ); return Math.min( Math.multiplyPipsByFraction( totalLiquidityInPips, baseQuantityInPips, self.baseAssetReserveInPips ), Math.multiplyPipsByFraction( totalLiquidityInPips, quoteQuantityInPips, self.quoteAssetReserveInPips ) ); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetTransfers } from './AssetTransfers.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { Constants } from './Constants.sol'; import { Depositing } from './Depositing.sol'; import { Hashing } from './Hashing.sol'; import { LiquidityChangeExecutionValidations } from './LiquidityChangeExecutionValidations.sol'; import { LiquidityPoolHelpers } from './LiquidityPoolHelpers.sol'; import { LiquidityProviderToken } from '../LiquidityProviderToken.sol'; import { PoolTradeHelpers } from './PoolTradeHelpers.sol'; import { Validations } from './Validations.sol'; import { Withdrawing } from './Withdrawing.sol'; import { ICustodian, IERC20, ILiquidityProviderToken, IWETH9 } from './Interfaces.sol'; import { LiquidityChangeOrigination, LiquidityChangeState, LiquidityChangeType, OrderSide } from './Enums.sol'; import { Asset, LiquidityAddition, LiquidityAdditionDepositResult, LiquidityChangeExecution, LiquidityPool, LiquidityRemoval, LiquidityRemovalDepositResult, PoolTrade } from './Structs.sol'; library LiquidityPools { using AssetRegistry for AssetRegistry.Storage; using BalanceTracking for BalanceTracking.Storage; using LiquidityPoolHelpers for LiquidityPool; using PoolTradeHelpers for PoolTrade; struct Storage { mapping(address => mapping(address => ILiquidityProviderToken)) liquidityProviderTokensByAddress; mapping(address => mapping(address => LiquidityPool)) poolsByAddresses; mapping(bytes32 => LiquidityChangeState) changes; } uint64 public constant MINIMUM_LIQUIDITY = 10**3; // Add liquidity // function addLiquidity( Storage storage self, LiquidityAddition memory addition, ICustodian custodian, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) public returns (LiquidityAdditionDepositResult memory) { require(addition.deadline >= block.timestamp, 'IDEX: EXPIRED'); bytes32 hash = Hashing.getLiquidityAdditionHash(addition); require( self.changes[hash] == LiquidityChangeState.NotInitiated, 'Already initiated' ); self.changes[hash] = LiquidityChangeState.Initiated; // Transfer assets to Custodian and credit balances return Depositing.depositLiquidityReserves( addition.wallet, addition.assetA, addition.assetB, addition.amountADesired, addition.amountBDesired, custodian, assetRegistry, balanceTracking ); } function executeAddLiquidity( Storage storage self, LiquidityAddition memory addition, LiquidityChangeExecution memory execution, address feeWallet, address custodianAddress, BalanceTracking.Storage storage balanceTracking ) external { ILiquidityProviderToken liquidityProviderToken = validateAndUpdateForLiquidityAddition(self, addition, execution); // Debit wallet Pair token balance and credit fee wallet reserve asset balances balanceTracking.updateForAddLiquidity( addition, execution, feeWallet, custodianAddress, liquidityProviderToken ); } function validateAndUpdateForLiquidityAddition( Storage storage self, LiquidityAddition memory addition, LiquidityChangeExecution memory execution ) private returns (ILiquidityProviderToken liquidityProviderToken) { { bytes32 hash = Hashing.getLiquidityAdditionHash(addition); LiquidityChangeState state = self.changes[hash]; if (addition.origination == LiquidityChangeOrigination.OnChain) { require( state == LiquidityChangeState.Initiated, 'Not executable from on-chain' ); } else { require( state == LiquidityChangeState.NotInitiated, 'Not executable from off-chain' ); require( Hashing.isSignatureValid(hash, addition.signature, addition.wallet), 'Invalid signature' ); } self.changes[hash] = LiquidityChangeState.Executed; } LiquidityPool storage pool = loadLiquidityPoolByAssetAddresses( self, execution.baseAssetAddress, execution.quoteAssetAddress ); liquidityProviderToken = pool.liquidityProviderToken; LiquidityChangeExecutionValidations.validateLiquidityAddition( addition, execution, pool ); validateAndUpdateReservesForLiquidityAddition(pool, execution); // Mint LP tokens to destination wallet liquidityProviderToken.mint( addition.wallet, AssetUnitConversions.pipsToAssetUnits( execution.liquidityInPips, Constants.liquidityProviderTokenDecimals ), AssetUnitConversions.pipsToAssetUnits( execution.netBaseQuantityInPips, pool.baseAssetDecimals ), AssetUnitConversions.pipsToAssetUnits( execution.netQuoteQuantityInPips, pool.quoteAssetDecimals ), addition.to ); } // Remove liquidity // function removeLiquidity( Storage storage self, LiquidityRemoval memory removal, ICustodian custodian, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) public returns (LiquidityRemovalDepositResult memory) { require(removal.deadline >= block.timestamp, 'IDEX: EXPIRED'); bytes32 hash = Hashing.getLiquidityRemovalHash(removal); require( self.changes[hash] == LiquidityChangeState.NotInitiated, 'Already initiated' ); self.changes[hash] = LiquidityChangeState.Initiated; // Resolve LP token address address liquidityProviderToken = address( loadLiquidityProviderTokenByAssetAddresses( self, removal.assetA, removal.assetB ) ); // Transfer LP tokens to Custodian and credit balances return Depositing.depositLiquidityTokens( removal.wallet, liquidityProviderToken, removal.liquidity, custodian, assetRegistry, balanceTracking ); } function executeRemoveLiquidity( Storage storage self, LiquidityRemoval memory removal, LiquidityChangeExecution memory execution, bool isWalletExited, ICustodian custodian, address feeWallet, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) public { ILiquidityProviderToken liquidityProviderToken = validateAndUpdateForLiquidityRemoval( self, removal, execution, isWalletExited ); Withdrawing.withdrawLiquidity( removal, execution, custodian, feeWallet, liquidityProviderToken, assetRegistry, balanceTracking ); } function validateAndUpdateForLiquidityRemoval( Storage storage self, LiquidityRemoval memory removal, LiquidityChangeExecution memory execution, bool isWalletExited ) private returns (ILiquidityProviderToken liquidityProviderToken) { { // Following a wallet exit the Dispatcher can liquidate the wallet's liquidity pool positions // without the need for the wallet itself to first initiate the removal. Without this // mechanism, the wallet could change the pool's price at any time following the exit by // calling `removeLiquidityExit` and cause the reversion of pending pool settlements from the // Dispatcher if (!isWalletExited) { bytes32 hash = Hashing.getLiquidityRemovalHash(removal); LiquidityChangeState state = self.changes[hash]; if (removal.origination == LiquidityChangeOrigination.OnChain) { require( state == LiquidityChangeState.Initiated, 'Not executable from on-chain' ); } else { require( state == LiquidityChangeState.NotInitiated, 'Not executable from off-chain' ); require( Hashing.isSignatureValid(hash, removal.signature, removal.wallet), 'Invalid signature' ); } self.changes[hash] = LiquidityChangeState.Executed; } } LiquidityPool storage pool = loadLiquidityPoolByAssetAddresses( self, execution.baseAssetAddress, execution.quoteAssetAddress ); liquidityProviderToken = pool.liquidityProviderToken; LiquidityChangeExecutionValidations.validateLiquidityRemoval( removal, execution, pool ); uint64 initialPrice = pool.calculateCurrentPoolPriceInPips(); // Debit pool reserves pool.baseAssetReserveInPips -= execution.grossBaseQuantityInPips; pool.quoteAssetReserveInPips -= execution.grossQuoteQuantityInPips; uint64 updatedPrice = pool.calculateCurrentPoolPriceInPips(); // Skip price validation if 1) emptying a pool of all liquidity since the price can validly // change to zero or 2) either reserve is below the minimum as prices can no longer be // represented with full pip precision if ( updatedPrice > 0 && pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips && pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips ) { require(initialPrice == updatedPrice, 'Pool price cannot change'); } liquidityProviderToken.burn( removal.wallet, AssetUnitConversions.pipsToAssetUnits( execution.liquidityInPips, Constants.liquidityProviderTokenDecimals ), AssetUnitConversions.pipsToAssetUnits( execution.grossBaseQuantityInPips, pool.baseAssetDecimals ), AssetUnitConversions.pipsToAssetUnits( execution.grossQuoteQuantityInPips, pool.quoteAssetDecimals ), removal.to ); } // Exit liquidity // function removeLiquidityExit( Storage storage self, address baseAssetAddress, address quoteAssetAddress, ICustodian custodian, BalanceTracking.Storage storage balanceTracking ) public returns ( uint64 outputBaseAssetQuantityInPips, uint64 outputQuoteAssetQuantityInPips ) { LiquidityPool storage pool = loadLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress ); uint64 liquidityToBurnInPips = balanceTracking.updateForExit( msg.sender, address(pool.liquidityProviderToken) ); // Calculate output asset quantities (outputBaseAssetQuantityInPips, outputQuoteAssetQuantityInPips) = pool .calculateOutputAssetQuantitiesInPips(liquidityToBurnInPips); uint256 outputBaseAssetQuantityInAssetUnits = AssetUnitConversions.pipsToAssetUnits( outputBaseAssetQuantityInPips, pool.baseAssetDecimals ); uint256 outputQuoteAssetQuantityInAssetUnits = AssetUnitConversions.pipsToAssetUnits( outputQuoteAssetQuantityInPips, pool.quoteAssetDecimals ); // Debit pool reserves pool.baseAssetReserveInPips -= outputBaseAssetQuantityInPips; pool.quoteAssetReserveInPips -= outputQuoteAssetQuantityInPips; // Burn deposited Pair tokens pool.liquidityProviderToken.burn( msg.sender, AssetUnitConversions.pipsToAssetUnits( liquidityToBurnInPips, Constants.liquidityProviderTokenDecimals ), outputBaseAssetQuantityInAssetUnits, outputQuoteAssetQuantityInAssetUnits, msg.sender ); // Transfer reserve assets to wallet custodian.withdraw( payable(msg.sender), baseAssetAddress, outputBaseAssetQuantityInAssetUnits ); custodian.withdraw( payable(msg.sender), quoteAssetAddress, outputQuoteAssetQuantityInAssetUnits ); } // Trading // function updateReservesForPoolTrade( Storage storage self, PoolTrade memory poolTrade, OrderSide orderSide ) internal returns (uint64 baseAssetReserveInPips, uint64 quoteAssetReserveInPips) { LiquidityPool storage pool = loadLiquidityPoolByAssetAddresses( self, poolTrade.baseAssetAddress, poolTrade.quoteAssetAddress ); uint128 initialProduct = uint128(pool.baseAssetReserveInPips) * uint128(pool.quoteAssetReserveInPips); uint128 updatedProduct; if (orderSide == OrderSide.Buy) { pool.baseAssetReserveInPips -= poolTrade.getPoolDebitQuantityInPips( orderSide ); pool.quoteAssetReserveInPips += poolTrade .calculatePoolCreditQuantityInPips(orderSide); updatedProduct = uint128(pool.baseAssetReserveInPips) * uint128( pool.quoteAssetReserveInPips - poolTrade.takerPoolFeeQuantityInPips ); } else { pool.baseAssetReserveInPips += poolTrade .calculatePoolCreditQuantityInPips(orderSide); if (poolTrade.takerPriceCorrectionFeeQuantityInPips > 0) { // Add the taker sell's price correction fee to the pool - there is no quote output pool.quoteAssetReserveInPips += poolTrade .takerPriceCorrectionFeeQuantityInPips; } else { pool.quoteAssetReserveInPips -= poolTrade.getPoolDebitQuantityInPips( orderSide ); } updatedProduct = uint128( pool.baseAssetReserveInPips - poolTrade.takerPoolFeeQuantityInPips ) * uint128(pool.quoteAssetReserveInPips); } // Constant product will increase when there are fees collected require( updatedProduct >= initialProduct, 'Constant product cannot decrease' ); // Disallow either ratio to dip below the minimum as prices can no longer be represented with // full pip precision require( pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips, 'Base reserves below min' ); require( pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips, 'Quote reserves below min' ); Validations.validatePoolReserveRatio(pool); return (pool.baseAssetReserveInPips, pool.quoteAssetReserveInPips); } // Helpers // function loadLiquidityPoolByAssetAddresses( Storage storage self, address baseAssetAddress, address quoteAssetAddress ) internal view returns (LiquidityPool storage pool) { pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress]; require(pool.exists, 'No pool for address pair'); } function loadLiquidityProviderTokenByAssetAddresses( Storage storage self, address assetA, address assetB ) private view returns (ILiquidityProviderToken liquidityProviderToken) { liquidityProviderToken = self.liquidityProviderTokensByAddress[assetA][ assetB ]; require( address(liquidityProviderToken) != address(0x0), 'No LP token for address pair' ); } function validateAndUpdateReservesForLiquidityAddition( LiquidityPool storage pool, LiquidityChangeExecution memory execution ) private { uint64 initialPrice = pool.calculateCurrentPoolPriceInPips(); // Credit pool reserves pool.baseAssetReserveInPips += execution.netBaseQuantityInPips; pool.quoteAssetReserveInPips += execution.netQuoteQuantityInPips; // Require pool price to remain constant on addition. Skip this validation if either reserve is // below the minimum as prices can no longer be represented with full pip precision if (initialPrice == 0) { // First liquidity addition to empty pool establishes price which must be within max ratio Validations.validatePoolReserveRatio(pool); } else if ( pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips && pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips ) { uint64 updatedPrice = pool.calculateCurrentPoolPriceInPips(); require(initialPrice == updatedPrice, 'Pool price cannot change'); } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { Address } from './Address.sol'; import { ERC20 } from './ERC20.sol'; import { Constants } from './Constants.sol'; import { ICustodian, IExchange, IERC20, ILiquidityProviderToken } from './Interfaces.sol'; /** * @notice Liquidity Provider ERC-20 token contract * * @dev Reference OpenZeppelin implementation with whitelisted minting and burning */ contract LiquidityProviderToken is ERC20, ILiquidityProviderToken { // Used to whitelist Exchange-only functions by loading address of current Exchange from Custodian ICustodian public override custodian; // Base and quote asset addresses provided only for informational purposes address public override baseAssetAddress; address public override quoteAssetAddress; string public override baseAssetSymbol; string public override quoteAssetSymbol; /** * @notice Emitted when the Exchange mints new LP tokens to a wallet via `mint` */ event Mint( address indexed sender, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits ); /** * @notice Emitted when the Exchange burns a wallet's LP tokens via `burn` */ event Burn( address indexed sender, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits, address indexed to ); modifier onlyExchange() { require(msg.sender == custodian.loadExchange(), 'Caller is not Exchange'); _; } /** * @notice Instantiate a new `LiquidityProviderToken` contract * * @dev Should be called by the Exchange via a CREATE2 op to generate stable deterministic * addresses and setup whitelist for `onlyExchange`-restricted functions. Asset addresses and * symbols are stored for informational purposes * * @param _baseAssetAddress The base asset address * @param _quoteAssetAddress The quote asset address * @param _baseAssetSymbol The base asset symbol * @param _quoteAssetSymbol The quote asset symbol */ constructor( address _baseAssetAddress, address _quoteAssetAddress, string memory _baseAssetSymbol, string memory _quoteAssetSymbol ) ERC20('', 'IDEX-LP') { custodian = IExchange(msg.sender).loadCustodian(); require(address(custodian) != address(0x0), 'Invalid Custodian address'); // Assets cannot be equal require( _baseAssetAddress != _quoteAssetAddress, 'Assets must be different' ); // Each asset must be the native asset or contract require( _baseAssetAddress == address(0x0) || Address.isContract(_baseAssetAddress), 'Invalid base asset' ); require( _quoteAssetAddress == address(0x0) || Address.isContract(_quoteAssetAddress), 'Invalid quote asset' ); baseAssetAddress = _baseAssetAddress; quoteAssetAddress = _quoteAssetAddress; baseAssetSymbol = _baseAssetSymbol; quoteAssetSymbol = _quoteAssetSymbol; } /** * @notice Returns the name of the token */ function name() public view override returns (string memory) { return string( abi.encodePacked('IDEX LP: ', baseAssetSymbol, '-', quoteAssetSymbol) ); } /** * @notice Returns the address of the base-quote pair asset with the lower sort order */ function token0() external view override returns (address) { return baseAssetAddress < quoteAssetAddress ? baseAssetAddress : quoteAssetAddress; } /** * @notice Returns the address of the base-quote pair asset with the higher sort order */ function token1() external view override returns (address) { return baseAssetAddress < quoteAssetAddress ? quoteAssetAddress : baseAssetAddress; } /** * @notice Burns LP tokens by removing them from `wallet`'s balance and total supply */ function burn( address wallet, uint256 liquidity, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits, address to ) external override onlyExchange { _burn(address(custodian), liquidity); emit Burn( wallet, baseAssetQuantityInAssetUnits, quoteAssetQuantityInAssetUnits, to ); } /** * @notice Mints LP tokens by adding them to `wallet`'s balance and total supply */ function mint( address wallet, uint256 liquidity, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits, address to ) external override onlyExchange { _mint(to, liquidity); emit Mint( wallet, baseAssetQuantityInAssetUnits, quoteAssetQuantityInAssetUnits ); } /** * @notice Reverses the asset pair represented by this token by swapping `baseAssetAddress` with * `quoteAssetAddress` and `baseAssetSymbol` with `quoteAssetSymbol` */ function reverseAssets() external override onlyExchange { // Assign swapped values to intermediate values first as Solidity won't allow multiple storage // writes in a single statement ( address _baseAssetAddress, address _quoteAssetAddress, string memory _baseAssetSymbol, string memory _quoteAssetSymbol ) = (quoteAssetAddress, baseAssetAddress, quoteAssetSymbol, baseAssetSymbol); (baseAssetAddress, quoteAssetAddress, baseAssetSymbol, quoteAssetSymbol) = ( _baseAssetAddress, _quoteAssetAddress, _baseAssetSymbol, _quoteAssetSymbol ); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; library Math { function multiplyPipsByFraction( uint64 multiplicand, uint64 fractionDividend, uint64 fractionDivisor ) internal pure returns (uint64) { uint256 dividend = uint256(multiplicand) * fractionDividend; uint256 result = dividend / fractionDivisor; 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; } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { NonceInvalidation } from './Structs.sol'; import { UUID } from './UUID.sol'; library NonceInvalidations { function invalidateOrderNonce( mapping(address => NonceInvalidation) storage self, uint128 nonce, uint256 chainPropagationPeriod ) external returns (uint64 timestampInMs, uint256 effectiveBlockNumber) { timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce); // Enforce a maximum skew for invalidating nonce timestamps in the future so the user doesn't // lock their wallet from trades indefinitely require(timestampInMs < getOneDayFromNowInMs(), 'Nonce timestamp too high'); if (self[msg.sender].exists) { require( self[msg.sender].timestampInMs < timestampInMs, 'Nonce timestamp invalidated' ); require( self[msg.sender].effectiveBlockNumber <= block.number, 'Last invalidation not finalized' ); } // Changing the Chain Propagation Period will not affect the effectiveBlockNumber for this invalidation effectiveBlockNumber = block.number + chainPropagationPeriod; self[msg.sender] = NonceInvalidation( true, timestampInMs, effectiveBlockNumber ); } function getOneDayFromNowInMs() private view returns (uint64) { uint64 secondsInOneDay = 24 * 60 * 60; // 24 hours/day * 60 min/hour * 60 seconds/min uint64 msInOneSecond = 1000; return (uint64(block.timestamp) + secondsInOneDay) * msInOneSecond; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { Constants } from './Constants.sol'; import { OrderSide } from './Enums.sol'; import { Hashing } from './Hashing.sol'; import { UUID } from './UUID.sol'; import { Validations } from './Validations.sol'; import { Asset, Order, OrderBookTrade, NonceInvalidation } from './Structs.sol'; library OrderBookTradeValidations { using AssetRegistry for AssetRegistry.Storage; function validateOrderBookTrade( Order memory buy, Order memory sell, OrderBookTrade memory trade, AssetRegistry.Storage storage assetRegistry, mapping(address => NonceInvalidation) storage nonceInvalidations ) internal view returns (bytes32, bytes32) { require( buy.walletAddress != sell.walletAddress, 'Self-trading not allowed' ); // Order book trade validations validateAssetPair(buy, sell, trade, assetRegistry); validateLimitPrices(buy, sell, trade); Validations.validateOrderNonces(buy, sell, nonceInvalidations); (bytes32 buyHash, bytes32 sellHash) = validateOrderSignatures(buy, sell, trade); validateFees(trade); return (buyHash, sellHash); } function validateAssetPair( Order memory buy, Order memory sell, OrderBookTrade memory trade, AssetRegistry.Storage storage assetRegistry ) internal view { require( trade.baseAssetAddress != trade.quoteAssetAddress, 'Trade assets must be different' ); // Fee asset validation require( (trade.makerFeeAssetAddress == trade.baseAssetAddress && trade.takerFeeAssetAddress == trade.quoteAssetAddress) || (trade.makerFeeAssetAddress == trade.quoteAssetAddress && trade.takerFeeAssetAddress == trade.baseAssetAddress), 'Fee assets mismatch trade pair' ); validateAssetPair(buy, trade, assetRegistry); validateAssetPair(sell, trade, assetRegistry); } function validateAssetPair( Order memory order, OrderBookTrade memory trade, AssetRegistry.Storage storage assetRegistry ) internal view { uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(order.nonce); Asset memory baseAsset = assetRegistry.loadAssetBySymbol(trade.baseAssetSymbol, timestampInMs); Asset memory quoteAsset = assetRegistry.loadAssetBySymbol(trade.quoteAssetSymbol, timestampInMs); require( baseAsset.assetAddress == trade.baseAssetAddress && quoteAsset.assetAddress == trade.quoteAssetAddress, 'Order symbol address mismatch' ); } function validateLimitPrices( Order memory buy, Order memory sell, OrderBookTrade memory trade ) internal pure { require( trade.grossBaseQuantityInPips > 0, 'Base quantity must be greater than zero' ); require( trade.grossQuoteQuantityInPips > 0, 'Quote quantity must be greater than zero' ); if (Validations.isLimitOrderType(buy.orderType)) { require( Validations.calculateImpliedQuoteQuantityInPips( trade.grossBaseQuantityInPips, buy.limitPriceInPips ) >= trade.grossQuoteQuantityInPips, 'Buy order limit price exceeded' ); } if (Validations.isLimitOrderType(sell.orderType)) { require( Validations.calculateImpliedQuoteQuantityInPips( trade.grossBaseQuantityInPips, sell.limitPriceInPips ) <= trade.grossQuoteQuantityInPips, 'Sell order limit price exceeded' ); } } function validateOrderSignatures( Order memory buy, Order memory sell, OrderBookTrade memory trade ) internal pure returns (bytes32, bytes32) { bytes32 buyOrderHash = validateOrderSignature( buy, trade.baseAssetSymbol, trade.quoteAssetSymbol ); bytes32 sellOrderHash = validateOrderSignature( sell, trade.baseAssetSymbol, trade.quoteAssetSymbol ); return (buyOrderHash, sellOrderHash); } 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 validateFees(OrderBookTrade memory trade) private pure { uint64 makerTotalQuantityInPips = trade.makerFeeAssetAddress == trade.baseAssetAddress ? trade.grossBaseQuantityInPips : trade.grossQuoteQuantityInPips; require( Validations.isFeeQuantityValid( trade.makerFeeQuantityInPips, makerTotalQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive maker fee' ); uint64 takerTotalQuantityInPips = trade.takerFeeAssetAddress == trade.baseAssetAddress ? trade.grossBaseQuantityInPips : trade.grossQuoteQuantityInPips; require( Validations.isFeeQuantityValid( trade.takerFeeQuantityInPips, takerTotalQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive taker fee' ); Validations.validateOrderBookTradeFees(trade); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; /** * @notice Mixin that provide separate owner and admin roles for RBAC */ abstract contract Owned { address immutable _owner; address _admin; modifier onlyOwner { require(msg.sender == _owner, 'Caller must be owner'); _; } modifier onlyAdmin { require(msg.sender == _admin, 'Caller must be admin'); _; } /** * @notice Sets both the owner and admin roles to the contract creator */ constructor() { _owner = msg.sender; _admin = msg.sender; } /** * @notice Sets a new whitelisted admin wallet * * @param newAdmin The new whitelisted admin wallet. Must be different from the current one */ function setAdmin(address newAdmin) external onlyOwner { require(newAdmin != address(0x0), 'Invalid wallet address'); require(newAdmin != _admin, 'Must be different from current admin'); _admin = newAdmin; } /** * @notice Clears the currently whitelisted admin wallet, effectively disabling any functions requiring * the admin role */ function removeAdmin() external onlyOwner { _admin = address(0x0); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { Constants } from './Constants.sol'; import { OrderSide } from './Enums.sol'; import { PoolTradeHelpers } from './PoolTradeHelpers.sol'; import { UUID } from './UUID.sol'; import { Validations } from './Validations.sol'; import { Asset, Order, NonceInvalidation, PoolTrade } from './Structs.sol'; library PoolTradeValidations { using AssetRegistry for AssetRegistry.Storage; using PoolTradeHelpers for PoolTrade; function validatePoolTrade( Order memory order, PoolTrade memory poolTrade, AssetRegistry.Storage storage assetRegistry, mapping(address => NonceInvalidation) storage nonceInvalidations ) internal view returns (bytes32 orderHash) { orderHash = Validations.validateOrderSignature( order, poolTrade.baseAssetSymbol, poolTrade.quoteAssetSymbol ); validateAssetPair(order, poolTrade, assetRegistry); validateLimitPrice(order, poolTrade); Validations.validateOrderNonce(order, nonceInvalidations); validateFees(order.side, poolTrade); } function validateAssetPair( Order memory order, PoolTrade memory poolTrade, AssetRegistry.Storage storage assetRegistry ) internal view { require( poolTrade.baseAssetAddress != poolTrade.quoteAssetAddress, 'Trade assets must be different' ); uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(order.nonce); Asset memory baseAsset = assetRegistry.loadAssetBySymbol(poolTrade.baseAssetSymbol, timestampInMs); Asset memory quoteAsset = assetRegistry.loadAssetBySymbol( poolTrade.quoteAssetSymbol, timestampInMs ); require( baseAsset.assetAddress == poolTrade.baseAssetAddress && quoteAsset.assetAddress == poolTrade.quoteAssetAddress, 'Order symbol address mismatch' ); } function validateLimitPrice(Order memory order, PoolTrade memory poolTrade) internal pure { require( poolTrade.grossBaseQuantityInPips > 0, 'Base quantity must be greater than zero' ); require( poolTrade.grossQuoteQuantityInPips > 0, 'Quote quantity must be greater than zero' ); if ( order.side == OrderSide.Buy && Validations.isLimitOrderType(order.orderType) ) { require( Validations.calculateImpliedQuoteQuantityInPips( poolTrade.grossBaseQuantityInPips, order.limitPriceInPips ) >= poolTrade.grossQuoteQuantityInPips, 'Buy order limit price exceeded' ); } if ( order.side == OrderSide.Sell && Validations.isLimitOrderType(order.orderType) ) { require( Validations.calculateImpliedQuoteQuantityInPips( poolTrade.grossBaseQuantityInPips - 1, order.limitPriceInPips ) <= poolTrade.grossQuoteQuantityInPips, 'Sell order limit price exceeded' ); } } function validateFees(OrderSide orderSide, PoolTrade memory poolTrade) private pure { require( Validations.isFeeQuantityValid( poolTrade.calculatePoolOutputAdjustment(orderSide), poolTrade.getOrderGrossReceivedQuantityInPips(orderSide), Constants.maxPoolOutputAdjustmentBasisPoints ), 'Excessive pool output adjustment' ); require( Validations.isFeeQuantityValid( poolTrade.takerGasFeeQuantityInPips, poolTrade.getOrderGrossReceivedQuantityInPips(orderSide), Constants.maxFeeBasisPoints ), 'Excessive gas fee' ); // Price correction only allowed for hybrid trades with a taker sell require( poolTrade.takerPriceCorrectionFeeQuantityInPips == 0, 'Price correction not allowed' ); Validations.validatePoolTradeInputFees(orderSide, poolTrade); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits"); return uint128(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits"); return uint64(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits"); return uint32(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits"); return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits. */ function toUint8(uint256 value) internal pure returns (uint8) { require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits"); return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { require(value >= 0, "SafeCast: value must be positive"); return uint256(value); } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v3.1._ */ function toInt128(int256 value) internal pure returns (int128) { require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits"); return int128(value); } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v3.1._ */ function toInt64(int256 value) internal pure returns (int64) { require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits"); return int64(value); } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v3.1._ */ function toInt32(int256 value) internal pure returns (int32) { require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits"); return int32(value); } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v3.1._ */ function toInt16(int256 value) internal pure returns (int16) { require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits"); return int16(value); } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits. * * _Available since v3.1._ */ function toInt8(int256 value) internal pure returns (int8) { require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits"); return int8(value); } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { require(value < 2**255, "SafeCast: value doesn't fit in an int256"); return int256(value); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; import { AssetRegistry } from './AssetRegistry.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { HybridTradeValidations } from './HybridTradeValidations.sol'; import { LiquidityPools } from './LiquidityPools.sol'; import { OrderBookTradeValidations } from './OrderBookTradeValidations.sol'; import { PoolTradeHelpers } from './PoolTradeHelpers.sol'; import { PoolTradeValidations } from './PoolTradeValidations.sol'; import { Validations } from './Validations.sol'; import { OrderSide, OrderType } from './Enums.sol'; import { HybridTrade, Order, OrderBookTrade, NonceInvalidation, PoolTrade } from './Structs.sol'; library Trading { using AssetRegistry for AssetRegistry.Storage; using BalanceTracking for BalanceTracking.Storage; using LiquidityPools for LiquidityPools.Storage; using PoolTradeHelpers for PoolTrade; function executeOrderBookTrade( Order memory buy, Order memory sell, OrderBookTrade memory orderBookTrade, address feeWallet, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking, mapping(bytes32 => bool) storage completedOrderHashes, mapping(address => NonceInvalidation) storage nonceInvalidations, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) public { (bytes32 buyHash, bytes32 sellHash) = OrderBookTradeValidations.validateOrderBookTrade( buy, sell, orderBookTrade, assetRegistry, nonceInvalidations ); updateOrderFilledQuantities( buy, buyHash, sell, sellHash, orderBookTrade, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); balanceTracking.updateForOrderBookTrade( buy, sell, orderBookTrade, feeWallet ); } function executePoolTrade( Order memory order, PoolTrade memory poolTrade, address feeWallet, AssetRegistry.Storage storage assetRegistry, LiquidityPools.Storage storage liquidityPoolRegistry, BalanceTracking.Storage storage balanceTracking, mapping(bytes32 => bool) storage completedOrderHashes, mapping(address => NonceInvalidation) storage nonceInvalidations, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) public { bytes32 orderHash = PoolTradeValidations.validatePoolTrade( order, poolTrade, assetRegistry, nonceInvalidations ); updateOrderFilledQuantity( order, orderHash, poolTrade.grossBaseQuantityInPips, poolTrade.grossQuoteQuantityInPips, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); balanceTracking.updateForPoolTrade(order, poolTrade, feeWallet); liquidityPoolRegistry.updateReservesForPoolTrade(poolTrade, order.side); } function executeHybridTrade( Order memory buy, Order memory sell, HybridTrade memory hybridTrade, address feeWallet, AssetRegistry.Storage storage assetRegistry, LiquidityPools.Storage storage liquidityPoolRegistry, BalanceTracking.Storage storage balanceTracking, mapping(bytes32 => bool) storage completedOrderHashes, mapping(address => NonceInvalidation) storage nonceInvalidations, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) public { (bytes32 buyHash, bytes32 sellHash) = HybridTradeValidations.validateHybridTrade( buy, sell, hybridTrade, assetRegistry, nonceInvalidations ); executeHybridTradePoolComponent( buy, sell, buyHash, sellHash, hybridTrade, feeWallet, liquidityPoolRegistry, balanceTracking, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); { // Order book trade updateOrderFilledQuantities( buy, buyHash, sell, sellHash, hybridTrade.orderBookTrade, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); balanceTracking.updateForOrderBookTrade( buy, sell, hybridTrade.orderBookTrade, feeWallet ); } { address takerWallet = hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? sell.walletAddress : buy.walletAddress; balanceTracking.updateForHybridTradeFees( hybridTrade, takerWallet, feeWallet ); } } function executeHybridTradePoolComponent( Order memory buy, Order memory sell, bytes32 buyHash, bytes32 sellHash, HybridTrade memory hybridTrade, address feeWallet, LiquidityPools.Storage storage liquidityPoolRegistry, BalanceTracking.Storage storage balanceTracking, mapping(bytes32 => bool) storage completedOrderHashes, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) private { (Order memory makerOrder, Order memory takerOrder, bytes32 takerOrderHash) = hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? (buy, sell, sellHash) : (sell, buy, buyHash); updateOrderFilledQuantity( takerOrder, takerOrderHash, hybridTrade.poolTrade.grossBaseQuantityInPips, hybridTrade.poolTrade.grossQuoteQuantityInPips, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); balanceTracking.updateForPoolTrade( takerOrder, hybridTrade.poolTrade, feeWallet ); (uint64 baseAssetReserveInPips, uint64 quoteAssetReserveInPips) = liquidityPoolRegistry.updateReservesForPoolTrade( hybridTrade.poolTrade, takerOrder.side ); HybridTradeValidations.validatePoolPrice( makerOrder, baseAssetReserveInPips, quoteAssetReserveInPips ); } function updateOrderFilledQuantities( Order memory buy, bytes32 buyHash, Order memory sell, bytes32 sellHash, OrderBookTrade memory orderBookTrade, mapping(bytes32 => bool) storage completedOrderHashes, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) private { // Buy side updateOrderFilledQuantity( buy, buyHash, orderBookTrade.grossBaseQuantityInPips, orderBookTrade.grossQuoteQuantityInPips, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); // Sell side updateOrderFilledQuantity( sell, sellHash, orderBookTrade.grossBaseQuantityInPips, orderBookTrade.grossQuoteQuantityInPips, completedOrderHashes, partiallyFilledOrderQuantitiesInPips ); } // Update filled quantities tracking for order to prevent over- or double-filling orders function updateOrderFilledQuantity( Order memory order, bytes32 orderHash, uint64 grossBaseQuantityInPips, uint64 grossQuoteQuantityInPips, mapping(bytes32 => bool) storage completedOrderHashes, mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips ) private { require(!completedOrderHashes[orderHash], 'Order double filled'); // Total quantity of above filled as a result of all trade executions, including this one uint64 newFilledQuantityInPips; // Market orders can express quantity in quote terms, and can be partially filled by multiple // limit maker orders necessitating tracking partially filled amounts in quote terms to // determine completion if (order.isQuantityInQuote) { require( isMarketOrderType(order.orderType), 'Order quote quantity only valid for market orders' ); newFilledQuantityInPips = grossQuoteQuantityInPips + partiallyFilledOrderQuantitiesInPips[orderHash]; } else { // All other orders track partially filled quantities in base terms newFilledQuantityInPips = grossBaseQuantityInPips + partiallyFilledOrderQuantitiesInPips[orderHash]; } uint64 quantityInPips = order.quantityInPips; require(newFilledQuantityInPips <= quantityInPips, 'Order overfilled'); if (newFilledQuantityInPips < quantityInPips) { // If the order was partially filled, track the new filled quantity partiallyFilledOrderQuantitiesInPips[orderHash] = newFilledQuantityInPips; } else { // If the order was completed, delete any partial fill tracking and instead track its completion // to prevent future double fills delete partiallyFilledOrderQuantitiesInPips[orderHash]; completedOrderHashes[orderHash] = true; } } function isMarketOrderType(OrderType orderType) private pure returns (bool) { return orderType == OrderType.Market || orderType == OrderType.StopLoss || orderType == OrderType.TakeProfit; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; /** * 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; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.10; 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 ) ); } } }
{ "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "optimizer": { "enabled": true, "runs": 1 }, "evmVersion": "berlin", "metadata": { "bytecodeHash": "ipfs" }, "libraries": { "Exchange.sol": { "AssetRegistry": "0xbbdba7cc65545fc38bd6b10bf61ede08cd47bd5a", "Depositing": "0xedb960d352a6a52709c1dea56659d81755e04c41", "LiquidityPoolAdmin": "0x64b882ab50c2f94cefeab1568b2161b2c83080ce", "LiquidityPools": "0xfc881c4b2e5044a5b648f89e5fb0c9579f4a5075", "NonceInvalidations": "0xc956d06ee0ee5f7af081d609a409425c250ea95d", "Trading": "0x572eb8eaa977e34ad34ba5729cc4e099d823da3d", "Withdrawing": "0xd253f0f299db07f6661188466cde4ae407fe7c08" }, "AssetRegistry.sol": { "AssetRegistry": "0xbbdba7cc65545fc38bd6b10bf61ede08cd47bd5a" }, "Depositing.sol": { "Depositing": "0xedb960d352a6a52709c1dea56659d81755e04c41" }, "LiquidityPoolAdmin.sol": { "LiquidityPoolAdmin": "0x64b882ab50c2f94cefeab1568b2161b2c83080ce" }, "LiquidityPools.sol": { "LiquidityPools": "0xfc881c4b2e5044a5b648f89e5fb0c9579f4a5075" }, "NonceInvalidations.sol": { "NonceInvalidations": "0xc956d06ee0ee5f7af081d609a409425c250ea95d" }, "Trading.sol": { "Trading": "0x572eb8eaa977e34ad34ba5729cc4e099d823da3d" }, "Withdrawing.sol": { "Withdrawing": "0xd253f0f299db07f6661188466cde4ae407fe7c08" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract IExchange","name":"balanceMigrationSource","type":"address"},{"internalType":"address","name":"feeWallet","type":"address"},{"internalType":"string","name":"nativeAssetSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ChainPropagationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"index","type":"uint64"},{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"orderBookBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"orderBookQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"poolBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"poolQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"totalBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"totalQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"HybridTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidityInPips","type":"uint64"}],"name":"LiquidityAdditionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetA","type":"address"},{"indexed":false,"internalType":"address","name":"assetB","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountADesired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountAMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBMin","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"LiquidityAdditionInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"originalBaseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"originalQuoteAssetAddress","type":"address"}],"name":"LiquidityPoolAssetsReversed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidityInPips","type":"uint64"}],"name":"LiquidityRemovalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetA","type":"address"},{"indexed":false,"internalType":"address","name":"assetB","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountAMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBMin","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"LiquidityRemovalInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"OrderBookTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint128","name":"nonce","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"timestampInMs","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"OrderNonceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"PoolTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"}],"name":"TokenSymbolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseAssetQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteAssetQuantityInPips","type":"uint64"}],"name":"WalletExitLiquidityRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"_depositIndex","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"_walletExits","outputs":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"}],"name":"addTokenSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"cleanupWalletBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"confirmTokenRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"createLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenByAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenBySymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"enum LiquidityChangeOrigination","name":"origination","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetA","type":"address"},{"internalType":"address","name":"assetB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct LiquidityAddition","name":"addition","type":"tuple"},{"components":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"liquidityInPips","type":"uint64"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"}],"internalType":"struct LiquidityChangeExecution","name":"execution","type":"tuple"}],"name":"executeAddLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"sell","type":"tuple"},{"components":[{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct OrderBookTrade","name":"orderBookTrade","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPoolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerProtocolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPriceCorrectionFeeQuantityInPips","type":"uint64"}],"internalType":"struct PoolTrade","name":"poolTrade","type":"tuple"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"}],"internalType":"struct HybridTrade","name":"hybridTrade","type":"tuple"}],"name":"executeHybridTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"sell","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct OrderBookTrade","name":"orderBookTrade","type":"tuple"}],"name":"executeOrderBookTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"enum OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPoolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerProtocolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPriceCorrectionFeeQuantityInPips","type":"uint64"}],"internalType":"struct PoolTrade","name":"poolTrade","type":"tuple"}],"name":"executePoolTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"enum LiquidityChangeOrigination","name":"origination","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetA","type":"address"},{"internalType":"address","name":"assetB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct LiquidityRemoval","name":"removal","type":"tuple"},{"components":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"liquidityInPips","type":"uint64"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"}],"internalType":"struct LiquidityChangeExecution","name":"execution","type":"tuple"}],"name":"executeRemoveLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"nonce","type":"uint128"}],"name":"invalidateOrderNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint64","name":"timestampInMs","type":"uint64"}],"name":"loadAssetBySymbol","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"bool","name":"isConfirmed","type":"bool"},{"internalType":"uint64","name":"confirmedTimestampInMs","type":"uint64"}],"internalType":"struct Asset","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInAssetUnitsByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInAssetUnitsBySymbol","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInPipsByAddress","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInPipsBySymbol","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadCustodian","outputs":[{"internalType":"contract ICustodian","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadFeeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadLiquidityMigrator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"loadLiquidityPoolByAssetAddresses","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"uint64","name":"baseAssetReserveInPips","type":"uint64"},{"internalType":"uint8","name":"baseAssetDecimals","type":"uint8"},{"internalType":"uint64","name":"quoteAssetReserveInPips","type":"uint64"},{"internalType":"uint8","name":"quoteAssetDecimals","type":"uint8"},{"internalType":"contract ILiquidityProviderToken","name":"liquidityProviderToken","type":"address"}],"internalType":"struct LiquidityPool","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"loadPartiallyFilledOrderQuantityInPips","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"bool","name":"isToken1Quote","type":"bool"},{"internalType":"uint256","name":"desiredLiquidity","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address payable","name":"WETH","type":"address"}],"name":"migrateLiquidityPool","outputs":[{"internalType":"address","name":"liquidityProviderToken","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"removeLiquidityExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"reverseLiquidityPoolAssets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newChainPropagationPeriod","type":"uint256"}],"name":"setChainPropagationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ICustodian","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setDepositIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"setDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newMigrator","type":"address"}],"name":"setMigrator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"upgradeLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum WithdrawalType","name":"withdrawalType","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address payable","name":"walletAddress","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint64","name":"grossQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"gasFeeInPips","type":"uint64"},{"internalType":"bool","name":"autoDispatchEnabled","type":"bool"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"assetAddress","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
60a06040523480156200001157600080fd5b50604051620063eb380380620063eb83398101604081905262000034916200033d565b336080819052600080546001600160a01b03191690911790556001600160a01b038316158062000075575062000075836200012c60201b620008f51760201c565b620000c75760405162461bcd60e51b815260206004820152601860248201527f496e76616c6964206d6967726174696f6e20736f75726365000000000000000060448201526064015b60405180910390fd5b600580546001600160a01b0319166001600160a01b038516179055620000ed8262000132565b80516200010290600390602084019062000268565b505060098054600160a01b600160e01b031916600160a01b600160e01b0317905550620004849050565b3b151590565b6000546001600160a01b031633146200018e5760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206d7573742062652061646d696e0000000000000000000000006044820152606401620000be565b6001600160a01b038116620001e65760405162461bcd60e51b815260206004820152601660248201527f496e76616c69642077616c6c65742061646472657373000000000000000000006044820152606401620000be565b6012546001600160a01b0382811691161415620002465760405162461bcd60e51b815260206004820152601e60248201527f4d75737420626520646966666572656e742066726f6d2063757272656e7400006044820152606401620000be565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b828054620002769062000447565b90600052602060002090601f0160209004810192826200029a5760008555620002e5565b82601f10620002b557805160ff1916838001178555620002e5565b82800160010185558215620002e5579182015b82811115620002e5578251825591602001919060010190620002c8565b50620002f3929150620002f7565b5090565b5b80821115620002f35760008155600101620002f8565b6001600160a01b03811681146200032457600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b6000806000606084860312156200035357600080fd5b835162000360816200030e565b8093505060208085015162000375816200030e565b60408601519093506001600160401b03808211156200039357600080fd5b818701915087601f830112620003a857600080fd5b815181811115620003bd57620003bd62000327565b604051601f8201601f19908116603f01168101908382118183101715620003e857620003e862000327565b816040528281528a868487010111156200040157600080fd5b600093505b8284101562000425578484018601518185018701529285019262000406565b82841115620004375760008684830101525b8096505050505050509250925092565b600181811c908216806200045c57607f821691505b602082108114156200047e57634e487b7160e01b600052602260045260246000fd5b50919050565b608051615f44620004a7600039600081816115d80152611b880152615f446000f3fe6080604052600436106102275760003560e01c80630226b70e1461025a578063026fc96e1461027a57806302751cec1461029a57806302ca6002146102ba57806309b388f1146102ea5780630c187a721461030a57806313cfda2c1461031f5780631abb58321461034d57806320e6a8e31461036d5780632384d3d01461038d57806323cf3118146103ad578063403f3731146103cd57806341968182146103ed578063457aa3c61461040d5780634a284ef91461042d578063503b37dc146104425780636bb509f214610462578063704b6c021461048257806372e8f08d146104a25780637cf0c1f4146104c2578063869af212146104e05780638d1d707d1461050d57806390d49b9d1461052d57806398166c0d1461054d578063985c4af51461059e57806398ea5fca146105be5780639a202d47146105c65780639fd96059146105db578063ae0e969e146105f0578063b39f07301461063e578063ba22bd7614610665578063baa2abde14610685578063bc25cf77146106a5578063c1b16c20146106c5578063c3e5af73146106e5578063c638199914610705578063ca2a245e14610725578063d3b2596d14610745578063d7677fbe14610765578063d7a6aec7146107ef578063dbb365351461080f578063dcc634901461082f578063e8e337001461084f578063eb5068f21461086f578063ed04a70714610884578063ef3b9d4a146108a2578063f305d719146108c2578063f91b6e68146108d557600080fd5b3661025557333b6102535760405162461bcd60e51b815260040161024a906138de565b60405180910390fd5b005b600080fd5b34801561026657600080fd5b5061025361027536600461397e565b6108fb565b34801561028657600080fd5b506102536102953660046139fc565b61099b565b3480156102a657600080fd5b506102536102b5366004613a5c565b610b55565b3480156102c657600080fd5b506012546001600160a01b03165b6040516102e19190613ac7565b60405180910390f35b3480156102f657600080fd5b506102d4610305366004613aeb565b610d4d565b34801561031657600080fd5b50610253610eac565b34801561032b57600080fd5b5061033f61033a366004613b62565b610ee8565b6040519081526020016102e1565b34801561035957600080fd5b5061033f610368366004613b9b565b610f73565b34801561037957600080fd5b50610253610388366004613bef565b611001565b34801561039957600080fd5b506102536103a8366004613b62565b6110b9565b3480156103b957600080fd5b506102536103c8366004613c08565b611156565b3480156103d957600080fd5b506102536103e8366004613c08565b6111ee565b3480156103f957600080fd5b50610253610408366004613dbe565b6112b1565b34801561041957600080fd5b5061025361042836600461397e565b611305565b34801561043957600080fd5b5061025361136f565b34801561044e57600080fd5b5061025361045d366004613b62565b6113e9565b34801561046e57600080fd5b5061025361047d366004613e48565b61147f565b34801561048e57600080fd5b5061025361049d366004613c08565b6115cd565b3480156104ae57600080fd5b506102536104bd366004613b9b565b6116c7565b3480156104ce57600080fd5b50600b546001600160a01b03166102d4565b3480156104ec57600080fd5b506105006104fb366004613f54565b61179f565b6040516102e19190614013565b34801561051957600080fd5b50610253610528366004614440565b6117eb565b34801561053957600080fd5b50610253610548366004613c08565b6119af565b34801561055957600080fd5b50610587610568366004613c08565b600a602052600090815260409020805460019091015460ff9091169082565b6040805192151583526020830191909152016102e1565b3480156105aa57600080fd5b506102536105b9366004613c08565b611a4f565b610253611b65565b3480156105d257600080fd5b50610253611b7d565b3480156105e757600080fd5b50610253611bd7565b3480156105fc57600080fd5b5061062661060b366004613bef565b6000908152600860205260409020546001600160401b031690565b6040516001600160401b0390911681526020016102e1565b34801561064a57600080fd5b5060095461062690600160a01b90046001600160401b031681565b34801561067157600080fd5b50610253610680366004613c08565b611d09565b34801561069157600080fd5b506102536106a0366004614531565b611da9565b3480156106b157600080fd5b506102536106c0366004613c08565b611fa2565b3480156106d157600080fd5b506102536106e03660046145a3565b612045565b3480156106f157600080fd5b50610253610700366004613b62565b6121c3565b34801561071157600080fd5b5061025361072036600461462a565b612298565b34801561073157600080fd5b50610253610740366004613b62565b6123d6565b34801561075157600080fd5b50610253610760366004613b62565b6124cf565b34801561077157600080fd5b50610785610780366004613b62565b612608565b6040516102e19190600060c082019050825115158252602083015160018060401b03808216602085015260ff6040860151166040850152806060860151166060850152505060ff608084015116608083015260018060a01b0360a08401511660a083015292915050565b3480156107fb57600080fd5b5061025361080a366004614667565b6126b9565b34801561081b57600080fd5b5061062661082a366004613b62565b6127a8565b34801561083b57600080fd5b5061025361084a366004614682565b612839565b34801561085b57600080fd5b5061025361086a3660046146ae565b61286e565b34801561087b57600080fd5b50610253612af2565b34801561089057600080fd5b506009546001600160a01b03166102d4565b3480156108ae57600080fd5b506106266108bd366004613b9b565b612be9565b6102536108d0366004613a5c565b612c6d565b3480156108e157600080fd5b506102536108f036600461472a565b612ede565b3b151590565b6000546001600160a01b031633146109255760405162461bcd60e51b815260040161024a9061478d565b60405163a7c5af2160e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063a7c5af21906109659060019088908890889088906004016147e4565b60006040518083038186803b15801561097d57600080fd5b505af4158015610991573d6000803e3d6000fd5b5050505050505050565b6011546001600160a01b031633146109c55760405162461bcd60e51b815260040161024a90614824565b73fc881c4b2e5044a5b648f89e5fb0c9579f4a507563089f7adc600c8484600a60006109f76080850160608601613c08565b6001600160a01b039081168252602082019290925260409081016000205460095460125492516001600160e01b031960e08a901b168152610a519796959460ff909316939183169291909116906001906004908101614982565b60006040518083038186803b158015610a6957600080fd5b505af4158015610a7d573d6000803e3d6000fd5b507f616311a023377d4cd48ac9f682cab7aa6e9876dd3a5f39d6ebd5cc14a6452c509250610ab49150506080840160608501613c08565b610ac16020840184613c08565b610ad16040850160208601613c08565b610ae16080860160608701614aef565b610af160a0870160808801614aef565b610b016060880160408901614aef565b604080516001600160a01b039788168152958716602087015293909516928401929092526001600160401b039081166060840152908116608083015290911660a082015260c0015b60405180910390a15050565b336000908152600a602052604090205460ff1615610b855760405162461bcd60e51b815260040161024a90614b0c565b60408051610180810182526004808252600060208084018290528385018290523360608501526001600160a01b038b8116608086015260a0850183905260c085018b905260e085018a905261010085018990528781166101208601526101408501879052855191820186528282526101608501919091526009549451639cffaf0f60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a507594639cffaf0f94610c4194600c94929390921691600191908101614b33565b600060405180830381865af4158015610c5e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c869190810190614c95565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316610cb890614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051610d089796959493929190614d8a565b60405180910390a1600080516020615eaf833981519152338860008989898989604051610d3c989796959493929190614de5565b60405180910390a150505050505050565b600b546000906001600160a01b03163314610da35760405162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1026b4b3b930ba37b960511b604482015260640161024a565b6040805160c0810182526001600160a01b03898116825288811660208301908152881515838501908152606084018981528884166080860190815288851660a087019081526009549751636c91badd60e01b8152600c600482015296518616602488015293518516604487015291511515606486015251608485015251821660a484015251811660c48301529190911660e482015260016101048201527364b882ab50c2f94cefeab1568b2161b2c83080ce90636c91badd9061012401602060405180830381865af4158015610e7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ea19190614e2e565b979650505050505050565b6000546001600160a01b03163314610ed65760405162461bcd60e51b815260040161024a9061478d565b601180546001600160a01b0319169055565b6040516347ac1b4560e01b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a906347ac1b4590610f2990600190879087906004908101614e4b565b602060405180830381865af4158015610f46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f6a9190614e70565b90505b92915050565b60405163a2cfc7a960e01b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063a2cfc7a990610fb6906001908890889088906004908101614e89565b602060405180830381865af4158015610fd3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ff79190614e70565b90505b9392505050565b6000546001600160a01b0316331461102b5760405162461bcd60e51b815260040161024a9061478d565b62031380811061107b5760405162461bcd60e51b815260206004820152601b60248201527a09ccaee40e0cae4d2dec840cee4cac2e8cae440e8d0c2dc40dac2f602b1b604482015260640161024a565b601080549082905560408051828152602081018490527f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f9101610b49565b6000546001600160a01b031633146110e35760405162461bcd60e51b815260040161024a9061478d565b60405163e7bcf67360e01b81527364b882ab50c2f94cefeab1568b2161b2c83080ce9063e7bcf6739061112290600c9086908690600190600401614e4b565b60006040518083038186803b15801561113a57600080fd5b505af415801561114e573d6000803e3d6000fd5b505050505050565b6000546001600160a01b031633146111805760405162461bcd60e51b815260040161024a9061478d565b803b61119e5760405162461bcd60e51b815260040161024a90614ec6565b600b546001600160a01b03828116911614156111cc5760405162461bcd60e51b815260040161024a90614eef565b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146112185760405162461bcd60e51b815260040161024a9061478d565b6009546001600160a01b0316156112715760405162461bcd60e51b815260206004820152601e60248201527f437573746f6469616e2063616e206f6e6c7920626520736574206f6e63650000604482015260640161024a565b803b61128f5760405162461bcd60e51b815260040161024a90614ec6565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b60006112c8836112bf613034565b60019190613049565b60208101519091506001600160a01b03166112f55760405162461bcd60e51b815260040161024a906138de565b611300338284613408565b505050565b6000546001600160a01b0316331461132f5760405162461bcd60e51b815260040161024a9061478d565b604051635e55073b60e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a90635e55073b906109659060019088908890889088906004016147e4565b611378336135ce565b6113945760405162461bcd60e51b815260040161024a90614f26565b336000818152600a6020526040808220805460ff1916815560010191909155517fb771d4b2a83beca38f442c8903629e0e8ab1a07cf76e94eb2977153167e20936916113df91613ac7565b60405180910390a1565b6000546001600160a01b031633146114135760405162461bcd60e51b815260040161024a9061478d565b600554600954604051632076351960e01b8152600c60048201526001600160a01b0380861660248301528085166044830152600160648301529283166084820152911660a48201527364b882ab50c2f94cefeab1568b2161b2c83080ce9063207635199060c401611122565b6011546001600160a01b031633146114a95760405162461bcd60e51b815260040161024a90614824565b6114b681604001516135ce565b156114d35760405162461bcd60e51b815260040161024a90614b0c565b600954601254604051635879725360e11b815260009283928392839273d253f0f299db07f6661188466cde4ae407fe7c089263b0f2e4a692611530928a926001600160a01b0391821692911690600190600490600f908201614f59565b600060405180830381865af415801561154d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526115759190810190615050565b93509350935093507f6960a1f64ecf9da0d1d1bcbfa3dd27f8c1c60de69b13faa28127dafa36c111e4856040015183838860a0015188886040516115be969594939291906150bd565b60405180910390a15050505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146116155760405162461bcd60e51b815260040161024a9061510e565b6001600160a01b03811661163b5760405162461bcd60e51b815260040161024a9061513c565b6000546001600160a01b03828116911614156116a55760405162461bcd60e51b8152602060048201526024808201527f4d75737420626520646966666572656e742066726f6d2063757272656e7420616044820152633236b4b760e11b606482015260840161024a565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146116f15760405162461bcd60e51b815260040161024a9061478d565b604051635ef40c1160e11b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063bde818229061172f9060019087908790879060040161516c565b60006040518083038186803b15801561174757600080fd5b505af415801561175b573d6000803e3d6000fd5b505050507f7f63a63f4a2ea318da0b031794d0b5a3183a1b2de54e053ceb17cabdba031735838383604051611792939291906151a1565b60405180910390a1505050565b6117a76138a8565b610ff784848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600193925086915050613049565b6011546001600160a01b031633146118155760405162461bcd60e51b815260040161024a90614824565b61182283604001516135ce565b1561183f5760405162461bcd60e51b815260040161024a906151cf565b61184c82604001516135ce565b156118695760405162461bcd60e51b815260040161024a90615202565b60125460405163279565bb60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d91634f2acb76916118c4918791879187916001600160a01b0390911690600190600c906004906006906007906008908401615592565b60006040518083038186803b1580156118dc57600080fd5b505af41580156118f0573d6000803e3d6000fd5b50505050604083810151908301518251805160208083015160808085015160a095860151938901519182015191909501517f0c2788d93efb3bdadabeb2b84b5900db0d210b5875d7e88a913b6a807a1f12cb97969592939190611953828561564d565b8a6020015160a001518b6000015160a0015161196f919061564d565b60008c516101a00151600181111561198957611989614856565b14611995576000611998565b60015b6040516117929b9a99989796959493929190615678565b6000546001600160a01b031633146119d95760405162461bcd60e51b815260040161024a9061478d565b6001600160a01b0381166119ff5760405162461bcd60e51b815260040161024a9061513c565b6012546001600160a01b0382811691161415611a2d5760405162461bcd60e51b815260040161024a90614eef565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b611a58336135ce565b611a745760405162461bcd60e51b815260040161024a90614f26565b60095460405163c42cc91f60e01b81526001600160a01b038084166004808401919091529216602482015260016044820152606481019190915260009073d253f0f299db07f6661188466cde4ae407fe7c089063c42cc91f90608401602060405180830381865af4158015611aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b119190615714565b604080513381526001600160a01b03851660208201526001600160401b038316918101919091529091507f8a8c919824df7fc8e14ab0532b92f8305abaa1947ec4ecdeb744c4281aecd16690606001610b49565b611b7b33611b75600160006135ff565b34613408565b565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611bc55760405162461bcd60e51b815260040161024a9061510e565b600080546001600160a01b0319169055565b6000546001600160a01b03163314611c015760405162461bcd60e51b815260040161024a9061478d565b600954600160a01b90046001600160401b0390811614611c5a5760405162461bcd60e51b815260206004820152601460248201527343616e206f6e6c7920626520736574206f6e636560601b604482015260640161024a565b6005546001600160a01b031615611cde5760055460408051630b39f07360e41b815290516001600160a01b039092169163b39f0730916004818101926020929091908290030181865afa158015611cb5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cd99190615714565b611ce1565b60005b600960146101000a8154816001600160401b0302191690836001600160401b03160217905550565b6000546001600160a01b03163314611d335760405162461bcd60e51b815260040161024a9061478d565b6001600160a01b038116611d595760405162461bcd60e51b815260040161024a9061513c565b6011546001600160a01b0382811691161415611d875760405162461bcd60e51b815260040161024a90614eef565b601180546001600160a01b0319166001600160a01b0392909216919091179055565b336000908152600a602052604090205460ff1615611dd95760405162461bcd60e51b815260040161024a90614b0c565b60408051610180810182526004808252600060208084018290528385018290523360608501526001600160a01b038c811660808601528b811660a086015260c085018b905260e085018a905261010085018990528781166101208601526101408501879052855191820186528282526101608501919091526009549451639cffaf0f60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a507594639cffaf0f94611e9694600c94929390921691600191908101614b33565b600060405180830381865af4158015611eb3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611edb9190810190614c95565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316611f0d90614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051611f5d9796959493929190614d8a565b60405180910390a1600080516020615eaf8339815191523389898989898989604051611f90989796959493929190614de5565b60405180910390a15050505050505050565b6000546001600160a01b03163314611fcc5760405162461bcd60e51b815260040161024a9061478d565b60125460405163712b772f60e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9163712b772f916120129185916001600160a01b0390911690600401615731565b60006040518083038186803b15801561202a57600080fd5b505af415801561203e573d6000803e3d6000fd5b5050505050565b6011546001600160a01b0316331461206f5760405162461bcd60e51b815260040161024a90614824565b61207c83604001516135ce565b156120995760405162461bcd60e51b815260040161024a906151cf565b6120a682604001516135ce565b156120c35760405162461bcd60e51b815260040161024a90615202565b6012546040516358a9b1ad60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d9163b153635a9161211b918791879187916001600160a01b0390911690600190600490600690600790600890840161574b565b60006040518083038186803b15801561213357600080fd5b505af4158015612147573d6000803e3d6000fd5b5050506040808501519084015183516020850151608086015160a08701517fe836416b090e27ecc87dd0bf23c36034cb0b2e2960c2f271f4a7b96fea65694296506000886101a0015160018111156121a1576121a1614856565b146121ad5760006121b0565b60015b60405161179297969594939291906157c5565b6000546001600160a01b031633146121ed5760405162461bcd60e51b815260040161024a9061478d565b60405163f065a68760e01b8152600c60048201526001600160a01b038084166024830152821660448201527364b882ab50c2f94cefeab1568b2161b2c83080ce9063f065a6879060640160006040518083038186803b15801561224f57600080fd5b505af4158015612263573d6000803e3d6000fd5b505050507f81f30d401791fa502c3a1610e6e194416482fcd7bbbfdba54bded335ff9edb598282604051610b49929190615731565b6011546001600160a01b031633146122c25760405162461bcd60e51b815260040161024a90614824565b6122da6122d56080840160608501613c08565b6135ce565b1561231f5760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08195e1a5d08199a5b985b1a5e9959605a1b604482015260640161024a565b601254600954604051637904862960e11b815273fc881c4b2e5044a5b648f89e5fb0c9579f4a50759263f2090c529261237392600c92889288926001600160a01b0392831692909116906004908101615839565b60006040518083038186803b15801561238b57600080fd5b505af415801561239f573d6000803e3d6000fd5b507f6e2fd2e6935d9da786db2536ac4061dca5dad5bacaac5b8b169d657c3e3475109250610ab49150506080840160608501613c08565b60095460408051633bffd49d60e01b815290516000926001600160a01b031691633bffd49d9160048083019260209291908290030181865afa158015612420573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124449190614e2e565b9050336001600160a01b038216146124975760405162461bcd60e51b815260206004820152601660248201527543616c6c6572206973206e6f742045786368616e676560501b604482015260640161024a565b506001600160a01b039182166000908152600460209081526040808320939094168252919091522080546001600160481b0319169055565b6124d8336135ce565b6124f45760405162461bcd60e51b815260040161024a90614f26565b6009546040516311a73c7560e31b8152600c6004808301919091526001600160a01b038086166024840152808516604484015290921660648201526084810191909152600090819073fc881c4b2e5044a5b648f89e5fb0c9579f4a507590638d39e3a89060a4016040805180830381865af4158015612577573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061259b919061599d565b604080513381526001600160a01b0388811660208301528716818301526001600160401b0384811660608301528316608082015290519294509092507f8102055af21849e835ad18fcbd334e529a8d18decf5cefc69e7120508e1aeaa4919081900360a00190a150505050565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152612646600c848461379d565b6040805160c081018252825460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b900490911660808201526001909101546001600160a01b031660a0820152905092915050565b6010546040516303710ea360e61b8152600760048201526001600160801b03831660248201526044810191909152600090819073c956d06ee0ee5f7af081d609a409425c250ea95d9063dc43a8c0906064016040805180830381865af4158015612727573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061274b91906159cc565b604080513381526001600160801b03871660208201526001600160401b038416918101919091526060810182905291935091507f10cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809b90608001611792565b604051634916cb7160e11b81526001600160a01b038084166004808401919091529083166024830152604482015260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063922d96e290606401602060405180830381865af4158015612815573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f6a9190615714565b60006128466001846135ff565b90506001600160a01b0383166112f55760405162461bcd60e51b815260040161024a906138de565b336000908152600a602052604090205460ff161561289e5760405162461bcd60e51b815260040161024a90614b0c565b604080516101a0810182526004808252600060208084018290528385018290523360608501526001600160a01b038d811660808601528c811660a086015260c085018c905260e085018b905261010085018a90526101208501899052878116610140860152610160850187905285519182018652828252610180850191909152600954945163467b41fb60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a50759463467b41fb9461296394600c949293909216916001919081016159fa565b600060405180830381865af4158015612980573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526129a89190810190615af1565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b03166129da90614d63565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338b8460000151856020015186604001518760600151604051612a269796959493929190614d8a565b60405180910390a1600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612a5e90614d63565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338a84608001518560a001518660c001518760e00151604051612aaa9796959493929190614d8a565b60405180910390a1600080516020615eef833981519152338a8a8a8a8a8a8a8a604051612adf99989796959493929190615bdd565b60405180910390a1505050505050505050565b336000908152600a602052604090205460ff1615612b4a5760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08185b1c9958591e48195e1a5d1959605a1b604482015260640161024a565b604051806040016040528060011515815260200160105443612b6c9190615c2c565b9052336000818152600a602090815260409091208351815460ff19169015151781559201516001909201919091556010547fd60f9f7b2f1a208268475a927bd727c4e198fc8b40aab3004ebcc2bc78ca84809190612bca9043615c2c565b604080516001600160a01b0390931683526020830191909152016113df565b60405163031b07f360e61b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063c6c1fcc090612c2c906001908890889088906004908101614e89565b602060405180830381865af4158015612c49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ff79190615714565b336000908152600a602052604090205460ff1615612c9d5760405162461bcd60e51b815260040161024a90614b0c565b604080516101a0810182526004808252600060208084018290528385018290523360608501526001600160a01b038b8116608086015260a0850183905260c085018b90523460e086015261010085018a90526101208501899052878116610140860152610160850187905285519182018652828252610180850191909152600954945163467b41fb60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a50759463467b41fb94612d6094600c949293909216916001919081016159fa565b600060405180830381865af4158015612d7d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612da59190810190615af1565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612dd790614d63565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533898460000151856020015186604001518760600151604051612e239796959493929190614d8a565b60405180910390a1600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612e5b90614d63565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533600084608001518560a001518660c001518760e00151604051612ea89796959493929190614d8a565b60405180910390a1600080516020615eef8339815191523388600089348a8a8a8a604051610d3c99989796959493929190615bdd565b6011546001600160a01b03163314612f085760405162461bcd60e51b815260040161024a90614824565b612f1582604001516135ce565b15612f605760405162461bcd60e51b815260206004820152601b60248201527a13dc99195c881dd85b1b195d08195e1a5d08199a5b985b1a5e9959602a1b604482015260640161024a565b601254604051636452281b60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d9163c8a4503691612fb791869186916001600160a01b031690600190600c906004906006906007906008908401615c44565b60006040518083038186803b158015612fcf57600080fd5b505af4158015612fe3573d6000803e3d6000fd5b5050506040808401518351602085015160808087015160a08801519189015195517fe33b9d086983d8450b17504f6bd50986f52a0f8c7aebad750c10a2cf7c7f7b289750610b499691929190615cae565b60006103e86130438142615d19565b91505090565b6130516138a8565b6130e784600201805461306390615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461308f90615d48565b80156130dc5780601f106130b1576101008083540402835291602001916130dc565b820191906000526020600020905b8154815290600101906020018083116130bf57829003601f168201915b505050505084613810565b15613188576131818460020180546130fe90615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461312a90615d48565b80156131775780601f1061314c57610100808354040283529160200191613177565b820191906000526020600020905b81548152906001019060200180831161315a57829003601f168201915b5050505050613869565b9050610ffa565b6131906138a8565b600085600101856040516131a49190615d7d565b9081526040519081900360200190205411156133a15760005b85600101856040516131cf9190615d7d565b9081526040519081900360200190205460ff8216101561339f57836001600160401b031686600101866040516132059190615d7d565b90815260200160405180910390208260ff168154811061322757613227615d99565b60009182526020909120600390910201600201546201000090046001600160401b03161161338d5785600101856040516132619190615d7d565b90815260200160405180910390208160ff168154811061328357613283615d99565b60009182526020918290206040805160c0810182526003909302909101805460ff8116151584526001600160a01b036101009091041693830193909352600183018054929392918401916132d690615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461330290615d48565b801561334f5780601f106133245761010080835404028352916020019161334f565b820191906000526020600020905b81548152906001019060200180831161333257829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015291505b8061339781615daf565b9150506131bd565b505b805180156133b0575080608001515b610ff75760405162461bcd60e51b815260206004820152602360248201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f722073796d604482015262189bdb60ea1b606482015260840161024a565b600954600160a01b90046001600160401b03908116141561345f5760405162461bcd60e51b815260206004820152601160248201527011195c1bdcda5d1cc8191a5cd8589b1959607a1b604482015260640161024a565b6001600160a01b0383166000908152600a602052604090205460ff16156134985760405162461bcd60e51b815260040161024a90614b0c565b60095460405163a16a597960e01b81526000918291829173edb960d352a6a52709c1dea56659d81755e04c419163a16a5979916134e9918a918a918a916001600160a01b0316906004908101615dcf565b606060405180830381865af4158015613506573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061352a9190615e6b565b600980549396509194509250600160a01b9091046001600160401b031690601461355383614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790555050600080516020615ecf833981519152600960149054906101000a90046001600160401b031687876020015188604001518787876040516135be9796959493929190614d8a565b60405180910390a1505050505050565b6001600160a01b0381166000908152600a60205260408120805460ff168015610ffa57506001015443101592915050565b6136076138a8565b6001600160a01b03821661362e576136278360020180546130fe90615d48565b9050610f6d565b6001600160a01b03808316600090815260208581526040808320815160c081018352815460ff8116151582526101009004909516928501929092526001820180549394939184019161367f90615d48565b80601f01602080910402602001604051908101604052809291908181526020018280546136ab90615d48565b80156136f85780601f106136cd576101008083540402835291602001916136f8565b820191906000526020600020905b8154815290600101906020018083116136db57829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015280519091508015613745575080608001515b610f6a5760405162461bcd60e51b8152602060048201526024808201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f72206164646044820152637265737360e01b606482015260840161024a565b6001600160a01b0380831660009081526001850160209081526040808320938516835292905220805460ff16610ffa5760405162461bcd60e51b81526020600482015260186024820152772737903837b7b6103337b91030b2323932b9b9903830b4b960411b604482015260640161024a565b6000816040516020016138239190615d7d565b604051602081830303815290604052805190602001208360405160200161384a9190615d7d565b6040516020818303038152906040528051906020012014905092915050565b6138716138a8565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b60208082526010908201526f2ab9b2903232b837b9b4ba22ba3432b960811b604082015260600190565b6001600160a01b038116811461391d57600080fd5b50565b60008083601f84011261393257600080fd5b5081356001600160401b0381111561394957600080fd5b60208301915083602082850101111561396157600080fd5b9250929050565b803560ff8116811461397957600080fd5b919050565b6000806000806060858703121561399457600080fd5b843561399f81613908565b935060208501356001600160401b038111156139ba57600080fd5b6139c687828801613920565b90945092506139d9905060408601613968565b905092959194509250565b600060e082840312156139f657600080fd5b50919050565b6000806101008385031215613a1057600080fd5b82356001600160401b03811115613a2657600080fd5b83016101808186031215613a3957600080fd5b9150613a4884602085016139e4565b90509250929050565b803561397981613908565b60008060008060008060c08789031215613a7557600080fd5b8635613a8081613908565b95506020870135945060408701359350606087013592506080870135613aa581613908565b8092505060a087013590509295509295509295565b6001600160a01b03169052565b6001600160a01b0391909116815260200190565b8035801515811461397957600080fd5b60008060008060008060c08789031215613b0457600080fd5b8635613b0f81613908565b95506020870135613b1f81613908565b9450613b2d60408801613adb565b9350606087013592506080870135613b4481613908565b915060a0870135613b5481613908565b809150509295509295509295565b60008060408385031215613b7557600080fd5b8235613b8081613908565b91506020830135613b9081613908565b809150509250929050565b600080600060408486031215613bb057600080fd5b8335613bbb81613908565b925060208401356001600160401b03811115613bd657600080fd5b613be286828701613920565b9497909650939450505050565b600060208284031215613c0157600080fd5b5035919050565b600060208284031215613c1a57600080fd5b8135610f6a81613908565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b0381118282101715613c5e57613c5e613c25565b60405290565b6040516101c081016001600160401b0381118282101715613c5e57613c5e613c25565b60405161018081016001600160401b0381118282101715613c5e57613c5e613c25565b604051606081016001600160401b0381118282101715613c5e57613c5e613c25565b60405160a081016001600160401b0381118282101715613c5e57613c5e613c25565b60405161010081016001600160401b0381118282101715613c5e57613c5e613c25565b604051601f8201601f191681016001600160401b0381118282101715613d3957613d39613c25565b604052919050565b60006001600160401b03821115613d5a57613d5a613c25565b50601f01601f191660200190565b600082601f830112613d7957600080fd5b8135613d8c613d8782613d41565b613d11565b818152846020838601011115613da157600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215613dd157600080fd5b82356001600160401b03811115613de757600080fd5b613df385828601613d68565b95602094909401359450505050565b80356002811061397957600080fd5b80356001600160801b038116811461397957600080fd5b6001600160401b038116811461391d57600080fd5b803561397981613e28565b600060208284031215613e5a57600080fd5b81356001600160401b0380821115613e7157600080fd5b908301906101208286031215613e8657600080fd5b613e8e613c3b565b613e9783613e02565b8152613ea560208401613e11565b6020820152613eb660408401613a51565b6040820152606083013582811115613ecd57600080fd5b613ed987828601613d68565b606083015250613eeb60808401613a51565b6080820152613efc60a08401613e3d565b60a0820152613f0d60c08401613e3d565b60c0820152613f1e60e08401613adb565b60e08201526101008084013583811115613f3757600080fd5b613f4388828701613d68565b918301919091525095945050505050565b600080600060408486031215613f6957600080fd5b83356001600160401b03811115613f7f57600080fd5b613f8b86828701613920565b9094509250506020840135613f9f81613e28565b809150509250925092565b60005b83811015613fc5578181015183820152602001613fad565b83811115613fd4576000848401525b50505050565b60008151808452613ff2816020860160208601613faa565b601f01601f19169290920160200192915050565b6001600160401b03169052565b6020815281511515602082015260018060a01b0360208301511660408201526000604083015160c0606084015261404d60e0840182613fda565b905060ff60608501511660808401526080840151151560a084015260018060401b0360a08501511660c08401528091505092915050565b80356007811061397957600080fd5b80356004811061397957600080fd5b60006101c082840312156140b557600080fd5b6140bd613c64565b90506140c882613968565b81526140d660208301613e11565b60208201526140e760408301613a51565b60408201526140f860608301614084565b606082015261410960808301613e02565b608082015261411a60a08301613e3d565b60a082015261412b60c08301613adb565b60c082015261413c60e08301613e3d565b60e082015261010061414f818401613e3d565b90820152610120828101356001600160401b038082111561416f57600080fd5b61417b86838701613d68565b83850152610140925061418f838601614093565b8385015261016092506141a3838601614093565b8385015261018092506141b7838601613e3d565b838501526101a09250828501359150808211156141d357600080fd5b506141e085828601613d68565b82840152505092915050565b60006101c082840312156141ff57600080fd5b614207613c64565b905081356001600160401b038082111561422057600080fd5b61422c85838601613d68565b8352602084013591508082111561424257600080fd5b5061424f84828501613d68565b60208301525061426160408301613a51565b604082015261427260608301613a51565b606082015261428360808301613e3d565b608082015261429460a08301613e3d565b60a08201526142a560c08301613e3d565b60c08201526142b660e08301613e3d565b60e08201526101006142c9818401613a51565b908201526101206142db838201613a51565b908201526101406142ed838201613e3d565b908201526101606142ff838201613e3d565b90820152610180614311838201613e3d565b908201526101a0614323838201613e02565b9082015292915050565b6000610180828403121561434057600080fd5b614348613c87565b905081356001600160401b038082111561436157600080fd5b61436d85838601613d68565b8352602084013591508082111561438357600080fd5b5061439084828501613d68565b6020830152506143a260408301613a51565b60408201526143b360608301613a51565b60608201526143c460808301613e3d565b60808201526143d560a08301613e3d565b60a08201526143e660c08301613e3d565b60c08201526143f760e08301613e3d565b60e082015261010061440a818401613e3d565b9082015261012061441c838201613e3d565b9082015261014061442e838201613e3d565b90820152610160614323838201613e3d565b60008060006060848603121561445557600080fd5b83356001600160401b038082111561446c57600080fd5b614478878388016140a2565b9450602086013591508082111561448e57600080fd5b61449a878388016140a2565b935060408601359150808211156144b057600080fd5b90850190606082880312156144c457600080fd5b6144cc613caa565b8235828111156144db57600080fd5b6144e7898286016141ec565b8252506020830135828111156144fc57600080fd5b6145088982860161432d565b6020830152506040830135925061451e83613e28565b8260408201528093505050509250925092565b600080600080600080600060e0888a03121561454c57600080fd5b873561455781613908565b9650602088013561456781613908565b955060408801359450606088013593506080880135925060a088013561458c81613908565b8092505060c0880135905092959891949750929550565b6000806000606084860312156145b857600080fd5b83356001600160401b03808211156145cf57600080fd5b6145db878388016140a2565b945060208601359150808211156145f157600080fd5b6145fd878388016140a2565b9350604086013591508082111561461357600080fd5b50614620868287016141ec565b9150509250925092565b600080610100838503121561463e57600080fd5b82356001600160401b0381111561465457600080fd5b83016101a08186031215613a3957600080fd5b60006020828403121561467957600080fd5b610f6a82613e11565b6000806040838503121561469557600080fd5b82356146a081613908565b946020939093013593505050565b600080600080600080600080610100898b0312156146cb57600080fd5b88356146d681613908565b975060208901356146e681613908565b965060408901359550606089013594506080890135935060a0890135925060c089013561471281613908565b8092505060e089013590509295985092959890939650565b6000806040838503121561473d57600080fd5b82356001600160401b038082111561475457600080fd5b614760868387016140a2565b9350602085013591508082111561477657600080fd5b506147838582860161432d565b9150509250929050565b60208082526014908201527321b0b63632b91036bab9ba1031329030b236b4b760611b604082015260600190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b8581526001600160a01b038516602082015260806040820181905260009061480f90830185876147bb565b905060ff831660608301529695505050505050565b60208082526018908201527721b0b63632b91034b9903737ba103234b9b830ba31b432b960411b604082015260600190565b634e487b7160e01b600052602160045260246000fd5b6002811061391d5761391d614856565b6148858161486c565b9052565b6001600160801b03169052565b6000808335601e198436030181126148ad57600080fd5b83016020810192503590506001600160401b038111156148cc57600080fd5b80360383131561396157600080fd5b80356148e681613908565b6001600160a01b03908116835260208201359061490282613908565b166020830152604081013561491681613e28565b6001600160401b03908116604084015260608201359061493582613e28565b908116606084015260808201359061494c82613e28565b908116608084015260a08201359061496382613e28565b1660a083015261497560c08201613e3d565b61130060c0840182614006565b60006101c08a83528060208401526149a681840161499f8c613968565b60ff169052565b506149b360208a01613e02565b6149c16101e084018261487c565b506149ce60408a01613e11565b6149dc610200840182614889565b506149e960608a01613a51565b6149f7610220840182613aba565b50614a0460808a01613a51565b614a12610240840182613aba565b50614a1f60a08a01613a51565b614a2d610260840182613aba565b5060c089013561028083015260e08901356102a08301526101008901356102c0830152610120614a5e818b01613a51565b614a6c6102e0850182613aba565b50610140808b0135610300850152610160614a89818d018d614896565b61018080610320890152614aa2610340890183856147bb565b9650614ab1604089018f6148db565b8c151588870152614ac48589018d613aba565b614ad08489018c613aba565b8981890152505050505050826101a08301529998505050505050505050565b600060208284031215614b0157600080fd5b8135610f6a81613e28565b6020808252600d908201526c15d85b1b195d08195e1a5d1959609a1b604082015260600190565b85815260a06020820152614b4d60a08201865160ff169052565b60006020860151614b6160c084018261487c565b506040860151614b7460e0840182614889565b506060860151610100614b8981850183613aba565b60808801519150610120614b9f81860184613aba565b60a08901519250610140614bb581870185613aba565b60c08a01519350610160848188015260e08b015194506101808581890152848c01516101a0890152838c01519550614bf16101c0890187613aba565b918b01516101e08801528a01516102008701919091529250614c1a915050610220840182613fda565b915050614c2a6040830186613aba565b6060820193909352608001529392505050565b600082601f830112614c4e57600080fd5b8151614c5c613d8782613d41565b818152846020838601011115614c7157600080fd5b614c82826020830160208701613faa565b949350505050565b805161397981613e28565b600060208284031215614ca757600080fd5b81516001600160401b0380821115614cbe57600080fd5b9083019060a08286031215614cd257600080fd5b614cda613ccc565b8251614ce581613908565b8152602083015182811115614cf957600080fd5b614d0587828601614c3d565b60208301525060408301519150614d1b82613e28565b81604082015260608301519150614d3182613e28565b8160608201526080830151608082015280935050505092915050565b634e487b7160e01b600052601160045260246000fd5b60006001600160401b0382811680821415614d8057614d80614d4d565b6001019392505050565b6001600160401b0388811682526001600160a01b0388811660208401528716604083015260e06060830181905260009190614dc790840188613fda565b95811660808401529390931660a082015260c0015250949350505050565b6001600160a01b039889168152968816602088015294871660408701526060860193909352608085019190915260a084015290921660c082015260e08101919091526101000190565b600060208284031215614e4057600080fd5b8151610f6a81613908565b9384526001600160a01b03928316602085015291166040830152606082015260800190565b600060208284031215614e8257600080fd5b5051919050565b8581526001600160a01b0385166020820152608060408201819052600090614eb490830185876147bb565b90508260608301529695505050505050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252601e908201527f4d75737420626520646966666572656e742066726f6d2063757272656e740000604082015260600190565b60208082526019908201527815d85b1b195d08195e1a5d081b9bdd08199a5b985b1a5e9959603a1b604082015260600190565b60c08152614f6b60c08201885161487c565b60006020880151614f7f60e0840182614889565b506040880151610100614f9481850183613aba565b60608a0151610120858101529150614fb06101e0850183613fda565b915060808a0151614fc5610140860182613aba565b5060a08a0151614fd9610160860182614006565b5060c08a0151614fed610180860182614006565b5060e08a015115156101a085015289015183820360bf19016101c08501526150158282613fda565b925050506150266020830188613aba565b6150336040830187613aba565b8460608301528360808301528260a0830152979650505050505050565b6000806000806080858703121561506657600080fd5b845161507181613e28565b60208601516040870151919550935061508981613908565b60608601519092506001600160401b038111156150a557600080fd5b6150b187828801614c3d565b91505092959194509250565b6001600160a01b0387811682528616602082015260c0604082018190526000906150e990830187613fda565b6001600160401b03958616606084015293909416608082015260a00152949350505050565b60208082526014908201527321b0b63632b91036bab9ba1031329037bbb732b960611b604082015260600190565b602080825260169082015275496e76616c69642077616c6c6574206164647265737360501b604082015260600190565b8481526001600160a01b038416602082015260606040820181905260009061519790830184866147bb565b9695505050505050565b6001600160a01b03841681526040602082018190526000906151c690830184866147bb565b95945050505050565b602080825260199082015278109d5e481dd85b1b195d08195e1a5d08199a5b985b1a5e9959603a1b604082015260600190565b6020808252601a908201527914d95b1b081dd85b1b195d08195e1a5d08199a5b985b1a5e995960321b604082015260600190565b6007811061488557614885614856565b6004811061488557614885614856565b805160ff16825260006101c060208301516152746020860182614889565b5060408301516152876040860182613aba565b50606083015161529a6060860182615236565b5060808301516152ad608086018261487c565b5060a08301516152c060a0860182614006565b5060c08301516152d460c086018215159052565b5060e08301516152e760e0860182614006565b50610100808401516152fb82870182614006565b505061012080840151828287015261531583870182613fda565b925050506101408084015161532c82870182615246565b50506101608084015161534182870182615246565b50506101808084015161535682870182614006565b50506101a080840151858303828701526151978382613fda565b60006101c0825181855261538682860182613fda565b915050602083015184820360208601526153a08282613fda565b91505060408301516153b56040860182613aba565b5060608301516153c86060860182613aba565b5060808301516153db6080860182614006565b5060a08301516153ee60a0860182614006565b5060c083015161540160c0860182614006565b5060e083015161541460e0860182614006565b506101008084015161542882870182613aba565b50506101208084015161543d82870182613aba565b50506101408084015161545282870182614006565b50506101608084015161546782870182614006565b50506101808084015161547c82870182614006565b50506101a0808401516154918287018261487c565b5090949350505050565b600061018082518185526154b182860182613fda565b915050602083015184820360208601526154cb8282613fda565b91505060408301516154e06040860182613aba565b5060608301516154f36060860182613aba565b5060808301516155066080860182614006565b5060a083015161551960a0860182614006565b5060c083015161552c60c0860182614006565b5060e083015161553f60e0860182614006565b506101008084015161555382870182614006565b50506101208084015161556882870182614006565b50506101408084015161557d82870182614006565b50506101608084015161549182870182614006565b60006101408083526155a68184018e615256565b905082810360208401526155ba818d615256565b905082810360408401528a51606082526155d76060830182615370565b905060208c015182820360208401526155f0828261549b565b60408e8101516001600160401b0316940193909352509091506156189050606083018a613aba565b8760808301528660a08301528560c08301528460e083015283610100830152826101208301529b9a5050505050505050505050565b60006001600160401b0382811684821680830382111561566f5761566f614d4d565b01949350505050565b6001600160a01b038c811682528b166020820152610160604082018190526000906156a58382018d613fda565b905082810360608401526156b9818c613fda565b6001600160401b038b811660808601528a811660a086015289811660c086015288811660e086015287811661010086015286166101208501529150615704905061014083018461487c565b9c9b505050505050505050505050565b60006020828403121561572657600080fd5b8151610f6a81613e28565b6001600160a01b0392831681529116602082015260400190565b600061012080835261575f8184018d615256565b90508281036020840152615773818c615256565b90508281036040840152615787818b615370565b6001600160a01b039990991660608401525050608081019590955260a085019390935260c084019190915260e0830152610100909101529392505050565b6001600160a01b0388811682528716602082015260e0604082018190526000906157f190830188613fda565b82810360608401526158038188613fda565b6001600160401b038781166080860152861660a0850152915061582790508361486c565b8260c083015298975050505050505050565b600061018088835280602084015261585681840161499f8a613968565b61586260208901613e02565b6101a06158718186018361487c565b61587d60408b01613e11565b915061588d6101c0860183614889565b61589960608b01613a51565b91506158a96101e0860183613aba565b6158b560808b01613a51565b91506158c5610200860183613aba565b6158d160a08b01613a51565b91506158e1610220860183613aba565b60c08a013561024086015260e08a01356102608601526101008a01356102808601526101209150818a01356102a0860152610140615920818c01613a51565b61592e6102c0880182613aba565b50610160808c01356102e0880152615948858d018d614896565b955083610300890152615960610320890187836147bb565b965050615970604088018c6148db565b61597c8488018b613aba565b6159888288018a613aba565b87818801525050505050979650505050505050565b600080604083850312156159b057600080fd5b82516159bb81613e28565b6020840151909250613b9081613e28565b600080604083850312156159df57600080fd5b82516159ea81613e28565b6020939093015192949293505050565b85815260a06020820152615a1460a08201865160ff169052565b60006020860151615a2860c084018261487c565b506040860151615a3b60e0840182614889565b506060860151610100615a5081850183613aba565b60808801519150610120615a6681860184613aba565b60a08901519250610140615a7c81870185613aba565b60c08a01516101608781019190915260e08b015161018080890191909152938b01516101a080890191909152838c01516101c0890152828c0151955093615ac76101e0890187613aba565b818c0151610200890152808c01519550505050508061022085015250614c1a610240840182613fda565b600060208284031215615b0357600080fd5b81516001600160401b0380821115615b1a57600080fd5b908301906101008286031215615b2f57600080fd5b615b37613cee565b825182811115615b4657600080fd5b615b5287828601614c3d565b825250615b6160208401614c8a565b6020820152615b7260408401614c8a565b604082015260608301516060820152608083015182811115615b9357600080fd5b615b9f87828601614c3d565b608083015250615bb160a08401614c8a565b60a0820152615bc260c08401614c8a565b60c082015260e083015160e082015280935050505092915050565b6001600160a01b03998a168152978916602089015295881660408801526060870194909452608086019290925260a085015260c084015290921660e08201526101008101919091526101200190565b60008219821115615c3f57615c3f614d4d565b500190565b6000610120808352615c588184018d615256565b90508281036020840152615c6c818c61549b565b6001600160a01b039a909a16604084015250506060810196909652608086019490945260a085019290925260c084015260e08301526101009091015292915050565b6001600160a01b038716815260c060208201819052600090615cd290830188613fda565b8281036040840152615ce48188613fda565b6001600160401b038781166060860152861660808501529150615d0890508361486c565b8260a0830152979650505050505050565b60006001600160401b0382811684821681151582840482111615615d3f57615d3f614d4d565b02949350505050565b600181811c90821680615d5c57607f821691505b602082108114156139f657634e487b7160e01b600052602260045260246000fd5b60008251615d8f818460208701613faa565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b600060ff821660ff811415615dc657615dc6614d4d565b60010192915050565b600060018060a01b03808816835260a060208401528651151560a08401528060208801511660c084015250604086015160c060e0840152615e14610160840182613fda565b60608881015160ff166101008601526080890151151561012086015260a08901516001600160401b031661014086015260408501889052909250615e5b9150830185613aba565b8260808301529695505050505050565b600080600060608486031215615e8057600080fd5b8351615e8b81613e28565b6020850151909350615e9c81613e28565b8092505060408401519050925092509256fe5cf09499daa9369ceef90c68574078750d8aea7c2b4cdd57ba6a72fc94d4f02acb6602dc4ee50f8b02bbfaa7ecd5a9af2c634c93398d6dfe2dffa763ea71484dd172c12542e63cd0b4344ff6e06391807624c50716e6b6ae6fd16b91a9b9f86aa2646970667358221220bba20503b4dea47ea42298ace14db144e178106234090b5bba685b59f05a50ae64736f6c634300080a0033000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : balanceMigrationSource (address): 0xab6912a6574af207a6b68276c204d39b97b9be69
Arg [1] : feeWallet (address): 0x034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : nativeAssetSymbol (string): MATIC
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69
Arg [1] : 000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000060
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [4] : 4d41544943000000000000000000000000000000000000000000000000000000
Deployed ByteCode Sourcemap
1456:47623:12:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;16917:10;1078:20:0;16890:59:12;;;;-1:-1:-1;;;16890:59:12;;;;;;;:::i;:::-;;;;;;;;;1456:47623;;;;;45378:212;;;;;;;;;;-1:-1:-1;45378:212:12;;;;;:::i;:::-;;:::i;40153:621::-;;;;;;;;;;-1:-1:-1;40153:621:12;;;;;:::i;:::-;;:::i;37992:1464::-;;;;;;;;;;-1:-1:-1;37992:1464:12;;;;;:::i;:::-;;:::i;14836:85::-;;;;;;;;;;-1:-1:-1;14906:10:12;;-1:-1:-1;;;;;14906:10:12;14836:85;;;;;;;:::i;:::-;;;;;;;;27603:500;;;;;;;;;;-1:-1:-1;27603:500:12;;;;;:::i;:::-;;:::i;47894:90::-;;;;;;;;;;;;;:::i;12311:267::-;;;;;;;;;;-1:-1:-1;12311:267:12;;;;;:::i;:::-;;:::i;:::-;;;5125:25:38;;;5113:2;5098:18;12311:267:12;4979:177:38;12958:271:12;;;;;;;;;;-1:-1:-1;12958:271:12;;;;;:::i;:::-;;:::i;10414:467::-;;;;;;;;;;-1:-1:-1;10414:467:12;;;;;:::i;:::-;;:::i;25752:236::-;;;;;;;;;;-1:-1:-1;25752:236:12;;;;;:::i;:::-;;:::i;11626:279::-;;;;;;;;;;-1:-1:-1;11626:279:12;;;;;:::i;:::-;;:::i;9264:292::-;;;;;;;;;;-1:-1:-1;9264:292:12;;;;;:::i;:::-;;:::i;18068:390::-;;;;;;;;;;-1:-1:-1;18068:390:12;;;;;:::i;:::-;;:::i;44832:190::-;;;;;;;;;;-1:-1:-1;44832:190:12;;;;;:::i;:::-;;:::i;43353:197::-;;;;;;;;;;;;;:::i;25245:296::-;;;;;;;;;;-1:-1:-1;25245:296:12;;;;;:::i;:::-;;:::i;24289:703::-;;;;;;;;;;-1:-1:-1;24289:703:12;;;;;:::i;:::-;;:::i;726:222:29:-;;;;;;;;;;-1:-1:-1;726:222:29;;;;;:::i;:::-;;:::i;45861:209:12:-;;;;;;;;;;-1:-1:-1;45861:209:12;;;;;:::i;:::-;;:::i;15643:101::-;;;;;;;;;;-1:-1:-1;15721:18:12;;-1:-1:-1;;;;;15721:18:12;15643:101;;46663:207;;;;;;;;;;-1:-1:-1;46663:207:12;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;22628:1407::-;;;;;;;;;;-1:-1:-1;22628:1407:12;;;;;:::i;:::-;;:::i;11216:242::-;;;;;;;;;;-1:-1:-1;11216:242:12;;;;;:::i;:::-;;:::i;7579:50::-;;;;;;;;;;-1:-1:-1;7579:50:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;20447:14:38;;20440:22;20422:41;;20494:2;20479:18;;20472:34;;;;20395:18;7579:50:12;20254:258:38;42743:452:12;;;;;;;;;;-1:-1:-1;42743:452:12;;;;;:::i;:::-;;:::i;16995:155::-;;;:::i;1090:74:29:-;;;;;;;;;;;;;:::i;9753:300:12:-;;;;;;;;;;;;;:::i;16236:178::-;;;;;;;;;;-1:-1:-1;16236:178:12;;;;;:::i;:::-;16338:6;16361:48;;;:37;:48;;;;;;-1:-1:-1;;;;;16361:48:12;;16236:178;;;;-1:-1:-1;;;;;20864:31:38;;;20846:50;;20834:2;20819:18;16236:178:12;20702:200:38;7537:27:12;;;;;;;;;;-1:-1:-1;7537:27:12;;;;-1:-1:-1;;;7537:27:12;;-1:-1:-1;;;;;7537:27:12;;;47268:304;;;;;;;;;;-1:-1:-1;47268:304:12;;;;;:::i;:::-;;:::i;35900:1443::-;;;;;;;;;;-1:-1:-1;35900:1443:12;;;;;:::i;:::-;;:::i;48445:110::-;;;;;;;;;;-1:-1:-1;48445:110:12;;;;;:::i;:::-;;:::i;19981:954::-;;;;;;;;;;-1:-1:-1;19981:954:12;;;;;:::i;:::-;;:::i;28317:304::-;;;;;;;;;;-1:-1:-1;28317:304:12;;;;;:::i;:::-;;:::i;34591:632::-;;;;;;;;;;-1:-1:-1;34591:632:12;;;;;:::i;:::-;;:::i;48778:299::-;;;;;;;;;;-1:-1:-1;48778:299:12;;;;;:::i;:::-;;:::i;41184:636::-;;;;;;;;;;-1:-1:-1;41184:636:12;;;;;:::i;:::-;;:::i;15221:296::-;;;;;;;;;;-1:-1:-1;15221:296:12;;;;;:::i;:::-;;:::i;:::-;;;;;;23288:4:38;23330:3;23319:9;23315:19;23307:27;;23381:6;23375:13;23368:21;23361:29;23350:9;23343:48;23438:4;23430:6;23426:17;23420:24;23479:1;23475;23471:2;23467:10;23463:18;23537:2;23523:12;23519:21;23512:4;23501:9;23497:20;23490:51;23609:4;23601;23593:6;23589:17;23583:24;23579:35;23572:4;23561:9;23557:20;23550:65;23683:2;23675:4;23667:6;23663:17;23657:24;23653:33;23646:4;23635:9;23631:20;23624:63;;;23755:4;23747;23739:6;23735:17;23729:24;23725:35;23718:4;23707:9;23703:20;23696:65;23846:1;23842;23837:3;23833:11;23829:19;23821:4;23813:6;23809:17;23803:24;23799:50;23792:4;23781:9;23777:20;23770:80;23132:724;;;;;44125:317:12;;;;;;;;;;-1:-1:-1;44125:317:12;;;;;:::i;:::-;;:::i;13602:268::-;;;;;;;;;;-1:-1:-1;13602:268:12;;;;;:::i;:::-;;:::i;17444:311::-;;;;;;;;;;-1:-1:-1;17444:311:12;;;;;:::i;:::-;;:::i;29651:1752::-;;;;;;;;;;-1:-1:-1;29651:1752:12;;;;;:::i;:::-;;:::i;42207:292::-;;;;;;;;;;;;;:::i;14627:97::-;;;;;;;;;;-1:-1:-1;14709:10:12;;-1:-1:-1;;;;;14709:10:12;14627:97;;14241:258;;;;;;;;;;-1:-1:-1;14241:258:12;;;;;:::i;:::-;;:::i;32301:1749::-;;;;;;:::i;:::-;;:::i;21265:705::-;;;;;;;;;;-1:-1:-1;21265:705:12;;;;;:::i;:::-;;:::i;718:413:0:-;1078:20;1116:8;;;718:413::o;45378:212:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;45514:71:12::1;::::0;-1:-1:-1;;;45514:71:12;;:39:::1;::::0;::::1;::::0;:71:::1;::::0;:14:::1;::::0;45554:12;;45568:6;;;;45576:8;;45514:71:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;45378:212:::0;;;;:::o;40153:621::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;40307:38:::1;;:15;40353:7:::0;40368:9;40385:12:::1;:28;40398:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;-1:-1:-1::0;;;;;40385:28:12;;::::1;::::0;;::::1;::::0;::::1;::::0;;;;;;;;-1:-1:-1;40385:28:12;:35;40439:10:::1;::::0;40458::::1;::::0;40307:213;;-1:-1:-1;;;;;;40307:213:12::1;::::0;;;;;;::::1;::::0;;;;40385:35:::1;::::0;;::::1;::::0;40439:10;;::::1;::::0;40458;;;::::1;::::0;40385:35;;40498:16:::1;::::0;40307:213;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;40532:237:12::1;::::0;-1:-1:-1;40564:14:12::1;::::0;-1:-1:-1;;40564:14:12;;;::::1;::::0;::::1;;:::i;:::-;40586:26;;::::0;::::1;:9:::0;:26:::1;:::i;:::-;40620:27;::::0;;;::::1;::::0;::::1;;:::i;:::-;40655:33;::::0;;;::::1;::::0;::::1;;:::i;:::-;40696:34;::::0;;;::::1;::::0;::::1;;:::i;:::-;40738:25;::::0;;;::::1;::::0;::::1;;:::i;:::-;40532:237;::::0;;-1:-1:-1;;;;;32925:15:38;;;32907:34;;32977:15;;;32972:2;32957:18;;32950:43;33029:15;;;;33009:18;;;33002:43;;;;-1:-1:-1;;;;;33118:15:38;;;33113:2;33098:18;;33091:43;33171:15;;;33165:3;33150:19;;33143:44;33224:15;;;32887:3;33203:19;;33196:44;32856:3;32841:19;40532:237:12::1;;;;;;;;40153:621:::0;;:::o;37992:1464::-;38475:10;38462:24;;;;:12;:24;;;;;:31;;;38461:32;38453:58;;;;-1:-1:-1;;;38453:58:12;;;;;;;:::i;:::-;38611:325;;;;;;;;1534:1:5;38611:325:12;;;-1:-1:-1;38611:325:12;;;;;;;;;;;;;38740:10;38611:325;;;;-1:-1:-1;;;;;38611:325:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;38917:9;;;;;;;;;;38611:325;;;;;;;38946:10;;38570:444;;-1:-1:-1;;;38570:444:12;;-1:-1:-1;;38570:31:12;;;;:444;;:15;;38611:325;;38946:10;;;;;;1534:1:5;38570:444:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;38570:444:12;;;;;;;;;;;;:::i;:::-;38518:496;;-1:-1:-1;;;;;;;;;;;39045:13:12;;39043:15;;;;;;;;;-1:-1:-1;;;;;39043:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;39043:15:12;;;;;-1:-1:-1;;;;;39043:15:12;;;;;39066:10;39084:6;:19;;;39111:6;:18;;;39137:6;:26;;;39171:6;:36;;;39215:6;:42;;;39026:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;39308:10:12;39326:5;39347:3;39359:9;39376:14;39398:12;39426:2;39437:8;39275:176;;;;;;;;;;;;;:::i;:::-;;;;;;;;38167:1289;37992:1464;;;;;;:::o;27603:500::-;48187:18;;27804:30;;-1:-1:-1;;;;;48187:18:12;48173:10;:32;48165:67;;;;-1:-1:-1;;;48165:67:12;;39295:2:38;48165:67:12;;;39277:21:38;39334:2;39314:18;;;39307:30;-1:-1:-1;;;39353:18:38;;;39346:52;39415:18;;48165:67:12;39093:346:38;48165:67:12;27911:141:::1;::::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;;;;;27911:141:12;;::::1;::::0;;;;::::1;;::::0;::::1;::::0;;;;::::1;;::::0;;;;;;;;;;;;;;::::1;::::0;;;;;;;;::::1;::::0;;;;;;28060:10:::1;::::0;27867:231;;-1:-1:-1;;;27867:231:12;;:15:::1;:231;::::0;::::1;39831:25:38::0;39934:13;;39930:22;;39910:18;;;39903:50;39993:22;;39989:31;;39969:18;;;39962:59;40071:22;;40064:30;40057:38;40037:18;;;40030:66;40133:22;40112:19;;;40105:51;40197:23;40193:32;;40172:19;;;40165:61;40267:23;40263:32;;40242:19;;;40235:61;28060:10:12;;;::::1;40312:19:38::0;;;40305:44;28060:10:12;40365:19:38;;;40358:35;27867:36:12::1;::::0;::::1;::::0;39803:19:38;;27867:231:12::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;27842:256:::0;27603:500;-1:-1:-1;;;;;;;27603:500:12:o;47894:90::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;47947:17:12::1;:32:::0;;-1:-1:-1;;;;;;47947:32:12::1;::::0;;47894:90::o;12311:267::-;12454:119;;-1:-1:-1;;;12454:119:12;;12426:7;;12454:47;;;;:119;;:14;;12511:6;;12527:12;;12549:16;;12454:119;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12441:132;;12311:267;;;;;:::o;12958:271::-;13107:117;;-1:-1:-1;;;13107:117:12;;13079:7;;13107:46;;;;:117;;:14;;13163:6;;13179:11;;;;13200:16;;13107:117;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;13094:130;;12958:271;;;;;;:::o;10414:467::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;475:22:5::1;10534:25:12;:63;10519:121;;;::::0;-1:-1:-1;;;10519:121:12;;42114:2:38;10519:121:12::1;::::0;::::1;42096:21:38::0;42153:2;42133:18;;;42126:30;-1:-1:-1;;;42172:18:38;;;42165:57;42239:18;;10519:121:12::1;41912:351:38::0;10519:121:12::1;10683:23;::::0;;10712:51;;;;10775:101:::1;::::0;;42442:25:38;;;42498:2;42483:18;;42476:34;;;10775:101:12::1;::::0;42415:18:38;10775:101:12::1;42268:248:38::0;25752:236:12;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;25871:112:12::1;::::0;-1:-1:-1;;;25871:112:12;;:35:::1;::::0;::::1;::::0;:112:::1;::::0;:15:::1;::::0;25914:16;;25938:17;;25963:14:::1;::::0;25871:112:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;25752:236:::0;;:::o;11626:279::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;1078:20:0;;11693:68:12::1;;;;-1:-1:-1::0;;;11693:68:12::1;;;;;;;:::i;:::-;11797:18;::::0;-1:-1:-1;;;;;11782:33:12;;::::1;11797:18:::0;::::1;11782:33;;11767:94;;;;-1:-1:-1::0;;;11767:94:12::1;;;;;;;:::i;:::-;11868:18;:32:::0;;-1:-1:-1;;;;;;11868:32:12::1;-1:-1:-1::0;;;;;11868:32:12;;;::::1;::::0;;;::::1;::::0;;11626:279::o;9264:292::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;9351:10:12::1;::::0;-1:-1:-1;;;;;9351:10:12::1;:47:::0;9336:108:::1;;;::::0;-1:-1:-1;;;9336:108:12;;43933:2:38;9336:108:12::1;::::0;::::1;43915:21:38::0;43972:2;43952:18;;;43945:30;44011:32;43991:18;;;43984:60;44061:18;;9336:108:12::1;43731:354:38::0;9336:108:12::1;1078:20:0::0;;9450:69:12::1;;;;-1:-1:-1::0;;;9450:69:12::1;;;;;;;:::i;:::-;9526:10;:25:::0;;-1:-1:-1;;;;;;9526:25:12::1;-1:-1:-1::0;;;;;9526:25:12;;;::::1;::::0;;;::::1;::::0;;9264:292::o;18068:390::-;18182:18;18209:110;18251:11;18272:39;:37;:39::i;:::-;18209:14;;:110;:32;:110::i;:::-;18342:18;;;;18182:137;;-1:-1:-1;;;;;;18334:43:12;18326:72;;;;-1:-1:-1;;;18326:72:12;;;;;;;:::i;:::-;18405:48;18413:10;18425:5;18432:20;18405:7;:48::i;:::-;18176:282;18068:390;;:::o;44832:190::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;44957:60:12::1;::::0;-1:-1:-1;;;44957:60:12;;:28:::1;::::0;::::1;::::0;:60:::1;::::0;:14:::1;::::0;44986:12;;45000:6;;;;45008:8;;44957:60:::1;;;:::i;43353:197::-:0;43403:33;43425:10;43403:21;:33::i;:::-;43395:71;;;;-1:-1:-1;;;43395:71:12;;;;;;;:::i;:::-;43493:10;43480:24;;;;:12;:24;;;;;;43473:31;;-1:-1:-1;;43473:31:12;;;;;;;;;43516:29;;;;;;:::i;:::-;;;;;;;;43353:197::o;25245:296::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;25480:32:12;;25520:10:::1;::::0;25365:171:::1;::::0;-1:-1:-1;;;25365:171:12;;:15:::1;25480:16;25365:171:::0;::::1;44831:25:38::0;-1:-1:-1;;;;;44930:15:38;;;44910:18;;;44903:43;44982:15;;;44962:18;;;44955:43;25458:14:12::1;45014:18:38::0;;;45007:34;25480:32:12;;::::1;45057:19:38::0;;;45050:44;25520:10:12;::::1;45110:19:38::0;;;45103:44;25365:36:12::1;::::0;::::1;::::0;44803:19:38;;25365:171:12::1;44444:709:38::0;24289:703:12;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;24374:47:::1;24396:10;:24;;;24374:21;:47::i;:::-;24373:48;24365:74;;;;-1:-1:-1::0;;;24365:74:12::1;;;;;;;:::i;:::-;24657:10;::::0;24677::::1;::::0;24607:174:::1;::::0;-1:-1:-1;;;24607:174:12;;24454:31:::1;::::0;;;;;;;24607:11:::1;::::0;:20:::1;::::0;:174:::1;::::0;24637:10;;-1:-1:-1;;;;;24657:10:12;;::::1;::::0;24677;::::1;::::0;24657;;24721:16:::1;::::0;24747:26:::1;::::0;24607:174;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;::::0;;::::1;-1:-1:-1::0;;24607:174:12::1;::::0;::::1;;::::0;::::1;::::0;;;::::1;::::0;::::1;:::i;:::-;24446:335;;;;;;;;24793:194;24810:10;:24;;;24842:12;24862:11;24881:10;:30;;;24919:24;24951:30;24793:194;;;;;;;;;;;:::i;:::-;;;;;;;;24359:633;;;;24289:703:::0;:::o;726:222:29:-;254:10;-1:-1:-1;;;;;268:6:29;254:20;;246:53;;;;-1:-1:-1;;;246:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;795:24:29;::::1;787:59;;;;-1:-1:-1::0;;;787:59:29::1;;;;;;;:::i;:::-;872:6;::::0;-1:-1:-1;;;;;860:18:29;;::::1;872:6:::0;::::1;860:18;;852:67;;;::::0;-1:-1:-1;;;852:67:29;;49315:2:38;852:67:29::1;::::0;::::1;49297:21:38::0;49354:2;49334:18;;;49327:30;49393:34;49373:18;;;49366:62;-1:-1:-1;;;49444:18:38;;;49437:34;49488:19;;852:67:29::1;49113:400:38::0;852:67:29::1;926:6;:17:::0;;-1:-1:-1;;;;;;926:17:29::1;-1:-1:-1::0;;;;;926:17:29;;;::::1;::::0;;;::::1;::::0;;726:222::o;45861:209:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;45965:51:12::1;::::0;-1:-1:-1;;;45965:51:12;;:29:::1;::::0;::::1;::::0;:51:::1;::::0;:14:::1;::::0;45995:12;;46009:6;;;;45965:51:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;46027:38;46044:12;46058:6;;46027:38;;;;;;;;:::i;:::-;;;;;;;;45861:209:::0;;;:::o;46663:207::-;46776:12;;:::i;:::-;46805:60;46838:11;;46805:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;46805:14:12;;:60;-1:-1:-1;46851:13:12;;-1:-1:-1;;46805:32:12;:60::i;22628:1407::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;22822:40:::1;22844:3;:17;;;22822:21;:40::i;:::-;22821:41;22806:97;;;;-1:-1:-1::0;;;22806:97:12::1;;;;;;;:::i;:::-;22925:41;22947:4;:18;;;22925:21;:41::i;:::-;22924:42;22909:99;;;;-1:-1:-1::0;;;22909:99:12::1;;;;;;;:::i;:::-;23091:10;::::0;23015:262:::1;::::0;-1:-1:-1;;;23015:262:12;;:7:::1;::::0;:26:::1;::::0;:262:::1;::::0;23049:3;;23060:4;;23072:11;;-1:-1:-1;;;;;23091:10:12;;::::1;::::0;;;23131:15:::1;::::0;23154:16:::1;::::0;23178:21:::1;::::0;23207:19:::1;::::0;23234:37:::1;::::0;23015:262;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;;23316:17:12::1;::::0;;::::1;::::0;23341:18;;::::1;::::0;23367:26;;:42;;23417:43:::1;::::0;;::::1;::::0;23468:50:::1;::::0;;::::1;::::0;23526:51:::1;::::0;;::::1;::::0;23585:21;;::::1;::::0;:45;;::::1;::::0;23638:46;;;::::1;::::0;23289:741:::1;::::0;23316:17;23341:18;23417:43;;23526:51;23585:45;23692:106:::1;23585:45:::0;23468:50;23692:106:::1;:::i;:::-;23868:11;:21;;;:46;;;23806:11;:26;;;:51;;;:108;;;;:::i;:::-;23962:13;23922:26:::0;;:36:::1;;::::0;:53:::1;::::0;::::1;;;;;;:::i;:::-;;:102;;24011:13;23922:102;;;23986:14;23922:102;23289:741;;;;;;;;;;;;;;;;:::i;11216:242::-:0;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;11291:28:12;::::1;11283:63;;;;-1:-1:-1::0;;;11283:63:12::1;;;;;;;:::i;:::-;11376:10;::::0;-1:-1:-1;;;;;11360:26:12;;::::1;11376:10:::0;::::1;11360:26;;11352:69;;;;-1:-1:-1::0;;;11352:69:12::1;;;;;;;:::i;:::-;11428:10;:25:::0;;-1:-1:-1;;;;;;11428:25:12::1;-1:-1:-1::0;;;;;11428:25:12;;;::::1;::::0;;;::::1;::::0;;11216:242::o;42743:452::-;42810:33;42832:10;42810:21;:33::i;:::-;42802:71;;;;-1:-1:-1;;;42802:71:12;;;;;;;:::i;:::-;43010:10;;42954:124;;-1:-1:-1;;;42954:124:12;;-1:-1:-1;;;;;60392:15:38;;;43054:16:12;42954:124;;;60374:34:38;;;;43010:10:12;;60424:18:38;;;60417:43;43010:10:12;60476:18:38;;;60469:34;60519:18;;;60512:34;;;;42909:36:12;;42954:11;;:24;;60308:19:38;;42954:124:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;43090:100;;;43117:10;61049:34:38;;-1:-1:-1;;;;;61119:15:38;;61114:2;61099:18;;61092:43;-1:-1:-1;;;;;61171:31:38;;61151:18;;;61144:59;;;;42909:169:12;;-1:-1:-1;43090:100:12;;60999:2:38;60984:18;43090:100:12;60811:398:38;16995:155:12;17042:103;17057:10;17075:47;:14;17117:3;17075:33;:47::i;:::-;17130:9;17042:7;:103::i;:::-;16995:155::o;1090:74:29:-;254:10;-1:-1:-1;;;;;268:6:29;254:20;;246:53;;;;-1:-1:-1;;;246:53:29;;;;;;;:::i;:::-;1155:3:::1;1138:21:::0;;-1:-1:-1;;;;;;1138:21:29::1;::::0;;1090:74::o;9753:300:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;9820:13:12::1;::::0;-1:-1:-1;;;9820:13:12;::::1;-1:-1:-1::0;;;;;9820:13:12;;::::1;:45;9805:96;;;::::0;-1:-1:-1;;;9805:96:12;;61416:2:38;9805:96:12::1;::::0;::::1;61398:21:38::0;61455:2;61435:18;;;61428:30;-1:-1:-1;;;61474:18:38;;;61467:50;61534:18;;9805:96:12::1;61214:344:38::0;9805:96:12::1;9932:32:::0;;-1:-1:-1;;;;;9932:32:12::1;9924:57:::0;:124:::1;;10000:32:::0;;:48:::1;::::0;;-1:-1:-1;;;10000:48:12;;;;-1:-1:-1;;;;;10000:32:12;;::::1;::::0;:46:::1;::::0;:16:::1;:48:::0;;::::1;::::0;::::1;::::0;;;;;;;;;:32;:48:::1;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;9924:124;;;9990:1;9924:124;9908:13;;:140;;;;;-1:-1:-1::0;;;;;9908:140:12::1;;;;;-1:-1:-1::0;;;;;9908:140:12::1;;;;;;9753:300::o:0;47268:304::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;47353:35:12;::::1;47345:70;;;;-1:-1:-1::0;;;47345:70:12::1;;;;;;;:::i;:::-;47459:17;::::0;-1:-1:-1;;;;;47436:40:12;;::::1;47459:17:::0;::::1;47436:40;;47421:101;;;;-1:-1:-1::0;;;47421:101:12::1;;;;;;;:::i;:::-;47528:17;:39:::0;;-1:-1:-1;;;;;;47528:39:12::1;-1:-1:-1::0;;;;;47528:39:12;;;::::1;::::0;;;::::1;::::0;;47268:304::o;35900:1443::-;36393:10;36380:24;;;;:12;:24;;;;;:31;;;36379:32;36371:58;;;;-1:-1:-1;;;36371:58:12;;;;;;;:::i;:::-;36529:314;;;;;;;;1534:1:5;36529:314:12;;;-1:-1:-1;36529:314:12;;;;;;;;;;;;;36658:10;36529:314;;;;-1:-1:-1;;;;;36529:314:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;36824:9;;;;;;;;;;36529:314;;;;;;;36853:10;;36488:433;;-1:-1:-1;;;36488:433:12;;-1:-1:-1;;36488:31:12;;;;:433;;:15;;36529:314;;36853:10;;;;;;1534:1:5;36488:433:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;36488:433:12;;;;;;;;;;;;:::i;:::-;36436:485;;-1:-1:-1;;;;;;;;;;;36952:13:12;;36950:15;;;;;;;;;-1:-1:-1;;;;;36950:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;36950:15:12;;;;;-1:-1:-1;;;;;36950:15:12;;;;;36973:10;36991:6;:19;;;37018:6;:18;;;37044:6;:26;;;37078:6;:36;;;37122:6;:42;;;36933:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;37215:10:12;37233:6;37247;37261:9;37278:10;37296;37314:2;37324:8;37182:156;;;;;;;;;;;;;:::i;:::-;;;;;;;;36085:1258;35900:1443;;;;;;;:::o;48445:110::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;48539:10:12::1;::::0;48506:44:::1;::::0;-1:-1:-1;;;48506:44:12;;:13:::1;::::0;:18:::1;::::0;:44:::1;::::0;48525:12;;-1:-1:-1;;;;;48539:10:12;;::::1;::::0;48506:44:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;48445:110:::0;:::o;19981:954::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;20149:40:::1;20171:3;:17;;;20149:21;:40::i;:::-;20148:41;20133:97;;;;-1:-1:-1::0;;;20133:97:12::1;;;;;;;:::i;:::-;20252:41;20274:4;:18;;;20252:21;:41::i;:::-;20251:42;20236:99;;;;-1:-1:-1::0;;;20236:99:12::1;;;;;;;:::i;:::-;20424:10;::::0;20342:245:::1;::::0;-1:-1:-1;;;20342:245:12;;:7:::1;::::0;:29:::1;::::0;:245:::1;::::0;20379:3;;20390:4;;20402:14;;-1:-1:-1;;;;;20424:10:12;;::::1;::::0;;;20464:16:::1;::::0;20488:21:::1;::::0;20517:19:::1;::::0;20544:37:::1;::::0;20342:245;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;20629:17:12::1;::::0;;::::1;::::0;20654:18;;::::1;::::0;20680:30;;20718:31:::1;::::0;::::1;::::0;20757:38:::1;::::0;::::1;::::0;20803:39:::1;::::0;::::1;::::0;20599:331:::1;::::0;-1:-1:-1;20680:30:12::1;20850:14;:24;;;:41;;;;;;;;:::i;:::-;;:74;;20911:13;20850:74;;;20894:14;20850:74;20599:331;;;;;;;;;;;;:::i;28317:304::-:0;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;28443:97:12::1;::::0;-1:-1:-1;;;28443:97:12;;:15:::1;:97;::::0;::::1;65120:25:38::0;-1:-1:-1;;;;;65219:15:38;;;65199:18;;;65192:43;65271:15;;65251:18;;;65244:43;28443:42:12::1;::::0;::::1;::::0;65093:18:38;;28443:97:12::1;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;28552:64;28580:16;28598:17;28552:64;;;;;;;:::i;34591:632::-:0;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;34753:38:::1;34775:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;34753:21;:38::i;:::-;34752:39;34744:73;;;::::0;-1:-1:-1;;;34744:73:12;;65809:2:38;34744:73:12::1;::::0;::::1;65791:21:38::0;65848:2;65828:18;;;65821:30;-1:-1:-1;;;65867:18:38;;;65860:51;65928:18;;34744:73:12::1;65607:345:38::0;34744:73:12::1;34900:10;::::0;34926::::1;::::0;34824:143:::1;::::0;-1:-1:-1;;;34824:143:12;;:35:::1;::::0;::::1;::::0;:143:::1;::::0;:15:::1;::::0;34867:8;;34883:9;;-1:-1:-1;;;;;34900:10:12;;::::1;::::0;34926;;::::1;::::0;34945:16:::1;::::0;34824:143;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;34979:239:12::1;::::0;-1:-1:-1;35012:15:12::1;::::0;-1:-1:-1;;35012:15:12;;;::::1;::::0;::::1;;:::i;48778:299::-:0;48898:10;;48887:37;;;-1:-1:-1;;;48887:37:12;;;;48861:23;;-1:-1:-1;;;;;48898:10:12;;48887:35;;:37;;;;;;;;;;;;;;48898:10;48887:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;48861:63;-1:-1:-1;48938:10:12;-1:-1:-1;;;;;48938:29:12;;;48930:64;;;;-1:-1:-1;;;48930:64:12;;68535:2:38;48930:64:12;;;68517:21:38;68574:2;68554:18;;;68547:30;-1:-1:-1;;;68593:18:38;;;68586:52;68655:18;;48930:64:12;68333:346:38;48930:64:12;-1:-1:-1;;;;;;49008:50:12;;;:42;:50;;;:16;:50;;;;;;;;:64;;;;;;;;;;;49001:71;;-1:-1:-1;;;;;;49001:71:12;;;48778:299::o;41184:636::-;41301:33;41323:10;41301:21;:33::i;:::-;41293:71;;;;-1:-1:-1;;;41293:71:12;;;;;;;:::i;:::-;41584:10;;41475:154;;-1:-1:-1;;;41475:154:12;;:15;41605:16;41475:154;;;69026:25:38;;;;-1:-1:-1;;;;;69125:15:38;;;69105:18;;;69098:43;69177:15;;;69157:18;;;69150:43;41584:10:12;;;69209:18:38;;;69202:43;69261:19;;;69254:35;;;;41379:36:12;;;;41475:35;;;;68998:19:38;;41475:154:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;41641:174;;;41675:10;69979:34:38;;-1:-1:-1;;;;;70049:15:38;;;70044:2;70029:18;;70022:43;70101:15;;70081:18;;;70074:43;-1:-1:-1;;;;;70190:15:38;;;70185:2;70170:18;;70163:43;70243:15;;70237:3;70222:19;;70215:44;41641:174:12;;70190:15:38;;-1:-1:-1;70243:15:38;;-1:-1:-1;41641:174:12;;;;;;69928:3:38;41641:174:12;;;41287:533;;41184:636;;:::o;15221:296::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15402:110:12;:15;15461:16;15487:17;15402:49;:110::i;:::-;15389:123;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;15389:123:12;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;;;;;;-1:-1:-1;;;;;15389:123:12;;;;;;-1:-1:-1;15221:296:12;;;;:::o;44125:317::-;44294:23;;44246:72;;-1:-1:-1;;;44246:72:12;;:19;:72;;;70538:25:38;-1:-1:-1;;;;;70599:32:38;;70579:18;;;70572:60;70648:18;;;70641:34;;;;44186:20:12;;;;44246:40;;;;70511:18:38;;44246:72:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;44330:107;;;44359:10;71231:51:38;;-1:-1:-1;;;;;71318:32:38;;71313:2;71298:18;;71291:60;-1:-1:-1;;;;;71387:31:38;;71367:18;;;71360:59;;;;71450:2;71435:18;;71428:34;;;44185:133:12;;-1:-1:-1;44185:133:12;-1:-1:-1;44330:107:12;;71218:3:38;71203:19;44330:107:12;71001:467:38;13602:268:12;13753:112;;-1:-1:-1;;;13753:112:12;;-1:-1:-1;;;;;71761:15:38;;;13841:16:12;13753:112;;;71743:34:38;;;;71813:15;;;71793:18;;;71786:43;71845:18;;;71838:34;13724:6:12;;13753:13;;:40;;71678:18:38;;13753:112:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;17444:311::-;17554:18;17575:47;:14;17609:12;17575:33;:47::i;:::-;17554:68;-1:-1:-1;;;;;;17637:37:12;;17629:66;;;;-1:-1:-1;;;17629:66:12;;;;;;;:::i;29651:1752::-;30173:10;30160:24;;;;:12;:24;;;;;:31;;;30159:32;30151:58;;;;-1:-1:-1;;;30151:58:12;;;;;;;:::i;:::-;30307:337;;;;;;;;1534:1:5;30307:337:12;;;-1:-1:-1;30307:337:12;;;;;;;;;;;;;30437:10;30307:337;;;;-1:-1:-1;;;;;30307:337:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30625:9;;;;;;;;;;30307:337;;;;;;;30654:10;;30269:453;;-1:-1:-1;;;30269:453:12;;-1:-1:-1;;30269:28:12;;;;:453;;:15;;30307:337;;30654:10;;;;;;1534:1:5;30269:453:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;30269:453:12;;;;;;;;;;;;:::i;:::-;30216:506;;-1:-1:-1;;;;;;;;;;;30753:13:12;;30751:15;;;;;;;;;-1:-1:-1;;;;;30751:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30751:15:12;;;;;-1:-1:-1;;;;;30751:15:12;;;;;30774:10;30792:6;30806;:19;;;30833:6;:27;;;30868:6;:37;;;30913:6;:43;;;30734:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;30993:13:12;;30991:15;;;;;;;;;-1:-1:-1;;;;;30991:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30991:15:12;;;;;-1:-1:-1;;;;;30991:15:12;;;;;31014:10;31032:6;31046;:19;;;31073:6;:27;;;31108:6;:37;;;31153:6;:43;;;30974:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;31248:10:12;31266:6;31280;31294:14;31316;31338:10;31356;31374:2;31384:8;31214:184;;;;;;;;;;;;;;:::i;:::-;;;;;;;;29868:1535;29651:1752;;;;;;;;:::o;42207:292::-;42266:10;42253:24;;;;:12;:24;;;;;:31;;;42252:32;42244:66;;;;-1:-1:-1;;;42244:66:12;;76216:2:38;42244:66:12;;;76198:21:38;76255:2;76235:18;;;76228:30;-1:-1:-1;;;76274:18:38;;;76267:51;76335:18;;42244:66:12;76014:345:38;42244:66:12;42344:74;;;;;;;;42362:4;42344:74;;;;;;42389:23;;42374:12;:38;;;;:::i;:::-;42344:74;;42330:10;42317:24;;;;:12;:24;;;;;;;;:101;;;;-1:-1:-1;;42317:101:12;;;;;;;;;;-1:-1:-1;42317:101:12;;;;;;;42470:23;;42430:64;;42330:10;42455:38;;:12;:38;:::i;:::-;42430:64;;;-1:-1:-1;;;;;76689:32:38;;;76671:51;;76753:2;76738:18;;76731:34;;;;76644:18;42430:64:12;76497:274:38;14241:258:12;14383:111;;-1:-1:-1;;;14383:111:12;;14356:6;;14383:40;;;;:111;;:14;;14433:6;;14449:11;;;;14470:16;;14383:111;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;32301:1749::-;32795:10;32782:24;;;;:12;:24;;;;;:31;;;32781:32;32773:58;;;;-1:-1:-1;;;32773:58:12;;;;;;;:::i;:::-;32929:347;;;;;;;;1534:1:5;32929:347:12;;;-1:-1:-1;32929:347:12;;;;;;;;;;;;;33059:10;32929:347;;;;-1:-1:-1;;;;;32929:347:12;;;;;;;;;;;;;;;;;;;33152:9;32929:347;;;;;;;;;;;;;;;;;;;;;;;;;;;;;33257:9;;;;;;;;;;32929:347;;;;;;;33286:10;;32891:463;;-1:-1:-1;;;32891:463:12;;-1:-1:-1;;32891:28:12;;;;:463;;:15;;32929:347;;33286:10;;;;;;1534:1:5;32891:463:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;32891:463:12;;;;;;;;;;;;:::i;:::-;32838:516;;-1:-1:-1;;;;;;;;;;;33385:13:12;;33383:15;;;;;;;;;-1:-1:-1;;;;;33383:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33383:15:12;;;;;-1:-1:-1;;;;;33383:15:12;;;;;33406:10;33424:5;33437:6;:19;;;33464:6;:27;;;33499:6;:37;;;33544:6;:43;;;33366:227;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33624:13:12;;33622:15;;;;;;;;;-1:-1:-1;;;;;33622:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33622:15:12;;;;;-1:-1:-1;;;;;33622:15:12;;;;;33645:10;33671:3;33683:6;:19;;;33710:6;:27;;;33745:6;:37;;;33790:6;:43;;;33605:234;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33885:10:12;33903:5;33924:3;33936:18;33962:9;33979:14;34001:12;34021:2;34031:8;33851:194;;;;;;;;;;;;;;:::i;21265:705::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;21395:42:::1;21417:5;:19;;;21395:21;:42::i;:::-;21394:43;21379:101;;;::::0;-1:-1:-1;;;21379:101:12;;76978:2:38;21379:101:12::1;::::0;::::1;76960:21:38::0;77017:2;76997:18;;;76990:30;-1:-1:-1;;;77036:18:38;;;77029:57;77103:18;;21379:101:12::1;76776:351:38::0;21379:101:12::1;21549:10;::::0;21487:248:::1;::::0;-1:-1:-1;;;21487:248:12;;:7:::1;::::0;:24:::1;::::0;:248:::1;::::0;21519:5;;21532:9;;-1:-1:-1;;;;;21549:10:12::1;::::0;;;21589:15:::1;::::0;21612:16:::1;::::0;21636:21:::1;::::0;21665:19:::1;::::0;21692:37:::1;::::0;21487:248;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;21772:19:12::1;::::0;;::::1;::::0;21799:25;;21832:26:::1;::::0;::::1;::::0;21866:33:::1;::::0;;::::1;::::0;21907:34:::1;::::0;::::1;::::0;21949:10;;::::1;::::0;21747:218;;::::1;::::0;-1:-1:-1;21747:218:12::1;::::0;21866:33;;21907:34;21949:10;21747:218:::1;:::i;7799:157:1:-:0;7857:6;7894:4;7912:39;7894:4;7919:15;7912:39;:::i;:::-;7905:46;;;7799:157;:::o;7067:714::-;7199:12;;:::i;:::-;7223:45;7237:4;:22;;7223:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7261:6;7223:13;:45::i;:::-;7219:108;;;7285:35;7297:4;:22;;7285:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;:35::i;:::-;7278:42;;;;7219:108;7333:18;;:::i;:::-;7398:1;7361:4;:19;;7381:6;7361:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;:38;7357:296;;;7414:7;7409:238;7431:4;:19;;7451:6;7431:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;7427:38;;;;7409:238;;;7554:13;-1:-1:-1;;;;;7497:70:1;:4;:19;;7517:6;7497:27;;;;;;:::i;:::-;;;;;;;;;;;;;7525:1;7497:30;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:53;;;;;;-1:-1:-1;;;;;7497:53:1;:70;7482:157;;7598:4;:19;;7618:6;7598:27;;;;;;:::i;:::-;;;;;;;;;;;;;7626:1;7598:30;;;;;;;;;;:::i;:::-;;;;;;;;;;7590:38;;;;;;;;7598:30;;;;;;;7590:38;;;;;;;;;-1:-1:-1;;;;;7590:38:1;;;;;;;;;;;;;;;;;;;7598:30;7590:38;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;7590:38:1;;;-1:-1:-1;;7590:38:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7590:38:1;;;;;;;-1:-1:-1;7482:157:1;7467:3;;;;:::i;:::-;;;;7409:238;;;;7357:296;7673:12;;:33;;;;;7689:5;:17;;;7673:33;7658:99;;;;-1:-1:-1;;;7658:99:1;;80612:2:38;7658:99:1;;;80594:21:38;80651:2;80631:18;;;80624:30;80690:34;80670:18;;;80663:62;-1:-1:-1;;;80741:18:38;;;80734:33;80784:19;;7658:99:1;80410:399:38;18462:1043:12;18659:13;;-1:-1:-1;;;18659:13:12;;-1:-1:-1;;;;;18659:13:12;;;:45;;18651:75;;;;-1:-1:-1;;;18651:75:12;;81016:2:38;18651:75:12;;;80998:21:38;81055:2;81035:18;;;81028:30;-1:-1:-1;;;81074:18:38;;;81067:47;81131:18;;18651:75:12;80814:341:38;18651:75:12;-1:-1:-1;;;;;18964:20:12;;;;;;:12;:20;;;;;:27;;;18963:28;18955:54;;;;-1:-1:-1;;;18955:54:12;;;;;;;:::i;:::-;19234:10;;19145:133;;-1:-1:-1;;;19145:133:12;;19024:21;;;;;;19145:10;;:18;;:133;;19173:6;;19189:5;;19204:20;;-1:-1:-1;;;;;19234:10:12;;19254:16;;19145:133;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19285:13;:15;;19016:262;;-1:-1:-1;19016:262:12;;-1:-1:-1;19016:262:12;-1:-1:-1;;;;19285:15:12;;;-1:-1:-1;;;;;19285:15:12;;:13;:15;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;19285:15:12;;;;;-1:-1:-1;;;;;19285:15:12;;;;;;;-1:-1:-1;;;;;;;;;;;19329:13:12;;;;;;;;;-1:-1:-1;;;;;19329:13:12;19350:6;19364:5;:18;;;19390:5;:12;;;19410:14;19432:24;19464:30;19312:188;;;;;;;;;;;;:::i;:::-;;;;;;;;18569:936;;;18462:1043;;;:::o;43554:202::-;-1:-1:-1;;;;;43662:20:12;;43624:4;43662:20;;;:12;:20;;;;;43695:11;;;;:56;;;;-1:-1:-1;43710:25:12;;;43739:12;-1:-1:-1;43710:41:12;;43688:63;-1:-1:-1;;43554:202:12:o;6292:411:1:-;6399:12;;:::i;:::-;-1:-1:-1;;;;;6425:28:1;;6421:91;;6470:35;6482:4;:22;;6470:35;;;;;:::i;:::-;6463:42;;;;6421:91;-1:-1:-1;;;;;6539:34:1;;;6518:18;6539:34;;;;;;;;;;;6518:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:18;;:55;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;6518:55:1;;;-1:-1:-1;;6518:55:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;6518:55:1;;;;;;6594:12;;;;-1:-1:-1;6594:33:1;;;;;6610:5;:17;;;6594:33;6579:100;;;;-1:-1:-1;;;6579:100:1;;82974:2:38;6579:100:1;;;82956:21:38;83013:2;82993:18;;;82986:30;83052:34;83032:18;;;83025:62;-1:-1:-1;;;83103:18:38;;;83096:34;83147:19;;6579:100:1;82772:400:38;14349:315:24;-1:-1:-1;;;;;14547:39:24;;;14506:26;14547:39;;;:21;;;:39;;;;;;;;:58;;;;;;;;;14619:11;;;;14611:48;;;;-1:-1:-1;;;14611:48:24;;83379:2:38;14611:48:24;;;83361:21:38;83418:2;83398:18;;;83391:30;-1:-1:-1;;;83437:18:38;;;83430:54;83501:18;;14611:48:24;83177:348:38;8335:181:1;8426:4;8508:1;8491:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8481:30;;;;;;8474:1;8457:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8447:30;;;;;;:64;8440:71;;8335:181;;;;:::o;8062:179::-;8150:12;;:::i;:::-;-1:-1:-1;8179:57:1;;;;;;;;8185:4;8179:57;;;-1:-1:-1;8179:57:1;;;;;;;;;;;;;8224:2;8179:57;;;;;;;;;;;;;;;;8062:179::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:340:38:-;216:2;198:21;;;255:2;235:18;;;228:30;-1:-1:-1;;;289:2:38;274:18;;267:46;345:2;330:18;;14:340::o;359:139::-;-1:-1:-1;;;;;442:31:38;;432:42;;422:70;;488:1;485;478:12;422:70;359:139;:::o;503:348::-;555:8;565:6;619:3;612:4;604:6;600:17;596:27;586:55;;637:1;634;627:12;586:55;-1:-1:-1;660:20:38;;-1:-1:-1;;;;;692:30:38;;689:50;;;735:1;732;725:12;689:50;772:4;764:6;760:17;748:29;;824:3;817:4;808:6;800;796:19;792:30;789:39;786:59;;;841:1;838;831:12;786:59;503:348;;;;;:::o;856:156::-;922:20;;982:4;971:16;;961:27;;951:55;;1002:1;999;992:12;951:55;856:156;;;:::o;1017:639::-;1119:6;1127;1135;1143;1196:2;1184:9;1175:7;1171:23;1167:32;1164:52;;;1212:1;1209;1202:12;1164:52;1251:9;1238:23;1270:39;1303:5;1270:39;:::i;:::-;1328:5;-1:-1:-1;1384:2:38;1369:18;;1356:32;-1:-1:-1;;;;;1400:30:38;;1397:50;;;1443:1;1440;1433:12;1397:50;1482:59;1533:7;1524:6;1513:9;1509:22;1482:59;:::i;:::-;1560:8;;-1:-1:-1;1456:85:38;-1:-1:-1;1614:36:38;;-1:-1:-1;1646:2:38;1631:18;;1614:36;:::i;:::-;1604:46;;1017:639;;;;;;;:::o;1661:173::-;1738:5;1783:3;1774:6;1769:3;1765:16;1761:26;1758:46;;;1800:1;1797;1790:12;1758:46;-1:-1:-1;1822:6:38;1661:173;-1:-1:-1;1661:173:38:o;1839:559::-;1989:6;1997;2050:3;2038:9;2029:7;2025:23;2021:33;2018:53;;;2067:1;2064;2057:12;2018:53;2094:23;;-1:-1:-1;;;;;2129:30:38;;2126:50;;;2172:1;2169;2162:12;2126:50;2195:22;;2251:3;2233:16;;;2229:26;2226:46;;;2268:1;2265;2258:12;2226:46;2291:2;-1:-1:-1;2312:80:38;2384:7;2379:2;2364:18;;2312:80;:::i;:::-;2302:90;;1839:559;;;;;:::o;2403:142::-;2471:20;;2500:39;2471:20;2500:39;:::i;2550:679::-;2654:6;2662;2670;2678;2686;2694;2747:3;2735:9;2726:7;2722:23;2718:33;2715:53;;;2764:1;2761;2754:12;2715:53;2803:9;2790:23;2822:39;2855:5;2822:39;:::i;:::-;2880:5;-1:-1:-1;2932:2:38;2917:18;;2904:32;;-1:-1:-1;2983:2:38;2968:18;;2955:32;;-1:-1:-1;3034:2:38;3019:18;;3006:32;;-1:-1:-1;3090:3:38;3075:19;;3062:33;3104:41;3062:33;3104:41;:::i;:::-;3164:7;3154:17;;;3218:3;3207:9;3203:19;3190:33;3180:43;;2550:679;;;;;;;;:::o;3234:104::-;-1:-1:-1;;;;;3300:31:38;3288:44;;3234:104::o;3343:203::-;-1:-1:-1;;;;;3507:32:38;;;;3489:51;;3477:2;3462:18;;3343:203::o;3551:160::-;3616:20;;3672:13;;3665:21;3655:32;;3645:60;;3701:1;3698;3691:12;3716:849;3825:6;3833;3841;3849;3857;3865;3918:3;3906:9;3897:7;3893:23;3889:33;3886:53;;;3935:1;3932;3925:12;3886:53;3974:9;3961:23;3993:39;4026:5;3993:39;:::i;:::-;4051:5;-1:-1:-1;4108:2:38;4093:18;;4080:32;4121:41;4080:32;4121:41;:::i;:::-;4181:7;-1:-1:-1;4207:35:38;4238:2;4223:18;;4207:35;:::i;:::-;4197:45;;4289:2;4278:9;4274:18;4261:32;4251:42;;4345:3;4334:9;4330:19;4317:33;4359:41;4392:7;4359:41;:::i;:::-;4419:7;-1:-1:-1;4478:3:38;4463:19;;4450:33;4492:41;4450:33;4492:41;:::i;:::-;4552:7;4542:17;;;3716:849;;;;;;;;:::o;4570:404::-;4638:6;4646;4699:2;4687:9;4678:7;4674:23;4670:32;4667:52;;;4715:1;4712;4705:12;4667:52;4754:9;4741:23;4773:39;4806:5;4773:39;:::i;:::-;4831:5;-1:-1:-1;4888:2:38;4873:18;;4860:32;4901:41;4860:32;4901:41;:::i;:::-;4961:7;4951:17;;;4570:404;;;;;:::o;5161:554::-;5241:6;5249;5257;5310:2;5298:9;5289:7;5285:23;5281:32;5278:52;;;5326:1;5323;5316:12;5278:52;5365:9;5352:23;5384:39;5417:5;5384:39;:::i;:::-;5442:5;-1:-1:-1;5498:2:38;5483:18;;5470:32;-1:-1:-1;;;;;5514:30:38;;5511:50;;;5557:1;5554;5547:12;5511:50;5596:59;5647:7;5638:6;5627:9;5623:22;5596:59;:::i;:::-;5161:554;;5674:8;;-1:-1:-1;5570:85:38;;-1:-1:-1;;;;5161:554:38:o;5720:180::-;5779:6;5832:2;5820:9;5811:7;5807:23;5803:32;5800:52;;;5848:1;5845;5838:12;5800:52;-1:-1:-1;5871:23:38;;5720:180;-1:-1:-1;5720:180:38:o;5905:255::-;5964:6;6017:2;6005:9;5996:7;5992:23;5988:32;5985:52;;;6033:1;6030;6023:12;5985:52;6072:9;6059:23;6091:39;6124:5;6091:39;:::i;6444:127::-;6505:10;6500:3;6496:20;6493:1;6486:31;6536:4;6533:1;6526:15;6560:4;6557:1;6550:15;6576:255;6648:2;6642:9;6690:6;6678:19;;-1:-1:-1;;;;;6712:34:38;;6748:22;;;6709:62;6706:88;;;6774:18;;:::i;:::-;6810:2;6803:22;6576:255;:::o;6836:::-;6908:2;6902:9;6950:6;6938:19;;-1:-1:-1;;;;;6972:34:38;;7008:22;;;6969:62;6966:88;;;7034:18;;:::i;7096:255::-;7168:2;7162:9;7210:6;7198:19;;-1:-1:-1;;;;;7232:34:38;;7268:22;;;7229:62;7226:88;;;7294:18;;:::i;7356:251::-;7428:2;7422:9;7470:2;7458:15;;-1:-1:-1;;;;;7488:34:38;;7524:22;;;7485:62;7482:88;;;7550:18;;:::i;7612:253::-;7684:2;7678:9;7726:4;7714:17;;-1:-1:-1;;;;;7746:34:38;;7782:22;;;7743:62;7740:88;;;7808:18;;:::i;7870:255::-;7942:2;7936:9;7984:6;7972:19;;-1:-1:-1;;;;;8006:34:38;;8042:22;;;8003:62;8000:88;;;8068:18;;:::i;8130:275::-;8201:2;8195:9;8266:2;8247:13;;-1:-1:-1;;8243:27:38;8231:40;;-1:-1:-1;;;;;8286:34:38;;8322:22;;;8283:62;8280:88;;;8348:18;;:::i;:::-;8384:2;8377:22;8130:275;;-1:-1:-1;8130:275:38:o;8410:187::-;8459:4;-1:-1:-1;;;;;8481:30:38;;8478:56;;;8514:18;;:::i;:::-;-1:-1:-1;8580:2:38;8559:15;-1:-1:-1;;8555:29:38;8586:4;8551:40;;8410:187::o;8602:464::-;8645:5;8698:3;8691:4;8683:6;8679:17;8675:27;8665:55;;8716:1;8713;8706:12;8665:55;8752:6;8739:20;8783:49;8799:32;8828:2;8799:32;:::i;:::-;8783:49;:::i;:::-;8857:2;8848:7;8841:19;8903:3;8896:4;8891:2;8883:6;8879:15;8875:26;8872:35;8869:55;;;8920:1;8917;8910:12;8869:55;8985:2;8978:4;8970:6;8966:17;8959:4;8950:7;8946:18;8933:55;9033:1;9008:16;;;9026:4;9004:27;8997:38;;;;9012:7;8602:464;-1:-1:-1;;;8602:464:38:o;9071:390::-;9149:6;9157;9210:2;9198:9;9189:7;9185:23;9181:32;9178:52;;;9226:1;9223;9216:12;9178:52;9253:23;;-1:-1:-1;;;;;9288:30:38;;9285:50;;;9331:1;9328;9321:12;9285:50;9354;9396:7;9387:6;9376:9;9372:22;9354:50;:::i;:::-;9344:60;9451:2;9436:18;;;;9423:32;;-1:-1:-1;;;;9071:390:38:o;9466:155::-;9546:20;;9595:1;9585:12;;9575:40;;9611:1;9608;9601:12;9626:173;9694:20;;-1:-1:-1;;;;;9743:31:38;;9733:42;;9723:70;;9789:1;9786;9779:12;9804:129;-1:-1:-1;;;;;9878:30:38;;9868:41;;9858:69;;9923:1;9920;9913:12;9938:132;10005:20;;10034:30;10005:20;10034:30;:::i;10075:1273::-;10163:6;10216:2;10204:9;10195:7;10191:23;10187:32;10184:52;;;10232:1;10229;10222:12;10184:52;10259:23;;-1:-1:-1;;;;;10331:14:38;;;10328:34;;;10358:1;10355;10348:12;10328:34;10381:22;;;;10437:6;10419:16;;;10415:29;10412:49;;;10457:1;10454;10447:12;10412:49;10483:22;;:::i;:::-;10528:34;10559:2;10528:34;:::i;:::-;10521:5;10514:49;10595:31;10622:2;10618;10614:11;10595:31;:::i;:::-;10590:2;10583:5;10579:14;10572:55;10659:31;10686:2;10682;10678:11;10659:31;:::i;:::-;10654:2;10647:5;10643:14;10636:55;10737:2;10733;10729:11;10716:25;10766:2;10756:8;10753:16;10750:36;;;10782:1;10779;10772:12;10750:36;10818:45;10855:7;10844:8;10840:2;10836:17;10818:45;:::i;:::-;10813:2;10806:5;10802:14;10795:69;;10897:32;10924:3;10920:2;10916:12;10897:32;:::i;:::-;10891:3;10884:5;10880:15;10873:57;10963:31;10989:3;10985:2;10981:12;10963:31;:::i;:::-;10957:3;10950:5;10946:15;10939:56;11028:31;11054:3;11050:2;11046:12;11028:31;:::i;:::-;11022:3;11015:5;11011:15;11004:56;11093:29;11117:3;11113:2;11109:12;11093:29;:::i;:::-;11087:3;11080:5;11076:15;11069:54;11142:3;11191:2;11187;11183:11;11170:25;11220:2;11210:8;11207:16;11204:36;;;11236:1;11233;11226:12;11204:36;11272:45;11309:7;11298:8;11294:2;11290:17;11272:45;:::i;:::-;11256:14;;;11249:69;;;;-1:-1:-1;11260:5:38;10075:1273;-1:-1:-1;;;;;10075:1273:38:o;11927:544::-;12006:6;12014;12022;12075:2;12063:9;12054:7;12050:23;12046:32;12043:52;;;12091:1;12088;12081:12;12043:52;12118:23;;-1:-1:-1;;;;;12153:30:38;;12150:50;;;12196:1;12193;12186:12;12150:50;12235:59;12286:7;12277:6;12266:9;12262:22;12235:59;:::i;:::-;12313:8;;-1:-1:-1;12209:85:38;-1:-1:-1;;12398:2:38;12383:18;;12370:32;12411:30;12370:32;12411:30;:::i;:::-;12460:5;12450:15;;;11927:544;;;;;:::o;12572:258::-;12644:1;12654:113;12668:6;12665:1;12662:13;12654:113;;;12744:11;;;12738:18;12725:11;;;12718:39;12690:2;12683:10;12654:113;;;12785:6;12782:1;12779:13;12776:48;;;12820:1;12811:6;12806:3;12802:16;12795:27;12776:48;;12572:258;;;:::o;12835:::-;12877:3;12915:5;12909:12;12942:6;12937:3;12930:19;12958:63;13014:6;13007:4;13002:3;12998:14;12991:4;12984:5;12980:16;12958:63;:::i;:::-;13075:2;13054:15;-1:-1:-1;;13050:29:38;13041:39;;;;13082:4;13037:50;;12835:258;-1:-1:-1;;12835:258:38:o;13178:102::-;-1:-1:-1;;;;;13243:30:38;13231:43;;13178:102::o;13285:762::-;13462:2;13451:9;13444:21;13521:6;13515:13;13508:21;13501:29;13496:2;13485:9;13481:18;13474:57;13612:1;13608;13603:3;13599:11;13595:19;13589:2;13581:6;13577:15;13571:22;13567:48;13562:2;13551:9;13547:18;13540:76;13425:4;13663:2;13655:6;13651:15;13645:22;13703:4;13698:2;13687:9;13683:18;13676:32;13731:52;13778:3;13767:9;13763:19;13749:12;13731:52;:::i;:::-;13717:66;;13848:4;13842:2;13834:6;13830:15;13824:22;13820:33;13814:3;13803:9;13799:19;13792:62;13923:3;13915:6;13911:16;13905:23;13898:31;13891:39;13885:3;13874:9;13870:19;13863:68;14014:1;14010;14006:2;14002:10;13998:18;13991:3;13983:6;13979:16;13973:23;13969:48;13962:4;13951:9;13947:20;13940:78;14035:6;14027:14;;;13285:762;;;;:::o;14052:150::-;14127:20;;14176:1;14166:12;;14156:40;;14192:1;14189;14182:12;14207:157;14289:20;;14338:1;14328:12;;14318:40;;14354:1;14351;14344:12;14369:1579;14421:5;14469:6;14457:9;14452:3;14448:19;14444:32;14441:52;;;14489:1;14486;14479:12;14441:52;14511:22;;:::i;:::-;14502:31;;14556:27;14573:9;14556:27;:::i;:::-;14549:5;14542:42;14616:38;14650:2;14639:9;14635:18;14616:38;:::i;:::-;14611:2;14604:5;14600:14;14593:62;14687:38;14721:2;14710:9;14706:18;14687:38;:::i;:::-;14682:2;14675:5;14671:14;14664:62;14758:45;14799:2;14788:9;14784:18;14758:45;:::i;:::-;14753:2;14746:5;14742:14;14735:69;14837:51;14883:3;14872:9;14868:19;14837:51;:::i;:::-;14831:3;14824:5;14820:15;14813:76;14922:38;14955:3;14944:9;14940:19;14922:38;:::i;:::-;14916:3;14909:5;14905:15;14898:63;14994:36;15025:3;15014:9;15010:19;14994:36;:::i;:::-;14988:3;14981:5;14977:15;14970:61;15064:38;15097:3;15086:9;15082:19;15064:38;:::i;:::-;15058:3;15051:5;15047:15;15040:63;15122:3;15157:37;15190:2;15179:9;15175:18;15157:37;:::i;:::-;15141:14;;;15134:61;15214:3;15253:18;;;15240:32;-1:-1:-1;;;;;15321:14:38;;;15318:34;;;15348:1;15345;15338:12;15318:34;15384:46;15426:3;15417:6;15406:9;15402:22;15384:46;:::i;:::-;15379:2;15372:5;15368:14;15361:70;15450:3;15440:13;;15485:52;15533:2;15522:9;15518:18;15485:52;:::i;:::-;15480:2;15473:5;15469:14;15462:76;15557:3;15547:13;;15592:52;15640:2;15629:9;15625:18;15592:52;:::i;:::-;15587:2;15580:5;15576:14;15569:76;15664:3;15654:13;;15699:37;15732:2;15721:9;15717:18;15699:37;:::i;:::-;15694:2;15687:5;15683:14;15676:61;15756:3;15746:13;;15812:2;15801:9;15797:18;15784:32;15768:48;;15841:2;15831:8;15828:16;15825:36;;;15857:1;15854;15847:12;15825:36;;15893:48;15937:3;15926:8;15915:9;15911:24;15893:48;:::i;:::-;15888:2;15881:5;15877:14;15870:72;;;14369:1579;;;;:::o;15953:1555::-;16014:5;16062:6;16050:9;16045:3;16041:19;16037:32;16034:52;;;16082:1;16079;16072:12;16034:52;16104:22;;:::i;:::-;16095:31;-1:-1:-1;16149:23:38;;-1:-1:-1;;;;;16221:14:38;;;16218:34;;;16248:1;16245;16238:12;16218:34;16275:46;16317:3;16308:6;16297:9;16293:22;16275:46;:::i;:::-;16268:5;16261:61;16375:2;16364:9;16360:18;16347:32;16331:48;;16404:2;16394:8;16391:16;16388:36;;;16420:1;16417;16410:12;16388:36;;16456:48;16500:3;16489:8;16478:9;16474:24;16456:48;:::i;:::-;16451:2;16444:5;16440:14;16433:72;;16537:38;16571:2;16560:9;16556:18;16537:38;:::i;:::-;16532:2;16525:5;16521:14;16514:62;16608:38;16642:2;16631:9;16627:18;16608:38;:::i;:::-;16603:2;16596:5;16592:14;16585:62;16680:38;16713:3;16702:9;16698:19;16680:38;:::i;:::-;16674:3;16667:5;16663:15;16656:63;16752:38;16785:3;16774:9;16770:19;16752:38;:::i;:::-;16746:3;16739:5;16735:15;16728:63;16824:38;16857:3;16846:9;16842:19;16824:38;:::i;:::-;16818:3;16811:5;16807:15;16800:63;16896:38;16929:3;16918:9;16914:19;16896:38;:::i;:::-;16890:3;16883:5;16879:15;16872:63;16954:3;16989:38;17023:2;17012:9;17008:18;16989:38;:::i;:::-;16973:14;;;16966:62;17047:3;17082:38;17101:18;;;17082:38;:::i;:::-;17066:14;;;17059:62;17140:3;17175:37;17193:18;;;17175:37;:::i;:::-;17159:14;;;17152:61;17232:3;17267:37;17285:18;;;17267:37;:::i;:::-;17251:14;;;17244:61;17324:3;17359:37;17377:18;;;17359:37;:::i;:::-;17343:14;;;17336:61;17416:3;17451:50;17482:18;;;17451:50;:::i;:::-;17435:14;;;17428:74;17439:5;15953:1555;-1:-1:-1;;15953:1555:38:o;17513:1351::-;17569:5;17617:6;17605:9;17600:3;17596:19;17592:32;17589:52;;;17637:1;17634;17627:12;17589:52;17659:22;;:::i;:::-;17650:31;-1:-1:-1;17704:23:38;;-1:-1:-1;;;;;17776:14:38;;;17773:34;;;17803:1;17800;17793:12;17773:34;17830:46;17872:3;17863:6;17852:9;17848:22;17830:46;:::i;:::-;17823:5;17816:61;17930:2;17919:9;17915:18;17902:32;17886:48;;17959:2;17949:8;17946:16;17943:36;;;17975:1;17972;17965:12;17943:36;;18011:48;18055:3;18044:8;18033:9;18029:24;18011:48;:::i;:::-;18006:2;17999:5;17995:14;17988:72;;18092:38;18126:2;18115:9;18111:18;18092:38;:::i;:::-;18087:2;18080:5;18076:14;18069:62;18163:38;18197:2;18186:9;18182:18;18163:38;:::i;:::-;18158:2;18151:5;18147:14;18140:62;18235:38;18268:3;18257:9;18253:19;18235:38;:::i;:::-;18229:3;18222:5;18218:15;18211:63;18307:38;18340:3;18329:9;18325:19;18307:38;:::i;:::-;18301:3;18294:5;18290:15;18283:63;18379:38;18412:3;18401:9;18397:19;18379:38;:::i;:::-;18373:3;18366:5;18362:15;18355:63;18451:38;18484:3;18473:9;18469:19;18451:38;:::i;:::-;18445:3;18438:5;18434:15;18427:63;18509:3;18544:37;18577:2;18566:9;18562:18;18544:37;:::i;:::-;18528:14;;;18521:61;18601:3;18636:37;18654:18;;;18636:37;:::i;:::-;18620:14;;;18613:61;18693:3;18728:37;18746:18;;;18728:37;:::i;:::-;18712:14;;;18705:61;18785:3;18820:37;18838:18;;;18820:37;:::i;18869:1380::-;19024:6;19032;19040;19093:2;19081:9;19072:7;19068:23;19064:32;19061:52;;;19109:1;19106;19099:12;19061:52;19136:23;;-1:-1:-1;;;;;19208:14:38;;;19205:34;;;19235:1;19232;19225:12;19205:34;19258:56;19306:7;19297:6;19286:9;19282:22;19258:56;:::i;:::-;19248:66;;19367:2;19356:9;19352:18;19339:32;19323:48;;19396:2;19386:8;19383:16;19380:36;;;19412:1;19409;19402:12;19380:36;19435:58;19485:7;19474:8;19463:9;19459:24;19435:58;:::i;:::-;19425:68;;19546:2;19535:9;19531:18;19518:32;19502:48;;19575:2;19565:8;19562:16;19559:36;;;19591:1;19588;19581:12;19559:36;19614:24;;;;19672:2;19654:16;;;19650:25;19647:45;;;19688:1;19685;19678:12;19647:45;19714:22;;:::i;:::-;19774:2;19761:16;19802:2;19792:8;19789:16;19786:36;;;19818:1;19815;19808:12;19786:36;19845:60;19897:7;19886:8;19882:2;19878:17;19845:60;:::i;:::-;19838:5;19831:75;;19952:2;19948;19944:11;19931:25;19981:2;19971:8;19968:16;19965:36;;;19997:1;19994;19987:12;19965:36;20033:55;20080:7;20069:8;20065:2;20061:17;20033:55;:::i;:::-;20028:2;20021:5;20017:14;20010:79;;20134:2;20130;20126:11;20113:25;20098:40;;20147:32;20171:7;20147:32;:::i;:::-;20211:7;20206:2;20199:5;20195:14;20188:31;20238:5;20228:15;;;;;18869:1380;;;;;:::o;20907:829::-;21020:6;21028;21036;21044;21052;21060;21068;21121:3;21109:9;21100:7;21096:23;21092:33;21089:53;;;21138:1;21135;21128:12;21089:53;21177:9;21164:23;21196:39;21229:5;21196:39;:::i;:::-;21254:5;-1:-1:-1;21311:2:38;21296:18;;21283:32;21324:41;21283:32;21324:41;:::i;:::-;21384:7;-1:-1:-1;21438:2:38;21423:18;;21410:32;;-1:-1:-1;21489:2:38;21474:18;;21461:32;;-1:-1:-1;21540:3:38;21525:19;;21512:33;;-1:-1:-1;21597:3:38;21582:19;;21569:33;21611:41;21569:33;21611:41;:::i;:::-;21671:7;21661:17;;;21725:3;21714:9;21710:19;21697:33;21687:43;;20907:829;;;;;;;;;;:::o;21741:821::-;21899:6;21907;21915;21968:2;21956:9;21947:7;21943:23;21939:32;21936:52;;;21984:1;21981;21974:12;21936:52;22011:23;;-1:-1:-1;;;;;22083:14:38;;;22080:34;;;22110:1;22107;22100:12;22080:34;22133:56;22181:7;22172:6;22161:9;22157:22;22133:56;:::i;:::-;22123:66;;22242:2;22231:9;22227:18;22214:32;22198:48;;22271:2;22261:8;22258:16;22255:36;;;22287:1;22284;22277:12;22255:36;22310:58;22360:7;22349:8;22338:9;22334:24;22310:58;:::i;:::-;22300:68;;22421:2;22410:9;22406:18;22393:32;22377:48;;22450:2;22440:8;22437:16;22434:36;;;22466:1;22463;22456:12;22434:36;;22489:67;22548:7;22537:8;22526:9;22522:24;22489:67;:::i;:::-;22479:77;;;21741:821;;;;;:::o;22567:560::-;22718:6;22726;22779:3;22767:9;22758:7;22754:23;22750:33;22747:53;;;22796:1;22793;22786:12;22747:53;22823:23;;-1:-1:-1;;;;;22858:30:38;;22855:50;;;22901:1;22898;22891:12;22855:50;22924:22;;22980:3;22962:16;;;22958:26;22955:46;;;22997:1;22994;22987:12;23861:186;23920:6;23973:2;23961:9;23952:7;23948:23;23944:32;23941:52;;;23989:1;23986;23979:12;23941:52;24012:29;24031:9;24012:29;:::i;24052:323::-;24120:6;24128;24181:2;24169:9;24160:7;24156:23;24152:32;24149:52;;;24197:1;24194;24187:12;24149:52;24236:9;24223:23;24255:39;24288:5;24255:39;:::i;:::-;24313:5;24365:2;24350:18;;;;24337:32;;-1:-1:-1;;;24052:323:38:o;24380:898::-;24502:6;24510;24518;24526;24534;24542;24550;24558;24611:3;24599:9;24590:7;24586:23;24582:33;24579:53;;;24628:1;24625;24618:12;24579:53;24667:9;24654:23;24686:39;24719:5;24686:39;:::i;:::-;24744:5;-1:-1:-1;24801:2:38;24786:18;;24773:32;24814:41;24773:32;24814:41;:::i;:::-;24874:7;-1:-1:-1;24928:2:38;24913:18;;24900:32;;-1:-1:-1;24979:2:38;24964:18;;24951:32;;-1:-1:-1;25030:3:38;25015:19;;25002:33;;-1:-1:-1;25082:3:38;25067:19;;25054:33;;-1:-1:-1;25139:3:38;25124:19;;25111:33;25153:41;25111:33;25153:41;:::i;:::-;25213:7;25203:17;;;25267:3;25256:9;25252:19;25239:33;25229:43;;24380:898;;;;;;;;;;;:::o;25518:591::-;25638:6;25646;25699:2;25687:9;25678:7;25674:23;25670:32;25667:52;;;25715:1;25712;25705:12;25667:52;25742:23;;-1:-1:-1;;;;;25814:14:38;;;25811:34;;;25841:1;25838;25831:12;25811:34;25864:56;25912:7;25903:6;25892:9;25888:22;25864:56;:::i;:::-;25854:66;;25973:2;25962:9;25958:18;25945:32;25929:48;;26002:2;25992:8;25989:16;25986:36;;;26018:1;26015;26008:12;25986:36;;26041:62;26095:7;26084:8;26073:9;26069:24;26041:62;:::i;:::-;26031:72;;;25518:591;;;;;:::o;26114:344::-;26316:2;26298:21;;;26355:2;26335:18;;;26328:30;-1:-1:-1;;;26389:2:38;26374:18;;26367:50;26449:2;26434:18;;26114:344::o;26463:267::-;26552:6;26547:3;26540:19;26604:6;26597:5;26590:4;26585:3;26581:14;26568:43;-1:-1:-1;26656:1:38;26631:16;;;26649:4;26627:27;;;26620:38;;;;26712:2;26691:15;;;-1:-1:-1;;26687:29:38;26678:39;;;26674:50;;26463:267::o;26735:543::-;27004:25;;;-1:-1:-1;;;;;27065:32:38;;27060:2;27045:18;;27038:60;27134:3;27129:2;27114:18;;27107:31;;;-1:-1:-1;;27155:63:38;;27198:19;;27190:6;27182;27155:63;:::i;:::-;27147:71;;27266:4;27258:6;27254:17;27249:2;27238:9;27234:18;27227:45;26735:543;;;;;;;;:::o;27283:348::-;27485:2;27467:21;;;27524:2;27504:18;;;27497:30;-1:-1:-1;;;27558:2:38;27543:18;;27536:54;27622:2;27607:18;;27283:348::o;27636:127::-;27697:10;27692:3;27688:20;27685:1;27678:31;27728:4;27725:1;27718:15;27752:4;27749:1;27742:15;27768:131;27867:1;27860:5;27857:12;27847:46;;27873:18;;:::i;27904:166::-;27982:55;28031:5;27982:55;:::i;:::-;28046:18;;27904:166::o;28075:104::-;-1:-1:-1;;;;;28141:31:38;28129:44;;28075:104::o;28184:503::-;28242:5;28249:6;28309:3;28296:17;28395:2;28391:7;28380:8;28364:14;28360:29;28356:43;28336:18;28332:68;28322:96;;28414:1;28411;28404:12;28322:96;28442:33;;28546:4;28533:18;;;-1:-1:-1;28494:21:38;;-1:-1:-1;;;;;;28563:30:38;;28560:50;;;28606:1;28603;28596:12;28560:50;28656:6;28640:14;28636:27;28626:8;28622:42;28619:62;;;28677:1;28674;28667:12;28692:1139;28807:5;28794:19;28822:41;28855:7;28822:41;:::i;:::-;-1:-1:-1;;;;;28922:16:38;;;28910:29;;28987:4;28976:16;;28963:30;;29002:41;28963:30;29002:41;:::i;:::-;29075:16;29068:4;29059:14;;29052:40;29140:4;29129:16;;29116:30;29155:32;29116:30;29155:32;:::i;:::-;-1:-1:-1;;;;;29256:16:38;;;29214:2;29240:14;;29233:40;29321:4;29310:16;;29297:30;;29336:32;29297:30;29336:32;:::i;:::-;29400:16;;;29393:4;29384:14;;29377:40;29465:4;29454:16;;29441:30;;29480:32;29441:30;29480:32;:::i;:::-;29544:16;;;29537:4;29528:14;;29521:40;29609:4;29598:16;;29585:30;;29624:32;29585:30;29624:32;:::i;:::-;29688:16;29681:4;29672:14;;29665:40;29734:35;29763:4;29752:16;;29734:35;:::i;:::-;29778:47;29819:4;29814:3;29810:14;29796:12;29778:47;:::i;29836:2497::-;30382:4;30411:3;30441:6;30430:9;30423:25;30484:2;30479;30468:9;30464:18;30457:30;30496:62;30554:2;30543:9;30539:18;30513:24;30530:6;30513:24;:::i;:::-;13165:4;13154:16;13142:29;;13098:75;30496:62;;30587:47;30630:2;30622:6;30618:15;30587:47;:::i;:::-;30643:77;30715:3;30704:9;30700:19;30686:12;30643:77;:::i;:::-;;30751:37;30782:4;30774:6;30770:17;30751:37;:::i;:::-;30797:55;30847:3;30836:9;30832:19;30816:14;30797:55;:::i;:::-;;30883:37;30914:4;30906:6;30902:17;30883:37;:::i;:::-;30929:55;30979:3;30968:9;30964:19;30948:14;30929:55;:::i;:::-;;31015:37;31046:4;31038:6;31034:17;31015:37;:::i;:::-;31061:55;31111:3;31100:9;31096:19;31080:14;31061:55;:::i;:::-;;31147:37;31178:4;31170:6;31166:17;31147:37;:::i;:::-;31193:55;31243:3;31232:9;31228:19;31212:14;31193:55;:::i;:::-;;31310:4;31302:6;31298:17;31285:31;31279:3;31268:9;31264:19;31257:60;31379:4;31371:6;31367:17;31354:31;31348:3;31337:9;31333:19;31326:60;31448:6;31440;31436:19;31423:33;31417:3;31406:9;31402:19;31395:62;31476:6;31513:35;31544:2;31536:6;31532:15;31513:35;:::i;:::-;31557:55;31607:3;31596:9;31592:19;31576:14;31557:55;:::i;:::-;;31631:6;31699:2;31691:6;31687:15;31674:29;31668:3;31657:9;31653:19;31646:58;31723:6;31774:55;31825:2;31817:6;31813:15;31805:6;31774:55;:::i;:::-;31848:6;31891:2;31885:3;31874:9;31870:19;31863:31;31911:77;31983:3;31972:9;31968:19;31954:12;31938:14;31911:77;:::i;:::-;31903:85;;31997:81;32072:4;32061:9;32057:20;32049:6;31997:81;:::i;:::-;12546:13;;12539:21;32111:18;;;12527:34;32139:46;32181:2;32170:9;32166:18;32158:6;32139:46;:::i;:::-;32194;32236:2;32225:9;32221:18;32213:6;32194:46;:::i;:::-;32276:6;32271:2;32260:9;32256:18;32249:34;;;;;;;32320:6;32314:3;32303:9;32299:19;32292:35;29836:2497;;;;;;;;;;;:::o;32338:245::-;32396:6;32449:2;32437:9;32428:7;32424:23;32420:32;32417:52;;;32465:1;32462;32455:12;32417:52;32504:9;32491:23;32523:30;32547:5;32523:30;:::i;33251:337::-;33453:2;33435:21;;;33492:2;33472:18;;;33465:30;-1:-1:-1;;;33526:2:38;33511:18;;33504:43;33579:2;33564:18;;33251:337::o;33593:1961::-;34008:6;33997:9;33990:25;34051:3;34046:2;34035:9;34031:18;34024:31;34064:52;34111:3;34100:9;34096:19;34087:6;34081:13;13165:4;13154:16;13142:29;;13098:75;34064:52;33971:4;34163:2;34155:6;34151:15;34145:22;34176:77;34248:3;34237:9;34233:19;34219:12;34176:77;:::i;:::-;;34302:4;34294:6;34290:17;34284:24;34317:55;34367:3;34356:9;34352:19;34336:14;34317:55;:::i;:::-;;34421:4;34413:6;34409:17;34403:24;34446:3;34458:54;34508:2;34497:9;34493:18;34477:14;34458:54;:::i;:::-;34561:4;34553:6;34549:17;34543:24;34521:46;;34586:3;34598:54;34648:2;34637:9;34633:18;34617:14;34598:54;:::i;:::-;34701:3;34693:6;34689:16;34683:23;34661:45;;34725:3;34737:54;34787:2;34776:9;34772:18;34756:14;34737:54;:::i;:::-;34828:3;34820:6;34816:16;34810:23;34800:33;;34852:3;34891:2;34886;34875:9;34871:18;34864:30;34931:3;34923:6;34919:16;34913:23;34903:33;;34955:6;34997:2;34992;34981:9;34977:18;34970:30;35055:2;35047:6;35043:15;35037:22;35031:3;35020:9;35016:19;35009:51;35109:2;35101:6;35097:15;35091:22;35069:44;;35122:55;35172:3;35161:9;35157:19;35141:14;35122:55;:::i;:::-;35220:15;;;35214:22;35208:3;35193:19;;35186:51;35274:15;;35268:22;35321:3;35306:19;;35299:31;;;;35268:22;-1:-1:-1;35347:54:38;;-1:-1:-1;;35396:3:38;35381:19;;35268:22;35347:54;:::i;:::-;35339:62;;;35410:48;35452:4;35441:9;35437:20;35429:6;35410:48;:::i;:::-;35489:4;35474:20;;35467:36;;;;35534:4;35519:20;35512:36;33593:1961;;-1:-1:-1;;;33593:1961:38:o;35559:430::-;35613:5;35666:3;35659:4;35651:6;35647:17;35643:27;35633:55;;35684:1;35681;35674:12;35633:55;35713:6;35707:13;35744:49;35760:32;35789:2;35760:32;:::i;35744:49::-;35818:2;35809:7;35802:19;35864:3;35857:4;35852:2;35844:6;35840:15;35836:26;35833:35;35830:55;;;35881:1;35878;35871:12;35830:55;35894:64;35955:2;35948:4;35939:7;35935:18;35928:4;35920:6;35916:17;35894:64;:::i;:::-;35976:7;35559:430;-1:-1:-1;;;;35559:430:38:o;35994:136::-;36072:13;;36094:30;36072:13;36094:30;:::i;36135:1071::-;36253:6;36306:2;36294:9;36285:7;36281:23;36277:32;36274:52;;;36322:1;36319;36312:12;36274:52;36349:16;;-1:-1:-1;;;;;36414:14:38;;;36411:34;;;36441:1;36438;36431:12;36411:34;36464:22;;;;36520:4;36502:16;;;36498:27;36495:47;;;36538:1;36535;36528:12;36495:47;36564:22;;:::i;:::-;36616:2;36610:9;36628:41;36661:7;36628:41;:::i;:::-;36678:22;;36739:2;36731:11;;36725:18;36755:16;;;36752:36;;;36784:1;36781;36774:12;36752:36;36820:56;36868:7;36857:8;36853:2;36849:17;36820:56;:::i;:::-;36815:2;36808:5;36804:14;36797:80;;36915:2;36911;36907:11;36901:18;36886:33;;36928:32;36952:7;36928:32;:::i;:::-;36992:7;36987:2;36980:5;36976:14;36969:31;37038:2;37034;37030:11;37024:18;37009:33;;37051:32;37075:7;37051:32;:::i;:::-;37115:7;37110:2;37103:5;37099:14;37092:31;37170:3;37166:2;37162:12;37156:19;37150:3;37143:5;37139:15;37132:44;37195:5;37185:15;;;;;36135:1071;;;;:::o;37211:127::-;37272:10;37267:3;37263:20;37260:1;37253:31;37303:4;37300:1;37293:15;37327:4;37324:1;37317:15;37343:209;37381:3;-1:-1:-1;;;;;37451:14:38;;;37477:15;;;37474:41;;;37495:18;;:::i;:::-;37544:1;37531:15;;37343:209;-1:-1:-1;;;37343:209:38:o;37557:765::-;-1:-1:-1;;;;;37905:15:38;;;37887:34;;-1:-1:-1;;;;;37995:15:38;;;37990:2;37975:18;;37968:43;38047:15;;37868:2;38027:18;;38020:43;38099:3;38094:2;38079:18;;38072:31;;;37831:4;;37860:18;38120:46;;38146:19;;38138:6;38120:46;:::i;:::-;38203:15;;;38197:3;38182:19;;38175:44;38256:15;;;;38250:3;38235:19;;38228:44;38303:3;38288:19;38281:35;-1:-1:-1;38112:54:38;37557:765;-1:-1:-1;;;;37557:765:38:o;38327:761::-;-1:-1:-1;;;;;38734:15:38;;;38716:34;;38786:15;;;38781:2;38766:18;;38759:43;38838:15;;;38833:2;38818:18;;38811:43;38885:2;38870:18;;38863:34;;;;38928:3;38913:19;;38906:35;;;;38696:3;38957:19;;38950:35;39022:15;;;39016:3;39001:19;;38994:44;39069:3;39054:19;;39047:35;;;;38665:3;38650:19;;38327:761::o;40404:259::-;40474:6;40527:2;40515:9;40506:7;40502:23;40498:32;40495:52;;;40543:1;40540;40533:12;40495:52;40575:9;40569:16;40594:39;40627:5;40594:39;:::i;40668:502::-;40954:25;;;-1:-1:-1;;;;;41053:15:38;;;41048:2;41033:18;;41026:43;41105:15;;41100:2;41085:18;;41078:43;41152:2;41137:18;;41130:34;40941:3;40926:19;;40668:502::o;41175:184::-;41245:6;41298:2;41286:9;41277:7;41273:23;41269:32;41266:52;;;41314:1;41311;41304:12;41266:52;-1:-1:-1;41337:16:38;;41175:184;-1:-1:-1;41175:184:38:o;41364:543::-;41644:25;;;-1:-1:-1;;;;;41705:32:38;;41700:2;41685:18;;41678:60;41774:3;41769:2;41754:18;;41747:31;;;-1:-1:-1;;41795:63:38;;41838:19;;41830:6;41822;41795:63;:::i;:::-;41787:71;;41894:6;41889:2;41878:9;41874:18;41867:34;41364:543;;;;;;;;:::o;43028:339::-;43230:2;43212:21;;;43269:2;43249:18;;;43242:30;-1:-1:-1;;;43303:2:38;43288:18;;43281:45;43358:2;43343:18;;43028:339::o;43372:354::-;43574:2;43556:21;;;43613:2;43593:18;;;43586:30;43652:32;43647:2;43632:18;;43625:60;43717:2;43702:18;;43372:354::o;44090:349::-;44292:2;44274:21;;;44331:2;44311:18;;;44304:30;-1:-1:-1;;;44365:2:38;44350:18;;44343:55;44430:2;44415:18;;44090:349::o;45158:1879::-;45585:3;45574:9;45567:22;45598:78;45671:3;45660:9;45656:19;45647:6;45641:13;45598:78;:::i;:::-;45548:4;45723;45715:6;45711:17;45705:24;45738:53;45786:3;45775:9;45771:19;45757:12;45738:53;:::i;:::-;;45840:4;45832:6;45828:17;45822:24;45865:3;45877:54;45927:2;45916:9;45912:18;45896:14;45877:54;:::i;:::-;45980:4;45968:17;;45962:24;46005:6;46027:18;;;46020:30;45962:24;-1:-1:-1;46073:54:38;46122:3;46107:19;;45962:24;46073:54;:::i;:::-;46059:68;;46176:4;46168:6;46164:17;46158:24;46191:55;46241:3;46230:9;46226:19;46210:14;46191:55;:::i;:::-;;46295:4;46287:6;46283:17;46277:24;46310:54;46359:3;46348:9;46344:19;46328:14;46310:54;:::i;:::-;;46413:3;46405:6;46401:16;46395:23;46427:54;46476:3;46465:9;46461:19;46445:14;46427:54;:::i;:::-;-1:-1:-1;46530:3:38;46518:16;;46512:23;12546:13;12539:21;46591:3;46576:19;;12527:34;46633:15;;46627:22;46690;;;-1:-1:-1;;46686:37:38;46680:3;46665:19;;46658:66;46741:41;46690:22;46627;46741:41;:::i;:::-;46733:49;;;;46791:48;46833:4;46822:9;46818:20;46810:6;46791:48;:::i;:::-;46848;46890:4;46879:9;46875:20;46867:6;46848:48;:::i;:::-;46934:6;46927:4;46916:9;46912:20;46905:36;46979:6;46972:4;46961:9;46957:20;46950:36;47024:6;47017:4;47006:9;47002:20;46995:36;45158:1879;;;;;;;;;:::o;47042:667::-;47148:6;47156;47164;47172;47225:3;47213:9;47204:7;47200:23;47196:33;47193:53;;;47242:1;47239;47232:12;47193:53;47274:9;47268:16;47293:30;47317:5;47293:30;:::i;:::-;47387:2;47372:18;;47366:25;47436:2;47421:18;;47415:25;47342:5;;-1:-1:-1;47366:25:38;-1:-1:-1;47449:41:38;47415:25;47449:41;:::i;:::-;47560:2;47545:18;;47539:25;47509:7;;-1:-1:-1;;;;;;47576:30:38;;47573:50;;;47619:1;47616;47609:12;47573:50;47642:61;47695:7;47686:6;47675:9;47671:22;47642:61;:::i;:::-;47632:71;;;47042:667;;;;;;;:::o;47714:694::-;-1:-1:-1;;;;;48045:15:38;;;48027:34;;48097:15;;48092:2;48077:18;;48070:43;48149:3;48144:2;48129:18;;48122:31;;;47970:4;;48170:46;;48196:19;;48188:6;48170:46;:::i;:::-;-1:-1:-1;;;;;48289:15:38;;;48284:2;48269:18;;48262:43;48342:15;;;;48336:3;48321:19;;48314:44;48389:3;48374:19;48367:35;48162:54;47714:694;-1:-1:-1;;;;47714:694:38:o;48413:344::-;48615:2;48597:21;;;48654:2;48634:18;;;48627:30;-1:-1:-1;;;48688:2:38;48673:18;;48666:50;48748:2;48733:18;;48413:344::o;48762:346::-;48964:2;48946:21;;;49003:2;48983:18;;;48976:30;-1:-1:-1;;;49037:2:38;49022:18;;49015:52;49099:2;49084:18;;48762:346::o;49518:463::-;49763:25;;;-1:-1:-1;;;;;49824:32:38;;49819:2;49804:18;;49797:60;49893:2;49888;49873:18;;49866:30;;;-1:-1:-1;;49913:62:38;;49956:18;;49948:6;49940;49913:62;:::i;:::-;49905:70;49518:463;-1:-1:-1;;;;;;49518:463:38:o;49986:359::-;-1:-1:-1;;;;;50188:32:38;;50170:51;;50257:2;50252;50237:18;;50230:30;;;-1:-1:-1;;50277:62:38;;50320:18;;50312:6;50304;50277:62;:::i;:::-;50269:70;49986:359;-1:-1:-1;;;;;49986:359:38:o;50350:349::-;50552:2;50534:21;;;50591:2;50571:18;;;50564:30;-1:-1:-1;;;50625:2:38;50610:18;;50603:55;50690:2;50675:18;;50350:349::o;50704:350::-;50906:2;50888:21;;;50945:2;50925:18;;;50918:30;-1:-1:-1;;;50979:2:38;50964:18;;50957:56;51045:2;51030:18;;50704:350::o;51059:140::-;51140:1;51133:5;51130:12;51120:46;;51146:18;;:::i;51204:147::-;51292:1;51285:5;51282:12;51272:46;;51298:18;;:::i;51356:1868::-;51464:12;;13165:4;13154:16;13142:29;;51404:3;51432:6;51528:4;51521:5;51517:16;51511:23;51543:48;51585:4;51580:3;51576:14;51562:12;51543:48;:::i;:::-;;51639:4;51632:5;51628:16;51622:23;51654:50;51698:4;51693:3;51689:14;51673;51654:50;:::i;:::-;;51752:4;51745:5;51741:16;51735:23;51767:57;51818:4;51813:3;51809:14;51793;51767:57;:::i;:::-;;51872:4;51865:5;51861:16;51855:23;51887:74;51955:4;51950:3;51946:14;51930;51887:74;:::i;:::-;;52009:4;52002:5;51998:16;51992:23;52024:49;52067:4;52062:3;52058:14;52042;52024:49;:::i;:::-;;52121:4;52114:5;52110:16;52104:23;52136:47;52177:4;52172:3;52168:14;52152;12546:13;12539:21;12527:34;;12476:91;52136:47;;52231:4;52224:5;52220:16;52214:23;52246:49;52289:4;52284:3;52280:14;52264;52246:49;:::i;:::-;;52314:6;52368:2;52361:5;52357:14;52351:21;52381:47;52424:2;52419:3;52415:12;52399:14;52381:47;:::i;:::-;;;52447:6;52501:2;52494:5;52490:14;52484:21;52535:2;52530;52525:3;52521:12;52514:24;52559:47;52602:2;52597:3;52593:12;52577:14;52559:47;:::i;:::-;52547:59;;;;52625:6;52679:2;52672:5;52668:14;52662:21;52692:62;52750:2;52745:3;52741:12;52725:14;52692:62;:::i;:::-;;;52773:6;52828:2;52821:5;52817:14;52811:21;52841:63;52900:2;52895:3;52891:12;52874:15;52841:63;:::i;:::-;;;52923:6;52978:2;52971:5;52967:14;52961:21;52991:48;53035:2;53030:3;53026:12;53009:15;52991:48;:::i;:::-;;;53058:6;53113:2;53106:5;53102:14;53096:21;53157:3;53151:4;53147:14;53142:2;53137:3;53133:12;53126:36;53178:40;53213:4;53196:15;53178:40;:::i;53229:1920::-;53286:3;53314:6;53355:5;53349:12;53382:2;53377:3;53370:15;53406:45;53447:2;53442:3;53438:12;53424;53406:45;:::i;:::-;53394:57;;;53499:4;53492:5;53488:16;53482:23;53547:3;53541:4;53537:14;53530:4;53525:3;53521:14;53514:38;53575:39;53609:4;53593:14;53575:39;:::i;:::-;53561:53;;;53662:4;53655:5;53651:16;53645:23;53677:50;53721:4;53716:3;53712:14;53696;53677:50;:::i;:::-;;53775:4;53768:5;53764:16;53758:23;53790:50;53834:4;53829:3;53825:14;53809;53790:50;:::i;:::-;;53888:4;53881:5;53877:16;53871:23;53903:49;53946:4;53941:3;53937:14;53921;53903:49;:::i;:::-;;54000:4;53993:5;53989:16;53983:23;54015:49;54058:4;54053:3;54049:14;54033;54015:49;:::i;:::-;;54112:4;54105:5;54101:16;54095:23;54127:49;54170:4;54165:3;54161:14;54145;54127:49;:::i;:::-;;54224:4;54217:5;54213:16;54207:23;54239:49;54282:4;54277:3;54273:14;54257;54239:49;:::i;:::-;;54307:6;54361:2;54354:5;54350:14;54344:21;54374:48;54418:2;54413:3;54409:12;54393:14;54374:48;:::i;:::-;;;54441:6;54495:2;54488:5;54484:14;54478:21;54508:48;54552:2;54547:3;54543:12;54527:14;54508:48;:::i;:::-;;;54575:6;54630:2;54623:5;54619:14;54613:21;54643:48;54687:2;54682:3;54678:12;54661:15;54643:48;:::i;:::-;;;54710:6;54765:2;54758:5;54754:14;54748:21;54778:48;54822:2;54817:3;54813:12;54796:15;54778:48;:::i;:::-;;;54845:6;54900:2;54893:5;54889:14;54883:21;54913:48;54957:2;54952:3;54948:12;54931:15;54913:48;:::i;:::-;;;54980:6;55035:2;55028:5;55024:14;55018:21;55048:73;55117:2;55112:3;55108:12;55091:15;55048:73;:::i;:::-;-1:-1:-1;55137:6:38;;53229:1920;-1:-1:-1;;;;53229:1920:38:o;55154:1618::-;55206:3;55234:6;55275:5;55269:12;55302:2;55297:3;55290:15;55326:45;55367:2;55362:3;55358:12;55344;55326:45;:::i;:::-;55314:57;;;55419:4;55412:5;55408:16;55402:23;55467:3;55461:4;55457:14;55450:4;55445:3;55441:14;55434:38;55495:39;55529:4;55513:14;55495:39;:::i;:::-;55481:53;;;55582:4;55575:5;55571:16;55565:23;55597:50;55641:4;55636:3;55632:14;55616;55597:50;:::i;:::-;;55695:4;55688:5;55684:16;55678:23;55710:50;55754:4;55749:3;55745:14;55729;55710:50;:::i;:::-;;55808:4;55801:5;55797:16;55791:23;55823:49;55866:4;55861:3;55857:14;55841;55823:49;:::i;:::-;;55920:4;55913:5;55909:16;55903:23;55935:49;55978:4;55973:3;55969:14;55953;55935:49;:::i;:::-;;56032:4;56025:5;56021:16;56015:23;56047:49;56090:4;56085:3;56081:14;56065;56047:49;:::i;:::-;;56144:4;56137:5;56133:16;56127:23;56159:49;56202:4;56197:3;56193:14;56177;56159:49;:::i;:::-;;56227:6;56281:2;56274:5;56270:14;56264:21;56294:47;56337:2;56332:3;56328:12;56312:14;56294:47;:::i;:::-;;;56360:6;56414:2;56407:5;56403:14;56397:21;56427:47;56470:2;56465:3;56461:12;56445:14;56427:47;:::i;:::-;;;56493:6;56548:2;56541:5;56537:14;56531:21;56561:48;56605:2;56600:3;56596:12;56579:15;56561:48;:::i;:::-;;;56628:6;56683:2;56676:5;56672:14;56666:21;56696:48;56740:2;56735:3;56731:12;56714:15;56696:48;:::i;56777:1763::-;57454:4;57483:3;57513:2;57502:9;57495:21;57539:51;57586:2;57575:9;57571:18;57563:6;57539:51;:::i;:::-;57525:65;;57638:9;57630:6;57626:22;57621:2;57610:9;57606:18;57599:50;57672:39;57704:6;57696;57672:39;:::i;:::-;57658:53;;57759:9;57751:6;57747:22;57742:2;57731:9;57727:18;57720:50;57805:6;57799:13;57836:4;57828:6;57821:20;57864:65;57923:4;57915:6;57911:17;57897:12;57864:65;:::i;:::-;57850:79;;57978:2;57970:6;57966:15;57960:22;58027:6;58019;58015:19;58010:2;58002:6;57998:15;57991:44;58058:51;58102:6;58086:14;58058:51;:::i;:::-;58178:2;58152:15;;;58146:22;-1:-1:-1;;;;;58142:47:38;58125:15;;58118:72;;;;-1:-1:-1;58044:65:38;;-1:-1:-1;58222:48:38;;-1:-1:-1;58264:4:38;58249:20;;58241:6;58222:48;:::i;:::-;58307:6;58301:3;58290:9;58286:19;58279:35;58351:6;58345:3;58334:9;58330:19;58323:35;58395:6;58389:3;58378:9;58374:19;58367:35;58439:6;58433:3;58422:9;58418:19;58411:35;58483:6;58477:3;58466:9;58462:19;58455:35;58527:6;58521:3;58510:9;58506:19;58499:35;56777:1763;;;;;;;;;;;;;:::o;58545:236::-;58584:3;-1:-1:-1;;;;;58650:10:38;;;58680;;;58710:12;;;58702:21;;58699:47;;;58726:18;;:::i;:::-;58762:13;;58545:236;-1:-1:-1;;;;58545:236:38:o;58786:1236::-;-1:-1:-1;;;;;59296:15:38;;;59278:34;;59348:15;;59343:2;59328:18;;59321:43;59228:3;59395:2;59380:18;;59373:30;;;59199:4;;59426:45;59452:18;;;59444:6;59426:45;:::i;:::-;59412:59;;59519:9;59511:6;59507:22;59502:2;59491:9;59487:18;59480:50;59547:33;59573:6;59565;59547:33;:::i;:::-;-1:-1:-1;;;;;59654:15:38;;;59648:3;59633:19;;59626:44;59707:15;;;59701:3;59686:19;;59679:44;59760:15;;;59754:3;59739:19;;59732:44;59813:15;;;59807:3;59792:19;;59785:44;59866:15;;;59860:3;59845:19;;59838:44;59919:15;;59913:3;59898:19;;59891:44;59539:41;-1:-1:-1;59944:72:38;;-1:-1:-1;60011:3:38;59996:19;;59987:7;59944:72;:::i;:::-;58786:1236;;;;;;;;;;;;;;:::o;60557:249::-;60626:6;60679:2;60667:9;60658:7;60654:23;60650:32;60647:52;;;60695:1;60692;60685:12;60647:52;60727:9;60721:16;60746:30;60770:5;60746:30;:::i;62321:312::-;-1:-1:-1;;;;;62559:15:38;;;62541:34;;62611:15;;62606:2;62591:18;;62584:43;62491:2;62476:18;;62321:312::o;62638:1309::-;63271:4;63300:3;63330:2;63319:9;63312:21;63356:51;63403:2;63392:9;63388:18;63380:6;63356:51;:::i;:::-;63342:65;;63455:9;63447:6;63443:22;63438:2;63427:9;63423:18;63416:50;63489:39;63521:6;63513;63489:39;:::i;:::-;63475:53;;63576:9;63568:6;63564:22;63559:2;63548:9;63544:18;63537:50;63604:48;63645:6;63637;63604:48;:::i;:::-;-1:-1:-1;;;;;63688:32:38;;;;63683:2;63668:18;;63661:60;-1:-1:-1;;63752:3:38;63737:19;;63730:35;;;;63708:3;63781:19;;63774:35;;;;63840:3;63825:19;;63818:35;;;;63884:3;63869:19;;63862:35;63928:3;63913:19;;;63906:35;63596:56;62638:1309;-1:-1:-1;;;62638:1309:38:o;63952:927::-;-1:-1:-1;;;;;64335:15:38;;;64317:34;;64387:15;;64382:2;64367:18;;64360:43;64439:3;64434:2;64419:18;;64412:31;;;64260:4;;64466:46;;64492:19;;64484:6;64466:46;:::i;:::-;64560:9;64552:6;64548:22;64543:2;64532:9;64528:18;64521:50;64588:33;64614:6;64606;64588:33;:::i;:::-;-1:-1:-1;;;;;64695:15:38;;;64689:3;64674:19;;64667:44;64748:15;;64742:3;64727:19;;64720:44;64580:41;-1:-1:-1;64773:56:38;;-1:-1:-1;64822:6:38;64773:56;:::i;:::-;64866:6;64860:3;64849:9;64845:19;64838:35;63952:927;;;;;;;;;;:::o;65957:2371::-;66407:4;66436:3;66466:6;66455:9;66448:25;66509:2;66504;66493:9;66489:18;66482:30;66521:62;66579:2;66568:9;66564:18;66538:24;66555:6;66538:24;:::i;66521:62::-;66612:47;66655:2;66647:6;66643:15;66612:47;:::i;:::-;66678:6;66693:76;66765:2;66754:9;66750:18;66736:12;66693:76;:::i;:::-;66800:37;66831:4;66823:6;66819:17;66800:37;:::i;:::-;66778:59;;66846:55;66896:3;66885:9;66881:19;66865:14;66846:55;:::i;:::-;66932:37;66963:4;66955:6;66951:17;66932:37;:::i;:::-;66910:59;;66978:55;67028:3;67017:9;67013:19;66997:14;66978:55;:::i;:::-;67064:37;67095:4;67087:6;67083:17;67064:37;:::i;:::-;67042:59;;67110:55;67160:3;67149:9;67145:19;67129:14;67110:55;:::i;:::-;67196:37;67227:4;67219:6;67215:17;67196:37;:::i;:::-;67174:59;;67242:55;67292:3;67281:9;67277:19;67261:14;67242:55;:::i;:::-;67359:4;67351:6;67347:17;67334:31;67328:3;67317:9;67313:19;67306:60;67428:4;67420:6;67416:17;67403:31;67397:3;67386:9;67382:19;67375:60;67497:6;67489;67485:19;67472:33;67466:3;67455:9;67451:19;67444:62;67525:6;67515:16;;67593:2;67585:6;67581:15;67568:29;67562:3;67551:9;67547:19;67540:58;67617:6;67654:35;67685:2;67677:6;67673:15;67654:35;:::i;:::-;67698:55;67748:3;67737:9;67733:19;67717:14;67698:55;:::i;:::-;;67772:6;67840:2;67832:6;67828:15;67815:29;67809:3;67798:9;67794:19;67787:58;67890:55;67941:2;67933:6;67929:15;67921:6;67890:55;:::i;:::-;67854:91;;67982:2;67976:3;67965:9;67961:19;67954:31;68002:77;68074:3;68063:9;68059:19;68045:12;68029:14;68002:77;:::i;:::-;67994:85;;;68088:81;68163:4;68152:9;68148:20;68140:6;68088:81;:::i;:::-;68178:46;68220:2;68209:9;68205:18;68197:6;68178:46;:::i;:::-;68233;68275:2;68264:9;68260:18;68252:6;68233:46;:::i;:::-;68315:6;68310:2;68299:9;68295:18;68288:34;;;;;;65957:2371;;;;;;;;;:::o;69300:381::-;69377:6;69385;69438:2;69426:9;69417:7;69413:23;69409:32;69406:52;;;69454:1;69451;69444:12;69406:52;69486:9;69480:16;69505:30;69529:5;69505:30;:::i;:::-;69604:2;69589:18;;69583:25;69554:5;;-1:-1:-1;69617:32:38;69583:25;69617:32;:::i;70686:310::-;70764:6;70772;70825:2;70813:9;70804:7;70800:23;70796:32;70793:52;;;70841:1;70838;70831:12;70793:52;70873:9;70867:16;70892:30;70916:5;70892:30;:::i;:::-;70986:2;70971:18;;;;70965:25;70941:5;;70965:25;;-1:-1:-1;;;70686:310:38:o;71883:2065::-;72300:6;72289:9;72282:25;72343:3;72338:2;72327:9;72323:18;72316:31;72356:52;72403:3;72392:9;72388:19;72379:6;72373:13;13165:4;13154:16;13142:29;;13098:75;72356:52;72263:4;72455:2;72447:6;72443:15;72437:22;72468:77;72540:3;72529:9;72525:19;72511:12;72468:77;:::i;:::-;;72594:4;72586:6;72582:17;72576:24;72609:55;72659:3;72648:9;72644:19;72628:14;72609:55;:::i;:::-;;72713:4;72705:6;72701:17;72695:24;72738:3;72750:54;72800:2;72789:9;72785:18;72769:14;72750:54;:::i;:::-;72853:4;72845:6;72841:17;72835:24;72813:46;;72878:3;72890:54;72940:2;72929:9;72925:18;72909:14;72890:54;:::i;:::-;72993:3;72985:6;72981:16;72975:23;72953:45;;73017:3;73029:54;73079:2;73068:9;73064:18;73048:14;73029:54;:::i;:::-;73120:3;73108:16;;73102:23;73144:3;73163:18;;;73156:30;;;;73223:3;73211:16;;73205:23;73247:3;73266:18;;;73259:30;;;;73314:15;;;73308:22;73349:6;73371:18;;;73364:30;;;;73437:15;;;73431:22;73425:3;73410:19;;73403:51;73491:15;;;73485:22;;-1:-1:-1;73349:6:38;73516:55;73566:3;73551:19;;73485:22;73516:55;:::i;:::-;73626:2;73618:6;73614:15;73608:22;73602:3;73591:9;73587:19;73580:51;73680:2;73672:6;73668:15;73662:22;73640:44;;;;;;73721:2;73715:3;73704:9;73700:19;73693:31;;73741:54;73790:3;73779:9;73775:19;73759:14;73741:54;:::i;73953:1226::-;74072:6;74125:2;74113:9;74104:7;74100:23;74096:32;74093:52;;;74141:1;74138;74131:12;74093:52;74168:16;;-1:-1:-1;;;;;74233:14:38;;;74230:34;;;74260:1;74257;74250:12;74230:34;74283:22;;;;74339:6;74321:16;;;74317:29;74314:49;;;74359:1;74356;74349:12;74314:49;74385:22;;:::i;:::-;74438:2;74432:9;74466:2;74456:8;74453:16;74450:36;;;74482:1;74479;74472:12;74450:36;74509:56;74557:7;74546:8;74542:2;74538:17;74509:56;:::i;:::-;74502:5;74495:71;;74598:41;74635:2;74631;74627:11;74598:41;:::i;:::-;74593:2;74586:5;74582:14;74575:65;74672:41;74709:2;74705;74701:11;74672:41;:::i;:::-;74667:2;74660:5;74656:14;74649:65;74760:2;74756;74752:11;74746:18;74741:2;74734:5;74730:14;74723:42;74804:3;74800:2;74796:12;74790:19;74834:2;74824:8;74821:16;74818:36;;;74850:1;74847;74840:12;74818:36;74887:56;74935:7;74924:8;74920:2;74916:17;74887:56;:::i;:::-;74881:3;74874:5;74870:15;74863:81;;74977:42;75014:3;75010:2;75006:12;74977:42;:::i;:::-;74971:3;74964:5;74960:15;74953:67;75053:42;75090:3;75086:2;75082:12;75053:42;:::i;:::-;75047:3;75040:5;75036:15;75029:67;75143:3;75139:2;75135:12;75129:19;75123:3;75116:5;75112:15;75105:44;75168:5;75158:15;;;;;73953:1226;;;;:::o;75184:825::-;-1:-1:-1;;;;;75611:15:38;;;75593:34;;75663:15;;;75658:2;75643:18;;75636:43;75715:15;;;75710:2;75695:18;;75688:43;75762:2;75747:18;;75740:34;;;;75805:3;75790:19;;75783:35;;;;75573:3;75834:19;;75827:35;75893:3;75878:19;;75871:35;75943:15;;;75937:3;75922:19;;75915:44;75990:3;75975:19;;75968:35;;;;75542:3;75527:19;;75184:825::o;76364:128::-;76404:3;76435:1;76431:6;76428:1;76425:13;76422:39;;;76441:18;;:::i;:::-;-1:-1:-1;76477:9:38;;76364:128::o;77132:1190::-;77729:4;77758:3;77788:2;77777:9;77770:21;77814:51;77861:2;77850:9;77846:18;77838:6;77814:51;:::i;:::-;77800:65;;77913:9;77905:6;77901:22;77896:2;77885:9;77881:18;77874:50;77941:43;77977:6;77969;77941:43;:::i;:::-;-1:-1:-1;;;;;78020:32:38;;;;78015:2;78000:18;;77993:60;-1:-1:-1;;78084:2:38;78069:18;;78062:34;;;;78127:3;78112:19;;78105:35;;;;78040:3;78156:19;;78149:35;;;;78215:3;78200:19;;78193:35;78259:3;78244:19;;78237:35;78303:3;78288:19;;;78281:35;77933:51;77132:1190;-1:-1:-1;;77132:1190:38:o;78327:825::-;-1:-1:-1;;;;;78644:32:38;;78626:51;;78713:3;78708:2;78693:18;;78686:31;;;-1:-1:-1;;78740:46:38;;78766:19;;78758:6;78740:46;:::i;:::-;78834:9;78826:6;78822:22;78817:2;78806:9;78802:18;78795:50;78862:33;78888:6;78880;78862:33;:::i;:::-;-1:-1:-1;;;;;78968:15:38;;;78963:2;78948:18;;78941:43;79021:15;;79015:3;79000:19;;78993:44;78854:41;-1:-1:-1;79046:56:38;;-1:-1:-1;79095:6:38;79046:56;:::i;:::-;79139:6;79133:3;79122:9;79118:19;79111:35;78327:825;;;;;;;;;:::o;79157:270::-;79196:7;-1:-1:-1;;;;;79266:10:38;;;79296;;;79329:11;;79322:19;79351:12;;;79343:21;;79318:47;79315:73;;;79368:18;;:::i;:::-;79408:13;;79157:270;-1:-1:-1;;;;79157:270:38:o;79432:380::-;79511:1;79507:12;;;;79554;;;79575:61;;79629:4;79621:6;79617:17;79607:27;;79575:61;79682:2;79674:6;79671:14;79651:18;79648:38;79645:161;;;79728:10;79723:3;79719:20;79716:1;79709:31;79763:4;79760:1;79753:15;79791:4;79788:1;79781:15;79817:276;79948:3;79986:6;79980:13;80002:53;80048:6;80043:3;80036:4;80028:6;80024:17;80002:53;:::i;:::-;80071:16;;;;;79817:276;-1:-1:-1;;79817:276:38:o;80098:127::-;80159:10;80154:3;80150:20;80147:1;80140:31;80190:4;80187:1;80180:15;80214:4;80211:1;80204:15;80230:175;80267:3;80311:4;80304:5;80300:16;80340:4;80331:7;80328:17;80325:43;;;80348:18;;:::i;:::-;80397:1;80384:15;;80230:175;-1:-1:-1;;80230:175:38:o;81160:1160::-;81469:4;81515:1;81511;81506:3;81502:11;81498:19;81556:2;81548:6;81544:15;81533:9;81526:34;81596:3;81591:2;81580:9;81576:18;81569:31;81657:6;81651:13;81644:21;81637:29;81631:3;81620:9;81616:19;81609:58;81733:2;81727;81719:6;81715:15;81709:22;81705:31;81698:4;81687:9;81683:20;81676:61;;81784:4;81776:6;81772:17;81766:24;81827:4;81821:3;81810:9;81806:19;81799:33;81855:52;81902:3;81891:9;81887:19;81873:12;81855:52;:::i;:::-;81966:4;81954:17;;;81948:24;81974:4;81944:35;81938:3;81923:19;;81916:64;82049:4;82037:17;;82031:24;82024:32;82017:40;82011:3;81996:19;;81989:69;82117:3;82105:16;;82099:23;-1:-1:-1;;;;;82095:48:38;82089:3;82074:19;;82067:77;82132:2;82183:20;;82176:36;;;81841:66;;-1:-1:-1;82221:48:38;;-1:-1:-1;82248:20:38;;82240:6;82221:48;:::i;:::-;82307:6;82300:4;82289:9;82285:20;82278:36;81160:1160;;;;;;;;:::o;82325:442::-;82411:6;82419;82427;82480:2;82468:9;82459:7;82455:23;82451:32;82448:52;;;82496:1;82493;82486:12;82448:52;82528:9;82522:16;82547:30;82571:5;82547:30;:::i;:::-;82646:2;82631:18;;82625:25;82596:5;;-1:-1:-1;82659:32:38;82625:25;82659:32;:::i;:::-;82710:7;82700:17;;;82757:2;82746:9;82742:18;82736:25;82726:35;;82325:442;;;;;:::o
Swarm Source
ipfs://bba20503b4dea47ea42298ace14db144e178106234090b5bba685b59f05a50ae
Age | Block | Fee Address | BC Fee Address | Voting Power | Jailed | Incoming |
---|
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.