Polygon Sponsored slots available. Book your slot here!
Contract Overview
My Name Tag:
Not Available, login to update
[ Download CSV Export ]
Latest 25 internal transaction
[ Download CSV Export ]
Contract Name:
Exchange
Compiler Version
v0.8.4+commit.c7e474f2
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.4; import { Address } from './Address.sol'; import { Asset } from './Structs.sol'; import { AssetTransfers } from './AssetTransfers.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { IERC20 } from './Interfaces.sol'; /** * @notice Library helper functions for reading from a registry of asset descriptors indexed by address and symbol */ library AssetRegistry { struct Storage { mapping(address => Asset) assetsByAddress; // Mapping value is array since the same symbol can be re-used for a different address // (usually as a result of a token swap or upgrade) mapping(string => Asset[]) assetsBySymbol; // Blockchain-specific native asset symbol string nativeAssetSymbol; } // Admin // function registerToken( AssetRegistry.Storage storage self, IERC20 tokenAddress, string calldata symbol, uint8 decimals ) external { require(decimals <= 32, 'Token cannot have more than 32 decimals'); require( tokenAddress != IERC20(address(0x0)) && Address.isContract(address(tokenAddress)), 'Invalid token address' ); // The string type does not have a length property so cast to bytes to check for empty string require(bytes(symbol).length > 0, 'Invalid token symbol'); require( !self.assetsByAddress[address(tokenAddress)].isConfirmed, 'Token already finalized' ); self.assetsByAddress[address(tokenAddress)] = Asset({ exists: true, assetAddress: address(tokenAddress), symbol: symbol, decimals: decimals, isConfirmed: false, confirmedTimestampInMs: 0 }); } function confirmTokenRegistration( AssetRegistry.Storage storage self, IERC20 tokenAddress, string calldata symbol, uint8 decimals ) external { Asset memory asset = self.assetsByAddress[address(tokenAddress)]; require(asset.exists, 'Unknown token'); require(!asset.isConfirmed, 'Token already finalized'); require(isStringEqual(asset.symbol, symbol), 'Symbols do not match'); require(asset.decimals == decimals, 'Decimals do not match'); asset.isConfirmed = true; asset.confirmedTimestampInMs = uint64(block.timestamp * 1000); // Block timestamp is in seconds, store ms self.assetsByAddress[address(tokenAddress)] = asset; self.assetsBySymbol[symbol].push(asset); } function addTokenSymbol( AssetRegistry.Storage storage self, IERC20 tokenAddress, string calldata symbol ) external { Asset memory asset = self.assetsByAddress[address(tokenAddress)]; require( asset.exists && asset.isConfirmed, 'Registration of token not finalized' ); require( !isStringEqual(symbol, self.nativeAssetSymbol), 'Symbol reserved for native asset' ); // This will prevent swapping assets for previously existing orders uint64 msInOneSecond = 1000; asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond); self.assetsBySymbol[symbol].push(asset); } function skim(address tokenAddress, address feeWallet) external { require(Address.isContract(tokenAddress), 'Invalid token address'); uint256 balance = IERC20(tokenAddress).balanceOf(address(this)); AssetTransfers.transferTo(payable(feeWallet), tokenAddress, balance); } // Accessors // function loadBalanceInAssetUnitsByAddress( AssetRegistry.Storage storage self, address wallet, address assetAddress, BalanceTracking.Storage storage balanceTracking ) external view returns (uint256) { require(wallet != address(0x0), 'Invalid wallet address'); Asset memory asset = loadAssetByAddress(self, assetAddress); return AssetUnitConversions.pipsToAssetUnits( loadBalanceInPipsFromMigrationSourceIfNeeded( wallet, assetAddress, balanceTracking ), asset.decimals ); } function loadBalanceInAssetUnitsBySymbol( AssetRegistry.Storage storage self, address wallet, string calldata assetSymbol, BalanceTracking.Storage storage balanceTracking ) external view returns (uint256) { require(wallet != address(0x0), 'Invalid wallet address'); Asset memory asset = loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs()); return AssetUnitConversions.pipsToAssetUnits( loadBalanceInPipsFromMigrationSourceIfNeeded( wallet, asset.assetAddress, balanceTracking ), asset.decimals ); } function loadBalanceInPipsByAddress( address wallet, address assetAddress, BalanceTracking.Storage storage balanceTracking ) external view returns (uint64) { require(wallet != address(0x0), 'Invalid wallet address'); return loadBalanceInPipsFromMigrationSourceIfNeeded( wallet, assetAddress, balanceTracking ); } function loadBalanceInPipsBySymbol( Storage storage self, address wallet, string calldata assetSymbol, BalanceTracking.Storage storage balanceTracking ) external view returns (uint64) { require(wallet != address(0x0), 'Invalid wallet address'); address assetAddress = loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs()) .assetAddress; return loadBalanceInPipsFromMigrationSourceIfNeeded( wallet, assetAddress, balanceTracking ); } function loadBalanceInPipsFromMigrationSourceIfNeeded( address wallet, address assetAddress, BalanceTracking.Storage storage balanceTracking ) private view returns (uint64) { BalanceTracking.Balance memory balance = balanceTracking.balancesByWalletAssetPair[wallet][assetAddress]; if ( !balance.isMigrated && address(balanceTracking.migrationSource) != address(0x0) ) { return balanceTracking.migrationSource.loadBalanceInPipsByAddress( wallet, assetAddress ); } return balance.balanceInPips; } /** * @dev Resolves an asset address into corresponding Asset struct * * @param assetAddress Ethereum address of asset */ function loadAssetByAddress(Storage storage self, address assetAddress) internal view returns (Asset memory) { if (assetAddress == address(0x0)) { return getEthAsset(self.nativeAssetSymbol); } Asset memory asset = self.assetsByAddress[assetAddress]; require( asset.exists && asset.isConfirmed, 'No confirmed asset found for address' ); return asset; } /** * @dev Resolves a asset symbol into corresponding Asset struct * * @param symbol Asset symbol, e.g. 'IDEX' * @param timestampInMs Milliseconds since Unix epoch, usually parsed from a UUID v1 order nonce. * Constrains symbol resolution to the asset most recently confirmed prior to timestampInMs. Reverts * if no such asset exists */ function loadAssetBySymbol( Storage storage self, string memory symbol, uint64 timestampInMs ) internal view returns (Asset memory) { if (isStringEqual(self.nativeAssetSymbol, symbol)) { return getEthAsset(self.nativeAssetSymbol); } Asset memory asset; if (self.assetsBySymbol[symbol].length > 0) { for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) { if ( self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs ) { asset = self.assetsBySymbol[symbol][i]; } } } require( asset.exists && asset.isConfirmed, 'No confirmed asset found for symbol' ); return asset; } // Util // function getCurrentTimestampInMs() internal view returns (uint64) { uint64 msInOneSecond = 1000; return uint64(block.timestamp) * msInOneSecond; } /** * @dev ETH is modeled as an always-confirmed Asset struct for programmatic consistency */ function getEthAsset(string memory nativeAssetSymbol) private pure returns (Asset memory) { return Asset(true, address(0x0), nativeAssetSymbol, 18, true, 0); } // See https://solidity.readthedocs.io/en/latest/types.html#bytes-and-strings-as-arrays function isStringEqual(string memory a, string memory b) private pure returns (bool) { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; import { IERC20 } from './Interfaces.sol'; /** * @notice This library provides helper utilities for transfering assets in and out of contracts. * It further validates ERC-20 compliant balance updates in the case of token assets */ library AssetTransfers { /** * @dev Transfers tokens from a wallet into a contract during deposits. `wallet` must already * have called `approve` on the token contract for at least `tokenQuantity`. Note this only * applies to tokens since ETH is sent in the deposit transaction via `msg.value` */ function transferFrom( address wallet, IERC20 tokenAddress, address to, uint256 quantityInAssetUnits ) internal { uint256 balanceBefore = tokenAddress.balanceOf(to); // Because we check for the expected balance change we can safely ignore the return value of transferFrom tokenAddress.transferFrom(wallet, to, quantityInAssetUnits); uint256 balanceAfter = tokenAddress.balanceOf(to); require( balanceAfter - balanceBefore == quantityInAssetUnits, 'Token contract returned transferFrom success without expected balance change' ); } /** * @dev Transfers ETH or token assets from a contract to a wallet when withdrawing or removing liquidity */ function transferTo( address payable walletOrContract, address asset, uint256 quantityInAssetUnits ) internal { if (asset == address(0x0)) { require( walletOrContract.send(quantityInAssetUnits), 'ETH transfer failed' ); } else { uint256 balanceBefore = IERC20(asset).balanceOf(walletOrContract); // Because we check for the expected balance change we can safely ignore the return value of transfer IERC20(asset).transfer(walletOrContract, quantityInAssetUnits); uint256 balanceAfter = IERC20(asset).balanceOf(walletOrContract); require( balanceAfter - balanceBefore == quantityInAssetUnits, 'Token contract returned transfer success without expected balance change' ); } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; /** * @notice Library helpers for converting asset quantities between asset units and pips */ library AssetUnitConversions { function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals) internal pure returns (uint256) { require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals'); // Exponents cannot be negative, so divide or multiply based on exponent signedness if (assetDecimals > 8) { return uint256(quantityInPips) * (uint256(10)**(assetDecimals - 8)); } return uint256(quantityInPips) / (uint256(10)**(8 - assetDecimals)); } function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals) internal pure returns (uint64) { require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals'); uint256 quantityInPips; // Exponents cannot be negative, so divide or multiply based on exponent signedness if (assetDecimals > 8) { quantityInPips = quantityInAssetUnits / (uint256(10)**(assetDecimals - 8)); } else { quantityInPips = quantityInAssetUnits * (uint256(10)**(8 - assetDecimals)); } require(quantityInPips < 2**64, 'Pip quantity overflows uint64'); return uint64(quantityInPips); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { Constants } from './Constants.sol'; import { OrderSide } from './Enums.sol'; import { PoolTradeHelpers } from './PoolTradeHelpers.sol'; import { Asset, HybridTrade, LiquidityAddition, LiquidityChangeExecution, LiquidityRemoval, Order, OrderBookTrade, PoolTrade, Withdrawal } from './Structs.sol'; import { IExchange, ILiquidityProviderToken } from './Interfaces.sol'; library BalanceTracking { using AssetRegistry for AssetRegistry.Storage; using PoolTradeHelpers for PoolTrade; struct Balance { bool isMigrated; uint64 balanceInPips; } struct Storage { mapping(address => mapping(address => Balance)) balancesByWalletAssetPair; // Predecessor Exchange contract from which to lazily migrate balances IExchange migrationSource; } // Depositing // function updateForDeposit( Storage storage self, address wallet, address assetAddress, uint64 quantityInPips ) internal returns (uint64 newBalanceInPips) { Balance storage balance = loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress); balance.balanceInPips += quantityInPips; return balance.balanceInPips; } // Trading // /** * @dev Updates buyer, seller, and fee wallet balances for both assets in trade pair according to * trade parameters */ function updateForOrderBookTrade( Storage storage self, Order memory buy, Order memory sell, OrderBookTrade memory trade, address feeWallet ) internal { Balance storage balance; // Seller gives base asset including fees balance = loadBalanceAndMigrateIfNeeded( self, sell.walletAddress, trade.baseAssetAddress ); balance.balanceInPips -= trade.grossBaseQuantityInPips; // Buyer receives base asset minus fees balance = loadBalanceAndMigrateIfNeeded( self, buy.walletAddress, trade.baseAssetAddress ); balance.balanceInPips += trade.netBaseQuantityInPips; // Buyer gives quote asset including fees balance = loadBalanceAndMigrateIfNeeded( self, buy.walletAddress, trade.quoteAssetAddress ); balance.balanceInPips -= trade.grossQuoteQuantityInPips; // Seller receives quote asset minus fees balance = loadBalanceAndMigrateIfNeeded( self, sell.walletAddress, trade.quoteAssetAddress ); balance.balanceInPips += trade.netQuoteQuantityInPips; // Maker fee to fee wallet balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, trade.makerFeeAssetAddress ); balance.balanceInPips += trade.makerFeeQuantityInPips; // Taker fee to fee wallet balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, trade.takerFeeAssetAddress ); balance.balanceInPips += trade.takerFeeQuantityInPips; } function updateForPoolTrade( Storage storage self, Order memory order, PoolTrade memory poolTrade, address feeWallet ) internal { Balance storage balance; // Debit from order wallet balance = loadBalanceAndMigrateIfNeeded( self, order.walletAddress, poolTrade.getOrderDebitAssetAddress(order.side) ); balance.balanceInPips -= poolTrade.getOrderDebitQuantityInPips(order.side); // Credit to order wallet balance = loadBalanceAndMigrateIfNeeded( self, order.walletAddress, poolTrade.getOrderCreditAssetAddress(order.side) ); balance.balanceInPips += poolTrade.calculateOrderCreditQuantityInPips( order.side ); // Fee wallet receives protocol fee from asset debited from order wallet balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, poolTrade.getOrderDebitAssetAddress(order.side) ); balance.balanceInPips += poolTrade.takerProtocolFeeQuantityInPips; // Fee wallet receives gas fee from asset credited to order wallet balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, poolTrade.getOrderCreditAssetAddress(order.side) ); balance.balanceInPips += poolTrade.takerGasFeeQuantityInPips; // Liquidity pool reserves are updated in LiquidityPoolRegistry } function updateForHybridTradeFees( Storage storage self, HybridTrade memory hybridTrade, address takerWallet, address feeWallet ) internal { Balance storage balance; balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, hybridTrade.orderBookTrade.takerFeeAssetAddress ); balance.balanceInPips += hybridTrade.takerGasFeeQuantityInPips; balance = loadBalanceAndMigrateIfNeeded( self, takerWallet, hybridTrade.orderBookTrade.takerFeeAssetAddress ); balance.balanceInPips -= hybridTrade.takerGasFeeQuantityInPips + hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips; // Liquidity pool reserves are updated in LiquidityPoolRegistry } // Withdrawing // function updateForWithdrawal( Storage storage self, Withdrawal memory withdrawal, address assetAddress, address feeWallet ) internal returns (uint64 newExchangeBalanceInPips) { Balance storage balance; balance = loadBalanceAndMigrateIfNeeded( self, withdrawal.walletAddress, assetAddress ); // Reverts if balance is overdrawn balance.balanceInPips -= withdrawal.grossQuantityInPips; newExchangeBalanceInPips = balance.balanceInPips; if (withdrawal.gasFeeInPips > 0) { balance = loadBalanceAndMigrateIfNeeded(self, feeWallet, assetAddress); balance.balanceInPips += withdrawal.gasFeeInPips; } } // Wallet exits // function updateForExit( Storage storage self, address wallet, address assetAddress ) internal returns (uint64 previousExchangeBalanceInPips) { Balance storage balance; balance = loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress); previousExchangeBalanceInPips = balance.balanceInPips; require(previousExchangeBalanceInPips > 0, 'No balance for asset'); balance.balanceInPips = 0; } // Liquidity pools // function updateForAddLiquidity( Storage storage self, LiquidityAddition memory addition, LiquidityChangeExecution memory execution, address feeWallet, address custodianAddress, ILiquidityProviderToken liquidityProviderToken ) internal returns (uint64 outputLiquidityInPips) { // Base gross debit Balance storage balance = loadBalanceAndMigrateIfNeeded( self, addition.wallet, execution.baseAssetAddress ); balance.balanceInPips -= execution.grossBaseQuantityInPips; // Base fee credit balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, execution.baseAssetAddress ); balance.balanceInPips += execution.grossBaseQuantityInPips - execution.netBaseQuantityInPips; // Quote gross debit balance = loadBalanceAndMigrateIfNeeded( self, addition.wallet, execution.quoteAssetAddress ); balance.balanceInPips -= execution.grossQuoteQuantityInPips; // Quote fee credit balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, execution.quoteAssetAddress ); balance.balanceInPips += execution.grossQuoteQuantityInPips - execution.netQuoteQuantityInPips; // Only add output assets to wallet's balances in the Exchange if Custodian is target if (addition.to == custodianAddress) { balance = loadBalanceAndMigrateIfNeeded( self, addition.wallet, address(liquidityProviderToken) ); balance.balanceInPips += execution.liquidityInPips; } else { outputLiquidityInPips = execution.liquidityInPips; } } function updateForRemoveLiquidity( Storage storage self, LiquidityRemoval memory removal, LiquidityChangeExecution memory execution, address feeWallet, address custodianAddress, ILiquidityProviderToken liquidityProviderToken ) internal returns ( uint64 outputBaseAssetQuantityInPips, uint64 outputQuoteAssetQuantityInPips ) { Balance storage balance; // Base asset updates { // Only add output assets to wallet's balances in the Exchange if Custodian is target if (removal.to == custodianAddress) { // Base net credit balance = loadBalanceAndMigrateIfNeeded( self, removal.wallet, execution.baseAssetAddress ); balance.balanceInPips += execution.netBaseQuantityInPips; } else { outputBaseAssetQuantityInPips = execution.netBaseQuantityInPips; } // Base fee credit balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, execution.baseAssetAddress ); balance.balanceInPips += execution.grossBaseQuantityInPips - execution.netBaseQuantityInPips; } // Quote asset updates { // Only add output assets to wallet's balances in the Exchange if Custodian is target if (removal.to == custodianAddress) { // Quote net credit balance = loadBalanceAndMigrateIfNeeded( self, removal.wallet, execution.quoteAssetAddress ); balance.balanceInPips += execution.netQuoteQuantityInPips; } else { outputQuoteAssetQuantityInPips = execution.netQuoteQuantityInPips; } // Quote fee credit balance = loadBalanceAndMigrateIfNeeded( self, feeWallet, execution.quoteAssetAddress ); balance.balanceInPips += execution.grossQuoteQuantityInPips - execution.netQuoteQuantityInPips; } // Pair token burn { balance = loadBalanceAndMigrateIfNeeded( self, removal.wallet, address(liquidityProviderToken) ); balance.balanceInPips -= execution.liquidityInPips; } } // Helpers // function loadBalanceAndMigrateIfNeeded( Storage storage self, address wallet, address assetAddress ) private returns (Balance storage) { Balance storage balance = self.balancesByWalletAssetPair[wallet][assetAddress]; if (!balance.isMigrated && address(self.migrationSource) != address(0x0)) { balance.balanceInPips = self.migrationSource.loadBalanceInPipsByAddress( wallet, assetAddress ); balance.isMigrated = true; } return balance; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; /** * @dev See GOVERNANCE.md for descriptions of fixed parameters and fees */ library Constants { // 100 basis points/percent * 100 percent/total uint64 public constant basisPointsInTotal = 100 * 100; uint64 public constant depositIndexNotSet = 2**64 - 1; uint8 public constant liquidityProviderTokenDecimals = 18; // 1 week at 3s/block uint256 public constant maxChainPropagationPeriod = (7 * 24 * 60 * 60) / 3; // 20% uint64 public constant maxFeeBasisPoints = 20 * 100; // Pool reserve balance ratio above which price dips below 1 pip and can no longer be represented uint64 public constant maxLiquidityPoolReserveRatio = 10**8; // Pool reserve balance below which prices can no longer be represented with full pip precision uint64 public constant minLiquidityPoolReserveInPips = 10**8; // 2% uint64 public constant maxPoolInputFeeBasisPoints = 2 * 100; // 5% uint64 public constant maxPoolOutputAdjustmentBasisPoints = 5 * 100; // 1% uint64 public constant maxPoolPriceCorrectionBasisPoints = 1 * 100; // To convert integer pips to a fractional price shift decimal left by the pip precision of 8 // decimals places uint64 public constant pipPriceMultiplier = 10**8; uint8 public constant signatureHashVersion = 3; }
// 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.4; 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.4; 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.4; /** * @notice Enums definitions */ // Liquidity pools // enum LiquidityChangeOrigination { OnChain, OffChain } enum LiquidityChangeType { Addition, Removal } enum LiquidityChangeState { NotInitiated, Initiated, Executed } // Order book // enum OrderSelfTradePrevention { // Decrement and cancel dc, // Cancel oldest co, // Cancel newest cn, // Cancel both cb } enum OrderSide { Buy, Sell } enum OrderTimeInForce { // Good until cancelled gtc, // Good until time gtt, // Immediate or cancel ioc, // Fill or kill fok } enum OrderType { Market, Limit, LimitMaker, StopLoss, StopLossLimit, TakeProfit, TakeProfitLimit } // Withdrawals // enum WithdrawalType { BySymbol, ByAddress }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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 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 a */ event LiquidityPoolCreated( address baseAssetAddress, address quoteAssetAddress, address liquidityProviderToken ); /** * @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 `_balanceMigrationSource` 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 * * @dev The Whistler Exchange does not expose its `_depositIndex` making this manual migration * necessary. If this Exchange is not upgraded from Whistler, call this function with * `newDepositIndex` set to 0. This value cannot be changed again once set * * @param newDepositIndex The value of `_depositIndex` currently set on the old Exchange contract */ function setDepositIndex(uint64 newDepositIndex) external onlyAdmin { require( _depositIndex == Constants.depositIndexNotSet, 'Can only be set once' ); require( newDepositIndex != Constants.depositIndexNotSet, 'Invalid deposit index' ); _depositIndex = newDepositIndex; } /*** 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 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 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.4; import { ERC20 } from './ERC20.sol'; contract FaucetToken is ERC20 { string private _name; string private _symbol; uint8 private _decimals; uint256 private _maximumSupply; uint256 private _numTokensReleasedByFaucet; uint256 constant INITIAL_SUPPLY = 10**12; uint256 constant MAX_SUPPLY = 10**15; constructor( string memory name_, string memory symbol_, uint8 decimals_, uint256 numTokensReleasedByFaucet ) ERC20('', '') { _name = name_; _symbol = symbol_; _decimals = decimals_; _numTokensReleasedByFaucet = numTokensReleasedByFaucet * 10**uint256(decimals_); _maximumSupply = MAX_SUPPLY * 10**uint256(decimals_); _mint(msg.sender, INITIAL_SUPPLY * 10**uint256(decimals_)); } function name() public view virtual override returns (string memory) { return _name; } function symbol() public view virtual override returns (string memory) { return _symbol; } function decimals() public view virtual override returns (uint8) { return _decimals; } function faucet(address wallet) public { require(wallet != address(0), 'Invalid wallet'); require(totalSupply() < _maximumSupply, 'Max supply exceeded'); _mint(wallet, _numTokensReleasedByFaucet); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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.4; import { ECDSA } from './ECDSA.sol'; import { Constants } from './Constants.sol'; import { LiquidityChangeType, WithdrawalType } from './Enums.sol'; import { LiquidityAddition, LiquidityRemoval, Order, Withdrawal } from './Structs.sol'; /** * @notice Library helpers for building hashes and verifying wallet signatures */ library Hashing { function isSignatureValid( bytes32 hash, bytes memory signature, address signer ) internal pure returns (bool) { return ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer; } // Hash construction // function getLiquidityAdditionHash(LiquidityAddition memory addition) internal pure returns (bytes32) { require( addition.signatureHashVersion == Constants.signatureHashVersion, 'Signature hash version invalid' ); return keccak256( abi.encodePacked( addition.signatureHashVersion, uint8(LiquidityChangeType.Addition), uint8(addition.origination), addition.nonce, addition.wallet, addition.assetA, addition.assetB, addition.amountADesired, addition.amountBDesired, addition.amountAMin, addition.amountBMin, addition.to, addition.deadline ) ); } function getLiquidityRemovalHash(LiquidityRemoval memory removal) internal pure returns (bytes32) { require( removal.signatureHashVersion == Constants.signatureHashVersion, 'Signature hash version invalid' ); return keccak256( abi.encodePacked( removal.signatureHashVersion, uint8(LiquidityChangeType.Removal), uint8(removal.origination), removal.nonce, removal.wallet, removal.assetA, removal.assetB, removal.liquidity, removal.amountAMin, removal.amountBMin, removal.to, removal.deadline ) ); } /** * @dev As a gas optimization, base and quote symbols are passed in separately and combined to * verify the wallet hash, since this is cheaper than splitting the market symbol into its two * constituent asset symbols */ function getOrderHash( Order memory order, string memory baseSymbol, string memory quoteSymbol ) internal pure returns (bytes32) { require( order.signatureHashVersion == Constants.signatureHashVersion, 'Signature hash version invalid' ); // Placing all the fields in a single `abi.encodePacked` call causes a `stack too deep` error return keccak256( abi.encodePacked( abi.encodePacked( order.signatureHashVersion, order.nonce, order.walletAddress, string(abi.encodePacked(baseSymbol, '-', quoteSymbol)), uint8(order.orderType), uint8(order.side), // Ledger qtys and prices are in pip, but order was signed by wallet owner with decimal // values pipToDecimal(order.quantityInPips) ), abi.encodePacked( order.isQuantityInQuote, order.limitPriceInPips > 0 ? pipToDecimal(order.limitPriceInPips) : '', order.stopPriceInPips > 0 ? pipToDecimal(order.stopPriceInPips) : '', order.clientOrderId, uint8(order.timeInForce), uint8(order.selfTradePrevention), order.cancelAfter ) ) ); } function getWithdrawalHash(Withdrawal memory withdrawal) internal pure returns (bytes32) { return keccak256( abi.encodePacked( withdrawal.nonce, withdrawal.walletAddress, // Ternary branches must resolve to the same type, so wrap in idempotent encodePacked withdrawal.withdrawalType == WithdrawalType.BySymbol ? abi.encodePacked(withdrawal.assetSymbol) : abi.encodePacked(withdrawal.assetAddress), pipToDecimal(withdrawal.grossQuantityInPips), withdrawal.autoDispatchEnabled ) ); } /** * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string * originally signed by the wallet. For example, 1234567890 becomes '12.34567890' */ function pipToDecimal(uint256 pips) private pure returns (string memory) { // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062 uint256 copy = pips; uint256 length; while (copy != 0) { length++; copy /= 10; } if (length < 9) { length = 9; // a zero before the decimal point plus 8 decimals } length++; // for the decimal point bytes memory decimal = new bytes(length); for (uint256 i = length; i > 0; i--) { if (length - i == 8) { decimal[i - 1] = bytes1(uint8(46)); // period } else { decimal[i - 1] = bytes1(uint8(48 + (pips % 10))); pips /= 10; } } return string(decimal); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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.4; 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.4; import { Order, OrderBookTrade, Withdrawal } from './Structs.sol'; /** * @notice Interface of the ERC20 standard as defined in the EIP, but with no return values for * transfer and transferFrom. By asserting expected balance changes when calling these two methods * we can safely ignore their return values. This allows support of non-compliant tokens that do not * return a boolean. See https://github.com/ethereum/solidity/issues/4116 */ interface IERC20 { /** * @notice Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @notice Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @notice Moves `amount` tokens from the caller's account to `recipient`. * * Most implementing contracts return a boolean value indicating whether the operation succeeded, but * we ignore this and rely on asserting balance changes instead * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external; /** * @notice Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @notice Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @notice Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Most implementing contracts return a boolean value indicating whether the operation succeeded, but * we ignore this and rely on asserting balance changes instead * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external; /** * @notice Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @notice Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @notice Interface to Custodian contract. Used by Exchange and Governance contracts for internal * delegate calls */ interface ICustodian { /** * @notice ETH can only be sent by the Exchange */ receive() external payable; /** * @notice Withdraw any asset and amount to a target wallet * * @dev No balance checking performed * * @param wallet The wallet to which assets will be returned * @param asset The address of the asset to withdraw (native asset or ERC-20 contract) * @param quantityInAssetUnits The quantity in asset units to withdraw */ function withdraw( address payable wallet, address asset, uint256 quantityInAssetUnits ) external; /** * @notice Load address of the currently whitelisted Exchange contract * * @return The address of the currently whitelisted Exchange contract */ function loadExchange() external view returns (address); /** * @notice Sets a new Exchange contract address * * @param newExchange The address of the new whitelisted Exchange contract */ function setExchange(address newExchange) external; /** * @notice Load address of the currently whitelisted Governance contract * * @return The address of the currently whitelisted Governance contract */ function loadGovernance() external view returns (address); /** * @notice Sets a new Governance contract address * * @param newGovernance The address of the new whitelisted Governance contract */ function setGovernance(address newGovernance) external; } /** * @notice Interface to Whistler Exchange contract * * @dev Used for lazy balance migrations from old to new Exchange after upgrade */ interface IExchange { /** * @notice Load a wallet's balance by asset address, in pips * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetAddress The asset address to load the wallet's balance for * * @return The quantity denominated in pips of asset at `assetAddress` currently deposited by `wallet` */ function loadBalanceInPipsByAddress(address wallet, address assetAddress) external view returns (uint64); /** * @notice Load the address of the Custodian contract * * @return The address of the Custodian contract */ function loadCustodian() external view returns (ICustodian); } interface ILiquidityProviderToken { function custodian() external returns (ICustodian); function baseAssetAddress() external returns (address); function quoteAssetAddress() external returns (address); function baseAssetSymbol() external returns (string memory); function quoteAssetSymbol() external returns (string memory); function token0() external returns (address); function token1() external returns (address); function burn( address wallet, uint256 liquidity, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits, address to ) external; function mint( address wallet, uint256 liquidity, uint256 baseAssetQuantityInAssetUnits, uint256 quoteAssetQuantityInAssetUnits, address to ) external; function reverseAssets() external; } interface IWETH9 is IERC20 { receive() external payable; function deposit() external payable; function withdraw(uint256 wad) external; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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.4; 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, uint256 expectedQuoteAssetQuantityInPips ) = pool.calculateOutputAssetQuantitiesInPips(execution.liquidityInPips); require( execution.grossBaseQuantityInPips == expectedBaseAssetQuantityInPips, 'Invalid base quantity' ); require( execution.grossQuoteQuantityInPips == expectedQuoteAssetQuantityInPips, 'Invalid quote 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.4; 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, ILiquidityProviderToken, IWETH9 } from './Interfaces.sol'; import { Asset, LiquidityMigration, LiquidityPool } from './Structs.sol'; library LiquidityPoolAdmin { using AssetRegistry for AssetRegistry.Storage; function createLiquidityPool( LiquidityPools.Storage storage self, address baseAssetAddress, address quoteAssetAddress, AssetRegistry.Storage storage assetRegistry ) public returns (address liquidityProviderToken) { { return address( createLiquidityPoolByAssetAddresses( self, baseAssetAddress, quoteAssetAddress, assetRegistry ) .liquidityProviderToken ); } } 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.4; 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 */ 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 outputQuoteAssetQuantityInPips = self.quoteAssetReserveInPips - Math.multiplyPipsByFraction( self.baseAssetReserveInPips - outputBaseAssetQuantityInPips, calculateCurrentPoolPriceInPips(self), Constants.pipPriceMultiplier, true ); } /** * @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.4; 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 ); // Debit pool reserves pool.baseAssetReserveInPips -= execution.grossBaseQuantityInPips; pool.quoteAssetReserveInPips -= execution.grossQuoteQuantityInPips; 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 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.4; 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.4; library Math { function multiplyPipsByFraction( uint64 multiplicand, uint64 fractionDividend, uint64 fractionDivisor ) internal pure returns (uint64) { return multiplyPipsByFraction( multiplicand, fractionDividend, fractionDivisor, false ); } function multiplyPipsByFraction( uint64 multiplicand, uint64 fractionDividend, uint64 fractionDivisor, bool roundUp ) internal pure returns (uint64) { uint256 dividend = uint256(multiplicand) * fractionDividend; uint256 result = dividend / fractionDivisor; if (roundUp && dividend % fractionDivisor > 0) { result += 1; } require(result < 2**64, 'Pip quantity overflows uint64'); return uint64(result); } function min(uint64 x, uint64 y) internal pure returns (uint64 z) { z = x < y ? x : y; } function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } }
// SPDX-License-Identifier: LGPL-3.0-only // IGNORE This is generated by Truffle // https://www.trufflesuite.com/docs/truffle/getting-started/running-migrations#initial-migration pragma solidity 0.8.4; contract Migrations { address public owner; uint256 public last_completed_migration; constructor() { owner = msg.sender; } modifier restricted() { if (msg.sender == owner) _; } function setCompleted(uint256 completed) public restricted { last_completed_migration = completed; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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.4; 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.4; /** * @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.4; import { OrderSide } from './Enums.sol'; import { PoolTrade } from './Structs.sol'; library PoolTradeHelpers { /** * @dev Address of asset order wallet is receiving from pool */ function getOrderCreditAssetAddress( PoolTrade memory self, OrderSide orderSide ) internal pure returns (address) { return orderSide == OrderSide.Buy ? self.baseAssetAddress : self.quoteAssetAddress; } /** * @dev Address of asset order wallet is giving to pool */ function getOrderDebitAssetAddress(PoolTrade memory self, OrderSide orderSide) internal pure returns (address) { return orderSide == OrderSide.Buy ? self.quoteAssetAddress : self.baseAssetAddress; } /** * @dev Quantity in pips of asset that order wallet is receiving from pool */ function calculateOrderCreditQuantityInPips( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return ( orderSide == OrderSide.Buy ? self.netBaseQuantityInPips : self.netQuoteQuantityInPips ) - self.takerGasFeeQuantityInPips; } /** * @dev Quantity in pips of asset that order wallet is giving to pool */ function getOrderDebitQuantityInPips( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return orderSide == OrderSide.Buy ? self.grossQuoteQuantityInPips : self.grossBaseQuantityInPips; } /** * @dev Quantity in pips of asset that pool receives from order wallet */ function calculatePoolCreditQuantityInPips( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return ( orderSide == OrderSide.Buy ? self.netQuoteQuantityInPips : self.netBaseQuantityInPips ) + self.takerPoolFeeQuantityInPips; } /** * @dev Quantity in pips of asset that leaves pool as output */ function getPoolDebitQuantityInPips( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return ( orderSide == OrderSide.Buy ? self.netBaseQuantityInPips // Pool gives net base asset plus taker gas fee : self.netQuoteQuantityInPips // Pool gives net quote asset plus taker gas fee ); } /** * @dev Gross quantity received by order wallet */ function getOrderGrossReceivedQuantityInPips( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return orderSide == OrderSide.Buy ? self.grossBaseQuantityInPips : self.grossQuoteQuantityInPips; } function calculatePoolOutputAdjustment( PoolTrade memory self, OrderSide orderSide ) internal pure returns (uint64) { return getOrderGrossReceivedQuantityInPips(self, orderSide) - getPoolDebitQuantityInPips(self, orderSide); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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: LGPL-3.0-only pragma solidity 0.8.4; import { ILiquidityProviderToken, IWETH9 } from './Interfaces.sol'; import { LiquidityChangeOrigination, OrderSelfTradePrevention, OrderSide, OrderTimeInForce, OrderType, WithdrawalType } from './Enums.sol'; /** * @notice Struct definitions */ /** * @notice State tracking for a hybrid liquidity pool * * @dev Base and quote asset decimals are denormalized here to avoid extra loads from * `AssetRegistry.Storage` */ struct LiquidityPool { // Flag to distinguish from empty struct bool exists; uint64 baseAssetReserveInPips; uint8 baseAssetDecimals; uint64 quoteAssetReserveInPips; uint8 quoteAssetDecimals; ILiquidityProviderToken liquidityProviderToken; } /** * @dev Internal struct capturing user-initiated liquidity addition request parameters */ struct LiquidityAddition { // Must equal `Constants.signatureHashVersion` uint8 signatureHashVersion; // Distinguishes between liquidity additions initated on- or off- chain LiquidityChangeOrigination origination; // UUIDv1 unique to wallet uint128 nonce; address wallet; address assetA; address assetB; uint256 amountADesired; uint256 amountBDesired; uint256 amountAMin; uint256 amountBMin; address to; uint256 deadline; bytes signature; } /** * @notice Internally used struct, return type from `LiquidityPools.addLiquidity` */ struct LiquidityAdditionDepositResult { string assetASymbol; uint64 assetAQuantityInPips; uint64 assetANewExchangeBalanceInPips; uint256 assetANewExchangeBalanceInAssetUnits; string assetBSymbol; uint64 assetBQuantityInPips; uint64 assetBNewExchangeBalanceInPips; uint256 assetBNewExchangeBalanceInAssetUnits; } /** * @notice Internally used struct, return type from `LiquidityPools.removeLiquidity` */ struct LiquidityRemovalDepositResult { address assetAddress; string assetSymbol; uint64 assetQuantityInPips; uint64 assetNewExchangeBalanceInPips; uint256 assetNewExchangeBalanceInAssetUnits; } /** * @dev Internal struct capturing user-initiated liquidity removal request parameters */ struct LiquidityRemoval { // Must equal `Constants.signatureHashVersion` uint8 signatureHashVersion; // Distinguishes between liquidity additions initated on- or off- chain LiquidityChangeOrigination origination; uint128 nonce; address wallet; address assetA; address assetB; uint256 liquidity; uint256 amountAMin; uint256 amountBMin; address payable to; uint256 deadline; bytes signature; } /** * @notice Argument type to `Exchange.executeAddLiquidity` and `Exchange.executeRemoveLiquidity` */ struct LiquidityChangeExecution { address baseAssetAddress; address quoteAssetAddress; uint64 liquidityInPips; // Gross amount including fees of base asset executed uint64 grossBaseQuantityInPips; // Gross amount including fees of quote asset executed uint64 grossQuoteQuantityInPips; // Net amount of base asset sent to pool for additions or received by wallet for removals uint64 netBaseQuantityInPips; // Net amount of quote asset sent to pool for additions or received by wallet for removals uint64 netQuoteQuantityInPips; } /** * @notice Internally used struct, argument type to `LiquidityPoolAdmin.migrateLiquidityPool` */ struct LiquidityMigration { address token0; address token1; bool isToken1Quote; uint256 desiredLiquidity; address to; IWETH9 WETH; } /** * @notice Internally used struct capturing wallet order nonce invalidations created via `invalidateOrderNonce` */ struct NonceInvalidation { bool exists; uint64 timestampInMs; uint256 effectiveBlockNumber; } /** * @notice Return type for `Exchange.loadAssetBySymbol`, and `Exchange.loadAssetByAddress`; also * used internally by `AssetRegistry` */ struct Asset { // Flag to distinguish from empty struct bool exists; // The asset's address address assetAddress; // The asset's symbol string symbol; // The asset's decimal precision uint8 decimals; // Flag set when asset registration confirmed. Asset deposits, trades, or withdrawals only // allowed if true bool isConfirmed; // Timestamp as ms since Unix epoch when isConfirmed was asserted uint64 confirmedTimestampInMs; } /** * @notice Argument type for `Exchange.executeOrderBookTrade` and `Hashing.getOrderWalletHash` */ struct Order { // Must equal `Constants.signatureHashVersion` uint8 signatureHashVersion; // UUIDv1 unique to wallet uint128 nonce; // Wallet address that placed order and signed hash address walletAddress; // Type of order OrderType orderType; // Order side wallet is on OrderSide side; // Order quantity in base or quote asset terms depending on isQuantityInQuote flag uint64 quantityInPips; // Is quantityInPips in quote terms bool isQuantityInQuote; // For limit orders, price in decimal pips * 10^8 in quote terms uint64 limitPriceInPips; // For stop orders, stop loss or take profit price in decimal pips * 10^8 in quote terms uint64 stopPriceInPips; // Optional custom client order ID string clientOrderId; // TIF option specified by wallet for order OrderTimeInForce timeInForce; // STP behavior specified by wallet for order OrderSelfTradePrevention selfTradePrevention; // Cancellation time specified by wallet for GTT TIF order uint64 cancelAfter; // The ECDSA signature of the order hash as produced by Hashing.getOrderWalletHash bytes walletSignature; } /** * @notice Argument type for `Exchange.executeOrderBookTrade` specifying execution parameters for matching orders */ struct OrderBookTrade { // Base asset symbol string baseAssetSymbol; // Quote asset symbol string quoteAssetSymbol; // Base asset address address baseAssetAddress; // Quote asset address address quoteAssetAddress; // Gross amount including fees of base asset executed uint64 grossBaseQuantityInPips; // Gross amount including fees of quote asset executed uint64 grossQuoteQuantityInPips; // Net amount of base asset received by buy side wallet after fees uint64 netBaseQuantityInPips; // Net amount of quote asset received by sell side wallet after fees uint64 netQuoteQuantityInPips; // Asset address for liquidity maker's fee address makerFeeAssetAddress; // Asset address for liquidity taker's fee address takerFeeAssetAddress; // Fee paid by liquidity maker uint64 makerFeeQuantityInPips; // Fee paid by liquidity taker, inclusive of gas fees uint64 takerFeeQuantityInPips; // Execution price of trade in decimal pips * 10^8 in quote terms uint64 priceInPips; // Which side of the order (buy or sell) the liquidity maker was on OrderSide makerSide; } /** * @notice Argument type for `Exchange.executePoolTrade` specifying execution parameters for an * order against pool liquidity */ struct PoolTrade { // Base asset symbol string baseAssetSymbol; // Quote asset symbol string quoteAssetSymbol; // Base asset address address baseAssetAddress; // Quote asset address address quoteAssetAddress; // Gross amount including fees of base asset executed uint64 grossBaseQuantityInPips; // Gross amount including fees of quote asset executed uint64 grossQuoteQuantityInPips; // If wallet is buy side, net amount of quote input to pool used to calculate output; otherwise, // net amount of base asset leaving pool uint64 netBaseQuantityInPips; // If wallet is buy side, net amount of base input to pool used to calculate output; otherwise, // net amount of quote asset leaving pool uint64 netQuoteQuantityInPips; // Fee paid by liquidity taker to pool from sent asset uint64 takerPoolFeeQuantityInPips; // Fee paid by liquidity taker to fee wallet from sent asset uint64 takerProtocolFeeQuantityInPips; // Fee paid by liquidity taker to fee wallet from received asset uint64 takerGasFeeQuantityInPips; // Fee paid by liquidity taker sell to pool taken from pool's quote asset output uint64 takerPriceCorrectionFeeQuantityInPips; } struct HybridTrade { OrderBookTrade orderBookTrade; PoolTrade poolTrade; // Fee paid by liquidity taker to fee wallet from received asset uint64 takerGasFeeQuantityInPips; } /** * @notice Argument type for `Exchange.withdraw` and `Hashing.getWithdrawalWalletHash` */ struct Withdrawal { // Distinguishes between withdrawals by asset symbol or address WithdrawalType withdrawalType; // UUIDv1 unique to wallet uint128 nonce; // Address of wallet to which funds will be returned address payable walletAddress; // Asset symbol string assetSymbol; // Asset address address assetAddress; // Used when assetSymbol not specified // Withdrawal quantity uint64 grossQuantityInPips; // Gas fee deducted from withdrawn quantity to cover dispatcher tx costs uint64 gasFeeInPips; // Not currently used but reserved for future use. Must be true bool autoDispatchEnabled; // The ECDSA signature of the withdrawal hash as produced by Hashing.getWithdrawalWalletHash bytes walletSignature; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; 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.4; /** * Library helper for extracting timestamp component of Version 1 UUIDs */ library UUID { /** * Extracts the timestamp component of a Version 1 UUID. Used to make time-based assertions * against a wallet-privided nonce */ function getTimestampInMsFromUuidV1(uint128 uuid) internal pure returns (uint64 msSinceUnixEpoch) { // https://tools.ietf.org/html/rfc4122#section-4.1.2 uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F; require(version == 1, 'Must be v1 UUID'); // Time components are in reverse order so shift+mask each to reassemble uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000; uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000; uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF; uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow); // Gregorian offset given in seconds by https://www.wolframalpha.com/input/?i=convert+1582-10-15+UTC+to+unix+time msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000) - 12219292800000; return msSinceUnixEpoch; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { Constants } from './Constants.sol'; import { Hashing } from './Hashing.sol'; import { IERC20 } from './Interfaces.sol'; import { Math } from './Math.sol'; import { UUID } from './UUID.sol'; import { OrderSide, OrderType } from './Enums.sol'; import { Asset, LiquidityPool, Order, OrderBookTrade, NonceInvalidation, PoolTrade, Withdrawal } from './Structs.sol'; library Validations { using AssetRegistry for AssetRegistry.Storage; /** * @dev Perform fee validations common to both orderbook-only and hybrid trades. Does not * validate if fees are excessive as taker fee structure differs between these trade types * */ function validateOrderBookTradeFees(OrderBookTrade memory trade) internal pure { require( trade.netBaseQuantityInPips + ( trade.makerFeeAssetAddress == trade.baseAssetAddress ? trade.makerFeeQuantityInPips : trade.takerFeeQuantityInPips ) == trade.grossBaseQuantityInPips, 'Orderbook base fees unbalanced' ); require( trade.netQuoteQuantityInPips + ( trade.makerFeeAssetAddress == trade.quoteAssetAddress ? trade.makerFeeQuantityInPips : trade.takerFeeQuantityInPips ) == trade.grossQuoteQuantityInPips, 'Orderbook quote fees unbalanced' ); } function validateOrderNonce( Order memory order, mapping(address => NonceInvalidation) storage nonceInvalidations ) internal view { require( UUID.getTimestampInMsFromUuidV1(order.nonce) > loadLastInvalidatedTimestamp(order.walletAddress, nonceInvalidations), 'Order nonce timestamp too low' ); } function validateOrderNonces( Order memory buy, Order memory sell, mapping(address => NonceInvalidation) storage nonceInvalidations ) internal view { require( UUID.getTimestampInMsFromUuidV1(buy.nonce) > loadLastInvalidatedTimestamp(buy.walletAddress, nonceInvalidations), 'Buy order nonce timestamp too low' ); require( UUID.getTimestampInMsFromUuidV1(sell.nonce) > loadLastInvalidatedTimestamp(sell.walletAddress, nonceInvalidations), 'Sell order nonce timestamp too low' ); } function validateOrderSignature( Order memory order, string memory baseAssetSymbol, string memory quoteAssetSymbol ) internal pure returns (bytes32) { bytes32 orderHash = Hashing.getOrderHash(order, baseAssetSymbol, quoteAssetSymbol); require( Hashing.isSignatureValid( orderHash, order.walletSignature, order.walletAddress ), order.side == OrderSide.Buy ? 'Invalid wallet signature for buy order' : 'Invalid wallet signature for sell order' ); return orderHash; } function validatePoolReserveRatio(LiquidityPool memory pool) internal pure { (uint64 sortedReserve0, uint64 sortedReserve1) = pool.baseAssetReserveInPips <= pool.quoteAssetReserveInPips ? (pool.baseAssetReserveInPips, pool.quoteAssetReserveInPips) : (pool.quoteAssetReserveInPips, pool.baseAssetReserveInPips); require( uint256(sortedReserve0) * Constants.maxLiquidityPoolReserveRatio >= sortedReserve1, 'Exceeded max reserve ratio' ); } /** * @dev Perform fee validations common to both pool-only and hybrid trades */ function validatePoolTradeInputFees( OrderSide orderSide, PoolTrade memory poolTrade ) internal pure { // Buy order sends quote as pool input, receives base as pool output; sell order sends base as // pool input, receives quote as pool output (uint64 netInputQuantityInPips, uint64 grossInputQuantityInPips) = orderSide == OrderSide.Buy ? (poolTrade.netQuoteQuantityInPips, poolTrade.grossQuoteQuantityInPips) : (poolTrade.netBaseQuantityInPips, poolTrade.grossBaseQuantityInPips); require( netInputQuantityInPips + poolTrade.takerPoolFeeQuantityInPips + poolTrade.takerProtocolFeeQuantityInPips == grossInputQuantityInPips, 'Pool input fees unbalanced' ); require( Validations.isFeeQuantityValid( grossInputQuantityInPips - netInputQuantityInPips, grossInputQuantityInPips, Constants.maxPoolInputFeeBasisPoints ), 'Excessive pool input fee' ); } function validateWithdrawalSignature(Withdrawal memory withdrawal) internal pure returns (bytes32) { bytes32 withdrawalHash = Hashing.getWithdrawalHash(withdrawal); require( Hashing.isSignatureValid( withdrawalHash, withdrawal.walletSignature, withdrawal.walletAddress ), 'Invalid wallet signature' ); return withdrawalHash; } // Utils // function calculateImpliedQuoteQuantityInPips( uint64 baseQuantityInPips, uint64 limitPriceInPips ) internal pure returns (uint64) { return Math.multiplyPipsByFraction( baseQuantityInPips, limitPriceInPips, Constants.pipPriceMultiplier ); } function loadLastInvalidatedTimestamp( address walletAddress, mapping(address => NonceInvalidation) storage nonceInvalidations ) private view returns (uint64) { if ( nonceInvalidations[walletAddress].exists && nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number ) { return nonceInvalidations[walletAddress].timestampInMs; } return 0; } function isFeeQuantityValid( uint64 fee, uint64 total, uint64 max ) internal pure returns (bool) { uint64 feeBasisPoints = (fee * Constants.basisPointsInTotal) / total; return feeBasisPoints <= max; } function isLimitOrderType(OrderType orderType) internal pure returns (bool) { return orderType == OrderType.Limit || orderType == OrderType.LimitMaker || orderType == OrderType.StopLossLimit || orderType == OrderType.TakeProfitLimit; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.4; import { AssetRegistry } from './AssetRegistry.sol'; import { AssetUnitConversions } from './AssetUnitConversions.sol'; import { BalanceTracking } from './BalanceTracking.sol'; import { Constants } from './Constants.sol'; import { UUID } from './UUID.sol'; import { Validations } from './Validations.sol'; import { WithdrawalType } from './Enums.sol'; import { Asset, LiquidityChangeExecution, LiquidityRemoval, Withdrawal } from './Structs.sol'; import { ICustodian, ILiquidityProviderToken } from './Interfaces.sol'; library Withdrawing { using AssetRegistry for AssetRegistry.Storage; using BalanceTracking for BalanceTracking.Storage; function withdraw( Withdrawal memory withdrawal, ICustodian custodian, address feeWallet, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking, mapping(bytes32 => bool) storage completedWithdrawalHashes ) public returns ( uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits, address assetAddress, string memory assetSymbol ) { // Validations require( Validations.isFeeQuantityValid( withdrawal.gasFeeInPips, withdrawal.grossQuantityInPips, Constants.maxFeeBasisPoints ), 'Excessive withdrawal fee' ); bytes32 withdrawalHash = Validations.validateWithdrawalSignature(withdrawal); require( !completedWithdrawalHashes[withdrawalHash], 'Hash already withdrawn' ); // If withdrawal is by asset symbol (most common) then resolve to asset address Asset memory asset = withdrawal.withdrawalType == WithdrawalType.BySymbol ? assetRegistry.loadAssetBySymbol( withdrawal.assetSymbol, UUID.getTimestampInMsFromUuidV1(withdrawal.nonce) ) : assetRegistry.loadAssetByAddress(withdrawal.assetAddress); assetSymbol = asset.symbol; assetAddress = asset.assetAddress; // Update wallet balances newExchangeBalanceInPips = balanceTracking.updateForWithdrawal( withdrawal, asset.assetAddress, feeWallet ); newExchangeBalanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits( newExchangeBalanceInPips, asset.decimals ); // Transfer funds from Custodian to wallet uint256 netAssetQuantityInAssetUnits = AssetUnitConversions.pipsToAssetUnits( withdrawal.grossQuantityInPips - withdrawal.gasFeeInPips, asset.decimals ); custodian.withdraw( withdrawal.walletAddress, asset.assetAddress, netAssetQuantityInAssetUnits ); // Replay prevention completedWithdrawalHashes[withdrawalHash] = true; } function withdrawExit( address assetAddress, ICustodian custodian, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) external returns (uint64 previousExchangeBalanceInPips) { // Update wallet balance previousExchangeBalanceInPips = balanceTracking.updateForExit( msg.sender, assetAddress ); // Transfer asset from Custodian to wallet Asset memory asset = assetRegistry.loadAssetByAddress(assetAddress); uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits( previousExchangeBalanceInPips, asset.decimals ); ICustodian(custodian).withdraw( payable(msg.sender), assetAddress, balanceInAssetUnits ); } function withdrawLiquidity( LiquidityRemoval memory removal, LiquidityChangeExecution memory execution, ICustodian custodian, address feeWallet, ILiquidityProviderToken liquidityProviderToken, AssetRegistry.Storage storage assetRegistry, BalanceTracking.Storage storage balanceTracking ) internal { ( uint64 outputBaseAssetQuantityInPips, uint64 outputQuoteAssetQuantityInPips ) = balanceTracking.updateForRemoveLiquidity( removal, execution, feeWallet, address(custodian), liquidityProviderToken ); Asset memory asset; if (outputBaseAssetQuantityInPips > 0) { asset = assetRegistry.loadAssetByAddress(execution.baseAssetAddress); custodian.withdraw( removal.to, execution.baseAssetAddress, AssetUnitConversions.pipsToAssetUnits( outputBaseAssetQuantityInPips, asset.decimals ) ); } if (outputQuoteAssetQuantityInPips > 0) { asset = assetRegistry.loadAssetByAddress(execution.quoteAssetAddress); custodian.withdraw( removal.to, execution.quoteAssetAddress, AssetUnitConversions.pipsToAssetUnits( outputQuoteAssetQuantityInPips, asset.decimals ) ); } } }
{ "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "optimizer": { "enabled": true, "runs": 1 }, "evmVersion": "berlin", "metadata": { "bytecodeHash": "ipfs" }, "libraries": { "AssetRegistry.sol": { "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB" }, "LiquidityPools.sol": { "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962" }, "LiquidityPoolAdmin.sol": { "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc" }, "NonceInvalidations.sol": { "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad" }, "Trading.sol": { "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1" }, "Depositing.sol": { "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac" }, "Exchange.sol": { "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB", "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac", "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc", "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962", "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad", "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1", "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2" }, "Withdrawing.sol": { "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2" } } }
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":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"liquidityProviderToken","type":"address"}],"name":"LiquidityPoolCreated","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":[{"internalType":"uint64","name":"newDepositIndex","type":"uint64"}],"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":[{"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
60a06040523480156200001157600080fd5b50604051620063cc380380620063cc833981016040819052620000349162000311565b33606081901b608052600080546001600160a01b03191690911790556001600160a01b038316158062000078575062000078836200012f60201b620008d51760201c565b620000ca5760405162461bcd60e51b815260206004820152601860248201527f496e76616c6964206d6967726174696f6e20736f75726365000000000000000060448201526064015b60405180910390fd5b600580546001600160a01b0319166001600160a01b038516179055620000f08262000135565b8051620001059060039060208401906200026b565b505060098054600160a01b600160e01b031916600160a01b600160e01b0317905550620004819050565b3b151590565b6000546001600160a01b03163314620001915760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206d7573742062652061646d696e0000000000000000000000006044820152606401620000c1565b6001600160a01b038116620001e95760405162461bcd60e51b815260206004820152601660248201527f496e76616c69642077616c6c65742061646472657373000000000000000000006044820152606401620000c1565b6012546001600160a01b0382811691161415620002495760405162461bcd60e51b815260206004820152601e60248201527f4d75737420626520646966666572656e742066726f6d2063757272656e7400006044820152606401620000c1565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b828054620002799062000415565b90600052602060002090601f0160209004810192826200029d5760008555620002e8565b82601f10620002b857805160ff1916838001178555620002e8565b82800160010185558215620002e8579182015b82811115620002e8578251825591602001919060010190620002cb565b50620002f6929150620002fa565b5090565b5b80821115620002f65760008155600101620002fb565b60008060006060848603121562000326578283fd5b8351620003338162000468565b80935050602080850151620003488162000468565b60408601519093506001600160401b038082111562000365578384fd5b818701915087601f83011262000379578384fd5b8151818111156200038e576200038e62000452565b604051601f8201601f19908116603f01168101908382118183101715620003b957620003b962000452565b816040528281528a86848701011115620003d1578687fd5b8693505b82841015620003f45784840186015181850187015292850192620003d5565b828411156200040557868684830101525b8096505050505050509250925092565b600181811c908216806200042a57607f821691505b602082108114156200044c57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03811681146200047e57600080fd5b50565b60805160601c615f25620004a7600039600081816116860152611c530152615f256000f3fe60806040526004361061021c5760003560e01c80630226b70e1461024f578063026fc96e1461026f57806302751cec1461028f57806302ca6002146102af5780630561d6d7146102df57806309b388f1146102ff5780630c187a721461031f57806313cfda2c146103345780631abb58321461036257806320e6a8e3146103825780632384d3d0146103a257806323cf3118146103c2578063403f3731146103e25780634196818214610402578063457aa3c6146104225780634a284ef9146104425780636bb509f214610457578063704b6c021461047757806372e8f08d146104975780637cf0c1f4146104b7578063869af212146104d55780638d1d707d1461050257806390d49b9d1461052257806398166c0d14610542578063985c4af51461059357806398ea5fca146105b35780639a202d47146105bb578063ae0e969e146105d0578063b39f07301461061e578063ba22bd7614610645578063baa2abde14610665578063bc25cf7714610685578063c1b16c20146106a5578063c3e5af73146106c5578063c6381999146106e5578063ca2a245e14610705578063d3b2596d14610725578063d7677fbe14610745578063d7a6aec7146107cf578063dbb36535146107ef578063dcc634901461080f578063e8e337001461082f578063eb5068f21461084f578063ed04a70714610864578063ef3b9d4a14610882578063f305d719146108a2578063f91b6e68146108b557600080fd5b3661024a57333b6102485760405162461bcd60e51b815260040161023f906150d2565b60405180910390fd5b005b600080fd5b34801561025b57600080fd5b5061024861026a366004614102565b6108db565b34801561027b57600080fd5b5061024861028a3660046143e9565b61097b565b34801561029b57600080fd5b506102486102aa36600461408d565b610b35565b3480156102bb57600080fd5b506012546001600160a01b03165b6040516102d69190614d1b565b60405180910390f35b3480156102eb57600080fd5b506102486102fa366004614729565b610d39565b34801561030b57600080fd5b506102c961031a366004613eae565b610e39565b34801561032b57600080fd5b50610248610fa7565b34801561034057600080fd5b5061035461034f366004613e76565b610fe3565b6040519081526020016102d6565b34801561036e57600080fd5b5061035461037d366004614010565b61107d565b34801561038e57600080fd5b5061024861039d3660046140ea565b61111a565b3480156103ae57600080fd5b506102486103bd366004613e76565b6111d2565b3480156103ce57600080fd5b506102486103dd366004613e3e565b611290565b3480156103ee57600080fd5b506102486103fd366004613e3e565b611328565b34801561040e57600080fd5b5061024861041d3660046141ba565b6113eb565b34801561042e57600080fd5b5061024861043d366004614102565b61143a565b34801561044e57600080fd5b506102486114a4565b34801561046357600080fd5b506102486104723660046145f0565b61151e565b34801561048357600080fd5b50610248610492366004613e3e565b61167b565b3480156104a357600080fd5b506102486104b2366004614010565b611775565b3480156104c357600080fd5b50600b546001600160a01b03166102c9565b3480156104e157600080fd5b506104f56104f0366004614166565b61184d565b6040516102d69190615280565b34801561050e57600080fd5b5061024861051d366004614423565b611899565b34801561052e57600080fd5b5061024861053d366004613e3e565b611a6b565b34801561054e57600080fd5b5061057c61055d366004613e3e565b600a602052600090815260409020805460019091015460ff9091169082565b6040805192151583526020830191909152016102d6565b34801561059f57600080fd5b506102486105ae366004613e3e565b611b0b565b610248611c30565b3480156105c757600080fd5b50610248611c48565b3480156105dc57600080fd5b506106066105eb3660046140ea565b6000908152600860205260409020546001600160401b031690565b6040516001600160401b0390911681526020016102d6565b34801561062a57600080fd5b5060095461060690600160a01b90046001600160401b031681565b34801561065157600080fd5b50610248610660366004613e3e565b611ca2565b34801561067157600080fd5b50610248610680366004613f24565b611d42565b34801561069157600080fd5b506102486106a0366004613e3e565b611f47565b3480156106b157600080fd5b506102486106c036600461450d565b611fea565b3480156106d157600080fd5b506102486106e0366004613e76565b612176565b3480156106f157600080fd5b506102486107003660046142e3565b61224b565b34801561071157600080fd5b50610248610720366004613e76565b612389565b34801561073157600080fd5b50610248610740366004613e76565b612491565b34801561075157600080fd5b50610765610760366004613e76565b6125d9565b6040516102d69190600060c082019050825115158252602083015160018060401b03808216602085015260ff6040860151166040850152806060860151166060850152505060ff608084015116608083015260018060a01b0360a08401511660a083015292915050565b3480156107db57600080fd5b506102486107ea3660046146f7565b61268a565b3480156107fb57600080fd5b5061060661080a366004613e76565b612788565b34801561081b57600080fd5b5061024861082a366004614062565b612828565b34801561083b57600080fd5b5061024861084a366004613f95565b61285d565b34801561085b57600080fd5b50610248612aed565b34801561087057600080fd5b506009546001600160a01b03166102c9565b34801561088e57600080fd5b5061060661089d366004614010565b612be4565b6102486108b036600461408d565b612c77565b3480156108c157600080fd5b506102486108d0366004614590565b612ef4565b3b151590565b6000546001600160a01b031633146109055760405162461bcd60e51b815260040161023f90615191565b60405163a7c5af2160e01b815273c2f05d03915e7c2d9038830f7888c97e351dd3db9063a7c5af219061094590600190889088908890889060040161551d565b60006040518083038186803b15801561095d57600080fd5b505af4158015610971573d6000803e3d6000fd5b5050505050505050565b6011546001600160a01b031633146109a55760405162461bcd60e51b815260040161023f90615077565b730f2c07f4ecc6c9d74d16e735d2a59d00985b196263089f7adc600c8484600a60006109d76080850160608601613e3e565b6001600160a01b039081168252602082019290925260409081016000205460095460125492516001600160e01b031960e08a901b168152610a319796959460ff9093169391831692919091169060019060049081016157e2565b60006040518083038186803b158015610a4957600080fd5b505af4158015610a5d573d6000803e3d6000fd5b507f616311a023377d4cd48ac9f682cab7aa6e9876dd3a5f39d6ebd5cc14a6452c509250610a949150506080840160608501613e3e565b610aa16020840184613e3e565b610ab16040850160208601613e3e565b610ac16080860160608701614729565b610ad160a0870160808801614729565b610ae16060880160408901614729565b604080516001600160a01b039788168152958716602087015293909516928401929092526001600160401b039081166060840152908116608083015290911660a082015260c0015b60405180910390a15050565b336000908152600a602052604090205460ff1615610b655760405162461bcd60e51b815260040161023f90615222565b604080516101808101825260038152600060208083018290528284018290523360608401526001600160a01b038a8116608085015260a0840183905260c084018a905260e0840189905261010084018890528681166101208501526101408401869052845191820185528282526101608401919091526009549351639cffaf0f60e01b81529193730f2c07f4ecc6c9d74d16e735d2a59d00985b196293639cffaf0f93610c1e93600c9316906001906004908101615948565b60006040518083038186803b158015610c3657600080fd5b505af4158015610c4a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c729190810190614335565b9050600080516020615eb08339815191526009601481819054906101000a90046001600160401b0316610ca490615dc9565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051610cf49796959493929190615b26565b60405180910390a1600080516020615e90833981519152338860008989898989604051610d28989796959493929190614d9a565b60405180910390a150505050505050565b6000546001600160a01b03163314610d635760405162461bcd60e51b815260040161023f90615191565b600954600160a01b90046001600160401b0390811614610dbc5760405162461bcd60e51b815260206004820152601460248201527343616e206f6e6c7920626520736574206f6e636560601b604482015260640161023f565b6001600160401b038181161415610e0d5760405162461bcd60e51b8152602060048201526015602482015274092dcecc2d8d2c840c8cae0dee6d2e840d2dcc8caf605b1b604482015260640161023f565b600980546001600160401b03909216600160a01b02600160a01b600160e01b0319909216919091179055565b600b546000906001600160a01b03163314610e8f5760405162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1026b4b3b930ba37b960511b604482015260640161023f565b6040805160c0810182526001600160a01b03898116825288811660208301908152881515838501908152606084018981528884166080860190815288851660a087019081526009549751636c91badd60e01b8152600c600482015296518616602488015293518516604487015291511515606486015251608485015251821660a484015251811660c48301529190911660e48201526001610104820152737a246e4434dd31df784bb88d3443e309e3143adc90636c91badd906101240160206040518083038186803b158015610f6457600080fd5b505af4158015610f78573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f9c9190613e5a565b979650505050505050565b6000546001600160a01b03163314610fd15760405162461bcd60e51b815260040161023f90615191565b601180546001600160a01b0319169055565b6040516347ac1b4560e01b815260009073c2f05d03915e7c2d9038830f7888c97e351dd3db906347ac1b459061102490600190879087906004908101615490565b60206040518083038186803b15801561103c57600080fd5b505af4158015611050573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110749190614711565b90505b92915050565b60405163a2cfc7a960e01b815260009073c2f05d03915e7c2d9038830f7888c97e351dd3db9063a2cfc7a9906110c09060019088908890889060049081016154b5565b60206040518083038186803b1580156110d857600080fd5b505af41580156110ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111109190614711565b90505b9392505050565b6000546001600160a01b031633146111445760405162461bcd60e51b815260040161023f90615191565b6203138081106111945760405162461bcd60e51b815260206004820152601b60248201527a09ccaee40e0cae4d2dec840cee4cac2e8cae440e8d0c2dc40dac2f602b1b604482015260640161023f565b601080549082905560408051828152602081018490527f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f9101610b29565b6000546001600160a01b031633146111fc5760405162461bcd60e51b815260040161023f90615191565b60405163e7bcf67360e01b8152737a246e4434dd31df784bb88d3443e309e3143adc9063e7bcf6739061123b90600c9086908690600190600401615490565b60206040518083038186803b15801561125357600080fd5b505af4158015611267573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061128b9190613e5a565b505050565b6000546001600160a01b031633146112ba5760405162461bcd60e51b815260040161023f90615191565b803b6112d85760405162461bcd60e51b815260040161023f906150a9565b600b546001600160a01b03828116911614156113065760405162461bcd60e51b815260040161023f90615249565b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146113525760405162461bcd60e51b815260040161023f90615191565b6009546001600160a01b0316156113ab5760405162461bcd60e51b815260206004820152601e60248201527f437573746f6469616e2063616e206f6e6c7920626520736574206f6e63650000604482015260640161023f565b803b6113c95760405162461bcd60e51b815260040161023f906150a9565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b6000611402836113f961304a565b6001919061305f565b60208101519091506001600160a01b031661142f5760405162461bcd60e51b815260040161023f906150d2565b61128b33828461343a565b6000546001600160a01b031633146114645760405162461bcd60e51b815260040161023f90615191565b604051635e55073b60e01b815273c2f05d03915e7c2d9038830f7888c97e351dd3db90635e55073b9061094590600190889088908890889060040161551d565b6114ad3361360f565b6114c95760405162461bcd60e51b815260040161023f90615130565b336000818152600a6020526040808220805460ff1916815560010191909155517fb771d4b2a83beca38f442c8903629e0e8ab1a07cf76e94eb2977153167e209369161151491614d1b565b60405180910390a1565b6011546001600160a01b031633146115485760405162461bcd60e51b815260040161023f90615077565b611555816040015161360f565b156115725760405162461bcd60e51b815260040161023f90615222565b600954601254604051635879725360e11b815260009283928392839273b3af24eeac0ee8b6f5798f8a75e3ecd51b18deb29263b0f2e4a6926115cf928a926001600160a01b0391821692911690600190600490600f908201615a2f565b60006040518083038186803b1580156115e757600080fd5b505af41580156115fb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611623919081019061478e565b93509350935093507f6960a1f64ecf9da0d1d1bcbfa3dd27f8c1c60de69b13faa28127dafa36c111e4856040015183838860a00151888860405161166c96959493929190614d2f565b60405180910390a15050505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146116c35760405162461bcd60e51b815260040161023f90615163565b6001600160a01b0381166116e95760405162461bcd60e51b815260040161023f906151bf565b6000546001600160a01b03828116911614156117535760405162461bcd60e51b8152602060048201526024808201527f4d75737420626520646966666572656e742066726f6d2063757272656e7420616044820152633236b4b760e11b606482015260840161023f565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b0316331461179f5760405162461bcd60e51b815260040161023f90615191565b604051635ef40c1160e11b815273c2f05d03915e7c2d9038830f7888c97e351dd3db9063bde81822906117dd906001908790879087906004016154f2565b60006040518083038186803b1580156117f557600080fd5b505af4158015611809573d6000803e3d6000fd5b505050507f7f63a63f4a2ea318da0b031794d0b5a3183a1b2de54e053ceb17cabdba03173583838360405161184093929190615049565b60405180910390a1505050565b6118556138e9565b61111084848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019392508691505061305f565b6011546001600160a01b031633146118c35760405162461bcd60e51b815260040161023f90615077565b6118d0836040015161360f565b156118ed5760405162461bcd60e51b815260040161023f906151ef565b6118fa826040015161360f565b156119175760405162461bcd60e51b815260040161023f906150fc565b60125460405163279565bb60e11b8152734d3250014ea4ecddd857fad48c3d64d2e4f037e191634f2acb7691611972918791879187916001600160a01b0390911690600190600c9060049060069060079060089084016152f1565b60006040518083038186803b15801561198a57600080fd5b505af415801561199e573d6000803e3d6000fd5b50505050604083810151908301518251805160208083015160808085015160a095860151938901519182015191909501517f0c2788d93efb3bdadabeb2b84b5900db0d210b5875d7e88a913b6a807a1f12cb97969592939190611a018285615d0a565b8a6020015160a001518b6000015160a00151611a1d9190615d0a565b60008c516101a001516001811115611a4557634e487b7160e01b600052602160045260246000fd5b14611a51576000611a54565b60015b6040516118409b9a99989796959493929190614ea6565b6000546001600160a01b03163314611a955760405162461bcd60e51b815260040161023f90615191565b6001600160a01b038116611abb5760405162461bcd60e51b815260040161023f906151bf565b6012546001600160a01b0382811691161415611ae95760405162461bcd60e51b815260040161023f90615249565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b611b143361360f565b611b305760405162461bcd60e51b815260040161023f90615130565b60095460405163c42cc91f60e01b81526001600160a01b038084166004808401919091529216602482015260016044820152606481019190915260009073b3af24eeac0ee8b6f5798f8a75e3ecd51b18deb29063c42cc91f9060840160206040518083038186803b158015611ba457600080fd5b505af4158015611bb8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bdc9190614745565b604080513381526001600160a01b03851660208201526001600160401b038316918101919091529091507f8a8c919824df7fc8e14ab0532b92f8305abaa1947ec4ecdeb744c4281aecd16690606001610b29565b611c4633611c4060016000613640565b3461343a565b565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611c905760405162461bcd60e51b815260040161023f90615163565b600080546001600160a01b0319169055565b6000546001600160a01b03163314611ccc5760405162461bcd60e51b815260040161023f90615191565b6001600160a01b038116611cf25760405162461bcd60e51b815260040161023f906151bf565b6011546001600160a01b0382811691161415611d205760405162461bcd60e51b815260040161023f90615249565b601180546001600160a01b0319166001600160a01b0392909216919091179055565b336000908152600a602052604090205460ff1615611d725760405162461bcd60e51b815260040161023f90615222565b604080516101808101825260038152600060208083018290528284018290523360608401526001600160a01b038b811660808501528a811660a085015260c084018a905260e0840189905261010084018890528681166101208501526101408401869052845191820185528282526101608401919091526009549351639cffaf0f60e01b81529193730f2c07f4ecc6c9d74d16e735d2a59d00985b196293639cffaf0f93611e2c93600c9316906001906004908101615948565b60006040518083038186803b158015611e4457600080fd5b505af4158015611e58573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611e809190810190614335565b9050600080516020615eb08339815191526009601481819054906101000a90046001600160401b0316611eb290615dc9565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051611f029796959493929190615b26565b60405180910390a1600080516020615e908339815191523389898989898989604051611f35989796959493929190614d9a565b60405180910390a15050505050505050565b6000546001600160a01b03163314611f715760405162461bcd60e51b815260040161023f90615191565b60125460405163712b772f60e01b815273c2f05d03915e7c2d9038830f7888c97e351dd3db9163712b772f91611fb79185916001600160a01b0390911690600401614d80565b60006040518083038186803b158015611fcf57600080fd5b505af4158015611fe3573d6000803e3d6000fd5b5050505050565b6011546001600160a01b031633146120145760405162461bcd60e51b815260040161023f90615077565b612021836040015161360f565b1561203e5760405162461bcd60e51b815260040161023f906151ef565b61204b826040015161360f565b156120685760405162461bcd60e51b815260040161023f906150fc565b6012546040516358a9b1ad60e11b8152734d3250014ea4ecddd857fad48c3d64d2e4f037e19163b153635a916120c0918791879187916001600160a01b039091169060019060049060069060079060089084016153ac565b60006040518083038186803b1580156120d857600080fd5b505af41580156120ec573d6000803e3d6000fd5b5050506040808501519084015183516020850151608086015160a08701517fe836416b090e27ecc87dd0bf23c36034cb0b2e2960c2f271f4a7b96fea65694296506000886101a00151600181111561215457634e487b7160e01b600052602160045260246000fd5b14612160576000612163565b60015b6040516118409796959493929190614e32565b6000546001600160a01b031633146121a05760405162461bcd60e51b815260040161023f90615191565b60405163f065a68760e01b8152600c60048201526001600160a01b03808416602483015282166044820152737a246e4434dd31df784bb88d3443e309e3143adc9063f065a6879060640160006040518083038186803b15801561220257600080fd5b505af4158015612216573d6000803e3d6000fd5b505050507f81f30d401791fa502c3a1610e6e194416482fcd7bbbfdba54bded335ff9edb598282604051610b29929190614d80565b6011546001600160a01b031633146122755760405162461bcd60e51b815260040161023f90615077565b61228d6122886080840160608501613e3e565b61360f565b156122d25760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08195e1a5d08199a5b985b1a5e9959605a1b604482015260640161023f565b601254600954604051637904862960e11b8152730f2c07f4ecc6c9d74d16e735d2a59d00985b19629263f2090c529261232692600c92889288926001600160a01b039283169290911690600490810161555d565b60006040518083038186803b15801561233e57600080fd5b505af4158015612352573d6000803e3d6000fd5b507f6e2fd2e6935d9da786db2536ac4061dca5dad5bacaac5b8b169d657c3e3475109250610a949150506080840160608501613e3e565b60095460408051633bffd49d60e01b815290516000926001600160a01b031691633bffd49d916004808301926020929190829003018186803b1580156123ce57600080fd5b505afa1580156123e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124069190613e5a565b9050336001600160a01b038216146124595760405162461bcd60e51b815260206004820152601660248201527543616c6c6572206973206e6f742045786368616e676560501b604482015260640161023f565b506001600160a01b039182166000908152600460209081526040808320939094168252919091522080546001600160481b0319169055565b61249a3361360f565b6124b65760405162461bcd60e51b815260040161023f90615130565b6009546040516311a73c7560e31b8152600c6004808301919091526001600160a01b0380861660248401528085166044840152909216606482015260848101919091526000908190730f2c07f4ecc6c9d74d16e735d2a59d00985b196290638d39e3a89060a401604080518083038186803b15801561253457600080fd5b505af4158015612548573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256c91906147f9565b604080513381526001600160a01b0388811660208301528716818301526001600160401b0384811660608301528316608082015290519294509092507f8102055af21849e835ad18fcbd334e529a8d18decf5cefc69e7120508e1aeaa4919081900360a00190a150505050565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152612617600c84846137de565b6040805160c081018252825460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b900490911660808201526001909101546001600160a01b031660a0820152905092915050565b6010546040516303710ea360e61b8152600760048201526001600160801b038316602482015260448101919091526000908190736c539e6143f70408076f35d19e7e549850c021ad9063dc43a8c090606401604080518083038186803b1580156126f357600080fd5b505af4158015612707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061272b9190614761565b604080513381526001600160801b03871660208201526001600160401b038416918101919091526060810182905291935091507f10cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809b90608001611840565b604051634916cb7160e11b81526001600160a01b038084166004808401919091529083166024830152604482015260009073c2f05d03915e7c2d9038830f7888c97e351dd3db9063922d96e29060640160206040518083038186803b1580156127f057600080fd5b505af4158015612804573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110749190614745565b6000612835600184613640565b90506001600160a01b03831661142f5760405162461bcd60e51b815260040161023f906150d2565b336000908152600a602052604090205460ff161561288d5760405162461bcd60e51b815260040161023f90615222565b604080516101a08101825260038152600060208083018290528284018290523360608401526001600160a01b038c811660808501528b811660a085015260c084018b905260e084018a905261010084018990526101208401889052868116610140850152610160840186905284519182018552828252610180840191909152600954935163467b41fb60e01b81529193730f2c07f4ecc6c9d74d16e735d2a59d00985b19629363467b41fb9361294f93600c93169060019060049081016156c8565b60006040518083038186803b15801561296757600080fd5b505af415801561297b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526129a391908101906141fc565b9050600080516020615eb08339815191526009601481819054906101000a90046001600160401b03166129d590615dc9565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338b8460000151856020015186604001518760600151604051612a219796959493929190615b26565b60405180910390a1600080516020615eb08339815191526009601481819054906101000a90046001600160401b0316612a5990615dc9565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338a84608001518560a001518660c001518760e00151604051612aa59796959493929190615b26565b60405180910390a1600080516020615ed0833981519152338a8a8a8a8a8a8a8a604051612ada99989796959493929190614de3565b60405180910390a1505050505050505050565b336000908152600a602052604090205460ff1615612b455760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08185b1c9958591e48195e1a5d1959605a1b604482015260640161023f565b604051806040016040528060011515815260200160105443612b679190615cf2565b9052336000818152600a602090815260409091208351815460ff19169015151781559201516001909201919091556010547fd60f9f7b2f1a208268475a927bd727c4e198fc8b40aab3004ebcc2bc78ca84809190612bc59043615cf2565b604080516001600160a01b039093168352602083019190915201611514565b60405163031b07f360e61b815260009073c2f05d03915e7c2d9038830f7888c97e351dd3db9063c6c1fcc090612c279060019088908890889060049081016154b5565b60206040518083038186803b158015612c3f57600080fd5b505af4158015612c53573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111109190614745565b336000908152600a602052604090205460ff1615612ca75760405162461bcd60e51b815260040161023f90615222565b604080516101a08101825260038152600060208083018290528284018290523360608401526001600160a01b038a8116608085015260a0840183905260c084018a90523460e085015261010084018990526101208401889052868116610140850152610160840186905284519182018552828252610180840191909152600954935163467b41fb60e01b81529193730f2c07f4ecc6c9d74d16e735d2a59d00985b19629363467b41fb93612d6793600c93169060019060049081016156c8565b60006040518083038186803b158015612d7f57600080fd5b505af4158015612d93573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612dbb91908101906141fc565b9050600080516020615eb08339815191526009601481819054906101000a90046001600160401b0316612ded90615dc9565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533898460000151856020015186604001518760600151604051612e399796959493929190615b26565b60405180910390a1600080516020615eb08339815191526009601481819054906101000a90046001600160401b0316612e7190615dc9565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533600084608001518560a001518660c001518760e00151604051612ebe9796959493929190615b26565b60405180910390a1600080516020615ed08339815191523388600089348a8a8a8a604051610d2899989796959493929190614de3565b6011546001600160a01b03163314612f1e5760405162461bcd60e51b815260040161023f90615077565b612f2b826040015161360f565b15612f765760405162461bcd60e51b815260206004820152601b60248201527a13dc99195c881dd85b1b195d08195e1a5d08199a5b985b1a5e9959602a1b604482015260640161023f565b601254604051636452281b60e11b8152734d3250014ea4ecddd857fad48c3d64d2e4f037e19163c8a4503691612fcd91869186916001600160a01b031690600190600c906004906006906007906008908401615426565b60006040518083038186803b158015612fe557600080fd5b505af4158015612ff9573d6000803e3d6000fd5b5050506040808401518351602085015160808087015160a08801519189015195517fe33b9d086983d8450b17504f6bd50986f52a0f8c7aebad750c10a2cf7c7f7b289750610b299691929190614f42565b60006103e86130598142615d35565b91505090565b6130676138e9565b6130fd84600201805461307990615d94565b80601f01602080910402602001604051908101604052809291908181526020018280546130a590615d94565b80156130f25780601f106130c7576101008083540402835291602001916130f2565b820191906000526020600020905b8154815290600101906020018083116130d557829003601f168201915b505050505084613851565b1561319e5761319784600201805461311490615d94565b80601f016020809104026020016040519081016040528092919081815260200182805461314090615d94565b801561318d5780601f106131625761010080835404028352916020019161318d565b820191906000526020600020905b81548152906001019060200180831161317057829003601f168201915b50505050506138aa565b9050611113565b6131a66138e9565b600085600101856040516131ba9190614cff565b9081526040519081900360200190205411156133d35760005b85600101856040516131e59190614cff565b9081526040519081900360200190205460ff821610156133d157836001600160401b0316866001018660405161321b9190614cff565b90815260200160405180910390208260ff168154811061324b57634e487b7160e01b600052603260045260246000fd5b60009182526020909120600390910201600201546201000090046001600160401b0316116133bf5785600101856040516132859190614cff565b90815260200160405180910390208160ff16815481106132b557634e487b7160e01b600052603260045260246000fd5b60009182526020918290206040805160c0810182526003909302909101805460ff8116151584526001600160a01b0361010090910416938301939093526001830180549293929184019161330890615d94565b80601f016020809104026020016040519081016040528092919081815260200182805461333490615d94565b80156133815780601f1061335657610100808354040283529160200191613381565b820191906000526020600020905b81548152906001019060200180831161336457829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015291505b806133c981615df0565b9150506131d3565b505b805180156133e2575080608001515b6111105760405162461bcd60e51b815260206004820152602360248201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f722073796d604482015262189bdb60ea1b606482015260840161023f565b600954600160a01b90046001600160401b0390811614156134915760405162461bcd60e51b815260206004820152601160248201527011195c1bdcda5d1cc8191a5cd8589b1959607a1b604482015260640161023f565b6001600160a01b0383166000908152600a602052604090205460ff16156134ca5760405162461bcd60e51b815260040161023f90615222565b60095460405163a16a597960e01b81526000918291829173116310b243dd287d4285d0e8a34ce3d4adb63dac9163a16a59799161351b918a918a918a916001600160a01b0316906004908101614fad565b60606040518083038186803b15801561353357600080fd5b505af4158015613547573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061356b9190614827565b600980549396509194509250600160a01b9091046001600160401b031690601461359483615dc9565b91906101000a8154816001600160401b0302191690836001600160401b0316021790555050600080516020615eb0833981519152600960149054906101000a90046001600160401b031687876020015188604001518787876040516135ff9796959493929190615b26565b60405180910390a1505050505050565b6001600160a01b0381166000908152600a60205260408120805460ff16801561111357506001015443101592915050565b6136486138e9565b6001600160a01b03821661366f5761366883600201805461311490615d94565b9050611077565b6001600160a01b03808316600090815260208581526040808320815160c081018352815460ff811615158252610100900490951692850192909252600182018054939493918401916136c090615d94565b80601f01602080910402602001604051908101604052809291908181526020018280546136ec90615d94565b80156137395780601f1061370e57610100808354040283529160200191613739565b820191906000526020600020905b81548152906001019060200180831161371c57829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015280519091508015613786575080608001515b6110745760405162461bcd60e51b8152602060048201526024808201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f72206164646044820152637265737360e01b606482015260840161023f565b6001600160a01b0380831660009081526001850160209081526040808320938516835292905220805460ff166111135760405162461bcd60e51b81526020600482015260186024820152772737903837b7b6103337b91030b2323932b9b9903830b4b960411b604482015260640161023f565b6000816040516020016138649190614cff565b604051602081830303815290604052805190602001208360405160200161388b9190614cff565b6040516020818303038152906040528051906020012014905092915050565b6138b26138e9565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b803561392a81615e65565b919050565b8035801515811461392a57600080fd5b600082601f83011261394f578081fd5b813561396261395d82615c87565b615c57565b818152846020838601011115613976578283fd5b816020850160208301379081016020019190915292915050565b80356002811061392a57600080fd5b80356004811061392a57600080fd5b80356007811061392a57600080fd5b60008083601f8401126139ce578182fd5b5081356001600160401b038111156139e4578182fd5b6020830191508360208285010111156139fc57600080fd5b9250929050565b600082601f830112613a13578081fd5b8151613a2161395d82615c87565b818152846020838601011115613a35578283fd5b613a46826020830160208701615d64565b949350505050565b600060e08284031215613a5f578081fd5b50919050565b60006101c08284031215613a77578081fd5b613a7f615b81565b905081356001600160401b0380821115613a9857600080fd5b613aa48583860161393f565b83526020840135915080821115613aba57600080fd5b50613ac78482850161393f565b602083015250613ad96040830161391f565b6040820152613aea6060830161391f565b6060820152613afb60808301613e17565b6080820152613b0c60a08301613e17565b60a0820152613b1d60c08301613e17565b60c0820152613b2e60e08301613e17565b60e0820152610100613b4181840161391f565b90820152610120613b5383820161391f565b90820152610140613b65838201613e17565b90820152610160613b77838201613e17565b90820152610180613b89838201613e17565b908201526101a0613b9b838201613990565b9082015292915050565b60006101c08284031215613bb7578081fd5b613bbf615b81565b9050613bca82613e2d565b8152613bd860208301613e00565b6020820152613be96040830161391f565b6040820152613bfa606083016139ae565b6060820152613c0b60808301613990565b6080820152613c1c60a08301613e17565b60a0820152613c2d60c0830161392f565b60c0820152613c3e60e08301613e17565b60e0820152610100613c51818401613e17565b90820152610120828101356001600160401b0380821115613c7157600080fd5b613c7d8683870161393f565b838501526101409250613c9183860161399f565b838501526101609250613ca583860161399f565b838501526101809250613cb9838601613e17565b838501526101a0925082850135915080821115613cd557600080fd5b50613ce28582860161393f565b82840152505092915050565b60006101808284031215613d00578081fd5b613d08615baa565b905081356001600160401b0380821115613d2157600080fd5b613d2d8583860161393f565b83526020840135915080821115613d4357600080fd5b50613d508482850161393f565b602083015250613d626040830161391f565b6040820152613d736060830161391f565b6060820152613d8460808301613e17565b6080820152613d9560a08301613e17565b60a0820152613da660c08301613e17565b60c0820152613db760e08301613e17565b60e0820152610100613dca818401613e17565b90820152610120613ddc838201613e17565b90820152610140613dee838201613e17565b90820152610160613b9b838201613e17565b80356001600160801b038116811461392a57600080fd5b803561392a81615e7a565b805161392a81615e7a565b803560ff8116811461392a57600080fd5b600060208284031215613e4f578081fd5b813561107481615e65565b600060208284031215613e6b578081fd5b815161107481615e65565b60008060408385031215613e88578081fd5b8235613e9381615e65565b91506020830135613ea381615e65565b809150509250929050565b60008060008060008060c08789031215613ec6578182fd5b8635613ed181615e65565b95506020870135613ee181615e65565b9450613eef6040880161392f565b9350606087013592506080870135613f0681615e65565b915060a0870135613f1681615e65565b809150509295509295509295565b600080600080600080600060e0888a031215613f3e578485fd5b8735613f4981615e65565b96506020880135613f5981615e65565b955060408801359450606088013593506080880135925060a0880135613f7e81615e65565b8092505060c0880135905092959891949750929550565b600080600080600080600080610100898b031215613fb1578182fd5b8835613fbc81615e65565b97506020890135613fcc81615e65565b965060408901359550606089013594506080890135935060a0890135925060c0890135613ff881615e65565b8092505060e089013590509295985092959890939650565b600080600060408486031215614024578081fd5b833561402f81615e65565b925060208401356001600160401b03811115614049578182fd5b614055868287016139bd565b9497909650939450505050565b60008060408385031215614074578182fd5b823561407f81615e65565b946020939093013593505050565b60008060008060008060c087890312156140a5578384fd5b86356140b081615e65565b955060208701359450604087013593506060870135925060808701356140d581615e65565b8092505060a087013590509295509295509295565b6000602082840312156140fb578081fd5b5035919050565b60008060008060608587031215614117578182fd5b843561412281615e65565b935060208501356001600160401b0381111561413c578283fd5b614148878288016139bd565b909450925061415b905060408601613e2d565b905092959194509250565b60008060006040848603121561417a578081fd5b83356001600160401b0381111561418f578182fd5b61419b868287016139bd565b90945092505060208401356141af81615e7a565b809150509250925092565b600080604083850312156141cc578182fd5b82356001600160401b038111156141e1578283fd5b6141ed8582860161393f565b95602094909401359450505050565b60006020828403121561420d578081fd5b81516001600160401b0380821115614223578283fd5b908301906101008286031215614237578283fd5b61423f615bcd565b82518281111561424d578485fd5b61425987828601613a03565b82525061426860208401613e22565b602082015261427960408401613e22565b604082015260608301516060820152608083015182811115614299578485fd5b6142a587828601613a03565b6080830152506142b760a08401613e22565b60a08201526142c860c08401613e22565b60c082015260e083015160e082015280935050505092915050565b60008061010083850312156142f6578182fd5b82356001600160401b0381111561430b578283fd5b83016101a0818603121561431d578283fd5b915061432c8460208501613a4e565b90509250929050565b600060208284031215614346578081fd5b81516001600160401b038082111561435c578283fd5b9083019060a0828603121561436f578283fd5b614377615bf0565b825161438281615e65565b8152602083015182811115614395578485fd5b6143a187828601613a03565b602083015250604083015191506143b782615e7a565b816040820152606083015191506143cd82615e7a565b8160608201526080830151608082015280935050505092915050565b60008061010083850312156143fc578182fd5b82356001600160401b03811115614411578283fd5b8301610180818603121561431d578283fd5b600080600060608486031215614437578081fd5b83356001600160401b038082111561444d578283fd5b61445987838801613ba5565b9450602086013591508082111561446e578283fd5b61447a87838801613ba5565b9350604086013591508082111561448f578283fd5b90850190606082880312156144a2578283fd5b6144aa615c12565b8235828111156144b8578485fd5b6144c489828601613a65565b8252506020830135828111156144d8578485fd5b6144e489828601613cee565b602083015250604083013592506144fa83615e7a565b8260408201528093505050509250925092565b600080600060608486031215614521578081fd5b83356001600160401b0380821115614537578283fd5b61454387838801613ba5565b94506020860135915080821115614558578283fd5b61456487838801613ba5565b93506040860135915080821115614579578283fd5b5061458686828701613a65565b9150509250925092565b600080604083850312156145a2578182fd5b82356001600160401b03808211156145b8578384fd5b6145c486838701613ba5565b935060208501359150808211156145d9578283fd5b506145e685828601613cee565b9150509250929050565b600060208284031215614601578081fd5b81356001600160401b0380821115614617578283fd5b90830190610120828603121561462b578283fd5b614633615c34565b61463c83613990565b815261464a60208401613e00565b602082015261465b6040840161391f565b6040820152606083013582811115614671578485fd5b61467d8782860161393f565b60608301525061468f6080840161391f565b60808201526146a060a08401613e17565b60a08201526146b160c08401613e17565b60c08201526146c260e0840161392f565b60e082015261010080840135838111156146da578586fd5b6146e68882870161393f565b918301919091525095945050505050565b600060208284031215614708578081fd5b61107482613e00565b600060208284031215614722578081fd5b5051919050565b60006020828403121561473a578081fd5b813561107481615e7a565b600060208284031215614756578081fd5b815161107481615e7a565b60008060408385031215614773578182fd5b825161477e81615e7a565b6020939093015192949293505050565b600080600080608085870312156147a3578182fd5b84516147ae81615e7a565b6020860151604087015191955093506147c681615e65565b60608601519092506001600160401b038111156147e1578182fd5b6147ed87828801613a03565b91505092959194509250565b6000806040838503121561480b578182fd5b825161481681615e7a565b6020840151909250613ea381615e7a565b60008060006060848603121561483b578081fd5b835161484681615e7a565b602085015190935061485781615e7a565b80925050604084015190509250925092565b6001600160a01b03169052565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b600081518084526148b7816020860160208601615d64565b601f01601f19169290920160200192915050565b6148d481615e52565b9052565b600481106148d4576148d4615e26565b600781106148d4576148d4615e26565b803561490381615e65565b6001600160a01b03908116835260208201359061491f82615e65565b166020830152604081013561493381615e7a565b6001600160401b03908116604084015260608201359061495282615e7a565b908116606084015260808201359061496982615e7a565b908116608084015260a08201359061498082615e7a565b1660a083015261499260c08201613e17565b61128b60c0840182614cf2565b60006101c082518185526149b58286018261489f565b915050602083015184820360208601526149cf828261489f565b91505060408301516149e46040860182614869565b5060608301516149f76060860182614869565b506080830151614a0a6080860182614cf2565b5060a0830151614a1d60a0860182614cf2565b5060c0830151614a3060c0860182614cf2565b5060e0830151614a4360e0860182614cf2565b5061010080840151614a5782870182614869565b505061012080840151614a6c82870182614869565b505061014080840151614a8182870182614cf2565b505061016080840151614a9682870182614cf2565b505061018080840151614aab82870182614cf2565b50506101a080840151614ac0828701826148cb565b5090949350505050565b805160ff16825260006101c06020830151614ae86020860182614ce5565b506040830151614afb6040860182614869565b506060830151614b0e60608601826148e8565b506080830151614b2160808601826148cb565b5060a0830151614b3460a0860182614cf2565b5060c0830151614b4860c086018215159052565b5060e0830151614b5b60e0860182614cf2565b5061010080840151614b6f82870182614cf2565b5050610120808401518282870152614b898387018261489f565b9250505061014080840151614ba0828701826148d8565b505061016080840151614bb5828701826148d8565b505061018080840151614bca82870182614cf2565b50506101a08084015185830382870152614be4838261489f565b9695505050505050565b60006101808251818552614c048286018261489f565b91505060208301518482036020860152614c1e828261489f565b9150506040830151614c336040860182614869565b506060830151614c466060860182614869565b506080830151614c596080860182614cf2565b5060a0830151614c6c60a0860182614cf2565b5060c0830151614c7f60c0860182614cf2565b5060e0830151614c9260e0860182614cf2565b5061010080840151614ca682870182614cf2565b505061012080840151614cbb82870182614cf2565b505061014080840151614cd082870182614cf2565b505061016080840151614ac082870182614cf2565b6001600160801b03169052565b6001600160401b03169052565b60008251614d11818460208701615d64565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b0387811682528616602082015260c060408201819052600090614d5b9083018761489f565b6001600160401b03958616606084015293909416608082015260a00152949350505050565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b039889168152968816602088015294871660408701526060860193909352608085019190915260a084015290921660c082015260e08101919091526101000190565b6001600160a01b03998a168152978916602089015295881660408801526060870194909452608086019290925260a085015260c084015290921660e08201526101008101919091526101200190565b6001600160a01b0388811682528716602082015260e060408201819052600090614e5e9083018861489f565b8281036060840152614e70818861489f565b6001600160401b038781166080860152861660a08501529150614e94905083615e52565b8260c083015298975050505050505050565b6001600160a01b038c811682528b16602082015261016060408201819052600090614ed38382018d61489f565b90508281036060840152614ee7818c61489f565b6001600160401b038b811660808601528a811660a086015289811660c086015288811660e086015287811661010086015286166101208501529150614f3290506101408301846148cb565b9c9b505050505050505050505050565b6001600160a01b038716815260c060208201819052600090614f669083018861489f565b8281036040840152614f78818861489f565b6001600160401b038781166060860152861660808501529150614f9c905083615e52565b8260a0830152979650505050505050565b600060018060a01b03808816835260a060208401528651151560a08401528060208801511660c084015250604086015160c060e0840152614ff261016084018261489f565b60608881015160ff166101008601526080890151151561012086015260a08901516001600160401b0316610140860152604085018890529092506150399150830185614869565b8260808301529695505050505050565b6001600160a01b038416815260406020820181905260009061506e9083018486614876565b95945050505050565b60208082526018908201527721b0b63632b91034b9903737ba103234b9b830ba31b432b960411b604082015260600190565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b60208082526010908201526f2ab9b2903232b837b9b4ba22ba3432b960811b604082015260600190565b6020808252601a908201527914d95b1b081dd85b1b195d08195e1a5d08199a5b985b1a5e995960321b604082015260600190565b60208082526019908201527815d85b1b195d08195e1a5d081b9bdd08199a5b985b1a5e9959603a1b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329037bbb732b960611b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329030b236b4b760611b604082015260600190565b602080825260169082015275496e76616c69642077616c6c6574206164647265737360501b604082015260600190565b602080825260199082015278109d5e481dd85b1b195d08195e1a5d08199a5b985b1a5e9959603a1b604082015260600190565b6020808252600d908201526c15d85b1b195d08195e1a5d1959609a1b604082015260600190565b6020808252601e908201527f4d75737420626520646966666572656e742066726f6d2063757272656e740000604082015260600190565b6020815281511515602082015260018060a01b0360208301511660408201526000604083015160c060608401526152ba60e084018261489f565b905060ff60608501511660808401526080840151151560a084015260018060401b0360a08501511660c08401528091505092915050565b60006101408083526153058184018e614aca565b90508281036020840152615319818d614aca565b905082810360408401528a5160608252615336606083018261499f565b905060208c0151828203602084015261534f8282614bee565b60408e8101516001600160401b0316940193909352509091506153779050606083018a614869565b8760808301528660a08301528560c08301528460e083015283610100830152826101208301529b9a5050505050505050505050565b60006101208083526153c08184018d614aca565b905082810360208401526153d4818c614aca565b905082810360408401526153e8818b61499f565b6001600160a01b039990991660608401525050608081019590955260a085019390935260c084019190915260e0830152610100909101529392505050565b600061012080835261543a8184018d614aca565b9050828103602084015261544e818c614bee565b6001600160a01b039a909a16604084015250506060810196909652608086019490945260a085019290925260c084015260e08301526101009091015292915050565b9384526001600160a01b03928316602085015291166040830152606082015260800190565b8581526001600160a01b03851660208201526080604082018190526000906154e09083018587614876565b90508260608301529695505050505050565b8481526001600160a01b0384166020820152606060408201819052600090614be49083018486614876565b8581526001600160a01b03851660208201526080604082018190526000906155489083018587614876565b905060ff831660608301529695505050505050565b600061018088835280602084015261558181840161557a8a613e2d565b60ff169052565b61558d60208901613990565b6101a061559c818601836148cb565b6155a860408b01613e00565b91506155b86101c0860183614ce5565b6155c460608b0161391f565b91506155d46101e0860183614869565b6155e060808b0161391f565b91506155f0610200860183614869565b6155fc60a08b0161391f565b915061560c610220860183614869565b60c08a013561024086015260e08a01356102608601526101008a01356102808601526101209150818a01356102a086015261014061564b818c0161391f565b6156596102c0880182614869565b50610160808c01356102e0880152615673858d018d615cae565b95508361030089015261568b61032089018783614876565b96505061569b604088018c6148f8565b6156a78488018b614869565b6156b38288018a614869565b87818801525050505050979650505050505050565b85815260a060208201526156e260a08201865160ff169052565b600060208601516156f660c08401826148cb565b50604086015161570960e0840182614ce5565b50606086015161010061571e81850183614869565b6080880151915061012061573481860184614869565b60a0890151925061014061574a81870185614869565b60c08a01516101608781019190915260e08b015161018080890191909152938b01516101a080890191909152838c01516101c0890152828c01519550936157956101e0890187614869565b818c0151610200890152808c015195505050505080610220850152506157bf61024084018261489f565b9150506157cf6040830186614869565b6060820193909352608001529392505050565b60006101c08a83528060208401526157ff81840161557a8c613e2d565b5061580c60208a01613990565b61581a6101e08401826148cb565b5061582760408a01613e00565b615835610200840182614ce5565b5061584260608a0161391f565b615850610220840182614869565b5061585d60808a0161391f565b61586b610240840182614869565b5061587860a08a0161391f565b615886610260840182614869565b5060c089013561028083015260e08901356102a08301526101008901356102c08301526101206158b7818b0161391f565b6158c56102e0850182614869565b50610140808b01356103008501526101606158e2818d018d615cae565b610180806103208901526158fb61034089018385614876565b965061590a604089018f6148f8565b8c15158887015261591d8589018d614869565b6159298489018c614869565b8981890152505050505050826101a08301529998505050505050505050565b85815260a0602082015261596260a08201865160ff169052565b6000602086015161597660c08401826148cb565b50604086015161598960e0840182614ce5565b50606086015161010061599e81850183614869565b608088015191506101206159b481860184614869565b60a089015192506101406159ca81870185614869565b60c08a01519350610160848188015260e08b015194506101808581890152848c01516101a0890152838c01519550615a066101c0890187614869565b918b01516101e08801528a015161020087019190915292506157bf91505061022084018261489f565b60c08152615a4160c0820188516148cb565b60006020880151615a5560e0840182614ce5565b506040880151610100615a6a81850183614869565b60608a0151610120858101529150615a866101e085018361489f565b915060808a0151615a9b610140860182614869565b5060a08a0151615aaf610160860182614cf2565b5060c08a0151615ac3610180860182614cf2565b5060e08a015115156101a085015289015183820360bf19016101c0850152615aeb828261489f565b92505050615afc6020830188614869565b615b096040830187614869565b8460608301528360808301528260a0830152979650505050505050565b6001600160401b0388811682526001600160a01b0388811660208401528716604083015260e06060830181905260009190615b639084018861489f565b95811660808401529390931660a082015260c0015250949350505050565b6040516101c081016001600160401b0381118282101715615ba457615ba4615e3c565b60405290565b60405161018081016001600160401b0381118282101715615ba457615ba4615e3c565b60405161010081016001600160401b0381118282101715615ba457615ba4615e3c565b60405160a081016001600160401b0381118282101715615ba457615ba4615e3c565b604051606081016001600160401b0381118282101715615ba457615ba4615e3c565b60405161012081016001600160401b0381118282101715615ba457615ba4615e3c565b604051601f8201601f191681016001600160401b0381118282101715615c7f57615c7f615e3c565b604052919050565b60006001600160401b03821115615ca057615ca0615e3c565b50601f01601f191660200190565b6000808335601e19843603018112615cc4578283fd5b83016020810192503590506001600160401b03811115615ce357600080fd5b8036038313156139fc57600080fd5b60008219821115615d0557615d05615e10565b500190565b60006001600160401b03828116848216808303821115615d2c57615d2c615e10565b01949350505050565b60006001600160401b0382811684821681151582840482111615615d5b57615d5b615e10565b02949350505050565b60005b83811015615d7f578181015183820152602001615d67565b83811115615d8e576000848401525b50505050565b600181811c90821680615da857607f821691505b60208210811415613a5f57634e487b7160e01b600052602260045260246000fd5b60006001600160401b0382811680821415615de657615de6615e10565b6001019392505050565b600060ff821660ff811415615e0757615e07615e10565b60010192915050565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60028110615e6257615e62615e26565b50565b6001600160a01b0381168114615e6257600080fd5b6001600160401b0381168114615e6257600080fdfe5cf09499daa9369ceef90c68574078750d8aea7c2b4cdd57ba6a72fc94d4f02acb6602dc4ee50f8b02bbfaa7ecd5a9af2c634c93398d6dfe2dffa763ea71484dd172c12542e63cd0b4344ff6e06391807624c50716e6b6ae6fd16b91a9b9f86aa26469706673582212207110f84fa10d276ff38d69069eddb0aa1d25c3b5937971da00aaf594e60ba9a364736f6c634300080400330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : balanceMigrationSource (address): 0x0000000000000000000000000000000000000000
Arg [1] : feeWallet (address): 0x034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : nativeAssetSymbol (string): MATIC
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [1] : 000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000060
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [4] : 4d41544943000000000000000000000000000000000000000000000000000000
Deployed ByteCode Sourcemap
1455:47592:12:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;17409:10;1078:20:0;17382:59:12;;;;-1:-1:-1;;;17382:59:12;;;;;;;:::i;:::-;;;;;;;;;1455:47592;;;;;45346:212;;;;;;;;;;-1:-1:-1;45346:212:12;;;;;:::i;:::-;;:::i;40121:621::-;;;;;;;;;;-1:-1:-1;40121:621:12;;;;;:::i;:::-;;:::i;37960:1464::-;;;;;;;;;;-1:-1:-1;37960:1464:12;;;;;:::i;:::-;;:::i;15337:85::-;;;;;;;;;;-1:-1:-1;15407:10:12;;-1:-1:-1;;;;;15407:10:12;15337:85;;;;;;;:::i;:::-;;;;;;;;10237:318;;;;;;;;;;-1:-1:-1;10237:318:12;;;;;:::i;:::-;;:::i;27571:500::-;;;;;;;;;;-1:-1:-1;27571:500:12;;;;;:::i;:::-;;:::i;47862:90::-;;;;;;;;;;;;;:::i;12812:267::-;;;;;;;;;;-1:-1:-1;12812:267:12;;;;;:::i;:::-;;:::i;:::-;;;77448:25:39;;;77436:2;77421:18;12812:267:12;77403:76:39;13459:271:12;;;;;;;;;;-1:-1:-1;13459:271:12;;;;;:::i;:::-;;:::i;10915:467::-;;;;;;;;;;-1:-1:-1;10915:467:12;;;;;:::i;:::-;;:::i;25720:236::-;;;;;;;;;;-1:-1:-1;25720:236:12;;;;;:::i;:::-;;:::i;12127:279::-;;;;;;;;;;-1:-1:-1;12127:279:12;;;;;:::i;:::-;;:::i;9420:292::-;;;;;;;;;;-1:-1:-1;9420:292:12;;;;;:::i;:::-;;:::i;18560:390::-;;;;;;;;;;-1:-1:-1;18560:390:12;;;;;:::i;:::-;;:::i;44800:190::-;;;;;;;;;;-1:-1:-1;44800:190:12;;;;;:::i;:::-;;:::i;43321:197::-;;;;;;;;;;;;;:::i;24781:703::-;;;;;;;;;;-1:-1:-1;24781:703:12;;;;;:::i;:::-;;:::i;725:222:31:-;;;;;;;;;;-1:-1:-1;725:222:31;;;;;:::i;:::-;;:::i;45829:209:12:-;;;;;;;;;;-1:-1:-1;45829:209:12;;;;;:::i;:::-;;:::i;16135:101::-;;;;;;;;;;-1:-1:-1;16213:18:12;;-1:-1:-1;;;;;16213:18:12;16135:101;;46631:207;;;;;;;;;;-1:-1:-1;46631:207:12;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;23120:1407::-;;;;;;;;;;-1:-1:-1;23120:1407:12;;;;;:::i;:::-;;:::i;11717:242::-;;;;;;;;;;-1:-1:-1;11717:242:12;;;;;:::i;:::-;;:::i;7744:50::-;;;;;;;;;;-1:-1:-1;7744:50:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;46085:14:39;;46078:22;46060:41;;46132:2;46117:18;;46110:34;;;;46033:18;7744:50:12;46015:135:39;42711:452:12;;;;;;;;;;-1:-1:-1;42711:452:12;;;;;:::i;:::-;;:::i;17487:155::-;;;:::i;1089:74:31:-;;;;;;;;;;;;;:::i;16728:178:12:-;;;;;;;;;;-1:-1:-1;16728:178:12;;;;;:::i;:::-;16830:6;16853:48;;;:37;:48;;;;;;-1:-1:-1;;;;;16853:48:12;;16728:178;;;;-1:-1:-1;;;;;77899:31:39;;;77881:50;;77869:2;77854:18;16728:178:12;77836:101:39;7702:27:12;;;;;;;;;;-1:-1:-1;7702:27:12;;;;-1:-1:-1;;;7702:27:12;;-1:-1:-1;;;;;7702:27:12;;;47236:304;;;;;;;;;;-1:-1:-1;47236:304:12;;;;;:::i;:::-;;:::i;35868:1443::-;;;;;;;;;;-1:-1:-1;35868:1443:12;;;;;:::i;:::-;;:::i;48413:110::-;;;;;;;;;;-1:-1:-1;48413:110:12;;;;;:::i;:::-;;:::i;20473:954::-;;;;;;;;;;-1:-1:-1;20473:954:12;;;;;:::i;:::-;;:::i;28285:304::-;;;;;;;;;;-1:-1:-1;28285:304:12;;;;;:::i;:::-;;:::i;34559:632::-;;;;;;;;;;-1:-1:-1;34559:632:12;;;;;:::i;:::-;;:::i;48746:299::-;;;;;;;;;;-1:-1:-1;48746:299:12;;;;;:::i;:::-;;:::i;41152:636::-;;;;;;;;;;-1:-1:-1;41152:636:12;;;;;:::i;:::-;;:::i;15722:287::-;;;;;;;;;;-1:-1:-1;15722:287:12;;;;;:::i;:::-;;:::i;:::-;;;;;;57031:4:39;57073:3;57062:9;57058:19;57050:27;;57124:6;57118:13;57111:21;57104:29;57093:9;57086:48;57181:4;57173:6;57169:17;57163:24;57222:1;57218;57214:2;57210:10;57206:18;57280:2;57266:12;57262:21;57255:4;57244:9;57240:20;57233:51;57352:4;57344;57336:6;57332:17;57326:24;57322:35;57315:4;57304:9;57300:20;57293:65;57426:2;57418:4;57410:6;57406:17;57400:24;57396:33;57389:4;57378:9;57374:20;57367:63;;;57498:4;57490;57482:6;57478:17;57472:24;57468:35;57461:4;57450:9;57446:20;57439:65;57589:1;57585;57580:3;57576:11;57572:19;57564:4;57556:6;57552:17;57546:24;57542:50;57535:4;57524:9;57520:20;57513:80;57040:559;;;;;44093:317:12;;;;;;;;;;-1:-1:-1;44093:317:12;;;;;:::i;:::-;;:::i;14103:268::-;;;;;;;;;;-1:-1:-1;14103:268:12;;;;;:::i;:::-;;:::i;17936:311::-;;;;;;;;;;-1:-1:-1;17936:311:12;;;;;:::i;:::-;;:::i;29619:1752::-;;;;;;;;;;-1:-1:-1;29619:1752:12;;;;;:::i;:::-;;:::i;42175:292::-;;;;;;;;;;;;;:::i;15128:97::-;;;;;;;;;;-1:-1:-1;15210:10:12;;-1:-1:-1;;;;;15210:10:12;15128:97;;14742:258;;;;;;;;;;-1:-1:-1;14742:258:12;;;;;:::i;:::-;;:::i;32269:1749::-;;;;;;:::i;:::-;;:::i;21757:705::-;;;;;;;;;;-1:-1:-1;21757:705:12;;;;;:::i;:::-;;:::i;718:413:0:-;1078:20;1116:8;;;718:413::o;45346:212:12:-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;45482:71:12::1;::::0;-1:-1:-1;;;45482:71:12;;:39:::1;::::0;::::1;::::0;:71:::1;::::0;:14:::1;::::0;45522:12;;45536:6;;;;45544:8;;45482:71:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;45346:212:::0;;;;:::o;40121:621::-;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;40275:38:::1;;:15;40321:7:::0;40336:9;40353:12:::1;:28;40366:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;-1:-1:-1::0;;;;;40353:28:12;;::::1;::::0;;::::1;::::0;::::1;::::0;;;;;;;;-1:-1:-1;40353:28:12;:35;40407:10:::1;::::0;40426::::1;::::0;40275:213;;-1:-1:-1;;;;;;40275:213:12::1;::::0;;;;;;::::1;::::0;;;;40353:35:::1;::::0;;::::1;::::0;40407:10;;::::1;::::0;40426;;;::::1;::::0;40353:35;;40466:16:::1;::::0;40275:213;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;40500:237:12::1;::::0;-1:-1:-1;40532:14:12::1;::::0;-1:-1:-1;;40532:14:12;;;::::1;::::0;::::1;;:::i;:::-;40554:26;;::::0;::::1;:9:::0;:26:::1;:::i;:::-;40588:27;::::0;;;::::1;::::0;::::1;;:::i;:::-;40623:33;::::0;;;::::1;::::0;::::1;;:::i;:::-;40664:34;::::0;;;::::1;::::0;::::1;;:::i;:::-;40706:25;::::0;;;::::1;::::0;::::1;;:::i;:::-;40500:237;::::0;;-1:-1:-1;;;;;39303:15:39;;;39285:34;;39355:15;;;39350:2;39335:18;;39328:43;39407:15;;;;39387:18;;;39380:43;;;;-1:-1:-1;;;;;39496:15:39;;;39491:2;39476:18;;39469:43;39549:15;;;39543:3;39528:19;;39521:44;39602:15;;;39265:3;39581:19;;39574:44;39234:3;39219:19;40500:237:12::1;;;;;;;;40121:621:::0;;:::o;37960:1464::-;38443:10;38430:24;;;;:12;:24;;;;;:31;;;38429:32;38421:58;;;;-1:-1:-1;;;38421:58:12;;;;;;;:::i;:::-;38579:325;;;;;;;;1340:1:5;38579:325:12;;-1:-1:-1;38579:325:12;;;;;;;;;;;;;38708:10;38579:325;;;;-1:-1:-1;;;;;38579:325:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;38885:9;;;;;;;;;;38579:325;;;;;;;38914:10;;38538:444;;-1:-1:-1;;;38538:444:12;;-1:-1:-1;;38538:31:12;;;;:444;;:15;;38914:10;;;;38958:16;;38538:444;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;38538:444:12;;;;;;;;;;;;:::i;:::-;38486:496;;-1:-1:-1;;;;;;;;;;;39013:13:12;;39011:15;;;;;;;;;-1:-1:-1;;;;;39011:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;39011:15:12;;;;;-1:-1:-1;;;;;39011:15:12;;;;;39034:10;39052:6;:19;;;39079:6;:18;;;39105:6;:26;;;39139:6;:36;;;39183:6;:42;;;38994:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;39276:10:12;39294:5;39315:3;39327:9;39344:14;39366:12;39394:2;39405:8;39243:176;;;;;;;;;;;;;:::i;:::-;;;;;;;;37960:1464;;;;;;;:::o;10237:318::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;10326:13:12::1;::::0;-1:-1:-1;;;10326:13:12;::::1;-1:-1:-1::0;;;;;10326:13:12;;::::1;:45;10311:96;;;::::0;-1:-1:-1;;;10311:96:12;;50931:2:39;10311:96:12::1;::::0;::::1;50913:21:39::0;50970:2;50950:18;;;50943:30;-1:-1:-1;;;50989:18:39;;;50982:50;51049:18;;10311:96:12::1;50903:170:39::0;10311:96:12::1;-1:-1:-1::0;;;;;10428:47:12;;::::1;;;10413:99;;;::::0;-1:-1:-1;;;10413:99:12;;50227:2:39;10413:99:12::1;::::0;::::1;50209:21:39::0;50266:2;50246:18;;;50239:30;-1:-1:-1;;;50285:18:39;;;50278:51;50346:18;;10413:99:12::1;50199:171:39::0;10413:99:12::1;10519:13;:31:::0;;-1:-1:-1;;;;;10519:31:12;;::::1;-1:-1:-1::0;;;10519:31:12::1;-1:-1:-1::0;;;;;;;;10519:31:12;;::::1;::::0;;;::::1;::::0;;10237:318::o;27571:500::-;48155:18;;27772:30;;-1:-1:-1;;;;;48155:18:12;48141:10;:32;48133:67;;;;-1:-1:-1;;;48133:67:12;;47371:2:39;48133:67:12;;;47353:21:39;47410:2;47390:18;;;47383:30;-1:-1:-1;;;47429:18:39;;;47422:52;47491:18;;48133:67:12;47343:172:39;48133:67:12;27879:141:::1;::::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;;;;;27879:141:12;;::::1;::::0;;;;::::1;;::::0;::::1;::::0;;;;::::1;;::::0;;;;;;;;;;;;;;::::1;::::0;;;;;;;;::::1;::::0;;;;;;28028:10:::1;::::0;27835:231;;-1:-1:-1;;;27835:231:12;;:15:::1;:231;::::0;::::1;70337:25:39::0;70440:13;;70436:22;;70416:18;;;70409:50;70499:22;;70495:31;;70475:18;;;70468:59;70577:22;;70570:30;70563:38;70543:18;;;70536:66;70639:22;70618:19;;;70611:51;70703:23;70699:32;;70678:19;;;70671:61;70773:23;70769:32;;70748:19;;;70741:61;28028:10:12;;;::::1;70818:19:39::0;;;70811:44;28028:10:12;70871:19:39;;;70864:35;27835:36:12::1;::::0;::::1;::::0;70309:19:39;;27835:231:12::1;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;27810:256:::0;27571:500;-1:-1:-1;;;;;;;27571:500:12:o;47862:90::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;47915:17:12::1;:32:::0;;-1:-1:-1;;;;;;47915:32:12::1;::::0;;47862:90::o;12812:267::-;12955:119;;-1:-1:-1;;;12955:119:12;;12927:7;;12955:47;;;;:119;;:14;;13012:6;;13028:12;;13050:16;;12955:119;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12942:132;;12812:267;;;;;:::o;13459:271::-;13608:117;;-1:-1:-1;;;13608:117:12;;13580:7;;13608:46;;;;:117;;:14;;13664:6;;13680:11;;;;13701:16;;13608:117;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;13595:130;;13459:271;;;;;;:::o;10915:467::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;474:22:5::1;11035:25:12;:63;11020:121;;;::::0;-1:-1:-1;;;11020:121:12;;53842:2:39;11020:121:12::1;::::0;::::1;53824:21:39::0;53881:2;53861:18;;;53854:30;-1:-1:-1;;;53900:18:39;;;53893:57;53967:18;;11020:121:12::1;53814:177:39::0;11020:121:12::1;11184:23;::::0;;11213:51;;;;11276:101:::1;::::0;;77658:25:39;;;77714:2;77699:18;;77692:34;;;11276:101:12::1;::::0;77631:18:39;11276:101:12::1;77613:119:39::0;25720:236:12;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;25839:112:12::1;::::0;-1:-1:-1;;;25839:112:12;;:35:::1;::::0;::::1;::::0;:112:::1;::::0;:15:::1;::::0;25882:16;;25906:17;;25931:14:::1;::::0;25839:112:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;25720:236:::0;;:::o;12127:279::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;1078:20:0;;12194:68:12::1;;;;-1:-1:-1::0;;;12194:68:12::1;;;;;;;:::i;:::-;12298:18;::::0;-1:-1:-1;;;;;12283:33:12;;::::1;12298:18:::0;::::1;12283:33;;12268:94;;;;-1:-1:-1::0;;;12268:94:12::1;;;;;;;:::i;:::-;12369:18;:32:::0;;-1:-1:-1;;;;;;12369:32:12::1;-1:-1:-1::0;;;;;12369:32:12;;;::::1;::::0;;;::::1;::::0;;12127:279::o;9420:292::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;9507:10:12::1;::::0;-1:-1:-1;;;;;9507:10:12::1;:47:::0;9492:108:::1;;;::::0;-1:-1:-1;;;9492:108:12;;52729:2:39;9492:108:12::1;::::0;::::1;52711:21:39::0;52768:2;52748:18;;;52741:30;52807:32;52787:18;;;52780:60;52857:18;;9492:108:12::1;52701:180:39::0;9492:108:12::1;1078:20:0::0;;9606:69:12::1;;;;-1:-1:-1::0;;;9606:69:12::1;;;;;;;:::i;:::-;9682:10;:25:::0;;-1:-1:-1;;;;;;9682:25:12::1;-1:-1:-1::0;;;;;9682:25:12;;;::::1;::::0;;;::::1;::::0;;9420:292::o;18560:390::-;18674:18;18701:110;18743:11;18764:39;:37;:39::i;:::-;18701:14;;:110;:32;:110::i;:::-;18834:18;;;;18674:137;;-1:-1:-1;;;;;;18826:43:12;18818:72;;;;-1:-1:-1;;;18818:72:12;;;;;;;:::i;:::-;18897:48;18905:10;18917:5;18924:20;18897:7;:48::i;44800:190::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;44925:60:12::1;::::0;-1:-1:-1;;;44925:60:12;;:28:::1;::::0;::::1;::::0;:60:::1;::::0;:14:::1;::::0;44954:12;;44968:6;;;;44976:8;;44925:60:::1;;;:::i;43321:197::-:0;43371:33;43393:10;43371:21;:33::i;:::-;43363:71;;;;-1:-1:-1;;;43363:71:12;;;;;;;:::i;:::-;43461:10;43448:24;;;;:12;:24;;;;;;43441:31;;-1:-1:-1;;43441:31:12;;;;;;;;;43484:29;;;;;;:::i;:::-;;;;;;;;43321:197::o;24781:703::-;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;24866:47:::1;24888:10;:24;;;24866:21;:47::i;:::-;24865:48;24857:74;;;;-1:-1:-1::0;;;24857:74:12::1;;;;;;;:::i;:::-;25149:10;::::0;25169::::1;::::0;25099:174:::1;::::0;-1:-1:-1;;;25099:174:12;;24946:31:::1;::::0;;;;;;;25099:11:::1;::::0;:20:::1;::::0;:174:::1;::::0;25129:10;;-1:-1:-1;;;;;25149:10:12;;::::1;::::0;25169;::::1;::::0;25149;;25213:16:::1;::::0;25239:26:::1;::::0;25099:174;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;::::0;;::::1;-1:-1:-1::0;;25099:174:12::1;::::0;::::1;;::::0;::::1;::::0;;;::::1;::::0;::::1;:::i;:::-;24938:335;;;;;;;;25285:194;25302:10;:24;;;25334:12;25354:11;25373:10;:30;;;25411:24;25443:30;25285:194;;;;;;;;;;;:::i;:::-;;;;;;;;48062:1;;;;24781:703:::0;:::o;725:222:31:-;253:10;-1:-1:-1;;;;;267:6:31;253:20;;245:53;;;;-1:-1:-1;;;245:53:31;;;;;;;:::i;:::-;-1:-1:-1;;;;;794:24:31;::::1;786:59;;;;-1:-1:-1::0;;;786:59:31::1;;;;;;;:::i;:::-;871:6;::::0;-1:-1:-1;;;;;859:18:31;;::::1;871:6:::0;::::1;859:18;;851:67;;;::::0;-1:-1:-1;;;851:67:31;;49122:2:39;851:67:31::1;::::0;::::1;49104:21:39::0;49161:2;49141:18;;;49134:30;49200:34;49180:18;;;49173:62;-1:-1:-1;;;49251:18:39;;;49244:34;49295:19;;851:67:31::1;49094:226:39::0;851:67:31::1;925:6;:17:::0;;-1:-1:-1;;;;;;925:17:31::1;-1:-1:-1::0;;;;;925:17:31;;;::::1;::::0;;;::::1;::::0;;725:222::o;45829:209:12:-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;45933:51:12::1;::::0;-1:-1:-1;;;45933:51:12;;:29:::1;::::0;::::1;::::0;:51:::1;::::0;:14:::1;::::0;45963:12;;45977:6;;;;45933:51:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;45995:38;46012:12;46026:6;;45995:38;;;;;;;;:::i;:::-;;;;;;;;45829:209:::0;;;:::o;46631:207::-;46744:12;;:::i;:::-;46773:60;46806:11;;46773:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;46773:14:12;;:60;-1:-1:-1;46819:13:12;;-1:-1:-1;;46773:32:12;:60::i;23120:1407::-;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;23314:40:::1;23336:3;:17;;;23314:21;:40::i;:::-;23313:41;23298:97;;;;-1:-1:-1::0;;;23298:97:12::1;;;;;;;:::i;:::-;23417:41;23439:4;:18;;;23417:21;:41::i;:::-;23416:42;23401:99;;;;-1:-1:-1::0;;;23401:99:12::1;;;;;;;:::i;:::-;23583:10;::::0;23507:262:::1;::::0;-1:-1:-1;;;23507:262:12;;:7:::1;::::0;:26:::1;::::0;:262:::1;::::0;23541:3;;23552:4;;23564:11;;-1:-1:-1;;;;;23583:10:12;;::::1;::::0;;;23623:15:::1;::::0;23646:16:::1;::::0;23670:21:::1;::::0;23699:19:::1;::::0;23726:37:::1;::::0;23507:262;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;;23808:17:12::1;::::0;;::::1;::::0;23833:18;;::::1;::::0;23859:26;;:42;;23909:43:::1;::::0;;::::1;::::0;23960:50:::1;::::0;;::::1;::::0;24018:51:::1;::::0;;::::1;::::0;24077:21;;::::1;::::0;:45;;::::1;::::0;24130:46;;;::::1;::::0;23781:741:::1;::::0;23808:17;23833:18;23909:43;;24018:51;24077:45;24184:106:::1;24077:45:::0;23960:50;24184:106:::1;:::i;:::-;24360:11;:21;;;:46;;;24298:11;:26;;;:51;;;:108;;;;:::i;:::-;24454:13;24414:26:::0;;:36:::1;;::::0;:53:::1;::::0;::::1;;;;-1:-1:-1::0;;;24414:53:12::1;;;;;;;;;;:102;;24503:13;24414:102;;;24478:14;24414:102;23781:741;;;;;;;;;;;;;;;;:::i;11717:242::-:0;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;-1:-1:-1;;;;;11792:28:12;::::1;11784:63;;;;-1:-1:-1::0;;;11784:63:12::1;;;;;;;:::i;:::-;11877:10;::::0;-1:-1:-1;;;;;11861:26:12;;::::1;11877:10:::0;::::1;11861:26;;11853:69;;;;-1:-1:-1::0;;;11853:69:12::1;;;;;;;:::i;:::-;11929:10;:25:::0;;-1:-1:-1;;;;;;11929:25:12::1;-1:-1:-1::0;;;;;11929:25:12;;;::::1;::::0;;;::::1;::::0;;11717:242::o;42711:452::-;42778:33;42800:10;42778:21;:33::i;:::-;42770:71;;;;-1:-1:-1;;;42770:71:12;;;;;;;:::i;:::-;42978:10;;42922:124;;-1:-1:-1;;;42922:124:12;;-1:-1:-1;;;;;42976:15:39;;;43022:16:12;42922:124;;;42958:34:39;;;;42978:10:12;;43008:18:39;;;43001:43;42978:10:12;43060:18:39;;;43053:34;43103:18;;;43096:34;;;;42877:36:12;;42922:11;;:24;;42892:19:39;;42922:124:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;43058:100;;;43085:10;42446:34:39;;-1:-1:-1;;;;;42516:15:39;;42511:2;42496:18;;42489:43;-1:-1:-1;;;;;42568:31:39;;42548:18;;;42541:59;;;;42877:169:12;;-1:-1:-1;43058:100:12;;42396:2:39;42381:18;43058:100:12;42363:243:39;17487:155:12;17534:103;17549:10;17567:47;:14;17609:3;17567:33;:47::i;:::-;17622:9;17534:7;:103::i;:::-;17487:155::o;1089:74:31:-;253:10;-1:-1:-1;;;;;267:6:31;253:20;;245:53;;;;-1:-1:-1;;;245:53:31;;;;;;;:::i;:::-;1154:3:::1;1137:21:::0;;-1:-1:-1;;;;;;1137:21:31::1;::::0;;1089:74::o;47236:304:12:-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;-1:-1:-1;;;;;47321:35:12;::::1;47313:70;;;;-1:-1:-1::0;;;47313:70:12::1;;;;;;;:::i;:::-;47427:17;::::0;-1:-1:-1;;;;;47404:40:12;;::::1;47427:17:::0;::::1;47404:40;;47389:101;;;;-1:-1:-1::0;;;47389:101:12::1;;;;;;;:::i;:::-;47496:17;:39:::0;;-1:-1:-1;;;;;;47496:39:12::1;-1:-1:-1::0;;;;;47496:39:12;;;::::1;::::0;;;::::1;::::0;;47236:304::o;35868:1443::-;36361:10;36348:24;;;;:12;:24;;;;;:31;;;36347:32;36339:58;;;;-1:-1:-1;;;36339:58:12;;;;;;;:::i;:::-;36497:314;;;;;;;;1340:1:5;36497:314:12;;-1:-1:-1;36497:314:12;;;;;;;;;;;;;36626:10;36497:314;;;;-1:-1:-1;;;;;36497:314:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;36792:9;;;;;;;;;;36497:314;;;;;;;36821:10;;36456:433;;-1:-1:-1;;;36456:433:12;;-1:-1:-1;;36456:31:12;;;;:433;;:15;;36821:10;;;;36865:16;;36456:433;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;36456:433:12;;;;;;;;;;;;:::i;:::-;36404:485;;-1:-1:-1;;;;;;;;;;;36920:13:12;;36918:15;;;;;;;;;-1:-1:-1;;;;;36918:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;36918:15:12;;;;;-1:-1:-1;;;;;36918:15:12;;;;;36941:10;36959:6;:19;;;36986:6;:18;;;37012:6;:26;;;37046:6;:36;;;37090:6;:42;;;36901:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;37183:10:12;37201:6;37215;37229:9;37246:10;37264;37282:2;37292:8;37150:156;;;;;;;;;;;;;:::i;:::-;;;;;;;;35868:1443;;;;;;;;:::o;48413:110::-;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;48507:10:12::1;::::0;48474:44:::1;::::0;-1:-1:-1;;;48474:44:12;;:13:::1;::::0;:18:::1;::::0;:44:::1;::::0;48493:12;;-1:-1:-1;;;;;48507:10:12;;::::1;::::0;48474:44:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;48413:110:::0;:::o;20473:954::-;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;20641:40:::1;20663:3;:17;;;20641:21;:40::i;:::-;20640:41;20625:97;;;;-1:-1:-1::0;;;20625:97:12::1;;;;;;;:::i;:::-;20744:41;20766:4;:18;;;20744:21;:41::i;:::-;20743:42;20728:99;;;;-1:-1:-1::0;;;20728:99:12::1;;;;;;;:::i;:::-;20916:10;::::0;20834:245:::1;::::0;-1:-1:-1;;;20834:245:12;;:7:::1;::::0;:29:::1;::::0;:245:::1;::::0;20871:3;;20882:4;;20894:14;;-1:-1:-1;;;;;20916:10:12;;::::1;::::0;;;20956:16:::1;::::0;20980:21:::1;::::0;21009:19:::1;::::0;21036:37:::1;::::0;20834:245;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;21121:17:12::1;::::0;;::::1;::::0;21146:18;;::::1;::::0;21172:30;;21210:31:::1;::::0;::::1;::::0;21249:38:::1;::::0;::::1;::::0;21295:39:::1;::::0;::::1;::::0;21091:331:::1;::::0;-1:-1:-1;21172:30:12::1;21342:14;:24;;;:41;;;;;;-1:-1:-1::0;;;21342:41:12::1;;;;;;;;;;:74;;21403:13;21342:74;;;21386:14;21342:74;21091:331;;;;;;;;;;;;:::i;28285:304::-:0;360:6:31;;-1:-1:-1;;;;;360:6:31;346:10;:20;338:53;;;;-1:-1:-1;;;338:53:31;;;;;;;:::i;:::-;28411:97:12::1;::::0;-1:-1:-1;;;28411:97:12;;:15:::1;:97;::::0;::::1;64193:25:39::0;-1:-1:-1;;;;;64292:15:39;;;64272:18;;;64265:43;64344:15;;64324:18;;;64317:43;28411:42:12::1;::::0;::::1;::::0;64166:18:39;;28411:97:12::1;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;28520:64;28548:16;28566:17;28520:64;;;;;;;:::i;34559:632::-:0;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;34721:38:::1;34743:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;34721:21;:38::i;:::-;34720:39;34712:73;;;::::0;-1:-1:-1;;;34712:73:12;;48075:2:39;34712:73:12::1;::::0;::::1;48057:21:39::0;48114:2;48094:18;;;48087:30;-1:-1:-1;;;48133:18:39;;;48126:51;48194:18;;34712:73:12::1;48047:171:39::0;34712:73:12::1;34868:10;::::0;34894::::1;::::0;34792:143:::1;::::0;-1:-1:-1;;;34792:143:12;;:35:::1;::::0;::::1;::::0;:143:::1;::::0;:15:::1;::::0;34835:8;;34851:9;;-1:-1:-1;;;;;34868:10:12;;::::1;::::0;34894;;::::1;::::0;34913:16:::1;::::0;34792:143;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;34947:239:12::1;::::0;-1:-1:-1;34980:15:12::1;::::0;-1:-1:-1;;34980:15:12;;;::::1;::::0;::::1;;:::i;48746:299::-:0;48866:10;;48855:37;;;-1:-1:-1;;;48855:37:12;;;;48829:23;;-1:-1:-1;;;;;48866:10:12;;48855:35;;:37;;;;;;;;;;;;;;48866:10;48855:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;48829:63;-1:-1:-1;48906:10:12;-1:-1:-1;;;;;48906:29:12;;;48898:64;;;;-1:-1:-1;;;48898:64:12;;54198:2:39;48898:64:12;;;54180:21:39;54237:2;54217:18;;;54210:30;-1:-1:-1;;;54256:18:39;;;54249:52;54318:18;;48898:64:12;54170:172:39;48898:64:12;-1:-1:-1;;;;;;48976:50:12;;;:42;:50;;;:16;:50;;;;;;;;:64;;;;;;;;;;;48969:71;;-1:-1:-1;;;;;;48969:71:12;;;48746:299::o;41152:636::-;41269:33;41291:10;41269:21;:33::i;:::-;41261:71;;;;-1:-1:-1;;;41261:71:12;;;;;;;:::i;:::-;41552:10;;41443:154;;-1:-1:-1;;;41443:154:12;;:15;41573:16;41443:154;;;64713:25:39;;;;-1:-1:-1;;;;;64812:15:39;;;64792:18;;;64785:43;64864:15;;;64844:18;;;64837:43;41552:10:12;;;64896:18:39;;;64889:43;64948:19;;;64941:35;;;;41347:36:12;;;;41443:35;;;;64685:19:39;;41443:154:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;41609:174;;;41643:10;38675:34:39;;-1:-1:-1;;;;;38745:15:39;;;38740:2;38725:18;;38718:43;38797:15;;38777:18;;;38770:43;-1:-1:-1;;;;;38886:15:39;;;38881:2;38866:18;;38859:43;38939:15;;38933:3;38918:19;;38911:44;41609:174:12;;38886:15:39;;-1:-1:-1;38939:15:39;;-1:-1:-1;41609:174:12;;;;;;38624:3:39;41609:174:12;;;41152:636;;;;:::o;15722:287::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15894:110:12;:15;15953:16;15979:17;15894:49;:110::i;:::-;15881:123;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;15881:123:12;;;;;;;;;-1:-1:-1;;;15881:123:12;;;;;;;;;;;-1:-1:-1;;;15881:123:12;;;;;;;;;-1:-1:-1;;;15881:123:12;;;;;;;;;;;;;;-1:-1:-1;;;;;15881:123:12;;;;;;-1:-1:-1;15722:287:12;;;;:::o;44093:317::-;44262:23;;44214:72;;-1:-1:-1;;;44214:72:12;;:19;:72;;;47021:25:39;-1:-1:-1;;;;;47082:32:39;;47062:18;;;47055:60;47131:18;;;47124:34;;;;44154:20:12;;;;44214:40;;;;46994:18:39;;44214:72:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;44298:107;;;44327:10;45371:51:39;;-1:-1:-1;;;;;45458:32:39;;45453:2;45438:18;;45431:60;-1:-1:-1;;;;;45527:31:39;;45507:18;;;45500:59;;;;45590:2;45575:18;;45568:34;;;44153:133:12;;-1:-1:-1;44153:133:12;-1:-1:-1;44298:107:12;;45358:3:39;45343:19;44298:107:12;45325:283:39;14103:268:12;14254:112;;-1:-1:-1;;;14254:112:12;;-1:-1:-1;;;;;42086:15:39;;;14342:16:12;14254:112;;;42068:34:39;;;;42138:15;;;42118:18;;;42111:43;42170:18;;;42163:34;14225:6:12;;14254:13;;:40;;42003:18:39;;14254:112:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;17936:311::-;18046:18;18067:47;:14;18101:12;18067:33;:47::i;:::-;18046:68;-1:-1:-1;;;;;;18129:37:12;;18121:66;;;;-1:-1:-1;;;18121:66:12;;;;;;;:::i;29619:1752::-;30141:10;30128:24;;;;:12;:24;;;;;:31;;;30127:32;30119:58;;;;-1:-1:-1;;;30119:58:12;;;;;;;:::i;:::-;30275:337;;;;;;;;1340:1:5;30275:337:12;;-1:-1:-1;30275:337:12;;;;;;;;;;;;;30405:10;30275:337;;;;-1:-1:-1;;;;;30275:337:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30593:9;;;;;;;;;;30275:337;;;;;;;30622:10;;30237:453;;-1:-1:-1;;;30237:453:12;;-1:-1:-1;;30237:28:12;;;;:453;;:15;;30622:10;;;;30666:16;;30237:453;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;30237:453:12;;;;;;;;;;;;:::i;:::-;30184:506;;-1:-1:-1;;;;;;;;;;;30721:13:12;;30719:15;;;;;;;;;-1:-1:-1;;;;;30719:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30719:15:12;;;;;-1:-1:-1;;;;;30719:15:12;;;;;30742:10;30760:6;30774;:19;;;30801:6;:27;;;30836:6;:37;;;30881:6;:43;;;30702:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;30961:13:12;;30959:15;;;;;;;;;-1:-1:-1;;;;;30959:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30959:15:12;;;;;-1:-1:-1;;;;;30959:15:12;;;;;30982:10;31000:6;31014;:19;;;31041:6;:27;;;31076:6;:37;;;31121:6;:43;;;30942:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;31216:10:12;31234:6;31248;31262:14;31284;31306:10;31324;31342:2;31352:8;31182:184;;;;;;;;;;;;;;:::i;:::-;;;;;;;;29619:1752;;;;;;;;;:::o;42175:292::-;42234:10;42221:24;;;;:12;:24;;;;;:31;;;42220:32;42212:66;;;;-1:-1:-1;;;42212:66:12;;51280:2:39;42212:66:12;;;51262:21:39;51319:2;51299:18;;;51292:30;-1:-1:-1;;;51338:18:39;;;51331:51;51399:18;;42212:66:12;51252:171:39;42212:66:12;42312:74;;;;;;;;42330:4;42312:74;;;;;;42357:23;;42342:12;:38;;;;:::i;:::-;42312:74;;42298:10;42285:24;;;;:12;:24;;;;;;;;:101;;;;-1:-1:-1;;42285:101:12;;;;;;;;;;-1:-1:-1;42285:101:12;;;;;;;42438:23;;42398:64;;42298:10;42423:38;;:12;:38;:::i;:::-;42398:64;;;-1:-1:-1;;;;;45805:32:39;;;45787:51;;45869:2;45854:18;;45847:34;;;;45760:18;42398:64:12;45742:145:39;14742:258:12;14884:111;;-1:-1:-1;;;14884:111:12;;14857:6;;14884:40;;;;:111;;:14;;14934:6;;14950:11;;;;14971:16;;14884:111;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;32269:1749::-;32763:10;32750:24;;;;:12;:24;;;;;:31;;;32749:32;32741:58;;;;-1:-1:-1;;;32741:58:12;;;;;;;:::i;:::-;32897:347;;;;;;;;1340:1:5;32897:347:12;;-1:-1:-1;32897:347:12;;;;;;;;;;;;;33027:10;32897:347;;;;-1:-1:-1;;;;;32897:347:12;;;;;;;;;;;;;;;;;;;33120:9;32897:347;;;;;;;;;;;;;;;;;;;;;;;;;;;;;33225:9;;;;;;;;;;32897:347;;;;;;;33254:10;;32859:463;;-1:-1:-1;;;32859:463:12;;-1:-1:-1;;32859:28:12;;;;:463;;:15;;33254:10;;;;33298:16;;32859:463;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;32859:463:12;;;;;;;;;;;;:::i;:::-;32806:516;;-1:-1:-1;;;;;;;;;;;33353:13:12;;33351:15;;;;;;;;;-1:-1:-1;;;;;33351:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33351:15:12;;;;;-1:-1:-1;;;;;33351:15:12;;;;;33374:10;33392:5;33405:6;:19;;;33432:6;:27;;;33467:6;:37;;;33512:6;:43;;;33334:227;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33592:13:12;;33590:15;;;;;;;;;-1:-1:-1;;;;;33590:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33590:15:12;;;;;-1:-1:-1;;;;;33590:15:12;;;;;33613:10;33639:3;33651:6;:19;;;33678:6;:27;;;33713:6;:37;;;33758:6;:43;;;33573:234;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33853:10:12;33871:5;33892:3;33904:18;33930:9;33947:14;33969:12;33989:2;33999:8;33819:194;;;;;;;;;;;;;;:::i;21757:705::-;48010:17;;-1:-1:-1;;;;;48010:17:12;47996:10;:31;47988:68;;;;-1:-1:-1;;;47988:68:12;;;;;;;:::i;:::-;21887:42:::1;21909:5;:19;;;21887:21;:42::i;:::-;21886:43;21871:101;;;::::0;-1:-1:-1;;;21871:101:12;;55254:2:39;21871:101:12::1;::::0;::::1;55236:21:39::0;55293:2;55273:18;;;55266:30;-1:-1:-1;;;55312:18:39;;;55305:57;55379:18;;21871:101:12::1;55226:177:39::0;21871:101:12::1;22041:10;::::0;21979:248:::1;::::0;-1:-1:-1;;;21979:248:12;;:7:::1;::::0;:24:::1;::::0;:248:::1;::::0;22011:5;;22024:9;;-1:-1:-1;;;;;22041:10:12::1;::::0;;;22081:15:::1;::::0;22104:16:::1;::::0;22128:21:::1;::::0;22157:19:::1;::::0;22184:37:::1;::::0;21979:248;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;22264:19:12::1;::::0;;::::1;::::0;22291:25;;22324:26:::1;::::0;::::1;::::0;22358:33:::1;::::0;;::::1;::::0;22399:34:::1;::::0;::::1;::::0;22441:10;;::::1;::::0;22239:218;;::::1;::::0;-1:-1:-1;22239:218:12::1;::::0;22358:33;;22399:34;22441:10;22239:218:::1;:::i;7798:157:1:-:0;7856:6;7893:4;7911:39;7893:4;7918:15;7911:39;:::i;:::-;7904:46;;;7798:157;:::o;7066:714::-;7198:12;;:::i;:::-;7222:45;7236:4;:22;;7222:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7260:6;7222:13;:45::i;:::-;7218:108;;;7284:35;7296:4;:22;;7284:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;:35::i;:::-;7277:42;;;;7218:108;7332:18;;:::i;:::-;7397:1;7360:4;:19;;7380:6;7360:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;:38;7356:296;;;7413:7;7408:238;7430:4;:19;;7450:6;7430:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;7426:38;;;;7408:238;;;7553:13;-1:-1:-1;;;;;7496:70:1;:4;:19;;7516:6;7496:27;;;;;;:::i;:::-;;;;;;;;;;;;;7524:1;7496:30;;;;;;;;-1:-1:-1;;;7496:30:1;;;;;;;;;;;;;;;;;;;;;;:53;;;;;;-1:-1:-1;;;;;7496:53:1;:70;7481:157;;7597:4;:19;;7617:6;7597:27;;;;;;:::i;:::-;;;;;;;;;;;;;7625:1;7597:30;;;;;;;;-1:-1:-1;;;7597:30:1;;;;;;;;;;;;;;;;;;7589:38;;;;;;;;7597:30;;;;;;;7589:38;;;;;;;;;-1:-1:-1;;;;;7589:38:1;;;;;;;;;;;;;;;;;;;7597:30;7589:38;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;7589:38:1;;;-1:-1:-1;;7589:38:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7589:38:1;;;;;;;-1:-1:-1;7481:157:1;7466:3;;;;:::i;:::-;;;;7408:238;;;;7356:296;7672:12;;:33;;;;;7688:5;:17;;;7672:33;7657:99;;;;-1:-1:-1;;;7657:99:1;;51630:2:39;7657:99:1;;;51612:21:39;51669:2;51649:18;;;51642:30;51708:34;51688:18;;;51681:62;-1:-1:-1;;;51759:18:39;;;51752:33;51802:19;;7657:99:1;51602:225:39;18954:1043:12;19151:13;;-1:-1:-1;;;19151:13:12;;-1:-1:-1;;;;;19151:13:12;;;:45;;19143:75;;;;-1:-1:-1;;;19143:75:12;;52034:2:39;19143:75:12;;;52016:21:39;52073:2;52053:18;;;52046:30;-1:-1:-1;;;52092:18:39;;;52085:47;52149:18;;19143:75:12;52006:167:39;19143:75:12;-1:-1:-1;;;;;19456:20:12;;;;;;:12;:20;;;;;:27;;;19455:28;19447:54;;;;-1:-1:-1;;;19447:54:12;;;;;;;:::i;:::-;19726:10;;19637:133;;-1:-1:-1;;;19637:133:12;;19516:21;;;;;;19637:10;;:18;;:133;;19665:6;;19681:5;;19696:20;;-1:-1:-1;;;;;19726:10:12;;19746:16;;19637:133;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19777:13;:15;;19508:262;;-1:-1:-1;19508:262:12;;-1:-1:-1;19508:262:12;-1:-1:-1;;;;19777:15:12;;;-1:-1:-1;;;;;19777:15:12;;:13;:15;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;19777:15:12;;;;;-1:-1:-1;;;;;19777:15:12;;;;;;;-1:-1:-1;;;;;;;;;;;19821:13:12;;;;;;;;;-1:-1:-1;;;;;19821:13:12;19842:6;19856:5;:18;;;19882:5;:12;;;19902:14;19924:24;19956:30;19804:188;;;;;;;;;;;;:::i;:::-;;;;;;;;18954:1043;;;;;;:::o;43522:202::-;-1:-1:-1;;;;;43630:20:12;;43592:4;43630:20;;;:12;:20;;;;;43663:11;;;;:56;;;;-1:-1:-1;43678:25:12;;;43707:12;-1:-1:-1;43678:41:12;;43656:63;-1:-1:-1;;43522:202:12:o;6291:411:1:-;6398:12;;:::i;:::-;-1:-1:-1;;;;;6424:28:1;;6420:91;;6469:35;6481:4;:22;;6469:35;;;;;:::i;:::-;6462:42;;;;6420:91;-1:-1:-1;;;;;6538:34:1;;;6517:18;6538:34;;;;;;;;;;;6517:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:18;;:55;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;6517:55:1;;;-1:-1:-1;;6517:55:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;6517:55:1;;;;;;6593:12;;;;-1:-1:-1;6593:33:1;;;;;6609:5;:17;;;6593:33;6578:100;;;;-1:-1:-1;;;6578:100:1;;53088:2:39;6578:100:1;;;53070:21:39;53127:2;53107:18;;;53100:30;53166:34;53146:18;;;53139:62;-1:-1:-1;;;53217:18:39;;;53210:34;53261:19;;6578:100:1;53060:226:39;13703:315:25;-1:-1:-1;;;;;13901:39:25;;;13860:26;13901:39;;;:21;;;:39;;;;;;;;:58;;;;;;;;;13973:11;;;;13965:48;;;;-1:-1:-1;;;13965:48:25;;48769:2:39;13965:48:25;;;48751:21:39;48808:2;48788:18;;;48781:30;-1:-1:-1;;;48827:18:39;;;48820:54;48891:18;;13965:48:25;48741:174:39;8334:181:1;8425:4;8507:1;8490:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8480:30;;;;;;8473:1;8456:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8446:30;;;;;;:64;8439:71;;8334:181;;;;:::o;8061:179::-;8149:12;;:::i;:::-;-1:-1:-1;8178:57:1;;;;;;;;8184:4;8178:57;;;-1:-1:-1;8178:57:1;;;;;;;;;;;;;8223:2;8178:57;;;;;;;;;;;;;;;;8061:179::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:134:39:-;82:20;;111:31;82:20;111:31;:::i;:::-;63:85;;;:::o;153:160::-;218:20;;274:13;;267:21;257:32;;247:2;;303:1;300;293:12;318:482;360:5;413:3;406:4;398:6;394:17;390:27;380:2;;435:5;428;421:20;380:2;475:6;462:20;506:48;522:31;550:2;522:31;:::i;:::-;506:48;:::i;:::-;579:2;570:7;563:19;625:3;618:4;613:2;605:6;601:15;597:26;594:35;591:2;;;646:5;639;632:20;591:2;715;708:4;700:6;696:17;689:4;680:7;676:18;663:55;738:16;;;756:4;734:27;727:42;;;;742:7;370:430;-1:-1:-1;;370:430:39:o;805:167::-;897:20;;946:1;936:12;;926:2;;962:1;959;952:12;977:165;1067:20;;1116:1;1106:12;;1096:2;;1132:1;1129;1122:12;1147:150;1222:20;;1271:1;1261:12;;1251:2;;1287:1;1284;1277:12;1302:376;1354:8;1364:6;1418:3;1411:4;1403:6;1399:17;1395:27;1385:2;;1443:8;1433;1426:26;1385:2;-1:-1:-1;1473:20:39;;-1:-1:-1;;;;;1505:30:39;;1502:2;;;1555:8;1545;1538:26;1502:2;1599:4;1591:6;1587:17;1575:29;;1651:3;1644:4;1635:6;1627;1623:19;1619:30;1616:39;1613:2;;;1668:1;1665;1658:12;1613:2;1375:303;;;;;:::o;1683:445::-;1737:5;1790:3;1783:4;1775:6;1771:17;1767:27;1757:2;;1812:5;1805;1798:20;1757:2;1845:6;1839:13;1876:48;1892:31;1920:2;1892:31;:::i;1876:48::-;1949:2;1940:7;1933:19;1995:3;1988:4;1983:2;1975:6;1971:15;1967:26;1964:35;1961:2;;;2016:5;2009;2002:20;1961:2;2033:64;2094:2;2087:4;2078:7;2074:18;2067:4;2059:6;2055:17;2033:64;:::i;:::-;2115:7;1747:381;-1:-1:-1;;;;1747:381:39:o;2133:181::-;2210:5;2255:3;2246:6;2241:3;2237:16;2233:26;2230:2;;;2276:5;2269;2262:20;2230:2;-1:-1:-1;2302:6:39;2220:94;-1:-1:-1;2220:94:39:o;2319:1573::-;2380:5;2428:6;2416:9;2411:3;2407:19;2403:32;2400:2;;;2452:5;2445;2438:20;2400:2;2478:22;;:::i;:::-;2469:31;-1:-1:-1;2523:23:39;;-1:-1:-1;;;;;2595:14:39;;;2592:2;;;2622:1;2619;2612:12;2592:2;2649:45;2690:3;2681:6;2670:9;2666:22;2649:45;:::i;:::-;2642:5;2635:60;2748:2;2737:9;2733:18;2720:32;2704:48;;2777:2;2767:8;2764:16;2761:2;;;2793:1;2790;2783:12;2761:2;;2829:47;2872:3;2861:8;2850:9;2846:24;2829:47;:::i;:::-;2824:2;2817:5;2813:14;2806:71;;2909:38;2943:2;2932:9;2928:18;2909:38;:::i;:::-;2904:2;2897:5;2893:14;2886:62;2980:38;3014:2;3003:9;2999:18;2980:38;:::i;:::-;2975:2;2968:5;2964:14;2957:62;3052:38;3085:3;3074:9;3070:19;3052:38;:::i;:::-;3046:3;3039:5;3035:15;3028:63;3124:38;3157:3;3146:9;3142:19;3124:38;:::i;:::-;3118:3;3111:5;3107:15;3100:63;3196:38;3229:3;3218:9;3214:19;3196:38;:::i;:::-;3190:3;3183:5;3179:15;3172:63;3268:38;3301:3;3290:9;3286:19;3268:38;:::i;:::-;3262:3;3255:5;3251:15;3244:63;3326:3;3361:38;3395:2;3384:9;3380:18;3361:38;:::i;:::-;3345:14;;;3338:62;3419:3;3454:38;3473:18;;;3454:38;:::i;:::-;3438:14;;;3431:62;3512:3;3547:37;3565:18;;;3547:37;:::i;:::-;3531:14;;;3524:61;3604:3;3639:37;3657:18;;;3639:37;:::i;:::-;3623:14;;;3616:61;3696:3;3731:37;3749:18;;;3731:37;:::i;:::-;3715:14;;;3708:61;3788:3;3823:62;3866:18;;;3823:62;:::i;:::-;3807:14;;;3800:86;3811:5;2390:1502;-1:-1:-1;;2390:1502:39:o;3897:1613::-;3949:5;3997:6;3985:9;3980:3;3976:19;3972:32;3969:2;;;4021:5;4014;4007:20;3969:2;4047:22;;:::i;:::-;4038:31;;4092:27;4109:9;4092:27;:::i;:::-;4085:5;4078:42;4152:38;4186:2;4175:9;4171:18;4152:38;:::i;:::-;4147:2;4140:5;4136:14;4129:62;4223:38;4257:2;4246:9;4242:18;4223:38;:::i;:::-;4218:2;4211:5;4207:14;4200:62;4294:45;4335:2;4324:9;4320:18;4294:45;:::i;:::-;4289:2;4282:5;4278:14;4271:69;4373:63;4431:3;4420:9;4416:19;4373:63;:::i;:::-;4367:3;4360:5;4356:15;4349:88;4470:38;4503:3;4492:9;4488:19;4470:38;:::i;:::-;4464:3;4457:5;4453:15;4446:63;4542:36;4573:3;4562:9;4558:19;4542:36;:::i;:::-;4536:3;4529:5;4525:15;4518:61;4612:38;4645:3;4634:9;4630:19;4612:38;:::i;:::-;4606:3;4599:5;4595:15;4588:63;4670:3;4705:37;4738:2;4727:9;4723:18;4705:37;:::i;:::-;4689:14;;;4682:61;4762:3;4801:18;;;4788:32;-1:-1:-1;;;;;4869:14:39;;;4866:2;;;4896:1;4893;4886:12;4866:2;4932:45;4973:3;4964:6;4953:9;4949:22;4932:45;:::i;:::-;4927:2;4920:5;4916:14;4909:69;4997:3;4987:13;;5032:60;5088:2;5077:9;5073:18;5032:60;:::i;:::-;5027:2;5020:5;5016:14;5009:84;5112:3;5102:13;;5147:60;5203:2;5192:9;5188:18;5147:60;:::i;:::-;5142:2;5135:5;5131:14;5124:84;5227:3;5217:13;;5262:37;5295:2;5284:9;5280:18;5262:37;:::i;:::-;5257:2;5250:5;5246:14;5239:61;5319:3;5309:13;;5375:2;5364:9;5360:18;5347:32;5331:48;;5404:2;5394:8;5391:16;5388:2;;;5420:1;5417;5410:12;5388:2;;5456:47;5499:3;5488:8;5477:9;5473:24;5456:47;:::i;:::-;5451:2;5444:5;5440:14;5433:71;;;3959:1551;;;;:::o;5515:1357::-;5571:5;5619:6;5607:9;5602:3;5598:19;5594:32;5591:2;;;5643:5;5636;5629:20;5591:2;5669:22;;:::i;:::-;5660:31;-1:-1:-1;5714:23:39;;-1:-1:-1;;;;;5786:14:39;;;5783:2;;;5813:1;5810;5803:12;5783:2;5840:45;5881:3;5872:6;5861:9;5857:22;5840:45;:::i;:::-;5833:5;5826:60;5939:2;5928:9;5924:18;5911:32;5895:48;;5968:2;5958:8;5955:16;5952:2;;;5984:1;5981;5974:12;5952:2;;6020:47;6063:3;6052:8;6041:9;6037:24;6020:47;:::i;:::-;6015:2;6008:5;6004:14;5997:71;;6100:38;6134:2;6123:9;6119:18;6100:38;:::i;:::-;6095:2;6088:5;6084:14;6077:62;6171:38;6205:2;6194:9;6190:18;6171:38;:::i;:::-;6166:2;6159:5;6155:14;6148:62;6243:38;6276:3;6265:9;6261:19;6243:38;:::i;:::-;6237:3;6230:5;6226:15;6219:63;6315:38;6348:3;6337:9;6333:19;6315:38;:::i;:::-;6309:3;6302:5;6298:15;6291:63;6387:38;6420:3;6409:9;6405:19;6387:38;:::i;:::-;6381:3;6374:5;6370:15;6363:63;6459:38;6492:3;6481:9;6477:19;6459:38;:::i;:::-;6453:3;6446:5;6442:15;6435:63;6517:3;6552:37;6585:2;6574:9;6570:18;6552:37;:::i;:::-;6536:14;;;6529:61;6609:3;6644:37;6662:18;;;6644:37;:::i;:::-;6628:14;;;6621:61;6701:3;6736:37;6754:18;;;6736:37;:::i;:::-;6720:14;;;6713:61;6793:3;6828:37;6846:18;;;6828:37;:::i;6877:173::-;6945:20;;-1:-1:-1;;;;;6994:31:39;;6984:42;;6974:2;;7040:1;7037;7030:12;7055:132;7122:20;;7151:30;7122:20;7151:30;:::i;7192:136::-;7270:13;;7292:30;7270:13;7292:30;:::i;7333:156::-;7399:20;;7459:4;7448:16;;7438:27;;7428:2;;7479:1;7476;7469:12;7494:257;7553:6;7606:2;7594:9;7585:7;7581:23;7577:32;7574:2;;;7627:6;7619;7612:22;7574:2;7671:9;7658:23;7690:31;7715:5;7690:31;:::i;7756:261::-;7826:6;7879:2;7867:9;7858:7;7854:23;7850:32;7847:2;;;7900:6;7892;7885:22;7847:2;7937:9;7931:16;7956:31;7981:5;7956:31;:::i;8022:398::-;8090:6;8098;8151:2;8139:9;8130:7;8126:23;8122:32;8119:2;;;8172:6;8164;8157:22;8119:2;8216:9;8203:23;8235:31;8260:5;8235:31;:::i;:::-;8285:5;-1:-1:-1;8342:2:39;8327:18;;8314:32;8355:33;8314:32;8355:33;:::i;:::-;8407:7;8397:17;;;8109:311;;;;;:::o;8425:827::-;8534:6;8542;8550;8558;8566;8574;8627:3;8615:9;8606:7;8602:23;8598:33;8595:2;;;8649:6;8641;8634:22;8595:2;8693:9;8680:23;8712:31;8737:5;8712:31;:::i;:::-;8762:5;-1:-1:-1;8819:2:39;8804:18;;8791:32;8832:33;8791:32;8832:33;:::i;:::-;8884:7;-1:-1:-1;8910:35:39;8941:2;8926:18;;8910:35;:::i;:::-;8900:45;;8992:2;8981:9;8977:18;8964:32;8954:42;;9048:3;9037:9;9033:19;9020:33;9062;9087:7;9062:33;:::i;:::-;9114:7;-1:-1:-1;9173:3:39;9158:19;;9145:33;9187;9145;9187;:::i;:::-;9239:7;9229:17;;;8585:667;;;;;;;;:::o;9257:815::-;9370:6;9378;9386;9394;9402;9410;9418;9471:3;9459:9;9450:7;9446:23;9442:33;9439:2;;;9493:6;9485;9478:22;9439:2;9537:9;9524:23;9556:31;9581:5;9556:31;:::i;:::-;9606:5;-1:-1:-1;9663:2:39;9648:18;;9635:32;9676:33;9635:32;9676:33;:::i;:::-;9728:7;-1:-1:-1;9782:2:39;9767:18;;9754:32;;-1:-1:-1;9833:2:39;9818:18;;9805:32;;-1:-1:-1;9884:3:39;9869:19;;9856:33;;-1:-1:-1;9941:3:39;9926:19;;9913:33;9955;9913;9955;:::i;:::-;10007:7;9997:17;;;10061:3;10050:9;10046:19;10033:33;10023:43;;9429:643;;;;;;;;;;:::o;10077:884::-;10199:6;10207;10215;10223;10231;10239;10247;10255;10308:3;10296:9;10287:7;10283:23;10279:33;10276:2;;;10330:6;10322;10315:22;10276:2;10374:9;10361:23;10393:31;10418:5;10393:31;:::i;:::-;10443:5;-1:-1:-1;10500:2:39;10485:18;;10472:32;10513:33;10472:32;10513:33;:::i;:::-;10565:7;-1:-1:-1;10619:2:39;10604:18;;10591:32;;-1:-1:-1;10670:2:39;10655:18;;10642:32;;-1:-1:-1;10721:3:39;10706:19;;10693:33;;-1:-1:-1;10773:3:39;10758:19;;10745:33;;-1:-1:-1;10830:3:39;10815:19;;10802:33;10844;10802;10844;:::i;:::-;10896:7;10886:17;;;10950:3;10939:9;10935:19;10922:33;10912:43;;10266:695;;;;;;;;;;;:::o;10966:566::-;11046:6;11054;11062;11115:2;11103:9;11094:7;11090:23;11086:32;11083:2;;;11136:6;11128;11121:22;11083:2;11180:9;11167:23;11199:31;11224:5;11199:31;:::i;:::-;11249:5;-1:-1:-1;11305:2:39;11290:18;;11277:32;-1:-1:-1;;;;;11321:30:39;;11318:2;;;11369:6;11361;11354:22;11318:2;11413:59;11464:7;11455:6;11444:9;11440:22;11413:59;:::i;:::-;11073:459;;11491:8;;-1:-1:-1;11387:85:39;;-1:-1:-1;;;;11073:459:39:o;11537:325::-;11605:6;11613;11666:2;11654:9;11645:7;11641:23;11637:32;11634:2;;;11687:6;11679;11672:22;11634:2;11731:9;11718:23;11750:31;11775:5;11750:31;:::i;:::-;11800:5;11852:2;11837:18;;;;11824:32;;-1:-1:-1;;;11624:238:39:o;11867:673::-;11971:6;11979;11987;11995;12003;12011;12064:3;12052:9;12043:7;12039:23;12035:33;12032:2;;;12086:6;12078;12071:22;12032:2;12130:9;12117:23;12149:31;12174:5;12149:31;:::i;:::-;12199:5;-1:-1:-1;12251:2:39;12236:18;;12223:32;;-1:-1:-1;12302:2:39;12287:18;;12274:32;;-1:-1:-1;12353:2:39;12338:18;;12325:32;;-1:-1:-1;12409:3:39;12394:19;;12381:33;12423;12381;12423;:::i;:::-;12475:7;12465:17;;;12529:3;12518:9;12514:19;12501:33;12491:43;;12022:518;;;;;;;;:::o;12545:190::-;12604:6;12657:2;12645:9;12636:7;12632:23;12628:32;12625:2;;;12678:6;12670;12663:22;12625:2;-1:-1:-1;12706:23:39;;12615:120;-1:-1:-1;12615:120:39:o;13607:651::-;13709:6;13717;13725;13733;13786:2;13774:9;13765:7;13761:23;13757:32;13754:2;;;13807:6;13799;13792:22;13754:2;13851:9;13838:23;13870:31;13895:5;13870:31;:::i;:::-;13920:5;-1:-1:-1;13976:2:39;13961:18;;13948:32;-1:-1:-1;;;;;13992:30:39;;13989:2;;;14040:6;14032;14025:22;13989:2;14084:59;14135:7;14126:6;14115:9;14111:22;14084:59;:::i;:::-;14162:8;;-1:-1:-1;14058:85:39;-1:-1:-1;14216:36:39;;-1:-1:-1;14248:2:39;14233:18;;14216:36;:::i;:::-;14206:46;;13744:514;;;;;;;:::o;14263:564::-;14342:6;14350;14358;14411:2;14399:9;14390:7;14386:23;14382:32;14379:2;;;14432:6;14424;14417:22;14379:2;14464:23;;-1:-1:-1;;;;;14499:30:39;;14496:2;;;14547:6;14539;14532:22;14496:2;14591:59;14642:7;14633:6;14622:9;14618:22;14591:59;:::i;:::-;14669:8;;-1:-1:-1;14565:85:39;-1:-1:-1;;14754:2:39;14739:18;;14726:32;14767:30;14726:32;14767:30;:::i;:::-;14816:5;14806:15;;;14369:458;;;;;:::o;14832:409::-;14910:6;14918;14971:2;14959:9;14950:7;14946:23;14942:32;14939:2;;;14992:6;14984;14977:22;14939:2;15024:23;;-1:-1:-1;;;;;15059:30:39;;15056:2;;;15107:6;15099;15092:22;15056:2;15135:49;15176:7;15167:6;15156:9;15152:22;15135:49;:::i;:::-;15125:59;15231:2;15216:18;;;;15203:32;;-1:-1:-1;;;;14929:312:39:o;15246:1276::-;15365:6;15418:2;15406:9;15397:7;15393:23;15389:32;15386:2;;;15439:6;15431;15424:22;15386:2;15471:16;;-1:-1:-1;;;;;15536:14:39;;;15533:2;;;15568:6;15560;15553:22;15533:2;15596:22;;;;15652:6;15634:16;;;15630:29;15627:2;;;15677:6;15669;15662:22;15627:2;15708:22;;:::i;:::-;15761:2;15755:9;15789:2;15779:8;15776:16;15773:2;;;15810:6;15802;15795:22;15773:2;15842:56;15890:7;15879:8;15875:2;15871:17;15842:56;:::i;:::-;15835:5;15828:71;;15931:41;15968:2;15964;15960:11;15931:41;:::i;:::-;15926:2;15919:5;15915:14;15908:65;16005:41;16042:2;16038;16034:11;16005:41;:::i;:::-;16000:2;15993:5;15989:14;15982:65;16093:2;16089;16085:11;16079:18;16074:2;16067:5;16063:14;16056:42;16137:3;16133:2;16129:12;16123:19;16167:2;16157:8;16154:16;16151:2;;;16188:6;16180;16173:22;16151:2;16230:56;16278:7;16267:8;16263:2;16259:17;16230:56;:::i;:::-;16224:3;16217:5;16213:15;16206:81;;16320:42;16357:3;16353:2;16349:12;16320:42;:::i;:::-;16314:3;16307:5;16303:15;16296:67;16396:42;16433:3;16429:2;16425:12;16396:42;:::i;:::-;16390:3;16383:5;16379:15;16372:67;16486:3;16482:2;16478:12;16472:19;16466:3;16459:5;16455:15;16448:44;16511:5;16501:15;;;;;15376:1146;;;;:::o;16527:590::-;16678:6;16686;16739:3;16727:9;16718:7;16714:23;16710:33;16707:2;;;16761:6;16753;16746:22;16707:2;16793:23;;-1:-1:-1;;;;;16828:30:39;;16825:2;;;16876:6;16868;16861:22;16825:2;16904:22;;16960:3;16942:16;;;16938:26;16935:2;;;16982:6;16974;16967:22;16935:2;17010;-1:-1:-1;17031:80:39;17103:7;17098:2;17083:18;;17031:80;:::i;:::-;17021:90;;16697:420;;;;;:::o;17122:1103::-;17240:6;17293:2;17281:9;17272:7;17268:23;17264:32;17261:2;;;17314:6;17306;17299:22;17261:2;17346:16;;-1:-1:-1;;;;;17411:14:39;;;17408:2;;;17443:6;17435;17428:22;17408:2;17471:22;;;;17527:4;17509:16;;;17505:27;17502:2;;;17550:6;17542;17535:22;17502:2;17581:22;;:::i;:::-;17633:2;17627:9;17645:33;17670:7;17645:33;:::i;:::-;17687:22;;17748:2;17740:11;;17734:18;17764:16;;;17761:2;;;17798:6;17790;17783:22;17761:2;17839:56;17887:7;17876:8;17872:2;17868:17;17839:56;:::i;:::-;17834:2;17827:5;17823:14;17816:80;;17934:2;17930;17926:11;17920:18;17905:33;;17947:32;17971:7;17947:32;:::i;:::-;18011:7;18006:2;17999:5;17995:14;17988:31;18057:2;18053;18049:11;18043:18;18028:33;;18070:32;18094:7;18070:32;:::i;:::-;18134:7;18129:2;18122:5;18118:14;18111:31;18189:3;18185:2;18181:12;18175:19;18169:3;18162:5;18158:15;18151:44;18214:5;18204:15;;;;;17251:974;;;;:::o;18230:589::-;18380:6;18388;18441:3;18429:9;18420:7;18416:23;18412:33;18409:2;;;18463:6;18455;18448:22;18409:2;18495:23;;-1:-1:-1;;;;;18530:30:39;;18527:2;;;18578:6;18570;18563:22;18527:2;18606:22;;18662:3;18644:16;;;18640:26;18637:2;;;18684:6;18676;18669:22;18824:1450;18979:6;18987;18995;19048:2;19036:9;19027:7;19023:23;19019:32;19016:2;;;19069:6;19061;19054:22;19016:2;19101:23;;-1:-1:-1;;;;;19173:14:39;;;19170:2;;;19205:6;19197;19190:22;19170:2;19233:56;19281:7;19272:6;19261:9;19257:22;19233:56;:::i;:::-;19223:66;;19342:2;19331:9;19327:18;19314:32;19298:48;;19371:2;19361:8;19358:16;19355:2;;;19392:6;19384;19377:22;19355:2;19420:58;19470:7;19459:8;19448:9;19444:24;19420:58;:::i;:::-;19410:68;;19531:2;19520:9;19516:18;19503:32;19487:48;;19560:2;19550:8;19547:16;19544:2;;;19581:6;19573;19566:22;19544:2;19609:24;;;;19667:2;19649:16;;;19645:25;19642:2;;;19688:6;19680;19673:22;19642:2;19719:22;;:::i;:::-;19779:2;19766:16;19807:2;19797:8;19794:16;19791:2;;;19828:6;19820;19813:22;19791:2;19860:60;19912:7;19901:8;19897:2;19893:17;19860:60;:::i;:::-;19853:5;19846:75;;19967:2;19963;19959:11;19946:25;19996:2;19986:8;19983:16;19980:2;;;20017:6;20009;20002:22;19980:2;20058:55;20105:7;20094:8;20090:2;20086:17;20058:55;:::i;:::-;20053:2;20046:5;20042:14;20035:79;;20159:2;20155;20151:11;20138:25;20123:40;;20172:32;20196:7;20172:32;:::i;:::-;20236:7;20231:2;20224:5;20220:14;20213:31;20263:5;20253:15;;;;;19006:1268;;;;;:::o;20279:861::-;20437:6;20445;20453;20506:2;20494:9;20485:7;20481:23;20477:32;20474:2;;;20527:6;20519;20512:22;20474:2;20559:23;;-1:-1:-1;;;;;20631:14:39;;;20628:2;;;20663:6;20655;20648:22;20628:2;20691:56;20739:7;20730:6;20719:9;20715:22;20691:56;:::i;:::-;20681:66;;20800:2;20789:9;20785:18;20772:32;20756:48;;20829:2;20819:8;20816:16;20813:2;;;20850:6;20842;20835:22;20813:2;20878:58;20928:7;20917:8;20906:9;20902:24;20878:58;:::i;:::-;20868:68;;20989:2;20978:9;20974:18;20961:32;20945:48;;21018:2;21008:8;21005:16;21002:2;;;21039:6;21031;21024:22;21002:2;;21067:67;21126:7;21115:8;21104:9;21100:24;21067:67;:::i;:::-;21057:77;;;20464:676;;;;;:::o;21145:621::-;21265:6;21273;21326:2;21314:9;21305:7;21301:23;21297:32;21294:2;;;21347:6;21339;21332:22;21294:2;21379:23;;-1:-1:-1;;;;;21451:14:39;;;21448:2;;;21483:6;21475;21468:22;21448:2;21511:56;21559:7;21550:6;21539:9;21535:22;21511:56;:::i;:::-;21501:66;;21620:2;21609:9;21605:18;21592:32;21576:48;;21649:2;21639:8;21636:16;21633:2;;;21670:6;21662;21655:22;21633:2;;21698:62;21752:7;21741:8;21730:9;21726:24;21698:62;:::i;:::-;21688:72;;;21284:482;;;;;:::o;21771:1333::-;21859:6;21912:2;21900:9;21891:7;21887:23;21883:32;21880:2;;;21933:6;21925;21918:22;21880:2;21965:23;;-1:-1:-1;;;;;22037:14:39;;;22034:2;;;22069:6;22061;22054:22;22034:2;22097:22;;;;22153:6;22135:16;;;22131:29;22128:2;;;22178:6;22170;22163:22;22128:2;22209:22;;:::i;:::-;22254:46;22297:2;22254:46;:::i;:::-;22247:5;22240:61;22333:31;22360:2;22356;22352:11;22333:31;:::i;:::-;22328:2;22321:5;22317:14;22310:55;22397:31;22424:2;22420;22416:11;22397:31;:::i;:::-;22392:2;22385:5;22381:14;22374:55;22475:2;22471;22467:11;22454:25;22504:2;22494:8;22491:16;22488:2;;;22525:6;22517;22510:22;22488:2;22566:44;22602:7;22591:8;22587:2;22583:17;22566:44;:::i;:::-;22561:2;22554:5;22550:14;22543:68;;22644:32;22671:3;22667:2;22663:12;22644:32;:::i;:::-;22638:3;22631:5;22627:15;22620:57;22710:31;22736:3;22732:2;22728:12;22710:31;:::i;:::-;22704:3;22697:5;22693:15;22686:56;22775:31;22801:3;22797:2;22793:12;22775:31;:::i;:::-;22769:3;22762:5;22758:15;22751:56;22840:29;22864:3;22860:2;22856:12;22840:29;:::i;:::-;22834:3;22827:5;22823:15;22816:54;22889:3;22938:2;22934;22930:11;22917:25;22967:2;22957:8;22954:16;22951:2;;;22988:6;22980;22973:22;22951:2;23029:44;23065:7;23054:8;23050:2;23046:17;23029:44;:::i;:::-;23013:14;;;23006:68;;;;-1:-1:-1;23017:5:39;21870:1234;-1:-1:-1;;;;;21870:1234:39:o;23109:196::-;23168:6;23221:2;23209:9;23200:7;23196:23;23192:32;23189:2;;;23242:6;23234;23227:22;23189:2;23270:29;23289:9;23270:29;:::i;23505:194::-;23575:6;23628:2;23616:9;23607:7;23603:23;23599:32;23596:2;;;23649:6;23641;23634:22;23596:2;-1:-1:-1;23677:16:39;;23586:113;-1:-1:-1;23586:113:39:o;23704:255::-;23762:6;23815:2;23803:9;23794:7;23790:23;23786:32;23783:2;;;23836:6;23828;23821:22;23783:2;23880:9;23867:23;23899:30;23923:5;23899:30;:::i;23964:259::-;24033:6;24086:2;24074:9;24065:7;24061:23;24057:32;24054:2;;;24107:6;24099;24092:22;24054:2;24144:9;24138:16;24163:30;24187:5;24163:30;:::i;24228:320::-;24306:6;24314;24367:2;24355:9;24346:7;24342:23;24338:32;24335:2;;;24388:6;24380;24373:22;24335:2;24425:9;24419:16;24444:30;24468:5;24444:30;:::i;:::-;24538:2;24523:18;;;;24517:25;24493:5;;24517:25;;-1:-1:-1;;;24325:223:39:o;24553:679::-;24659:6;24667;24675;24683;24736:3;24724:9;24715:7;24711:23;24707:33;24704:2;;;24758:6;24750;24743:22;24704:2;24795:9;24789:16;24814:30;24838:5;24814:30;:::i;:::-;24908:2;24893:18;;24887:25;24957:2;24942:18;;24936:25;24863:5;;-1:-1:-1;24887:25:39;-1:-1:-1;24970:33:39;24936:25;24970:33;:::i;:::-;25073:2;25058:18;;25052:25;25022:7;;-1:-1:-1;;;;;;25089:30:39;;25086:2;;;25137:6;25129;25122:22;25086:2;25165:61;25218:7;25209:6;25198:9;25194:22;25165:61;:::i;:::-;25155:71;;;24694:538;;;;;;;:::o;25237:391::-;25314:6;25322;25375:2;25363:9;25354:7;25350:23;25346:32;25343:2;;;25396:6;25388;25381:22;25343:2;25433:9;25427:16;25452:30;25476:5;25452:30;:::i;:::-;25551:2;25536:18;;25530:25;25501:5;;-1:-1:-1;25564:32:39;25530:25;25564:32;:::i;25633:452::-;25719:6;25727;25735;25788:2;25776:9;25767:7;25763:23;25759:32;25756:2;;;25809:6;25801;25794:22;25756:2;25846:9;25840:16;25865:30;25889:5;25865:30;:::i;:::-;25964:2;25949:18;;25943:25;25914:5;;-1:-1:-1;25977:32:39;25943:25;25977:32;:::i;:::-;26028:7;26018:17;;;26075:2;26064:9;26060:18;26054:25;26044:35;;25746:339;;;;;:::o;26090:112::-;-1:-1:-1;;;;;26164:31:39;26152:44;;26142:60::o;26303:268::-;26391:6;26386:3;26379:19;26443:6;26436:5;26429:4;26424:3;26420:14;26407:43;-1:-1:-1;26361:3:39;26470:16;;;26488:4;26466:27;;;26459:40;;;;26553:2;26532:15;;;-1:-1:-1;;26528:29:39;26519:39;;;26515:50;;26369:202::o;26576:257::-;26617:3;26655:5;26649:12;26682:6;26677:3;26670:19;26698:63;26754:6;26747:4;26742:3;26738:14;26731:4;26724:5;26720:16;26698:63;:::i;:::-;26815:2;26794:15;-1:-1:-1;;26790:29:39;26781:39;;;;26822:4;26777:50;;26625:208;-1:-1:-1;;26625:208:39:o;26838:166::-;26916:55;26965:5;26916:55;:::i;:::-;26980:18;;26906:98::o;27009:155::-;27105:1;27098:5;27095:12;27085:2;;27111:18;;:::i;27169:140::-;27250:1;27243:5;27240:12;27230:2;;27256:18;;:::i;27314:1113::-;27429:5;27416:19;27444:33;27469:7;27444:33;:::i;:::-;-1:-1:-1;;;;;27536:16:39;;;27524:29;;27601:4;27590:16;;27577:30;;27616:33;27577:30;27616:33;:::i;:::-;27681:16;27674:4;27665:14;;27658:40;27746:4;27735:16;;27722:30;27761:32;27722:30;27761:32;:::i;:::-;-1:-1:-1;;;;;27862:16:39;;;27820:2;27846:14;;27839:40;27927:4;27916:16;;27903:30;;27942:32;27903:30;27942:32;:::i;:::-;28006:16;;;27999:4;27990:14;;27983:40;28071:4;28060:16;;28047:30;;28086:32;28047:30;28086:32;:::i;:::-;28150:16;;;28143:4;28134:14;;28127:40;28215:4;28204:16;;28191:30;;28230:32;28191:30;28230:32;:::i;:::-;28294:16;28287:4;28278:14;;28271:40;28335:35;28364:4;28353:16;;28335:35;:::i;:::-;28379:42;28415:4;28410:3;28406:14;28397:7;28379:42;:::i;28432:1950::-;28489:3;28517:6;28558:5;28552:12;28585:2;28580:3;28573:15;28609:44;28649:2;28644:3;28640:12;28626;28609:44;:::i;:::-;28597:56;;;28701:4;28694:5;28690:16;28684:23;28749:3;28743:4;28739:14;28732:4;28727:3;28723:14;28716:38;28777;28810:4;28794:14;28777:38;:::i;:::-;28763:52;;;28863:4;28856:5;28852:16;28846:23;28878:58;28930:4;28925:3;28921:14;28905;28878:58;:::i;:::-;;28984:4;28977:5;28973:16;28967:23;28999:58;29051:4;29046:3;29042:14;29026;28999:58;:::i;:::-;;29105:4;29098:5;29094:16;29088:23;29120:49;29163:4;29158:3;29154:14;29138;29120:49;:::i;:::-;;29217:4;29210:5;29206:16;29200:23;29232:49;29275:4;29270:3;29266:14;29250;29232:49;:::i;:::-;;29329:4;29322:5;29318:16;29312:23;29344:49;29387:4;29382:3;29378:14;29362;29344:49;:::i;:::-;;29441:4;29434:5;29430:16;29424:23;29456:49;29499:4;29494:3;29490:14;29474;29456:49;:::i;:::-;;29524:6;29578:2;29571:5;29567:14;29561:21;29591:56;29643:2;29638:3;29634:12;29618:14;29591:56;:::i;:::-;;;29666:6;29720:2;29713:5;29709:14;29703:21;29733:56;29785:2;29780:3;29776:12;29760:14;29733:56;:::i;:::-;;;29808:6;29863:2;29856:5;29852:14;29846:21;29876:48;29920:2;29915:3;29911:12;29894:15;29876:48;:::i;:::-;;;29943:6;29998:2;29991:5;29987:14;29981:21;30011:48;30055:2;30050:3;30046:12;30029:15;30011:48;:::i;:::-;;;30078:6;30133:2;30126:5;30122:14;30116:21;30146:48;30190:2;30185:3;30181:12;30164:15;30146:48;:::i;:::-;;;30213:6;30268:2;30261:5;30257:14;30251:21;30281:73;30350:2;30345:3;30341:12;30324:15;30281:73;:::i;:::-;-1:-1:-1;30370:6:39;;28497:1885;-1:-1:-1;;;;28497:1885:39:o;30387:1890::-;30495:12;;34202:4;34191:16;34179:29;;30435:3;30463:6;30559:4;30552:5;30548:16;30542:23;30574:48;30616:4;30611:3;30607:14;30593:12;30574:48;:::i;:::-;;30670:4;30663:5;30659:16;30653:23;30685:58;30737:4;30732:3;30728:14;30712;30685:58;:::i;:::-;;30791:4;30784:5;30780:16;30774:23;30806:57;30857:4;30852:3;30848:14;30832;30806:57;:::i;:::-;;30911:4;30904:5;30900:16;30894:23;30926:74;30994:4;30989:3;30985:14;30969;30926:74;:::i;:::-;;31048:4;31041:5;31037:16;31031:23;31063:49;31106:4;31101:3;31097:14;31081;31063:49;:::i;:::-;;31160:4;31153:5;31149:16;31143:23;31175:47;31216:4;31211:3;31207:14;31191;26277:13;26270:21;26258:34;;26248:50;31175:47;;31270:4;31263:5;31259:16;31253:23;31285:49;31328:4;31323:3;31319:14;31303;31285:49;:::i;:::-;;31353:6;31407:2;31400:5;31396:14;31390:21;31420:47;31463:2;31458:3;31454:12;31438:14;31420:47;:::i;:::-;;;31486:6;31540:2;31533:5;31529:14;31523:21;31574:2;31569;31564:3;31560:12;31553:24;31598:46;31640:2;31635:3;31631:12;31615:14;31598:46;:::i;:::-;31586:58;;;;31663:6;31717:2;31710:5;31706:14;31700:21;31730:70;31796:2;31791:3;31787:12;31771:14;31730:70;:::i;:::-;;;31819:6;31874:2;31867:5;31863:14;31857:21;31887:71;31954:2;31949:3;31945:12;31928:15;31887:71;:::i;:::-;;;31977:6;32032:2;32025:5;32021:14;32015:21;32045:48;32089:2;32084:3;32080:12;32063:15;32045:48;:::i;:::-;;;32112:6;32167:2;32160:5;32156:14;32150:21;32211:3;32205:4;32201:14;32196:2;32191:3;32187:12;32180:36;32232:39;32266:4;32249:15;32232:39;:::i;:::-;32225:46;30443:1834;-1:-1:-1;;;;;;30443:1834:39:o;32282:1632::-;32334:3;32362:6;32403:5;32397:12;32430:2;32425:3;32418:15;32454:44;32494:2;32489:3;32485:12;32471;32454:44;:::i;:::-;32442:56;;;32546:4;32539:5;32535:16;32529:23;32594:3;32588:4;32584:14;32577:4;32572:3;32568:14;32561:38;32622;32655:4;32639:14;32622:38;:::i;:::-;32608:52;;;32708:4;32701:5;32697:16;32691:23;32723:58;32775:4;32770:3;32766:14;32750;32723:58;:::i;:::-;;32829:4;32822:5;32818:16;32812:23;32844:58;32896:4;32891:3;32887:14;32871;32844:58;:::i;:::-;;32950:4;32943:5;32939:16;32933:23;32965:49;33008:4;33003:3;32999:14;32983;32965:49;:::i;:::-;;33062:4;33055:5;33051:16;33045:23;33077:49;33120:4;33115:3;33111:14;33095;33077:49;:::i;:::-;;33174:4;33167:5;33163:16;33157:23;33189:49;33232:4;33227:3;33223:14;33207;33189:49;:::i;:::-;;33286:4;33279:5;33275:16;33269:23;33301:49;33344:4;33339:3;33335:14;33319;33301:49;:::i;:::-;;33369:6;33423:2;33416:5;33412:14;33406:21;33436:47;33479:2;33474:3;33470:12;33454:14;33436:47;:::i;:::-;;;33502:6;33556:2;33549:5;33545:14;33539:21;33569:47;33612:2;33607:3;33603:12;33587:14;33569:47;:::i;:::-;;;33635:6;33690:2;33683:5;33679:14;33673:21;33703:48;33747:2;33742:3;33738:12;33721:15;33703:48;:::i;:::-;;;33770:6;33825:2;33818:5;33814:14;33808:21;33838:48;33882:2;33877:3;33873:12;33856:15;33838:48;:::i;33919:104::-;-1:-1:-1;;;;;33985:31:39;33973:44;;33963:60::o;34028:102::-;-1:-1:-1;;;;;34093:30:39;34081:43;;34071:59::o;34215:276::-;34346:3;34384:6;34378:13;34400:53;34446:6;34441:3;34434:4;34426:6;34422:17;34400:53;:::i;:::-;34469:16;;;;;34354:137;-1:-1:-1;;34354:137:39:o;34496:203::-;-1:-1:-1;;;;;34660:32:39;;;;34642:51;;34630:2;34615:18;;34597:102::o;34704:693::-;-1:-1:-1;;;;;35035:15:39;;;35017:34;;35087:15;;35082:2;35067:18;;35060:43;35139:3;35134:2;35119:18;;35112:31;;;34960:4;;35160:45;;35185:19;;35177:6;35160:45;:::i;:::-;-1:-1:-1;;;;;35278:15:39;;;35273:2;35258:18;;35251:43;35331:15;;;;35325:3;35310:19;;35303:44;35378:3;35363:19;35356:35;35152:53;34969:428;-1:-1:-1;;;;34969:428:39:o;35402:312::-;-1:-1:-1;;;;;35640:15:39;;;35622:34;;35692:15;;35687:2;35672:18;;35665:43;35572:2;35557:18;;35539:175::o;36028:761::-;-1:-1:-1;;;;;36435:15:39;;;36417:34;;36487:15;;;36482:2;36467:18;;36460:43;36539:15;;;36534:2;36519:18;;36512:43;36586:2;36571:18;;36564:34;;;;36629:3;36614:19;;36607:35;;;;36397:3;36658:19;;36651:35;36723:15;;;36717:3;36702:19;;36695:44;36770:3;36755:19;;36748:35;;;;36366:3;36351:19;;36333:456::o;37552:825::-;-1:-1:-1;;;;;37979:15:39;;;37961:34;;38031:15;;;38026:2;38011:18;;38004:43;38083:15;;;38078:2;38063:18;;38056:43;38130:2;38115:18;;38108:34;;;;38173:3;38158:19;;38151:35;;;;37941:3;38202:19;;38195:35;38261:3;38246:19;;38239:35;38311:15;;;38305:3;38290:19;;38283:44;38358:3;38343:19;;38336:35;;;;37910:3;37895:19;;37877:500::o;39629:925::-;-1:-1:-1;;;;;40012:15:39;;;39994:34;;40064:15;;40059:2;40044:18;;40037:43;40116:3;40111:2;40096:18;;40089:31;;;39937:4;;40143:45;;40168:19;;40160:6;40143:45;:::i;:::-;40236:9;40228:6;40224:22;40219:2;40208:9;40204:18;40197:50;40264:32;40289:6;40281;40264:32;:::i;:::-;-1:-1:-1;;;;;40370:15:39;;;40364:3;40349:19;;40342:44;40423:15;;40417:3;40402:19;;40395:44;40256:40;-1:-1:-1;40448:56:39;;-1:-1:-1;40497:6:39;40448:56;:::i;:::-;40541:6;40535:3;40524:9;40520:19;40513:35;39946:608;;;;;;;;;;:::o;40559:1234::-;-1:-1:-1;;;;;41069:15:39;;;41051:34;;41121:15;;41116:2;41101:18;;41094:43;41001:3;41168:2;41153:18;;41146:30;;;40972:4;;41199:44;41224:18;;;41216:6;41199:44;:::i;:::-;41185:58;;41291:9;41283:6;41279:22;41274:2;41263:9;41259:18;41252:50;41319:32;41344:6;41336;41319:32;:::i;:::-;-1:-1:-1;;;;;41425:15:39;;;41419:3;41404:19;;41397:44;41478:15;;;41472:3;41457:19;;41450:44;41531:15;;;41525:3;41510:19;;41503:44;41584:15;;;41578:3;41563:19;;41556:44;41637:15;;;41631:3;41616:19;;41609:44;41690:15;;41684:3;41669:19;;41662:44;41311:40;-1:-1:-1;41715:72:39;;-1:-1:-1;41782:3:39;41767:19;;41758:7;41715:72;:::i;:::-;40981:812;;;;;;;;;;;;;;:::o;43141:823::-;-1:-1:-1;;;;;43458:32:39;;43440:51;;43527:3;43522:2;43507:18;;43500:31;;;-1:-1:-1;;43554:45:39;;43579:19;;43571:6;43554:45;:::i;:::-;43647:9;43639:6;43635:22;43630:2;43619:9;43615:18;43608:50;43675:32;43700:6;43692;43675:32;:::i;:::-;-1:-1:-1;;;;;43780:15:39;;;43775:2;43760:18;;43753:43;43833:15;;43827:3;43812:19;;43805:44;43667:40;-1:-1:-1;43858:56:39;;-1:-1:-1;43907:6:39;43858:56;:::i;:::-;43951:6;43945:3;43934:9;43930:19;43923:35;43430:534;;;;;;;;;:::o;43969:1167::-;44278:4;44324:1;44320;44315:3;44311:11;44307:19;44365:2;44357:6;44353:15;44342:9;44335:34;44405:3;44400:2;44389:9;44385:18;44378:31;44466:6;44460:13;44453:21;44446:29;44440:3;44429:9;44425:19;44418:58;44542:2;44536;44528:6;44524:15;44518:22;44514:31;44507:4;44496:9;44492:20;44485:61;;44593:4;44585:6;44581:17;44575:24;44636:4;44630:3;44619:9;44615:19;44608:33;44664:51;44710:3;44699:9;44695:19;44681:12;44664:51;:::i;:::-;44774:4;44762:17;;;44756:24;44782:4;44752:35;44746:3;44731:19;;44724:64;44857:4;44845:17;;44839:24;44832:32;44825:40;44819:3;44804:19;;44797:69;44925:3;44913:16;;44907:23;-1:-1:-1;;;;;44903:48:39;44897:3;44882:19;;44875:77;44940:2;44991:20;;44984:36;;;44650:65;;-1:-1:-1;45029:56:39;;-1:-1:-1;45064:20:39;;45056:6;45029:56;:::i;:::-;45123:6;45116:4;45105:9;45101:20;45094:36;44287:849;;;;;;;;:::o;46390:358::-;-1:-1:-1;;;;;46592:32:39;;46574:51;;46661:2;46656;46641:18;;46634:30;;;-1:-1:-1;;46681:61:39;;46723:18;;46715:6;46707;46681:61;:::i;:::-;46673:69;46564:184;-1:-1:-1;;;;;46564:184:39:o;47520:348::-;47722:2;47704:21;;;47761:2;47741:18;;;47734:30;-1:-1:-1;;;47795:2:39;47780:18;;47773:54;47859:2;47844:18;;47694:174::o;48223:339::-;48425:2;48407:21;;;48464:2;48444:18;;;48437:30;-1:-1:-1;;;48498:2:39;48483:18;;48476:45;48553:2;48538:18;;48397:165::o;49325:340::-;49527:2;49509:21;;;49566:2;49546:18;;;49539:30;-1:-1:-1;;;49600:2:39;49585:18;;49578:46;49656:2;49641:18;;49499:166::o;49670:350::-;49872:2;49854:21;;;49911:2;49891:18;;;49884:30;-1:-1:-1;;;49945:2:39;49930:18;;49923:56;50011:2;49996:18;;49844:176::o;50375:349::-;50577:2;50559:21;;;50616:2;50596:18;;;50589:30;-1:-1:-1;;;50650:2:39;50635:18;;50628:55;50715:2;50700:18;;50549:175::o;52178:344::-;52380:2;52362:21;;;52419:2;52399:18;;;52392:30;-1:-1:-1;;;52453:2:39;52438:18;;52431:50;52513:2;52498:18;;52352:170::o;53291:344::-;53493:2;53475:21;;;53532:2;53512:18;;;53505:30;-1:-1:-1;;;53566:2:39;53551:18;;53544:50;53626:2;53611:18;;53465:170::o;54347:346::-;54549:2;54531:21;;;54588:2;54568:18;;;54561:30;-1:-1:-1;;;54622:2:39;54607:18;;54600:52;54684:2;54669:18;;54521:172::o;54698:349::-;54900:2;54882:21;;;54939:2;54919:18;;;54912:30;-1:-1:-1;;;54973:2:39;54958:18;;54951:55;55038:2;55023:18;;54872:175::o;55408:337::-;55610:2;55592:21;;;55649:2;55629:18;;;55622:30;-1:-1:-1;;;55683:2:39;55668:18;;55661:43;55736:2;55721:18;;55582:163::o;55750:354::-;55952:2;55934:21;;;55991:2;55971:18;;;55964:30;56030:32;56025:2;56010:18;;56003:60;56095:2;56080:18;;55924:180::o;56109:761::-;56286:2;56275:9;56268:21;56345:6;56339:13;56332:21;56325:29;56320:2;56309:9;56305:18;56298:57;56436:1;56432;56427:3;56423:11;56419:19;56413:2;56405:6;56401:15;56395:22;56391:48;56386:2;56375:9;56371:18;56364:76;56249:4;56487:2;56479:6;56475:15;56469:22;56527:4;56522:2;56511:9;56507:18;56500:32;56555:51;56601:3;56590:9;56586:19;56572:12;56555:51;:::i;:::-;56541:65;;56671:4;56665:2;56657:6;56653:15;56647:22;56643:33;56637:3;56626:9;56622:19;56615:62;56746:3;56738:6;56734:16;56728:23;56721:31;56714:39;56708:3;56697:9;56693:19;56686:68;56837:1;56833;56829:2;56825:10;56821:18;56814:3;56806:6;56802:16;56796:23;56792:48;56785:4;56774:9;56770:20;56763:78;56858:6;56850:14;;;56258:612;;;;:::o;57604:1771::-;58281:4;58310:3;58340:2;58329:9;58322:21;58366:51;58413:2;58402:9;58398:18;58390:6;58366:51;:::i;:::-;58352:65;;58465:9;58457:6;58453:22;58448:2;58437:9;58433:18;58426:50;58499:39;58531:6;58523;58499:39;:::i;:::-;58485:53;;58586:9;58578:6;58574:22;58569:2;58558:9;58554:18;58547:50;58632:6;58626:13;58663:4;58655:6;58648:20;58691:65;58750:4;58742:6;58738:17;58724:12;58691:65;:::i;:::-;58677:79;;58805:2;58797:6;58793:15;58787:22;58854:6;58846;58842:19;58837:2;58829:6;58825:15;58818:44;58885:51;58929:6;58913:14;58885:51;:::i;:::-;59005:2;58979:15;;;58973:22;-1:-1:-1;;;;;58969:47:39;58952:15;;58945:72;;;;-1:-1:-1;58871:65:39;;-1:-1:-1;59049:56:39;;-1:-1:-1;59099:4:39;59084:20;;59076:6;59049:56;:::i;:::-;59142:6;59136:3;59125:9;59121:19;59114:35;59186:6;59180:3;59169:9;59165:19;59158:35;59230:6;59224:3;59213:9;59209:19;59202:35;59274:6;59268:3;59257:9;59253:19;59246:35;59318:6;59312:3;59301:9;59297:19;59290:35;59362:6;59356:3;59345:9;59341:19;59334:35;58290:1085;;;;;;;;;;;;;:::o;59380:1309::-;60013:4;60042:3;60072:2;60061:9;60054:21;60098:51;60145:2;60134:9;60130:18;60122:6;60098:51;:::i;:::-;60084:65;;60197:9;60189:6;60185:22;60180:2;60169:9;60165:18;60158:50;60231:39;60263:6;60255;60231:39;:::i;:::-;60217:53;;60318:9;60310:6;60306:22;60301:2;60290:9;60286:18;60279:50;60346:48;60387:6;60379;60346:48;:::i;:::-;-1:-1:-1;;;;;60430:32:39;;;;60425:2;60410:18;;60403:60;-1:-1:-1;;60494:3:39;60479:19;;60472:35;;;;60450:3;60523:19;;60516:35;;;;60582:3;60567:19;;60560:35;;;;60626:3;60611:19;;60604:35;60670:3;60655:19;;;60648:35;60338:56;60022:667;-1:-1:-1;;;60022:667:39:o;60694:1190::-;61291:4;61320:3;61350:2;61339:9;61332:21;61376:51;61423:2;61412:9;61408:18;61400:6;61376:51;:::i;:::-;61362:65;;61475:9;61467:6;61463:22;61458:2;61447:9;61443:18;61436:50;61503:43;61539:6;61531;61503:43;:::i;:::-;-1:-1:-1;;;;;61582:32:39;;;;61577:2;61562:18;;61555:60;-1:-1:-1;;61646:2:39;61631:18;;61624:34;;;;61689:3;61674:19;;61667:35;;;;61602:3;61718:19;;61711:35;;;;61777:3;61762:19;;61755:35;61821:3;61806:19;;61799:35;61865:3;61850:19;;;61843:35;61495:51;61300:584;-1:-1:-1;;61300:584:39:o;61889:502::-;62175:25;;;-1:-1:-1;;;;;62274:15:39;;;62269:2;62254:18;;62247:43;62326:15;;62321:2;62306:18;;62299:43;62373:2;62358:18;;62351:34;62162:3;62147:19;;62129:262::o;62396:542::-;62676:25;;;-1:-1:-1;;;;;62737:32:39;;62732:2;62717:18;;62710:60;62806:3;62801:2;62786:18;;62779:31;;;-1:-1:-1;;62827:62:39;;62869:19;;62861:6;62853;62827:62;:::i;:::-;62819:70;;62925:6;62920:2;62909:9;62905:18;62898:34;62666:272;;;;;;;;:::o;62943:462::-;63188:25;;;-1:-1:-1;;;;;63249:32:39;;63244:2;63229:18;;63222:60;63318:2;63313;63298:18;;63291:30;;;-1:-1:-1;;63338:61:39;;63380:18;;63372:6;63364;63338:61;:::i;63410:542::-;63679:25;;;-1:-1:-1;;;;;63740:32:39;;63735:2;63720:18;;63713:60;63809:3;63804:2;63789:18;;63782:31;;;-1:-1:-1;;63830:62:39;;63872:19;;63864:6;63856;63830:62;:::i;:::-;63822:70;;63940:4;63932:6;63928:17;63923:2;63912:9;63908:18;63901:45;63669:283;;;;;;;;:::o;65494:2342::-;65944:4;65973:3;66003:6;65992:9;65985:25;66046:2;66041;66030:9;66026:18;66019:30;66058:62;66116:2;66105:9;66101:18;66075:24;66092:6;66075:24;:::i;:::-;34202:4;34191:16;34179:29;;34177:33;66058:62;66142:59;66197:2;66189:6;66185:15;66142:59;:::i;:::-;66220:6;66235:69;66300:2;66289:9;66285:18;66278:5;66235:69;:::i;:::-;66328:37;66359:4;66351:6;66347:17;66328:37;:::i;:::-;66313:52;;66374:48;66417:3;66406:9;66402:19;66393:7;66374:48;:::i;:::-;66446:37;66477:4;66469:6;66465:17;66446:37;:::i;:::-;66431:52;;66492:56;66543:3;66532:9;66528:19;66519:7;66492:56;:::i;:::-;66572:37;66603:4;66595:6;66591:17;66572:37;:::i;:::-;66557:52;;66618:56;66669:3;66658:9;66654:19;66645:7;66618:56;:::i;:::-;66698:37;66729:4;66721:6;66717:17;66698:37;:::i;:::-;66683:52;;66744:56;66795:3;66784:9;66780:19;66771:7;66744:56;:::i;:::-;66862:4;66854:6;66850:17;66837:31;66831:3;66820:9;66816:19;66809:60;66931:4;66923:6;66919:17;66906:31;66900:3;66889:9;66885:19;66878:60;67000:6;66992;66988:19;66975:33;66969:3;66958:9;66954:19;66947:62;67028:6;67018:16;;67096:2;67088:6;67084:15;67071:29;67065:3;67054:9;67050:19;67043:58;67120:6;67150:35;67181:2;67173:6;67169:15;67150:35;:::i;:::-;67194:56;67245:3;67234:9;67230:19;67221:7;67194:56;:::i;:::-;;67269:6;67337:2;67329:6;67325:15;67312:29;67306:3;67295:9;67291:19;67284:58;67385:55;67436:2;67428:6;67424:15;67416:6;67385:55;:::i;:::-;67351:89;;67477:2;67471:3;67460:9;67456:19;67449:31;67497:74;67566:3;67555:9;67551:19;67537:12;67523;67497:74;:::i;:::-;67489:82;;;67580:81;67655:4;67644:9;67640:20;67632:6;67580:81;:::i;:::-;67670:54;67720:2;67709:9;67705:18;67697:6;67670:54;:::i;:::-;67733;67783:2;67772:9;67768:18;67760:6;67733:54;:::i;:::-;67823:6;67818:2;67807:9;67803:18;67796:34;;;;;;65953:1883;;;;;;;;;:::o;67841:2104::-;68258:6;68247:9;68240:25;68301:3;68296:2;68285:9;68281:18;68274:31;68314:52;68361:3;68350:9;68346:19;68337:6;68331:13;34202:4;34191:16;34179:29;;34177:33;68314:52;68221:4;68413:2;68405:6;68401:15;68395:22;68426:77;68498:3;68487:9;68483:19;68469:12;68426:77;:::i;:::-;;68552:4;68544:6;68540:17;68534:24;68567:55;68617:3;68606:9;68602:19;68586:14;68567:55;:::i;:::-;;68671:4;68663:6;68659:17;68653:24;68696:3;68708:62;68766:2;68755:9;68751:18;68735:14;68708:62;:::i;:::-;68819:4;68811:6;68807:17;68801:24;68779:46;;68844:3;68856:62;68914:2;68903:9;68899:18;68883:14;68856:62;:::i;:::-;68967:3;68959:6;68955:16;68949:23;68927:45;;68991:3;69003:62;69061:2;69050:9;69046:18;69030:14;69003:62;:::i;:::-;69102:3;69090:16;;69084:23;69126:3;69145:18;;;69138:30;;;;69205:3;69193:16;;69187:23;69229:3;69248:18;;;69241:30;;;;69296:15;;;69290:22;69331:6;69353:18;;;69346:30;;;;69419:15;;;69413:22;69407:3;69392:19;;69385:51;69473:15;;;69467:22;;-1:-1:-1;69331:6:39;69498:63;69556:3;69541:19;;69467:22;69498:63;:::i;:::-;69616:2;69608:6;69604:15;69598:22;69592:3;69581:9;69577:19;69570:51;69670:2;69662:6;69658:15;69652:22;69630:44;;;;;;69711:2;69705:3;69694:9;69690:19;69683:31;;69731:53;69779:3;69768:9;69764:19;69748:14;69731:53;:::i;:::-;69723:61;;;69793:56;69843:4;69832:9;69828:20;69820:6;69793:56;:::i;:::-;69880:4;69865:20;;69858:36;;;;69925:4;69910:20;69903:36;68230:1715;;-1:-1:-1;;;68230:1715:39:o;70910:2468::-;71456:4;71485:3;71515:6;71504:9;71497:25;71558:2;71553;71542:9;71538:18;71531:30;71570:62;71628:2;71617:9;71613:18;71587:24;71604:6;71587:24;:::i;71570:62::-;;71654:59;71709:2;71701:6;71697:15;71654:59;:::i;:::-;71722:70;71787:3;71776:9;71772:19;71765:5;71722:70;:::i;:::-;;71816:37;71847:4;71839:6;71835:17;71816:37;:::i;:::-;71862:48;71905:3;71894:9;71890:19;71881:7;71862:48;:::i;:::-;;71934:37;71965:4;71957:6;71953:17;71934:37;:::i;:::-;71980:56;72031:3;72020:9;72016:19;72007:7;71980:56;:::i;:::-;;72060:37;72091:4;72083:6;72079:17;72060:37;:::i;:::-;72106:56;72157:3;72146:9;72142:19;72133:7;72106:56;:::i;:::-;;72186:37;72217:4;72209:6;72205:17;72186:37;:::i;:::-;72232:56;72283:3;72272:9;72268:19;72259:7;72232:56;:::i;:::-;;72350:4;72342:6;72338:17;72325:31;72319:3;72308:9;72304:19;72297:60;72419:4;72411:6;72407:17;72394:31;72388:3;72377:9;72373:19;72366:60;72488:6;72480;72476:19;72463:33;72457:3;72446:9;72442:19;72435:62;72516:6;72546:35;72577:2;72569:6;72565:15;72546:35;:::i;:::-;72590:56;72641:3;72630:9;72626:19;72617:7;72590:56;:::i;:::-;;72665:6;72733:2;72725:6;72721:15;72708:29;72702:3;72691:9;72687:19;72680:58;72757:6;72806:55;72857:2;72849:6;72845:15;72837:6;72806:55;:::i;:::-;72880:6;72923:2;72917:3;72906:9;72902:19;72895:31;72943:74;73012:3;73001:9;72997:19;72983:12;72969;72943:74;:::i;:::-;72935:82;;73026:81;73101:4;73090:9;73086:20;73078:6;73026:81;:::i;:::-;26277:13;;26270:21;73140:18;;;26258:34;73168:54;73218:2;73207:9;73203:18;73195:6;73168:54;:::i;:::-;73231;73281:2;73270:9;73266:18;73258:6;73231:54;:::i;:::-;73321:6;73316:2;73305:9;73301:18;73294:34;;;;;;;73365:6;73359:3;73348:9;73344:19;73337:35;71465:1913;;;;;;;;;;;:::o;73383:2000::-;73798:6;73787:9;73780:25;73841:3;73836:2;73825:9;73821:18;73814:31;73854:52;73901:3;73890:9;73886:19;73877:6;73871:13;34202:4;34191:16;34179:29;;34177:33;73854:52;73761:4;73953:2;73945:6;73941:15;73935:22;73966:77;74038:3;74027:9;74023:19;74009:12;73966:77;:::i;:::-;;74092:4;74084:6;74080:17;74074:24;74107:55;74157:3;74146:9;74142:19;74126:14;74107:55;:::i;:::-;;74211:4;74203:6;74199:17;74193:24;74236:3;74248:62;74306:2;74295:9;74291:18;74275:14;74248:62;:::i;:::-;74359:4;74351:6;74347:17;74341:24;74319:46;;74384:3;74396:62;74454:2;74443:9;74439:18;74423:14;74396:62;:::i;:::-;74507:3;74499:6;74495:16;74489:23;74467:45;;74531:3;74543:62;74601:2;74590:9;74586:18;74570:14;74543:62;:::i;:::-;74642:3;74634:6;74630:16;74624:23;74614:33;;74666:3;74705:2;74700;74689:9;74685:18;74678:30;74745:3;74737:6;74733:16;74727:23;74717:33;;74769:6;74811:2;74806;74795:9;74791:18;74784:30;74869:2;74861:6;74857:15;74851:22;74845:3;74834:9;74830:19;74823:51;74923:2;74915:6;74911:15;74905:22;74883:44;;74936:63;74994:3;74983:9;74979:19;74963:14;74936:63;:::i;:::-;75042:15;;;75036:22;75030:3;75015:19;;75008:51;75096:15;;75090:22;75143:3;75128:19;;75121:31;;;;75090:22;-1:-1:-1;75169:53:39;;-1:-1:-1;;75217:3:39;75202:19;;75090:22;75169:53;:::i;75388:1909::-;75815:3;75804:9;75797:22;75828:78;75901:3;75890:9;75886:19;75877:6;75871:13;75828:78;:::i;:::-;75778:4;75953;75945:6;75941:17;75935:24;75968:53;76016:3;76005:9;76001:19;75987:12;75968:53;:::i;:::-;;76070:4;76062:6;76058:17;76052:24;76095:3;76107:62;76165:2;76154:9;76150:18;76134:14;76107:62;:::i;:::-;76218:4;76206:17;;76200:24;76243:6;76265:18;;;76258:30;76200:24;-1:-1:-1;76311:53:39;76359:3;76344:19;;76200:24;76311:53;:::i;:::-;76297:67;;76413:4;76405:6;76401:17;76395:24;76428:63;76486:3;76475:9;76471:19;76455:14;76428:63;:::i;:::-;;76540:4;76532:6;76528:17;76522:24;76555:54;76604:3;76593:9;76589:19;76573:14;76555:54;:::i;:::-;;76658:3;76650:6;76646:16;76640:23;76672:54;76721:3;76710:9;76706:19;76690:14;76672:54;:::i;:::-;-1:-1:-1;76775:3:39;76763:16;;76757:23;26277:13;26270:21;76836:3;76821:19;;26258:34;76878:15;;76872:22;76935;;;-1:-1:-1;;76931:37:39;76925:3;76910:19;;76903:66;76986:40;76935:22;76872;76986:40;:::i;:::-;76978:48;;;;77035:56;77085:4;77074:9;77070:20;77062:6;77035:56;:::i;:::-;77100;77150:4;77139:9;77135:20;77127:6;77100:56;:::i;:::-;77194:6;77187:4;77176:9;77172:20;77165:36;77239:6;77232:4;77221:9;77217:20;77210:36;77284:6;77277:4;77266:9;77262:20;77255:36;75787:1510;;;;;;;;;:::o;77942:764::-;-1:-1:-1;;;;;78290:15:39;;;78272:34;;-1:-1:-1;;;;;78380:15:39;;;78375:2;78360:18;;78353:43;78432:15;;78253:2;78412:18;;78405:43;78484:3;78479:2;78464:18;;78457:31;;;78216:4;;78245:18;78505:45;;78530:19;;78522:6;78505:45;:::i;:::-;78587:15;;;78581:3;78566:19;;78559:44;78640:15;;;;78634:3;78619:19;;78612:44;78687:3;78672:19;78665:35;-1:-1:-1;78497:53:39;78225:481;-1:-1:-1;;;;78225:481:39:o;78711:255::-;78783:2;78777:9;78825:6;78813:19;;-1:-1:-1;;;;;78847:34:39;;78883:22;;;78844:62;78841:2;;;78909:18;;:::i;:::-;78945:2;78938:22;78757:209;:::o;78971:255::-;79043:2;79037:9;79085:6;79073:19;;-1:-1:-1;;;;;79107:34:39;;79143:22;;;79104:62;79101:2;;;79169:18;;:::i;79231:255::-;79303:2;79297:9;79345:6;79333:19;;-1:-1:-1;;;;;79367:34:39;;79403:22;;;79364:62;79361:2;;;79429:18;;:::i;79491:253::-;79563:2;79557:9;79605:4;79593:17;;-1:-1:-1;;;;;79625:34:39;;79661:22;;;79622:62;79619:2;;;79687:18;;:::i;79749:251::-;79821:2;79815:9;79863:2;79851:15;;-1:-1:-1;;;;;79881:34:39;;79917:22;;;79878:62;79875:2;;;79943:18;;:::i;80005:255::-;80077:2;80071:9;80119:6;80107:19;;-1:-1:-1;;;;;80141:34:39;;80177:22;;;80138:62;80135:2;;;80203:18;;:::i;80265:275::-;80336:2;80330:9;80401:2;80382:13;;-1:-1:-1;;80378:27:39;80366:40;;-1:-1:-1;;;;;80421:34:39;;80457:22;;;80418:62;80415:2;;;80483:18;;:::i;:::-;80519:2;80512:22;80310:230;;-1:-1:-1;80310:230:39:o;80545:186::-;80593:4;-1:-1:-1;;;;;80615:30:39;;80612:2;;;80648:18;;:::i;:::-;-1:-1:-1;80714:2:39;80693:15;-1:-1:-1;;80689:29:39;80720:4;80685:40;;80602:129::o;80736:511::-;80794:5;80801:6;80861:3;80848:17;80947:2;80943:7;80932:8;80916:14;80912:29;80908:43;80888:18;80884:68;80874:2;;80970:5;80963;80956:20;80874:2;81002:33;;81106:4;81093:18;;;-1:-1:-1;81054:21:39;;-1:-1:-1;;;;;;81123:30:39;;81120:2;;;81166:1;81163;81156:12;81120:2;81216:6;81200:14;81196:27;81186:8;81182:42;81179:2;;;81237:1;81234;81227:12;81252:128;81292:3;81323:1;81319:6;81316:1;81313:13;81310:2;;;81329:18;;:::i;:::-;-1:-1:-1;81365:9:39;;81300:80::o;81385:236::-;81424:3;-1:-1:-1;;;;;81490:10:39;;;81520;;;81550:12;;;81542:21;;81539:2;;;81566:18;;:::i;:::-;81602:13;;81432:189;-1:-1:-1;;;;81432:189:39:o;81626:270::-;81665:7;-1:-1:-1;;;;;81735:10:39;;;81765;;;81798:11;;81791:19;81820:12;;;81812:21;;81787:47;81784:2;;;81837:18;;:::i;:::-;81877:13;;81677:219;-1:-1:-1;;;;81677:219:39:o;81901:258::-;81973:1;81983:113;81997:6;81994:1;81991:13;81983:113;;;82073:11;;;82067:18;82054:11;;;82047:39;82019:2;82012:10;81983:113;;;82114:6;82111:1;82108:13;82105:2;;;82149:1;82140:6;82135:3;82131:16;82124:27;82105:2;;81954:205;;;:::o;82164:380::-;82243:1;82239:12;;;;82286;;;82307:2;;82361:4;82353:6;82349:17;82339:27;;82307:2;82414;82406:6;82403:14;82383:18;82380:38;82377:2;;;82460:10;82455:3;82451:20;82448:1;82441:31;82495:4;82492:1;82485:15;82523:4;82520:1;82513:15;82549:209;82587:3;-1:-1:-1;;;;;82657:14:39;;;82683:15;;;82680:2;;;82701:18;;:::i;:::-;82750:1;82737:15;;82595:163;-1:-1:-1;;;82595:163:39:o;82763:175::-;82800:3;82844:4;82837:5;82833:16;82873:4;82864:7;82861:17;82858:2;;;82881:18;;:::i;:::-;82930:1;82917:15;;82808:130;-1:-1:-1;;82808:130:39:o;82943:127::-;83004:10;82999:3;82995:20;82992:1;82985:31;83035:4;83032:1;83025:15;83059:4;83056:1;83049:15;83075:127;83136:10;83131:3;83127:20;83124:1;83117:31;83167:4;83164:1;83157:15;83191:4;83188:1;83181:15;83207:127;83268:10;83263:3;83259:20;83256:1;83249:31;83299:4;83296:1;83289:15;83323:4;83320:1;83313:15;83339:131;83438:1;83431:5;83428:12;83418:2;;83444:18;;:::i;:::-;83408:62;:::o;83475:131::-;-1:-1:-1;;;;;83550:31:39;;83540:42;;83530:2;;83596:1;83593;83586:12;83611:129;-1:-1:-1;;;;;83685:30:39;;83675:41;;83665:2;;83730:1;83727;83720:12
Swarm Source
ipfs://7110f84fa10d276ff38d69069eddb0aa1d25c3b5937971da00aaf594e60ba9a3
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.