Contract Overview
Balance:
0 MATIC
MATIC Value:
$0.00
My Name Tag:
Not Available, login to update
Txn Hash | Method |
Block
|
From
|
To
|
Value | [Txn Fee] | |||
---|---|---|---|---|---|---|---|---|---|
0x1ca66276bf4f2957b2f69ad59c254fccf2920f53fdaa7e1d5b2da38cad4f0c72 | 0x6141fc61 | 21255715 | 194 days 6 hrs ago | 0x8af97264482b59c7aa11010907710dee6d8d8c6c | IN | Create: Trading | 0 MATIC | 0.11104275 |
[ Download CSV Export ]
Contract Name:
Trading
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 { 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 { 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 { 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 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; 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": { "Exchange.sol": { "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB", "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac", "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc", "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962", "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad", "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1", "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2" }, "AssetRegistry.sol": { "AssetRegistry": "0xc2f05d03915E7c2D9038830F7888c97e351dd3dB" }, "Depositing.sol": { "Depositing": "0x116310b243dd287d4285d0e8a34ce3d4adb63dac" }, "LiquidityPoolAdmin.sol": { "LiquidityPoolAdmin": "0x7a246e4434dd31df784bb88d3443e309e3143adc" }, "LiquidityPools.sol": { "LiquidityPools": "0x0f2c07f4ecc6c9d74d16e735d2a59d00985b1962" }, "NonceInvalidations.sol": { "NonceInvalidations": "0x6c539e6143f70408076f35d19e7e549850c021ad" }, "Trading.sol": { "Trading": "0x4d3250014ea4ecddd857fad48c3d64d2e4f037e1" }, "Withdrawing.sol": { "Withdrawing": "0xb3af24eeac0ee8b6f5798f8a75e3ecd51b18deb2" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract Creation Code
6141fc61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361061004b5760003560e01c80634f2acb7614610050578063b153635a14610072578063c8a4503614610092575b600080fd5b81801561005c57600080fd5b5061007061006b36600461386d565b6100b2565b005b81801561007e57600080fd5b5061007061008d366004613938565b610158565b81801561009e57600080fd5b506100706100ad3660046139fa565b610195565b6000806100c28c8c8c8b886101e7565b915091506100d88c8c84848e8e8d8d8d8c61035d565b6100eb8c838d848e600001518a89610410565b89516100fd9087908e908e908d610445565b6000808b516101a00151600181111561012657634e487b7160e01b600052602160045260246000fd5b14610135578c6040015161013b565b8b604001515b9050610149878c838d61068b565b50505050505050505050505050565b6000806101688b8b8b8a88610768565b9150915061017b8b838c848d8a89610410565b610188868c8c8c8c610445565b5050505050505050505050565b60006101a38a8a89866107f0565b90506101bb8a828b608001518c60a00151888761083c565b6101c7858b8b8b610a31565b60808a01516101d99087908b90610be2565b505050505050505050505050565b60008085604001516001600160a01b031687604001516001600160a01b0316141561022d5760405162461bcd60e51b815260040161022490613cf7565b60405180910390fd5b8460200151604001516001600160a01b03168560000151604001516001600160a01b031614801561027f57508460200151606001516001600160a01b03168560000151606001516001600160a01b0316145b6102c55760405162461bcd60e51b81526020600482015260176024820152764d69736d6174636865642074726164652061737365747360481b6044820152606401610224565b6102ce8561102d565b6102d987878561130d565b6102e887878760000151611416565b865191935091506102fd908890889087611453565b61030c87878760000151611583565b60008086516101a00151600181111561033557634e487b7160e01b600052602160045260246000fd5b146103405787610342565b865b905061035281876020015161168f565b509550959350505050565b600080808089516101a00151600181111561038857634e487b7160e01b600052602160045260246000fd5b14610395578b8d8c610399565b8c8c8b5b9250925092506103bd82828b60200151608001518c6020015160a00151898961083c565b60208901516103d090879084908b610a31565b6000806103f08b6020015185608001518b610be29092919063ffffffff16565b915091506103ff858383611807565b505050505050505050505050505050565b610426878785608001518660a00151868661083c565b61043c858585608001518660a00151868661083c565b50505050505050565b600061045a8685604001518560400151611984565b6080840151815491925090829060019061048390849061010090046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506104ba8686604001518560400151611984565b60c084015181549192509082906001906104e390849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555061051a8686604001518560600151611984565b60a0840151815491925090829060019061054390849061010090046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555061057a8685604001518560600151611984565b60e084015181549192509082906001906105a390849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506105d78683856101000151611984565b610140840151815491925090829060019061060190849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506106358683856101200151611984565b610160840151815491925090829060019061065f90849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050505050565b60006106a1858386600001516101200151611984565b604085015181549192509082906001906106ca90849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610702858486600001516101200151611984565b905083602001516101600151846040015161071d9190613f2c565b8154829060019061073d90849061010090046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b031602179055505050505050565b60008085604001516001600160a01b031687604001516001600160a01b031614156107a55760405162461bcd60e51b815260040161022490613cf7565b6107b187878787611453565b6107bc878787611583565b6107c787878561130d565b6000806107d5898989611416565b915091506107e287611a89565b909890975095505050505050565b60006108058585600001518660200151611b5a565b9050610812858585611c08565b61081c858561168f565b6108268583611d1a565b610834856080015185611d94565b949350505050565b60008581526020839052604090205460ff16156108915760405162461bcd60e51b815260206004820152601360248201527213dc99195c88191bdd589b1948199a5b1b1959606a1b6044820152606401610224565b60008660c0015115610939576108aa8760600151611e61565b6109105760405162461bcd60e51b815260206004820152603160248201527f4f726465722071756f7465207175616e74697479206f6e6c792076616c696420604482015270666f72206d61726b6574206f726465727360781b6064820152608401610224565b600086815260208390526040902054610932906001600160401b031685613f2c565b905061095e565b60008681526020839052604090205461095b906001600160401b031686613f2c565b90505b60a08701516001600160401b0380821690831611156109b25760405162461bcd60e51b815260206004820152601060248201526f13dc99195c881bdd995c999a5b1b195960821b6044820152606401610224565b806001600160401b0316826001600160401b031610156109f857600087815260208490526040902080546001600160401b0319166001600160401b038416179055610a27565b60008781526020848152604080832080546001600160401b0319169055908690529020805460ff191660011790555b5050505050505050565b6000610a58858560400151610a53876080015187611ee090919063ffffffff16565b611984565b9050610a71846080015184611f1b90919063ffffffff16565b81548290600190610a9190849061010090046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610ad5858560400151610a53876080015187611f5690919063ffffffff16565b9050610aee846080015184611f9190919063ffffffff16565b81548290600190610b0e90849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610b4e8583610a53876080015187611ee090919063ffffffff16565b6101208401518154919250908290600190610b7890849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bb88583610a53876080015187611f5690919063ffffffff16565b610140840151815491925090829060019061073d90849061010090046001600160401b0316613f2c565b6000806000610bfa8686604001518760600151611fda565b8054909150600090610c23906001600160401b03600160501b8204811691610100900416613fa2565b9050600080866001811115610c4857634e487b7160e01b600052602160045260246000fd5b1415610d3057610c58878761204d565b83548490600190610c7890849061010090046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610caf868861208890919063ffffffff16565b83548490600a90610cd1908490600160501b90046001600160401b0316613f2c565b82546001600160401b0391821661010093840a90810290830219909116179092558901518554610d0c93509091600160501b9091041661402d565b8354610d29916001600160401b0390811691610100900416613fa2565b9050610e74565b610d3a8787612088565b83548490600190610d5a90849061010090046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555060008761016001516001600160401b03161115610de65761016087015183548490600a90610dbd908490600160501b90046001600160401b0316613f2c565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610e37565b610df0878761204d565b83548490600a90610e12908490600160501b90046001600160401b031661402d565b92506101000a8154816001600160401b0302191690836001600160401b031602179055505b8254610100888101516001600160401b03600160501b8404811693610e5e9390041661402d565b6001600160401b0316610e719190613fa2565b90505b816001600160801b0316816001600160801b03161015610ed65760405162461bcd60e51b815260206004820181905260248201527f436f6e7374616e742070726f647563742063616e6e6f742064656372656173656044820152606401610224565b82546305f5e1006101009091046001600160401b03161015610f345760405162461bcd60e51b81526020600482015260176024820152762130b9b2903932b9b2b93b32b9903132b637bb9036b4b760491b6044820152606401610224565b82546305f5e100600160501b9091046001600160401b03161015610f955760405162461bcd60e51b815260206004820152601860248201527728bab7ba32903932b9b2b93b32b9903132b637bb9036b4b760411b6044820152606401610224565b6040805160c081018252845460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b9004909116608082015260018401546001600160a01b031660a0820152611008906120d1565b5050546001600160401b036101008204811697600160501b9092041695509350505050565b602081015161014001516001600160401b0316156110855760405162461bcd60e51b81526020600482015260156024820152744e6f6e2d7a65726f20706f6f6c206761732066656560581b6044820152606401610224565b60006110908261218b565b90506110b161109e836121cb565b6110a8908361402d565b826107d061220b565b6110cd5760405162461bcd60e51b815260040161022490613cca565b60008083516101a0015160018111156110f657634e487b7160e01b600052602160045260246000fd5b14611102576000611105565b60015b90506111108361223e565b915061113561112c8285602001516122a790919063ffffffff16565b836101f461220b565b6111515760405162461bcd60e51b815260040161022490613e04565b61116661115d846122bd565b836107d061220b565b6111825760405162461bcd60e51b815260040161022490613c9d565b602083015161016001516001600160401b0316156112ef57600083516101a0015160018111156111c257634e487b7160e01b600052602160045260246000fd5b146111df5760405162461bcd60e51b815260040161022490613d97565b602083015160e001516001600160401b0316156112525760405162461bcd60e51b815260206004820152602b60248201527f51756f7465206f7574206e6f7420616c6c6f776564207769746820707269636560448201526a1031b7b93932b1ba34b7b760a91b6064820152608401610224565b602083015161126190826122d5565b9150816001600160401b0316836020015161016001516001600160401b031611156112ef576112a6828460200151610160015161129e919061402d565b83606461220b565b6112ef5760405162461bcd60e51b815260206004820152601a60248201527922bc31b2b9b9b4bb3290383934b1b29031b7b93932b1ba34b7b760311b6044820152606401610224565b6112fd818460200151612310565b825161130890612431565b505050565b61131b836040015182612582565b6001600160401b03166113318460200151612600565b6001600160401b0316116113915760405162461bcd60e51b815260206004820152602160248201527f427579206f72646572206e6f6e63652074696d657374616d7020746f6f206c6f6044820152607760f81b6064820152608401610224565b61139f826040015182612582565b6001600160401b03166113b58360200151612600565b6001600160401b0316116113085760405162461bcd60e51b815260206004820152602260248201527f53656c6c206f72646572206e6f6e63652074696d657374616d7020746f6f206c6044820152616f7760f01b6064820152608401610224565b600080600061142e8685600001518660200151611b5a565b905060006114458686600001518760200151611b5a565b919791965090945050505050565b81606001516001600160a01b031682604001516001600160a01b0316141561148d5760405162461bcd60e51b815260040161022490613d29565b81604001516001600160a01b03168261010001516001600160a01b03161480156114d1575081606001516001600160a01b03168261012001516001600160a01b0316145b8061151b575081606001516001600160a01b03168261010001516001600160a01b031614801561151b575081604001516001600160a01b03168261012001516001600160a01b0316145b6115675760405162461bcd60e51b815260206004820152601e60248201527f46656520617373657473206d69736d61746368207472616465207061697200006044820152606401610224565b611572848383611c42565b61157d838383611c42565b50505050565b600081608001516001600160401b0316116115b05760405162461bcd60e51b815260040161022490613e81565b60008160a001516001600160401b0316116115dd5760405162461bcd60e51b815260040161022490613e39565b6115ea836060015161269c565b15611636578060a001516001600160401b031661160f82608001518560e00151612722565b6001600160401b031610156116365760405162461bcd60e51b815260040161022490613d60565b611643826060015161269c565b15611308578060a001516001600160401b031661166882608001518460e00151612722565b6001600160401b031611156113085760405162461bcd60e51b815260040161022490613dcd565b600081608001516001600160401b0316116116bc5760405162461bcd60e51b815260040161022490613e81565b60008160a001516001600160401b0316116116e95760405162461bcd60e51b815260040161022490613e39565b60008260800151600181111561170f57634e487b7160e01b600052602160045260246000fd5b1480156117245750611724826060015161269c565b15611770578060a001516001600160401b031661174982608001518460e00151612722565b6001600160401b031610156117705760405162461bcd60e51b815260040161022490613d60565b60018260800151600181111561179657634e487b7160e01b600052602160045260246000fd5b1480156117ab57506117ab826060015161269c565b15611803578060a001516001600160401b03166117dc600183608001516117d2919061402d565b8460e00151612722565b6001600160401b031611156118035760405162461bcd60e51b815260040161022490613dcd565b5050565b60008360800151600181111561182d57634e487b7160e01b600052602160045260246000fd5b1480156118425750611842836060015161269c565b156118b657806001600160401b031661185f838560e00151612722565b6001600160401b031611156118b65760405162461bcd60e51b815260206004820181905260248201527f506f6f6c206d617267696e616c206275792070726963652065786365656465646044820152606401610224565b6001836080015160018111156118dc57634e487b7160e01b600052602160045260246000fd5b1480156118f157506118f1836060015161269c565b156113085761190160018261402d565b6001600160401b0316611918838560e00151612722565b611923906001613f2c565b6001600160401b031610156113085760405162461bcd60e51b815260206004820152602160248201527f506f6f6c206d617267696e616c2073656c6c20707269636520657863656564656044820152601960fa1b6064820152608401610224565b6001600160a01b038083166000908152602085815260408083209385168352929052908120805460ff161580156119c7575060018501546001600160a01b031615155b15611a7f57600185015460405163dbb3653560e01b81526001600160a01b03868116600483015285811660248301529091169063dbb365359060440160206040518083038186803b158015611a1b57600080fd5b505afa158015611a2f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a539190613aa2565b815460ff196001600160401b039290921661010002919091166001600160481b03199091161760011781555b90505b9392505050565b600081604001516001600160a01b03168261010001516001600160a01b031614611ab7578160a00151611abd565b81608001515b9050611ad1826101400151826107d061220b565b611aed5760405162461bcd60e51b815260040161022490613cca565b600082604001516001600160a01b03168361012001516001600160a01b031614611b1b578260a00151611b21565b82608001515b9050611b35836101600151826107d061220b565b611b515760405162461bcd60e51b815260040161022490613c9d565b61130883612431565b600080611b68858585612733565b9050611b7e81866101a00151876040015161296e565b600086608001516001811115611ba457634e487b7160e01b600052602160045260246000fd5b14611bc7576040518060600160405280602781526020016141a060279139611be1565b60405180606001604052806026815260200161417a602691395b90611bff5760405162461bcd60e51b81526004016102249190613c6a565b50949350505050565b81606001516001600160a01b031682604001516001600160a01b03161415611c425760405162461bcd60e51b815260040161022490613d29565b6000611c518460200151612600565b8351909150600090611c65908490846129eb565b90506000611c82856020015184866129eb9092919063ffffffff16565b905084604001516001600160a01b031682602001516001600160a01b0316148015611cc6575084606001516001600160a01b031681602001516001600160a01b0316145b611d125760405162461bcd60e51b815260206004820152601d60248201527f4f726465722073796d626f6c2061646472657373206d69736d617463680000006044820152606401610224565b505050505050565b611d28826040015182612582565b6001600160401b0316611d3e8360200151612600565b6001600160401b0316116118035760405162461bcd60e51b815260206004820152601d60248201527f4f72646572206e6f6e63652074696d657374616d7020746f6f206c6f770000006044820152606401610224565b611db3611da182846122a7565b611dab83856122d5565b6101f461220b565b611dcf5760405162461bcd60e51b815260040161022490613e04565b610140810151611deb90611de383856122d5565b6107d061220b565b611e2b5760405162461bcd60e51b8152602060048201526011602482015270457863657373697665206761732066656560781b6044820152606401610224565b6101608101516001600160401b031615611e575760405162461bcd60e51b815260040161022490613d97565b6118038282612310565b600080826006811115611e8457634e487b7160e01b600052602160045260246000fd5b1480611eaf57506003826006811115611ead57634e487b7160e01b600052602160045260246000fd5b145b80611eda575060055b826006811115611ed857634e487b7160e01b600052602160045260246000fd5b145b92915050565b600080826001811115611f0357634e487b7160e01b600052602160045260246000fd5b14611f12578260400151611a82565b50506060015190565b600080826001811115611f3e57634e487b7160e01b600052602160045260246000fd5b14611f4d578260800151611a82565b505060a0015190565b600080826001811115611f7957634e487b7160e01b600052602160045260246000fd5b14611f88578260600151611a82565b50506040015190565b61014082015160009081836001811115611fbb57634e487b7160e01b600052602160045260246000fd5b14611fca578360e00151611fd0565b8360c001515b611a82919061402d565b6001600160a01b0380831660009081526001850160209081526040808320938516835292905220805460ff16611a825760405162461bcd60e51b81526020600482015260186024820152772737903837b7b6103337b91030b2323932b9b9903830b4b960411b6044820152606401610224565b60008082600181111561207057634e487b7160e01b600052602160045260246000fd5b1461207f578260e00151611a82565b505060c0015190565b610100820151600090818360018111156120b257634e487b7160e01b600052602160045260246000fd5b146120c1578360c001516120c7565b8360e001515b611a829190613f2c565b60008082606001516001600160401b031683602001516001600160401b031611156121055782606001518360200151612110565b826020015183606001515b91509150806001600160401b03166305f5e1006001600160401b0316836001600160401b03166121409190613fd1565b10156113085760405162461bcd60e51b815260206004820152601a6024820152794578636565646564206d6178207265736572766520726174696f60301b6044820152606401610224565b60008160000151604001516001600160a01b0316826000015161012001516001600160a01b0316146121c257815160800151611eda565b505160a0015190565b60008160000151604001516001600160a01b0316826000015161012001516001600160a01b03161461220257815160c00151611eda565b505160e0015190565b6000808361221b61271087613ff0565b6122259190613f88565b6001600160401b03808516911611159150509392505050565b60008160000151604001516001600160a01b0316826000015161012001516001600160a01b03161461228b57816020015160a00151826000015160a001516122869190613f2c565b611eda565b816020015160800151826000015160800151611eda9190613f2c565b60006122b3838361204d565b611fd084846122d5565b805161016001516040820151600091611eda91613f2c565b6000808260018111156122f857634e487b7160e01b600052602160045260246000fd5b14612307578260a00151611a82565b50506080015190565b6000808084600181111561233457634e487b7160e01b600052602160045260246000fd5b14612348578260c001518360800151612353565b8260e001518360a001515b91509150806001600160401b0316836101200151846101000151846123789190613f2c565b6123829190613f2c565b6001600160401b0316146123d55760405162461bcd60e51b815260206004820152601a602482015279141bdbdb081a5b9c1d5d081999595cc81d5b98985b185b98d95960321b6044820152606401610224565b6123ea6123e2838361402d565b8260c861220b565b61157d5760405162461bcd60e51b815260206004820152601860248201527745786365737369766520706f6f6c20696e7075742066656560401b6044820152606401610224565b80608001516001600160401b031681604001516001600160a01b03168261010001516001600160a01b03161461246c57816101600151612473565b8161014001515b8260c001516124829190613f2c565b6001600160401b0316146124d85760405162461bcd60e51b815260206004820152601e60248201527f4f72646572626f6f6b2062617365206665657320756e62616c616e63656400006044820152606401610224565b8060a001516001600160401b031681606001516001600160a01b03168261010001516001600160a01b0316146125135781610160015161251a565b8161014001515b8260e001516125299190613f2c565b6001600160401b03161461257f5760405162461bcd60e51b815260206004820152601f60248201527f4f72646572626f6f6b2071756f7465206665657320756e62616c616e636564006044820152606401610224565b50565b6001600160a01b03821660009081526020829052604081205460ff1680156125c557506001600160a01b0383166000908152602083905260409020600101544310155b156125f757506001600160a01b03821660009081526020829052604090205461010090046001600160401b0316611eda565b50600092915050565b6000600f604c83901c166001811461264c5760405162461bcd60e51b815260206004820152600f60248201526e135d5cdd081899481d8c4815555251608a1b6044820152606401610224565b610fff60301b601084901c1661ffff60201b603085901c1663ffffffff606086901c168183178117650b1d069b540061268761271083613f4e565b612691919061402d565b979650505050505050565b600060018260068111156126c057634e487b7160e01b600052602160045260246000fd5b14806126eb575060028260068111156126e957634e487b7160e01b600052602160045260246000fd5b145b806127155750600482600681111561271357634e487b7160e01b600052602160045260246000fd5b145b80611eda57506006611eb8565b6000611a8283836305f5e100612dc6565b825160009060ff1660031461278a5760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726520686173682076657273696f6e20696e76616c696400006044820152606401610224565b83600001518460200151856040015185856040516020016127ac929190613b9c565b604051602081830303815290604052876060015160068111156127df57634e487b7160e01b600052602160045260246000fd5b8860800151600181111561280357634e487b7160e01b600052602160045260246000fd5b6128198a60a001516001600160401b0316612dd5565b60405160200161282f9796959493929190613bd8565b6040516020818303038152906040528460c0015160008660e001516001600160401b03161161286d5760405180602001604052806000815250612883565b6128838660e001516001600160401b0316612dd5565b60008761010001516001600160401b0316116128ae57604051806020016040528060008152506128c5565b6128c58761010001516001600160401b0316612dd5565b87610120015188610140015160038111156128f057634e487b7160e01b600052602160045260246000fd5b896101600151600381111561291557634e487b7160e01b600052602160045260246000fd5b8a61018001516040516020016129319796959493929190613abe565b60408051601f198184030181529082905261294f9291602001613b51565b6040516020818303038152906040528051906020012090509392505050565b6000816001600160a01b03166129d96129d3866040517b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b85612f4f565b6001600160a01b031614949350505050565b6129f36132ee565b612a89846002018054612a0590614098565b80601f0160208091040260200160405190810160405280929190818152602001828054612a3190614098565b8015612a7e5780601f10612a5357610100808354040283529160200191612a7e565b820191906000526020600020905b815481529060010190602001808311612a6157829003601f168201915b505050505084613005565b15612b2a57612b23846002018054612aa090614098565b80601f0160208091040260200160405190810160405280929190818152602001828054612acc90614098565b8015612b195780601f10612aee57610100808354040283529160200191612b19565b820191906000526020600020905b815481529060010190602001808311612afc57829003601f168201915b505050505061305e565b9050611a82565b612b326132ee565b60008560010185604051612b469190613b80565b908152604051908190036020019020541115612d5f5760005b8560010185604051612b719190613b80565b9081526040519081900360200190205460ff82161015612d5d57836001600160401b03168660010186604051612ba79190613b80565b90815260200160405180910390208260ff1681548110612bd757634e487b7160e01b600052603260045260246000fd5b60009182526020909120600390910201600201546201000090046001600160401b031611612d4b578560010185604051612c119190613b80565b90815260200160405180910390208160ff1681548110612c4157634e487b7160e01b600052603260045260246000fd5b60009182526020918290206040805160c0810182526003909302909101805460ff8116151584526001600160a01b03610100909104169383019390935260018301805492939291840191612c9490614098565b80601f0160208091040260200160405190810160405280929190818152602001828054612cc090614098565b8015612d0d5780601f10612ce257610100808354040283529160200191612d0d565b820191906000526020600020905b815481529060010190602001808311612cf057829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015291505b80612d55816140ee565b915050612b5f565b505b80518015612d6e575080608001515b611a7f5760405162461bcd60e51b815260206004820152602360248201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f722073796d604482015262189bdb60ea1b6064820152608401610224565b6000611a7f848484600061309d565b60608160005b8115612e015780612deb816140d3565b9150612dfa9050600a83613f74565b9150612ddb565b6009811015612e0e575060095b80612e18816140d3565b9150506000816001600160401b03811115612e4357634e487b7160e01b600052604160045260246000fd5b6040519080825280601f01601f191660200182016040528015612e6d576020820181803683370190505b509050815b8015611bff57612e828184614016565b60081415612ed557601760f91b82612e9b600184614016565b81518110612eb957634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350612f3d565b612ee0600a8761410e565b612eeb906030613f14565b60f81b82612efa600184614016565b81518110612f1857634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350612f3a600a87613f74565b95505b80612f4781614081565b915050612e72565b600080600080845160411415612f795750505060208201516040830151606084015160001a612fef565b845160401415612fa75750505060408201516020830151906001600160ff1b0381169060ff1c601b01612fef565b60405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610224565b612ffb86828585613154565b9695505050505050565b6000816040516020016130189190613b80565b604051602081830303815290604052805190602001208360405160200161303f9190613b80565b6040516020818303038152906040528051906020012014905092915050565b6130666132ee565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b6000806130b66001600160401b03808716908816613fd1565b905060006130cd6001600160401b03861683613f74565b90508380156130ee575060006130ec6001600160401b0387168461410e565b115b15613101576130fe600182613f14565b90505b600160401b8110612ffb5760405162461bcd60e51b815260206004820152601d60248201527f506970207175616e74697479206f766572666c6f77732075696e7436340000006044820152606401610224565b60006fa2a8918ca85bafe22016d0b997e4df60600160ff1b038211156131c75760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610224565b8360ff16601b14806131dc57508360ff16601c145b6132335760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610224565b6040805160008082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa158015613287573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166132e55760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606401610224565b95945050505050565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b80356001600160a01b038116811461333b57600080fd5b919050565b8035801515811461333b57600080fd5b600082601f830112613360578081fd5b81356001600160401b038082111561337a5761337a61414e565b604051601f8301601f19908116603f011681019082821181831017156133a2576133a261414e565b816040528381528660208588010111156133ba578485fd5b8360208701602083013792830160200193909352509392505050565b80356004811061333b57600080fd5b80356002811061333b57600080fd5b80356007811061333b57600080fd5b600060608284031215613414578081fd5b604051606081016001600160401b0380821183831017156134375761343761414e565b81604052829350843591508082111561344f57600080fd5b61345b8683870161349f565b8352602085013591508082111561347157600080fd5b5061347e85828601613728565b602083015250604083013561349281614164565b6040919091015292915050565b60006101c082840312156134b1578081fd5b6134b9613ec8565b905081356001600160401b03808211156134d257600080fd5b6134de85838601613350565b835260208401359150808211156134f457600080fd5b5061350184828501613350565b60208301525061351360408301613324565b604082015261352460608301613324565b606082015261353560808301613851565b608082015261354660a08301613851565b60a082015261355760c08301613851565b60c082015261356860e08301613851565b60e082015261010061357b818401613324565b9082015261012061358d838201613324565b9082015261014061359f838201613851565b908201526101606135b1838201613851565b908201526101806135c3838201613851565b908201526101a06135d58382016133e5565b9082015292915050565b60006101c082840312156135f1578081fd5b6135f9613ec8565b90506136048261385c565b81526136126020830161383a565b602082015261362360408301613324565b6040820152613634606083016133f4565b6060820152613645608083016133e5565b608082015261365660a08301613851565b60a082015261366760c08301613340565b60c082015261367860e08301613851565b60e082015261010061368b818401613851565b90820152610120828101356001600160401b03808211156136ab57600080fd5b6136b786838701613350565b8385015261014092506136cb8386016133d6565b8385015261016092506136df8386016133d6565b8385015261018092506136f3838601613851565b838501526101a092508285013591508082111561370f57600080fd5b5061371c85828601613350565b82840152505092915050565b6000610180828403121561373a578081fd5b613742613ef1565b905081356001600160401b038082111561375b57600080fd5b61376785838601613350565b8352602084013591508082111561377d57600080fd5b5061378a84828501613350565b60208301525061379c60408301613324565b60408201526137ad60608301613324565b60608201526137be60808301613851565b60808201526137cf60a08301613851565b60a08201526137e060c08301613851565b60c08201526137f160e08301613851565b60e0820152610100613804818401613851565b90820152610120613816838201613851565b90820152610140613828838201613851565b908201526101606135d5838201613851565b80356001600160801b038116811461333b57600080fd5b803561333b81614164565b803560ff8116811461333b57600080fd5b6000806000806000806000806000806101408b8d03121561388c578586fd5b8a356001600160401b03808211156138a2578788fd5b6138ae8e838f016135df565b9b5060208d01359150808211156138c3578788fd5b6138cf8e838f016135df565b9a5060408d01359150808211156138e4578788fd5b506138f18d828e01613403565b98505061390060608c01613324565b999c989b50969960808101359860a0820135985060c0820135975060e082013596506101008201359550610120909101359350915050565b60008060008060008060008060006101208a8c031215613956578283fd5b89356001600160401b038082111561396c578485fd5b6139788d838e016135df565b9a5060208c013591508082111561398d578485fd5b6139998d838e016135df565b995060408c01359150808211156139ae578485fd5b506139bb8c828d0161349f565b9750506139ca60608b01613324565b989b979a50959860808101359760a0820135975060c0820135965060e08201359550610100909101359350915050565b60008060008060008060008060006101208a8c031215613a18578283fd5b89356001600160401b0380821115613a2e578485fd5b613a3a8d838e016135df565b9a5060208c0135915080821115613a4f578485fd5b50613a5c8c828d01613728565b985050613a6b60408b01613324565b989b979a5097986060810135985060808101359760a0820135975060c0820135965060e08201359550610100909101359350915050565b600060208284031215613ab3578081fd5b8151611a8281614164565b87151560f81b815260008751613adb816001850160208c01614055565b875190830190613af2816001840160208c01614055565b8751910190613b08816001840160208b01614055565b60f896871b6001600160f81b0319908116600193909201928301919091529490951b90931660028501525060c01b6001600160c01b031916600383015250600b01949350505050565b60008351613b63818460208801614055565b835190830190613b77818360208801614055565b01949350505050565b60008251613b92818460208701614055565b9190910192915050565b60008351613bae818460208801614055565b602d60f81b9083019081528351613bcc816001840160208801614055565b01600101949350505050565b6001600160f81b031960f889901b811682526001600160801b0319608089901b1660018301526001600160601b0319606088901b166011830152855160009190613c29816025860160208b01614055565b8084019050818760f81b166025820152818660f81b16602682015284519150613c59826027830160208801614055565b016027019998505050505050505050565b6020815260008251806020840152613c89816040850160208701614055565b601f01601f19169190910160400192915050565b6020808252601390820152724578636573736976652074616b65722066656560681b604082015260600190565b602080825260139082015272457863657373697665206d616b65722066656560681b604082015260600190565b60208082526018908201527714d95b198b5d1c98591a5b99c81b9bdd08185b1b1bddd95960421b604082015260600190565b6020808252601e908201527f547261646520617373657473206d75737420626520646966666572656e740000604082015260600190565b6020808252601e908201527f427579206f72646572206c696d69742070726963652065786365656465640000604082015260600190565b6020808252601c908201527b141c9a58d94818dbdc9c9958dd1a5bdb881b9bdd08185b1b1bddd95960221b604082015260600190565b6020808252601f908201527f53656c6c206f72646572206c696d697420707269636520657863656564656400604082015260600190565b6020808252818101527f45786365737369766520706f6f6c206f75747075742061646a7573746d656e74604082015260600190565b60208082526028908201527f51756f7465207175616e74697479206d7573742062652067726561746572207460408201526768616e207a65726f60c01b606082015260800190565b60208082526027908201527f42617365207175616e74697479206d7573742062652067726561746572207468604082015266616e207a65726f60c81b606082015260800190565b6040516101c081016001600160401b0381118282101715613eeb57613eeb61414e565b60405290565b60405161018081016001600160401b0381118282101715613eeb57613eeb61414e565b60008219821115613f2757613f27614122565b500190565b60006001600160401b03828116848216808303821115613b7757613b77614122565b60006001600160801b0383811680613f6857613f68614138565b92169190910492915050565b600082613f8357613f83614138565b500490565b60006001600160401b0383811680613f6857613f68614138565b60006001600160801b0382811684821681151582840482111615613fc857613fc8614122565b02949350505050565b6000816000190483118215151615613feb57613feb614122565b500290565b60006001600160401b0382811684821681151582840482111615613fc857613fc8614122565b60008282101561402857614028614122565b500390565b60006001600160401b038381169083168181101561404d5761404d614122565b039392505050565b60005b83811015614070578181015183820152602001614058565b8381111561157d5750506000910152565b60008161409057614090614122565b506000190190565b600181811c908216806140ac57607f821691505b602082108114156140cd57634e487b7160e01b600052602260045260246000fd5b50919050565b60006000198214156140e7576140e7614122565b5060010190565b600060ff821660ff81141561410557614105614122565b60010192915050565b60008261411d5761411d614138565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160401b038116811461257f57600080fdfe496e76616c69642077616c6c6574207369676e617475726520666f7220627579206f72646572496e76616c69642077616c6c6574207369676e617475726520666f722073656c6c206f72646572a2646970667358221220ee7b2c74fdc947097f93238c12d3f8c0e5e56c04a156b57a959a68c3bd42edd064736f6c63430008040033
Deployed ByteCode Sourcemap
716:8180:27:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2933:1647;;;;;;;;;;-1:-1:-1;2933:1647:27;;;;;:::i;:::-;;:::i;:::-;;930:975;;;;;;;;;;-1:-1:-1;930:975:27;;;;;:::i;:::-;;:::i;1909:1020::-;;;;;;;;;;-1:-1:-1;1909:1020:27;;;;;:::i;:::-;;:::i;2933:1647::-;3449:15;3466:16;3492:149;3544:3;3557:4;3571:11;3592:13;3615:18;3492:42;:149::i;:::-;3448:193;;;;3648:251;3687:3;3698:4;3710:7;3725:8;3741:11;3760:9;3777:21;3806:15;3829:20;3857:36;3648:31;:251::i;:::-;3940:209;3977:3;3990:7;4007:4;4021:8;4039:11;:26;;;4075:20;4105:36;3940:27;:209::i;:::-;4234:26;;4158:129;;:15;;4207:3;;4220:4;;4270:9;4158:39;:129::i;:::-;4308:19;;4338:26;;:36;;;:53;;;;;;-1:-1:-1;;;4338:53:27;;;;;;;;;;:114;;4435:3;:17;;;4338:114;;;4404:4;:18;;;4338:114;4308:144;-1:-1:-1;4460:109:27;:15;4510:11;4308:144;4552:9;4460:40;:109::i;:::-;2933:1647;;;;;;;;;;;;;:::o;930:975::-;1397:15;1414:16;1440:158;1498:3;1511:4;1525:14;1549:13;1572:18;1440:48;:158::i;:::-;1396:202;;;;1605:181;1640:3;1651:7;1666:4;1678:8;1694:14;1716:20;1744:36;1605:27;:181::i;:::-;1793:107;:15;1840:3;1851:4;1863:14;1885:9;1793:39;:107::i;:::-;930:975;;;;;;;;;;;:::o;1909:1020::-;2397:17;2423:131;2471:5;2486:9;2505:13;2528:18;2423:38;:131::i;:::-;2397:157;;2561:216;2594:5;2607:9;2624;:33;;;2665:9;:34;;;2707:20;2735:36;2561:25;:216::i;:::-;2784:63;:15;2819:5;2826:9;2837;2784:34;:63::i;:::-;2913:10;;;;2853:71;;:21;;2902:9;;2853:48;:71::i;:::-;;;1909:1020;;;;;;;;;;:::o;808:1395:13:-;1064:15;1081:16;1141:4;:18;;;-1:-1:-1;;;;;1120:39:13;:3;:17;;;-1:-1:-1;;;;;1120:39:13;;;1105:94;;;;-1:-1:-1;;;1105:94:13;;;;;;;:::i;:::-;;;;;;;;;1276:11;:21;;;:38;;;-1:-1:-1;;;;;1221:93:13;:11;:26;;;:43;;;-1:-1:-1;;;;;1221:93:13;;:200;;;;;1382:11;:21;;;:39;;;-1:-1:-1;;;;;1326:95:13;:11;:26;;;:44;;;-1:-1:-1;;;;;1326:95:13;;1221:200;1206:254;;;;-1:-1:-1;;;1206:254:13;;31746:2:31;1206:254:13;;;31728:21:31;31785:2;31765:18;;;31758:30;-1:-1:-1;;;31804:18:31;;;31797:53;31867:18;;1206:254:13;31718:173:31;1206:254:13;1466:25;1479:11;1466:12;:25::i;:::-;1534:62;1566:3;1571:4;1577:18;1534:31;:62::i;:::-;1624:112;1681:3;1692:4;1704:11;:26;;;1624:49;:112::i;:::-;1816:26;;1602:134;;-1:-1:-1;1602:134:13;-1:-1:-1;1742:127:13;;1793:3;;1804:4;;1850:13;1742:43;:127::i;:::-;1875:108;1928:3;1939:4;1951:11;:26;;;1875:45;:108::i;:::-;2020:23;;2052:26;;:36;;;:53;;;;;;-1:-1:-1;;;2052:53:13;;;;;;;;;;:66;;2115:3;2052:66;;;2108:4;2052:66;2020:98;;2124:74;2164:10;2176:11;:21;;;2124:39;:74::i;:::-;808:1395;;;;;;;;;:::o;4584:1348:27:-;5038:23;;;;5120:26;;:36;;;:53;;;;;;-1:-1:-1;;;5120:53:27;;;;;;;;;;:116;;5217:4;5223:3;5228:7;5120:116;;;5185:3;5190:4;5196:8;5120:116;5037:199;;;;;;5243:250;5276:10;5294:14;5316:11;:21;;;:45;;;5369:11;:21;;;:46;;;5423:20;5451:36;5243:25;:250::i;:::-;5560:21;;;;5500:104;;:15;;5542:10;;5589:9;5500:34;:104::i;:::-;5612:29;5643:30;5683:112;5741:11;:21;;;5772:10;:15;;;5683:21;:48;;:112;;;;;:::i;:::-;5611:184;;;;5802:125;5850:10;5868:22;5898:23;5802:40;:125::i;:::-;4584:1348;;;;;;;;;;;;;;;:::o;5936:811::-;6273:222;6306:3;6317:7;6332:14;:38;;;6378:14;:39;;;6425:20;6453:36;6273:25;:222::i;:::-;6518:224;6551:4;6563:8;6579:14;:38;;;6625:14;:39;;;6672:20;6700:36;6518:25;:224::i;:::-;5936:811;;;;;;;:::o;1508:1514:4:-;1687:23;1773:103;1810:4;1822;:18;;;1848:5;:22;;;1773:29;:103::i;:::-;1907:29;;;;1882:54;;;;-1:-1:-1;1907:29:4;1882:54;;:21;;:54;;1907:29;;1882:54;;;-1:-1:-1;;;;;1882:54:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;1882:54:4;;;;;-1:-1:-1;;;;;1882:54:4;;;;;;1996:102;2033:4;2045:3;:17;;;2070:5;:22;;;1996:29;:102::i;:::-;2129:27;;;;2104:52;;;;-1:-1:-1;2129:27:4;2104:52;;:21;;:52;;2129:27;;2104:52;;;-1:-1:-1;;;;;2104:52:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2104:52:4;;;;;-1:-1:-1;;;;;2104:52:4;;;;;;2219:103;2256:4;2268:3;:17;;;2293:5;:23;;;2219:29;:103::i;:::-;2353:30;;;;2328:55;;;;-1:-1:-1;2353:30:4;2328:55;;:21;;:55;;2353:30;;2328:55;;;-1:-1:-1;;;;;2328:55:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2328:55:4;;;;;-1:-1:-1;;;;;2328:55:4;;;;;;2445:104;2482:4;2494;:18;;;2520:5;:23;;;2445:29;:104::i;:::-;2580:28;;;;2555:53;;;;-1:-1:-1;2580:28:4;2555:53;;:21;;:53;;2580:28;;2555:53;;;-1:-1:-1;;;;;2555:53:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2555:53:4;;;;;-1:-1:-1;;;;;2555:53:4;;;;;;2656:98;2693:4;2705:9;2722:5;:26;;;2656:29;:98::i;:::-;2785:28;;;;2760:53;;;;-1:-1:-1;2785:28:4;2760:53;;:21;;:53;;2785:28;;2760:53;;;-1:-1:-1;;;;;2760:53:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2760:53:4;;;;;-1:-1:-1;;;;;2760:53:4;;;;;;2860:98;2897:4;2909:9;2926:5;:26;;;2860:29;:98::i;:::-;2989:28;;;;2964:53;;;;-1:-1:-1;2989:28:4;2964:53;;:21;;:53;;2989:28;;2964:53;;;-1:-1:-1;;;;;2964:53:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2964:53:4;;;;;-1:-1:-1;;;;;2964:53:4;;;;;;1508:1514;;;;;;:::o;4368:743::-;4531:23;4570:119;4607:4;4619:9;4636:11;:26;;;:47;;;4570:29;:119::i;:::-;4720:37;;;;4695:62;;;;-1:-1:-1;4720:37:4;4695:62;;:21;;:62;;4720:37;;4695:62;;;-1:-1:-1;;;;;4695:62:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;4695:62:4;;;;;-1:-1:-1;;;;;4695:62:4;;;;;;4774:121;4811:4;4823:11;4842;:26;;;:47;;;4774:29;:121::i;:::-;4764:131;;4978:11;:21;;;:59;;;4932:11;:37;;;:105;;;;:::i;:::-;4901:136;;;;:21;;:136;;;;;;;-1:-1:-1;;;;;4901:136:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;4901:136:4;;;;;-1:-1:-1;;;;;4901:136:4;;;;;;4368:743;;;;;:::o;501:732:23:-;757:7;766;817:4;:18;;;-1:-1:-1;;;;;796:39:23;:3;:17;;;-1:-1:-1;;;;;796:39:23;;;781:94;;;;-1:-1:-1;;;781:94:23;;;;;;;:::i;:::-;918:50;936:3;941:4;947:5;954:13;918:17;:50::i;:::-;974:37;994:3;999:4;1005:5;974:19;:37::i;:::-;1017:62;1049:3;1054:4;1060:18;1017:31;:62::i;:::-;1086:15;1103:16;1129:41;1153:3;1158:4;1164:5;1129:23;:41::i;:::-;1085:85;;;;1176:19;1189:5;1176:12;:19::i;:::-;1210:7;;;;-1:-1:-1;501:732:23;-1:-1:-1;;;;;;501:732:23:o;549:593:25:-;778:17;815:120;857:5;870:9;:25;;;903:9;:26;;;815:34;:120::i;:::-;803:132;;941:50;959:5;966:9;977:13;941:17;:50::i;:::-;997:36;1016:5;1023:9;997:18;:36::i;:::-;1039:57;1070:5;1077:18;1039:30;:57::i;:::-;1102:35;1115:5;:10;;;1127:9;1102:12;:35::i;:::-;549:593;;;;;;:::o;6842:1835:27:-;7160:31;;;;;;;;;;;;;;7159:32;7151:64;;;;-1:-1:-1;;;7151:64:27;;27720:2:31;7151:64:27;;;27702:21:31;27759:2;27739:18;;;27732:30;-1:-1:-1;;;27778:18:31;;;27771:49;27837:18;;7151:64:27;27692:169:31;7151:64:27;7316:30;7575:5;:23;;;7571:498;;;7625:34;7643:5;:15;;;7625:17;:34::i;:::-;7608:120;;;;-1:-1:-1;;;7608:120:27;;21371:2:31;7608:120:27;;;21353:21:31;21410:2;21390:18;;;21383:30;21449:34;21429:18;;;21422:62;-1:-1:-1;;;21500:18:31;;;21493:47;21557:19;;7608:120:27;21343:239:31;7608:120:27;7805:47;;;;;;;;;;;;7770:82;;-1:-1:-1;;;;;7805:47:27;7770:24;:82;:::i;:::-;7736:116;;7571:498;;;8015:47;;;;;;;;;;;;7981:81;;-1:-1:-1;;;;;8015:47:27;7981:23;:81;:::i;:::-;7947:115;;7571:498;8099:20;;;;-1:-1:-1;;;;;8133:41:27;;;;;;;;8125:70;;;;-1:-1:-1;;;8125:70:27;;24878:2:31;8125:70:27;;;24860:21:31;24917:2;24897:18;;;24890:30;-1:-1:-1;;;24936:18:31;;;24929:46;24992:18;;8125:70:27;24850:166:31;8125:70:27;8231:14;-1:-1:-1;;;;;8205:40:27;:23;-1:-1:-1;;;;;8205:40:27;;8201:472;;;8329:47;;;;;;;;;;;:73;;-1:-1:-1;;;;;;8329:73:27;-1:-1:-1;;;;;8329:73:27;;;;;8201:472;;;8573:47;;;;;;;;;;;;8566:54;;-1:-1:-1;;;;;;8566:54:27;;;8628:31;;;;;;:38;;-1:-1:-1;;8628:38:27;8566:54;8628:38;;;8201:472;6842:1835;;;;;;;;:::o;3026:1338:4:-;3178:23;3249:129;3286:4;3298:5;:19;;;3325:47;3361:5;:10;;;3325:9;:35;;:47;;;;:::i;:::-;3249:29;:129::i;:::-;3239:139;;3409:49;3447:5;:10;;;3409:9;:37;;:49;;;;:::i;:::-;3384:74;;;;:21;;:74;;;;;;;-1:-1:-1;;;;;3384:74:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;3384:74:4;;;;;-1:-1:-1;;;;;3384:74:4;;;;;;3504:130;3541:4;3553:5;:19;;;3580:48;3617:5;:10;;;3580:9;:36;;:48;;;;:::i;3504:130::-;3494:140;;3665:68;3717:5;:10;;;3665:9;:44;;:68;;;;:::i;:::-;3640:93;;;;:21;;:93;;;;;;;-1:-1:-1;;;;;3640:93:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;3640:93:4;;;;;-1:-1:-1;;;;;3640:93:4;;;;;;3827:119;3864:4;3876:9;3893:47;3929:5;:10;;;3893:9;:35;;:47;;;;:::i;3827:119::-;3977:40;;;;3952:65;;;;-1:-1:-1;3977:40:4;3952:65;;:21;;:65;;3977:40;;3952:65;;;-1:-1:-1;;;;;3952:65:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;3952:65:4;;;;;-1:-1:-1;;;;;3952:65:4;;;;;;4104:120;4141:4;4153:9;4170:48;4207:5;:10;;;4170:9;:36;;:48;;;;:::i;4104:120::-;4255:35;;;;4230:60;;;;-1:-1:-1;4255:35:4;4230:60;;:21;;:60;;4255:35;;4230:60;;;-1:-1:-1;;;;;4230:60:4;;:::i;11358:2324:20:-;11507:29;11538:30;11578:26;11613:128;11656:4;11670:9;:26;;;11706:9;:27;;;11613:33;:128::i;:::-;11834:28;;11578:163;;-1:-1:-1;11748:22:20;;11779:84;;-1:-1:-1;;;;;;;;11834:28:20;;;;;;11787:27;;;11779:84;:::i;:::-;11748:115;-1:-1:-1;11869:22:20;;11902:9;:26;;;;;;-1:-1:-1;;;11902:26:20;;;;;;;;;;11898:1102;;;11969:63;:9;12015;11969:36;:63::i;:::-;11938:94;;;;:27;;:94;;;;;;;-1:-1:-1;;;;;11938:94:20;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;11938:94:20;;;;;-1:-1:-1;;;;;11938:94:20;;;;;;12072:63;12125:9;12072;:52;;:63;;;;:::i;:::-;12040:95;;;;:28;;:95;;;;-1:-1:-1;;;12040:95:20;;-1:-1:-1;;;;;12040:95:20;;:::i;:::-;;;-1:-1:-1;;;;;12040:95:20;;;;;;;;;;;;;;;;;;;;;12266:36;;;12235:28;;:67;;-1:-1:-1;12266:36:20;;-1:-1:-1;;;12235:28:20;;;;:67;:::i;:::-;12177:27;;12169:143;;-1:-1:-1;;;;;12216:96:20;;;;12177:27;;;;12169:143;:::i;:::-;12144:168;;11898:1102;;;12364:63;:9;12417;12364:52;:63::i;:::-;12333:94;;;;:27;;:94;;;;;;;-1:-1:-1;;;;;12333:94:20;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;12333:94:20;;;;;-1:-1:-1;;;;;12333:94:20;;;;;;12489:1;12439:9;:47;;;-1:-1:-1;;;;;12439:51:20;;12435:382;;;12626:58;;;;12594:90;;;;:28;;:90;;12626:58;;-1:-1:-1;;;12594:90:20;;-1:-1:-1;;;;;12594:90:20;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;12594:90:20;;;;;-1:-1:-1;;;;;12594:90:20;;;;;;12435:382;;;12741:67;:9;12789;12741:36;:67::i;:::-;12709:99;;;;:28;;:99;;;;-1:-1:-1;;;12709:99:20;;-1:-1:-1;;;;;12709:99:20;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;12709:99:20;;;;;-1:-1:-1;;;;;12709:99:20;;;;;;12435:382;12964:28;;;12899:36;;;;-1:-1:-1;;;;;;;;12964:28:20;;;;;12869:66;;:27;;;:66;:::i;:::-;-1:-1:-1;;;;;12850:95:20;:143;;;;:::i;:::-;12825:168;;11898:1102;13107:14;-1:-1:-1;;;;;13089:32:20;:14;-1:-1:-1;;;;;13089:32:20;;;13074:95;;;;-1:-1:-1;;;13074:95:20;;30256:2:31;13074:95:20;;;30238:21:31;;;30275:18;;;30268:30;30334:34;30314:18;;;30307:62;30386:18;;13074:95:20;30228:182:31;13074:95:20;13315:27;;883:5:5;13315:27:20;;;;-1:-1:-1;;;;;13315:27:20;:70;;13300:124;;;;-1:-1:-1;;;13300:124:20;;18522:2:31;13300:124:20;;;18504:21:31;18561:2;18541:18;;;18534:30;-1:-1:-1;;;18580:18:31;;;18573:53;18643:18;;13300:124:20;18494:173:31;13300:124:20;13445:28;;883:5:5;-1:-1:-1;;;13445:28:20;;;-1:-1:-1;;;;;13445:28:20;:71;;13430:126;;;;-1:-1:-1;;;13430:126:20;;24525:2:31;13430:126:20;;;24507:21:31;24564:2;24544:18;;;24537:30;-1:-1:-1;;;24583:18:31;;;24576:54;24647:18;;13430:126:20;24497:174:31;13430:126:20;13562:42;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;13562:42:20;;;;;;;;;-1:-1:-1;;;13562:42:20;;;;;;;;;;;-1:-1:-1;;;13562:42:20;;;;;;;;;-1:-1:-1;;;13562:42:20;;;;;;;;;;;;;-1:-1:-1;;;;;13562:42:20;;;;;;;:36;:42::i;:::-;-1:-1:-1;;13619:27:20;-1:-1:-1;;;;;13619:27:20;;;;;;-1:-1:-1;;;13648:28:20;;;;;-1:-1:-1;13619:27:20;-1:-1:-1;;;;11358:2324:20:o;3315:2646:13:-;3403:21;;;;:47;;;-1:-1:-1;;;;;3403:52:13;;3388:104;;;;-1:-1:-1;;;3388:104:13;;28068:2:31;3388:104:13;;;28050:21:31;28107:2;28087:18;;;28080:30;-1:-1:-1;;;28126:18:31;;;28119:51;28187:18;;3388:104:13;28040:171:31;3388:104:13;3544:26;3573:41;:11;:39;:41::i;:::-;3544:70;;3635:177;3698:39;:11;:37;:39::i;:::-;3676:61;;:19;:61;:::i;:::-;3748:19;553:8:5;3635:30:13;:177::i;:::-;3620:227;;;;-1:-1:-1;;;3620:227:13;;;;;;;:::i;:::-;3854:24;;3887:26;;:36;;;:53;;;;;;-1:-1:-1;;;3887:53:13;;;;;;;;;;:102;;3976:13;3887:102;;;3951:14;3887:102;3854:135;;4078:62;:11;:60;:62::i;:::-;4056:84;;4161:198;4201:67;4253:14;4201:11;:21;;;:51;;:67;;;;:::i;:::-;4278:19;1033:7:5;4161:30:13;:198::i;:::-;4146:261;;;;-1:-1:-1;;;4146:261:13;;;;;;;:::i;:::-;4428:159;4468:45;:11;:43;:45::i;:::-;4523:19;553:8:5;4428:30:13;:159::i;:::-;4413:209;;;;-1:-1:-1;;;4413:209:13;;;;;;;:::i;:::-;4678:21;;;;:59;;;-1:-1:-1;;;;;4678:63:13;;4674:1109;;4883:13;4843:26;;:36;;;:53;;;;;;-1:-1:-1;;;4843:53:13;;;;;;;;;;4826:118;;;;-1:-1:-1;;;4826:118:13;;;;;;;:::i;:::-;5095:21;;;;:44;;;-1:-1:-1;;;;;5095:49:13;;5078:129;;;;-1:-1:-1;;;5078:129:13;;23709:2:31;5078:129:13;;;23691:21:31;23748:2;23728:18;;;23721:30;23787:34;23767:18;;;23760:62;-1:-1:-1;;;23838:18:31;;;23831:41;23889:19;;5078:129:13;23681:233:31;5078:129:13;5238:30;;;;:91;;5314:14;5238:75;:91::i;:::-;5216:113;;5420:19;-1:-1:-1;;;;;5350:89:13;:11;:21;;;:59;;;-1:-1:-1;;;;;5350:89:13;;5337:440;;;5477:241;5597:19;5521:11;:21;;;:59;;;:95;;;;:::i;:::-;5630:19;1112:7:5;5477:30:13;:241::i;:::-;5458:310;;;;-1:-1:-1;;;5458:310:13;;19919:2:31;5458:310:13;;;19901:21:31;19958:2;19938:18;;;19931:30;-1:-1:-1;;;19977:18:31;;;19970:56;20043:18;;5458:310:13;19891:176:31;5458:310:13;5789:95;5835:14;5857:11;:21;;;5789:38;:95::i;:::-;5929:26;;5890:66;;:38;:66::i;:::-;3315:2646;;;:::o;1901:550:29:-;2137:67;2166:3;:17;;;2185:18;2137:28;:67::i;:::-;-1:-1:-1;;;;;2084:120:29;:42;2116:3;:9;;;2084:31;:42::i;:::-;-1:-1:-1;;;;;2084:120:29;;2069:184;;;;-1:-1:-1;;;2069:184:29;;28418:2:31;2069:184:29;;;28400:21:31;28457:2;28437:18;;;28430:30;28496:34;28476:18;;;28469:62;-1:-1:-1;;;28547:18:31;;;28540:31;28588:19;;2069:184:29;28390:223:31;2069:184:29;2328:68;2357:4;:18;;;2377;2328:28;:68::i;:::-;-1:-1:-1;;;;;2274:122:29;:43;2306:4;:10;;;2274:31;:43::i;:::-;-1:-1:-1;;;;;2274:122:29;;2259:187;;;;-1:-1:-1;;;2259:187:29;;20968:2:31;2259:187:29;;;20950:21:31;21007:2;20987:18;;;20980:30;21046:34;21026:18;;;21019:62;-1:-1:-1;;;21097:18:31;;;21090:32;21139:19;;2259:187:29;20940:224:31;3567:488:23;3705:7;3714;3729:20;3758:106;3790:3;3803:5;:21;;;3834:5;:22;;;3758;:106::i;:::-;3729:135;;3870:21;3900:107;3932:4;3946:5;:21;;;3977:5;:22;;;3900;:107::i;:::-;4022:12;;;;-1:-1:-1;3567:488:23;;-1:-1:-1;;;;;3567:488:23:o;1237:743::-;1456:5;:23;;;-1:-1:-1;;;;;1430:49:23;:5;:22;;;-1:-1:-1;;;;;1430:49:23;;;1415:110;;;;-1:-1:-1;;;1415:110:23;;;;;;;:::i;:::-;1606:5;:22;;;-1:-1:-1;;;;;1576:52:23;:5;:26;;;-1:-1:-1;;;;;1576:52:23;;:117;;;;;1670:5;:23;;;-1:-1:-1;;;;;1640:53:23;:5;:26;;;-1:-1:-1;;;;;1640:53:23;;1576:117;1575:252;;;;1737:5;:23;;;-1:-1:-1;;;;;1707:53:23;:5;:26;;;-1:-1:-1;;;;;1707:53:23;;:119;;;;;1804:5;:22;;;-1:-1:-1;;;;;1774:52:23;:5;:26;;;-1:-1:-1;;;;;1774:52:23;;1707:119;1560:313;;;;-1:-1:-1;;;1560:313:23;;21789:2:31;1560:313:23;;;21771:21:31;21828:2;21808:18;;;21801:30;21867:32;21847:18;;;21840:60;21917:18;;1560:313:23;21761:180:31;1560:313:23;1880:44;1898:3;1903:5;1910:13;1880:17;:44::i;:::-;1930:45;1948:4;1954:5;1961:13;1930:17;:45::i;:::-;1237:743;;;;:::o;2608:955::-;2786:1;2754:5;:29;;;-1:-1:-1;;;;;2754:33:23;;2739:103;;;;-1:-1:-1;;;2739:103:23;;;;;;;:::i;:::-;2896:1;2863:5;:30;;;-1:-1:-1;;;;;2863:34:23;;2848:105;;;;-1:-1:-1;;;2848:105:23;;;;;;;:::i;:::-;2964:43;2993:3;:13;;;2964:28;:43::i;:::-;2960:295;;;3168:5;:30;;;-1:-1:-1;;;;;3034:164:23;:130;3093:5;:29;;;3134:3;:20;;;3034:47;:130::i;:::-;-1:-1:-1;;;;;3034:164:23;;;3017:231;;;;-1:-1:-1;;;3017:231:23;;;;;;;:::i;:::-;3265:44;3294:4;:14;;;3265:28;:44::i;:::-;3261:298;;;3471:5;:30;;;-1:-1:-1;;;;;3336:165:23;:131;3395:5;:29;;;3436:4;:21;;;3336:47;:131::i;:::-;-1:-1:-1;;;;;3336:165:23;;;3319:233;;;;-1:-1:-1;;;3319:233:23;;;;;;;:::i;1934:1063:25:-;2091:1;2055:9;:33;;;-1:-1:-1;;;;;2055:37:25;;2040:107;;;;-1:-1:-1;;;2040:107:25;;;;;;;:::i;:::-;2205:1;2168:9;:34;;;-1:-1:-1;;;;;2168:38:25;;2153:109;;;;-1:-1:-1;;;2153:109:25;;;;;;;:::i;:::-;2294:13;2280:5;:10;;;:27;;;;;;-1:-1:-1;;;2280:27:25;;;;;;;;;;:82;;;;;2317:45;2346:5;:15;;;2317:28;:45::i;:::-;2269:356;;;2534:9;:34;;;-1:-1:-1;;;;;2394:174:25;:136;2453:9;:33;;;2498:5;:22;;;2394:47;:136::i;:::-;-1:-1:-1;;;;;2394:174:25;;;2377:241;;;;-1:-1:-1;;;2377:241:25;;;;;;;:::i;:::-;2656:14;2642:5;:10;;;:28;;;;;;-1:-1:-1;;;2642:28:25;;;;;;;;;;:83;;;;;2680:45;2709:5;:15;;;2680:28;:45::i;:::-;2631:362;;;2901:9;:34;;;-1:-1:-1;;;;;2757:178:25;:140;2852:1;2816:9;:33;;;:37;;;;:::i;:::-;2865:5;:22;;;2757:47;:140::i;:::-;-1:-1:-1;;;;;2757:178:25;;;2740:246;;;;-1:-1:-1;;;2740:246:25;;;;;;;:::i;:::-;1934:1063;;:::o;2207:1104:13:-;2388:13;2369:10;:15;;;:32;;;;;;-1:-1:-1;;;2369:32:13;;;;;;;;;;:92;;;;;2411:50;2440:10;:20;;;2411:28;:50::i;:::-;2358:424;;;2700:23;-1:-1:-1;;;;;2566:157:13;:130;2625:22;2659:10;:27;;;2566:47;:130::i;:::-;-1:-1:-1;;;;;2566:157:13;;;2549:226;;;;-1:-1:-1;;;2549:226:13;;17443:2:31;2549:226:13;;;17425:21:31;;;17462:18;;;17455:30;17521:34;17501:18;;;17494:62;17573:18;;2549:226:13;17415:182:31;2549:226:13;2818:14;2799:10;:15;;;:33;;;;;;-1:-1:-1;;;2799:33:13;;;;;;;;;;:93;;;;;2842:50;2871:10;:20;;;2842:28;:50::i;:::-;2788:519;;;3220:27;3246:1;3220:23;:27;:::i;:::-;-1:-1:-1;;;;;2999:248:13;:193;3058:22;3092:10;:27;;;2999:47;:193::i;:::-;:207;;3205:1;2999:207;:::i;:::-;-1:-1:-1;;;;;2999:248:13;;;2982:318;;;;-1:-1:-1;;;2982:318:13;;22551:2:31;2982:318:13;;;22533:21:31;22590:2;22570:18;;;22563:30;22629:34;22609:18;;;22602:62;-1:-1:-1;;;22680:18:31;;;22673:31;22721:19;;2982:318:13;22523:223:31;10166:510:4;-1:-1:-1;;;;;10353:38:4;;;10298:15;10353:38;;;;;;;;;;;:52;;;;;;;;;;;10417:18;;;;10416:19;:68;;;;-1:-1:-1;10447:20:4;;;;-1:-1:-1;;;;;10447:20:4;10439:45;;10416:68;10412:239;;;10518:20;;;;:93;;-1:-1:-1;;;10518:93:4;;-1:-1:-1;;;;;16018:15:31;;;10518:93:4;;;16000:34:31;16070:15;;;16050:18;;;16043:43;10518:20:4;;;;:47;;15935:18:31;;10518:93:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10494:117;;-1:-1:-1;;;;;;;10494:117:4;;;;;;10619:25;;;;-1:-1:-1;;;;;;10619:25:4;;;;10494:21;10619:25;;;10412:239;10664:7;-1:-1:-1;10166:510:4;;;;;;:::o;4625:886:23:-;4695:31;4765:5;:22;;;-1:-1:-1;;;;;4735:52:23;:5;:26;;;-1:-1:-1;;;;;4735:52:23;;:133;;4838:5;:30;;;4735:133;;;4798:5;:29;;;4735:133;4695:173;;4889:147;4929:5;:28;;;4967:24;553:8:5;4889:30:23;:147::i;:::-;4874:197;;;;-1:-1:-1;;;4874:197:23;;;;;;;:::i;:::-;5078:31;5148:5;:22;;;-1:-1:-1;;;;;5118:52:23;:5;:26;;;-1:-1:-1;;;;;5118:52:23;;:133;;5221:5;:30;;;5118:133;;;5181:5;:29;;;5118:133;5078:173;;5272:147;5312:5;:28;;;5350:24;553:8:5;5272:30:23;:147::i;:::-;5257:197;;;;-1:-1:-1;;;5257:197:23;;;;;;;:::i;:::-;5461:45;5500:5;5461:38;:45::i;2455:562:29:-;2609:7;2624:17;2650:62;2671:5;2678:15;2695:16;2650:20;:62::i;:::-;2624:88;;2734:111;2768:9;2787:5;:21;;;2818:5;:19;;;2734:24;:111::i;:::-;2867:13;2853:5;:10;;;:27;;;;;;-1:-1:-1;;;2853:27:29;;;;;;;;;;:130;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2719:270;;;;;-1:-1:-1;;;2719:270:29;;;;;;;;:::i;:::-;-1:-1:-1;3003:9:29;2455:562;-1:-1:-1;;;;2455:562:29:o;1146:784:25:-;1347:9;:27;;;-1:-1:-1;;;;;1317:57:25;:9;:26;;;-1:-1:-1;;;;;1317:57:25;;;1302:118;;;;-1:-1:-1;;;1302:118:25;;;;;;;:::i;:::-;1427:20;1450:44;1482:5;:11;;;1450:31;:44::i;:::-;1563:25;;1427:67;;-1:-1:-1;1500:22:25;;1531:73;;:13;;1427:67;1531:31;:73::i;:::-;1500:104;;1610:23;1642:98;1683:9;:26;;;1719:13;1642;:31;;:98;;;;;:::i;:::-;1610:130;;1788:9;:26;;;-1:-1:-1;;;;;1762:52:25;:9;:22;;;-1:-1:-1;;;;;1762:52:25;;:118;;;;;1853:9;:27;;;-1:-1:-1;;;;;1826:54:25;:10;:23;;;-1:-1:-1;;;;;1826:54:25;;1762:118;1747:178;;;;-1:-1:-1;;;1747:178:25;;17804:2:31;1747:178:25;;;17786:21:31;17843:2;17823:18;;;17816:30;17882:31;17862:18;;;17855:59;17931:18;;1747:178:25;17776:179:31;1747:178:25;1146:784;;;;;;:::o;1562:335:29:-;1778:69;1807:5;:19;;;1828:18;1778:28;:69::i;:::-;-1:-1:-1;;;;;1723:124:29;:44;1755:5;:11;;;1723:31;:44::i;:::-;-1:-1:-1;;;;;1723:124:29;;1708:184;;;;-1:-1:-1;;;1708:184:29;;25578:2:31;1708:184:29;;;25560:21:31;25617:2;25597:18;;;25590:30;25656:31;25636:18;;;25629:59;25705:18;;1708:184:29;25550:179:31;3001:885:25;3116:218;3156:50;:9;3196;3156:39;:50::i;:::-;3216:56;:9;3262;3216:45;:56::i;:::-;1033:7:5;3116:30:25;:218::i;:::-;3101:281;;;;-1:-1:-1;;;3101:281:25;;;;;;;:::i;:::-;3444:35;;;;3404:186;;3489:56;3444:9;3535;3489:45;:56::i;:::-;553:8:5;3404:30:25;:186::i;:::-;3389:234;;;;-1:-1:-1;;;3389:234:25;;20622:2:31;3389:234:25;;;20604:21:31;20661:2;20641:18;;;20634:30;-1:-1:-1;;;20680:18:31;;;20673:47;20737:18;;3389:234:25;20594:167:31;3389:234:25;3718:47;;;;-1:-1:-1;;;;;3718:52:25;;3703:111;;;;-1:-1:-1;;;3703:111:25;;;;;;;:::i;:::-;3821:60;3860:9;3871;3821:38;:60::i;8681:213:27:-;8751:4;;8776:9;:29;;;;;;-1:-1:-1;;;8776:29:27;;;;;;;;;;:70;;;-1:-1:-1;8828:18:27;8815:9;:31;;;;;;-1:-1:-1;;;8815:31:27;;;;;;;;;;8776:70;:113;;;-1:-1:-1;8869:20:27;8856:33;:9;:33;;;;;;-1:-1:-1;;;8856:33:27;;;;;;;;;;8776:113;8763:126;8681:213;-1:-1:-1;;8681:213:27:o;569:240:24:-;683:7;;713:9;:26;;;;;;-1:-1:-1;;;713:26:24;;;;;;;;;;:91;;783:4;:21;;;713:91;;;-1:-1:-1;;750:22:24;;;;569:240::o;1301:253::-;1417:6;;1444:9;:26;;;;;;-1:-1:-1;;;1444:26:24;;;;;;;;;;:105;;1521:4;:28;;;1444:105;;;-1:-1:-1;;1481:29:24;;;;1301:253::o;256:239::-;371:7;;399:9;:26;;;;;;-1:-1:-1;;;399:26:24;;;;;;;;;;:91;;468:4;:22;;;399:91;;;-1:-1:-1;;436:21:24;;;;256:239::o;902:311::-;1178:30;;;;1025:6;;;1062:9;:26;;;;;;-1:-1:-1;;;1062:26:24;;;;;;;;;;:105;;1140:4;:27;;;1062:105;;;1101:4;:26;;;1062:105;1052:156;;;;:::i;13703:315:20:-;-1:-1:-1;;;;;13901:39:20;;;13860:26;13901:39;;;:21;;;:39;;;;;;;;:58;;;;;;;;;13973:11;;;;13965:48;;;;-1:-1:-1;;;13965:48:20;;19222:2:31;13965:48:20;;;19204:21:31;19261:2;19241:18;;;19234:30;-1:-1:-1;;;19280:18:31;;;19273:54;19344:18;;13965:48:20;19194:174:31;2033:353:24;2148:6;;2177:9;:26;;;;;;-1:-1:-1;;;2177:26:24;;;;;;;;;;:149;;2299:4;:27;;;2177:149;;;-1:-1:-1;;2214:26:24;;;;2033:353::o;1643:311::-;1918:31;;;;1765:6;;;1802:9;:26;;;;;;-1:-1:-1;;;1802:26:24;;;;;;;;;;:105;;1881:4;:26;;;1802:105;;;1841:4;:27;;;1802:105;1792:157;;;;:::i;3021:493:29:-;3103:21;3126;3188:4;:28;;;-1:-1:-1;;;;;3157:59:29;:4;:27;;;-1:-1:-1;;;;;3157:59:29;;;:199;;3298:4;:28;;;3328:4;:27;;;3157:199;;;3228:4;:27;;;3257:4;:28;;;3157:199;3102:254;;;;3453:14;-1:-1:-1;;;;;3377:90:29;720:5:5;-1:-1:-1;;;;;3377:64:29;3385:14;-1:-1:-1;;;;;3377:23:29;:64;;;;:::i;:::-;:90;;3362:147;;;;-1:-1:-1;;;3362:147:29;;26653:2:31;3362:147:29;;;26635:21:31;26692:2;26672:18;;;26665:30;-1:-1:-1;;;26711:18:31;;;26704:56;26777:18;;3362:147:29;26625:176:31;1097:328:12;1194:6;1275:4;:19;;;:36;;;-1:-1:-1;;;;;1223:88:12;:4;:19;;;:40;;;-1:-1:-1;;;;;1223:88:12;;:197;;1377:19;;:43;;;1223:197;;;-1:-1:-1;1322:19:12;:44;;;;1097:328::o;1482:322::-;1577:6;1658:4;:19;;;:36;;;-1:-1:-1;;;;;1606:88:12;:4;:19;;;:40;;;-1:-1:-1;;;;;1606:88:12;;:193;;1758:19;;:41;;;1606:193;;;-1:-1:-1;1705:19:12;:42;;;;1482:322::o;5719:224:29:-;5824:4;;5899:5;5861:34;264:9:5;5861:3:29;:34;:::i;:::-;5860:44;;;;:::i;:::-;-1:-1:-1;;;;;5917:21:29;;;;;;;;-1:-1:-1;;5719:224:29;;;;;:::o;585:453:12:-;696:6;779:4;:19;;;:36;;;-1:-1:-1;;;;;727:88:12;:4;:19;;;:40;;;-1:-1:-1;;;;;727:88:12;;:300;;988:4;:14;;;:39;;;931:4;:19;;;:44;;;:96;;;;:::i;:::-;727:300;;;882:4;:14;;;:38;;;826:4;:19;;;:43;;;:94;;;;:::i;2717:254:24:-;2835:6;2923:43;2950:4;2956:9;2923:26;:43::i;:::-;2862:52;2898:4;2904:9;2862:35;:52::i;310:216:12:-;479:19;;:42;;;440:30;;;;411:6;;440:81;;;:::i;2452:261:24:-;2576:6;;2603:9;:26;;;;;;-1:-1:-1;;;2603:26:24;;;;;;;;;;:105;;2679:4;:29;;;2603:105;;;-1:-1:-1;;2640:28:24;;;;2452:261::o;3607:987:29:-;3873:29;;;3945:9;:26;;;;;;-1:-1:-1;;;3945:26:29;;;;;;;;;;:186;;4064:9;:31;;;4097:9;:33;;;3945:186;;;3983:9;:32;;;4017:9;:34;;;3945:186;3872:259;;;;4285:24;-1:-1:-1;;;;;4153:156:29;4233:9;:40;;;4186:9;:36;;;4153:22;:69;;;;:::i;:::-;:120;;;;:::i;:::-;-1:-1:-1;;;;;4153:156:29;;4138:213;;;;-1:-1:-1;;;4138:213:29;;25223:2:31;4138:213:29;;;25205:21:31;25262:2;25242:18;;;25235:30;-1:-1:-1;;;25281:18:31;;;25274:56;25347:18;;4138:213:29;25195:176:31;4138:213:29;4372:177;4412:49;4439:22;4412:24;:49;:::i;:::-;4471:24;953:7:5;4372:30:29;:177::i;:::-;4357:232;;;;-1:-1:-1;;;4357:232:29;;27008:2:31;4357:232:29;;;26990:21:31;27047:2;27027:18;;;27020:30;-1:-1:-1;;;27066:18:31;;;27059:54;27130:18;;4357:232:29;26980:174:31;848:710:29;1168:5;:29;;;-1:-1:-1;;;;;958:239:29;1038:5;:22;;;-1:-1:-1;;;;;1008:52:29;:5;:26;;;-1:-1:-1;;;;;1008:52:29;;:138;;1118:5;:28;;;1008:138;;;1075:5;:28;;;1008:138;958:5;:27;;;:198;;;;:::i;:::-;-1:-1:-1;;;;;958:239:29;;943:300;;;;-1:-1:-1;;;943:300:29;;30978:2:31;943:300:29;;;30960:21:31;31017:2;30997:18;;;30990:30;31056:32;31036:18;;;31029:60;31106:18;;943:300:29;30950:180:31;943:300:29;1476:5;:30;;;-1:-1:-1;;;;;1264:242:29;1345:5;:23;;;-1:-1:-1;;;;;1315:53:29;:5;:26;;;-1:-1:-1;;;;;1315:53:29;;:139;;1426:5;:28;;;1315:139;;;1383:5;:28;;;1315:139;1264:5;:28;;;:200;;;;:::i;:::-;-1:-1:-1;;;;;1264:242:29;;1249:304;;;;-1:-1:-1;;;1249:304:29;;28820:2:31;1249:304:29;;;28802:21:31;28859:2;28839:18;;;28832:30;28898:33;28878:18;;;28871:61;28949:18;;1249:304:29;28792:181:31;1249:304:29;848:710;:::o;5314:401::-;-1:-1:-1;;;;;5500:33:29;;5475:6;5500:33;;;;;;;;;;:40;;;:120;;;;-1:-1:-1;;;;;;5550:33:29;;;;;;;;;;;;;:54;;;5608:12;-1:-1:-1;5550:70:29;5500:120;5489:207;;;-1:-1:-1;;;;;;5642:33:29;;;;;;;;;;;;;:47;;;;-1:-1:-1;;;;;5642:47:29;5635:54;;5489:207;-1:-1:-1;5709:1:29;5314:401;;;;:::o;307:886:28:-;392:23;515:34;509:2;501:10;;;500:49;574:1;563:12;;555:40;;;;-1:-1:-1;;;555:40:28;;19575:2:31;555:40:28;;;19557:21:31;19614:2;19594:18;;;19587:30;-1:-1:-1;;;19633:18:31;;;19626:45;19688:18;;555:40:28;19547:165:31;555:40:28;-1:-1:-1;;;707:2:28;699:10;;;698:49;-1:-1:-1;;;780:2:28;772:10;;;771:49;845:10;853:2;845:10;;;;932:18;;;:28;;1144:14;1111:29;1135:5;932:28;1111:29;:::i;:::-;1104:54;;;;:::i;:::-;1085:73;307:886;-1:-1:-1;;;;;;;307:886:28:o;5947:265:29:-;6017:4;6055:15;6042:9;:28;;;;;;-1:-1:-1;;;6042:28:29;;;;;;;;;;:71;;;-1:-1:-1;6093:20:29;6080:9;:33;;;;;;-1:-1:-1;;;6080:33:29;;;;;;;;;;6042:71;:117;;;-1:-1:-1;6136:23:29;6123:9;:36;;;;;;-1:-1:-1;;;6123:36:29;;;;;;;;;;6042:117;:165;;;-1:-1:-1;6182:25:29;6169:38;;5019:291;5151:6;5178:127;5215:18;5243:16;1285:5:5;5178:27:29;:127::i;2342:1335:11:-;2506:26;;2476:7;;2506:60;;1340:1:5;2506:60:11;2491:121;;;;-1:-1:-1;;;2491:121:11;;29180:2:31;2491:121:11;;;29162:21:31;29219:2;29199:18;;;29192:30;29258:32;29238:18;;;29231:60;29308:18;;2491:121:11;29152:180:31;2491:121:11;2806:5;:26;;;2846:5;:11;;;2871:5;:19;;;2928:10;2945:11;2911:46;;;;;;;;;:::i;:::-;;;;;;;;;;;;;2978:5;:15;;;2972:22;;;;;;-1:-1:-1;;;2972:22:11;;;;;;;;;3014:5;:10;;;3008:17;;;;;;-1:-1:-1;;;3008:17:11;;;;;;;;;3161:34;3174:5;:20;;;-1:-1:-1;;;;;3161:34:11;:12;:34::i;:::-;2776:431;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;3249:5;:23;;;3311:1;3286:5;:22;;;-1:-1:-1;;;;;3286:26:11;;:98;;;;;;;;;;;;;;;;;3329:36;3342:5;:22;;;-1:-1:-1;;;;;3329:36:11;:12;:36::i;:::-;3422:1;3398:5;:21;;;-1:-1:-1;;;;;3398:25:11;;:96;;;;;;;;;;;;;;;;;3440:35;3453:5;:21;;;-1:-1:-1;;;;;3440:35:11;:12;:35::i;:::-;3508:5;:19;;;3547:5;:17;;;3541:24;;;;;;-1:-1:-1;;;3541:24:11;;;;;;;;;3585:5;:25;;;3579:32;;;;;;-1:-1:-1;;;3579:32:11;;;;;;;;;3625:5;:17;;;3219:435;;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;3219:435:11;;;;;;;;;;2748:916;;;3219:435;2748:916;;:::i;:::-;;;;;;;;;;;;;2729:943;;;;;;2716:956;;2342:1335;;;;;:::o;421:219::-;540:4;629:6;-1:-1:-1;;;;;565:70:11;:60;579:34;608:4;4559:58:8;;-1:-1:-1;;;4559:58:8;;;14669:80:31;14765:12;;;14758:28;;;4429:7:8;;14802:12:31;;4559:58:8;;;;;;;;;;;;4549:69;;;;;;4542:76;;4360:265;;;;579:34:11;615:9;565:13;:60::i;:::-;-1:-1:-1;;;;;565:70:11;;;421:219;-1:-1:-1;;;;421:219:11:o;7066:714:1:-;7198:12;;:::i;:::-;7222:45;7236:4;:22;;7222:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7260:6;7222:13;:45::i;:::-;7218:108;;;7284:35;7296:4;:22;;7284:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::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;;24121:2:31;7657:99:1;;;24103:21:31;24160:2;24140:18;;;24133:30;24199:34;24179:18;;;24172:62;-1:-1:-1;;;24250:18:31;;;24243:33;24293:19;;7657:99:1;24093:225:31;84:291:22;225:6;252:118;284:12;306:16;332:15;357:5;252:22;:118::i;4486:776:11:-;4544:13;4725:4;4710:12;4755:59;4762:9;;4755:59;;4781:8;;;;:::i;:::-;;-1:-1:-1;4797:10:11;;-1:-1:-1;4805:2:11;4797:10;;:::i;:::-;;;4755:59;;;4832:1;4823:6;:10;4819:92;;;-1:-1:-1;4852:1:11;4819:92;4916:8;;;;:::i;:::-;;;;4956:20;4989:6;-1:-1:-1;;;;;4979:17:11;;;;;-1:-1:-1;;;4979:17:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;4979:17:11;-1:-1:-1;4956:40:11;-1:-1:-1;5019:6:11;5002:228;5027:5;;5002:228;;5051:10;5060:1;5051:6;:10;:::i;:::-;5065:1;5051:15;5047:177;;;-1:-1:-1;;;5078:7:11;5086:5;5090:1;5086;:5;:::i;:::-;5078:14;;;;;;-1:-1:-1;;;5078:14:11;;;;;;;;;;;;:34;-1:-1:-1;;;;;5078:34:11;;;;;;;;;5047:177;;;5183:9;5190:2;5183:4;:9;:::i;:::-;5177:16;;:2;:16;:::i;:::-;5164:31;;5147:7;5155:5;5159:1;5155;:5;:::i;:::-;5147:14;;;;;;-1:-1:-1;;;5147:14:11;;;;;;;;;;;;:48;-1:-1:-1;;;;;5147:48:11;;;;;;;;-1:-1:-1;5205:10:11;5213:2;5205:10;;:::i;:::-;;;5047:177;5034:3;;;;:::i;:::-;;;;5002:228;;1064:1459:8;1142:7;1217:9;1236;1255:7;1470:9;:16;1490:2;1470:22;1466:1011;;;-1:-1:-1;;;1752:4:8;1737:20;;1731:27;1801:4;1786:20;;1780:27;1858:4;1843:20;;1837:27;1834:1;1829:36;1708:171;;;1899:9;:16;1919:2;1899:22;1895:582;;;-1:-1:-1;;;2186:4:8;2171:20;;2165:27;2235:4;2220:20;;2214:27;;-1:-1:-1;;;;;2263:75:8;;;2368:3;2364:12;2378:2;2360:21;2137:258;;;2425:41;;-1:-1:-1;;;2425:41:8;;18162:2:31;2425:41:8;;;18144:21:31;18201:2;18181:18;;;18174:30;18240:33;18220:18;;;18213:61;18291:18;;2425:41:8;18134:181:31;1895:582:8;2494:22;2502:4;2508:1;2511;2514;2494:7;:22::i;:::-;2487:29;1064:1459;-1:-1:-1;;;;;;1064:1459:8:o;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;379:454:22:-;538:6;;571:40;-1:-1:-1;;;;;571:40:22;;;;:21;;:40;:::i;:::-;552:59;-1:-1:-1;617:14:22;634:26;-1:-1:-1;;;;;634:26:22;;552:59;634:26;:::i;:::-;617:43;;670:7;:41;;;;-1:-1:-1;710:1:22;681:26;-1:-1:-1;;;;;681:26:22;;:8;:26;:::i;:::-;:30;670:41;666:73;;;721:11;731:1;721:11;;:::i;:::-;;;666:73;-1:-1:-1;;;752:6:22;:14;744:56;;;;-1:-1:-1;;;744:56:22;;25936:2:31;744:56:22;;;25918:21:31;25975:2;25955:18;;;25948:30;26014:31;25994:18;;;25987:59;26063:18;;744:56:22;25908:179:31;2656:1414:8;2741:7;-1:-1:-1;;;;;3642:80:8;;;3634:127;;;;-1:-1:-1;;;3634:127:8;;22148:2:31;3634:127:8;;;22130:21:31;22187:2;22167:18;;;22160:30;22226:34;22206:18;;;22199:62;-1:-1:-1;;;22277:18:31;;;22270:32;22319:19;;3634:127:8;22120:224:31;3634:127:8;3779:1;:7;;3784:2;3779:7;:18;;;;3790:1;:7;;3795:2;3790:7;3779:18;3771:65;;;;-1:-1:-1;;;3771:65:8;;23306:2:31;3771:65:8;;;23288:21:31;23345:2;23325:18;;;23318:30;23384:34;23364:18;;;23357:62;-1:-1:-1;;;23435:18:31;;;23428:32;23477:19;;3771:65:8;23278:224:31;3771:65:8;3948:24;;;3931:14;3948:24;;;;;;;;;16324:25:31;;;16397:4;16385:17;;16365:18;;;16358:45;;;;16419:18;;;16412:34;;;16462:18;;;16455:34;;;3948:24:8;;16296:19:31;;3948:24:8;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3948:24:8;;-1:-1:-1;;3948:24:8;;;-1:-1:-1;;;;;;;3990:20:8;;3982:57;;;;-1:-1:-1;;;3982:57:8;;17090:2:31;3982:57:8;;;17072:21:31;17129:2;17109:18;;;17102:30;-1:-1:-1;;;17148:18:31;;;17141:54;17212:18;;3982:57:8;17062:174:31;3982:57:8;4057:6;2656:1414;-1:-1:-1;;;;;2656:1414:8:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:173:31:-;82:20;;-1:-1:-1;;;;;131:31:31;;121:42;;111:2;;177:1;174;167:12;111:2;63:124;;;:::o;192:160::-;257:20;;313:13;;306:21;296:32;;286:2;;342:1;339;332:12;357:738;399:5;452:3;445:4;437:6;433:17;429:27;419:2;;474:5;467;460:20;419:2;501:20;;-1:-1:-1;;;;;570:10:31;;;567:2;;;583:18;;:::i;:::-;658:2;652:9;626:2;712:13;;-1:-1:-1;;708:22:31;;;732:2;704:31;700:40;688:53;;;756:18;;;776:22;;;753:46;750:2;;;802:18;;:::i;:::-;842:10;838:2;831:22;877:2;869:6;862:18;923:3;916:4;911:2;903:6;899:15;895:26;892:35;889:2;;;944:5;937;930:20;889:2;1012;1005:4;997:6;993:17;986:4;978:6;974:17;961:54;1035:15;;;1052:4;1031:26;1024:41;;;;-1:-1:-1;1039:6:31;409:686;-1:-1:-1;;;409:686:31:o;1100:165::-;1190:20;;1239:1;1229:12;;1219:2;;1255:1;1252;1245:12;1270:150;1345:20;;1394:1;1384:12;;1374:2;;1410:1;1407;1400:12;1425:150;1500:20;;1549:1;1539:12;;1529:2;;1565:1;1562;1555:12;1580:898;1638:5;1686:4;1674:9;1669:3;1665:19;1661:30;1658:2;;;1708:5;1701;1694:20;1658:2;1745;1739:9;1787:4;1775:17;;-1:-1:-1;;;;;1844:18:31;;;1864:22;;;1841:46;1838:2;;;1890:18;;:::i;:::-;1930:10;1926:2;1919:22;1959:6;1950:15;;2001:9;1988:23;1974:37;;2034:2;2026:6;2023:14;2020:2;;;2050:1;2047;2040:12;2020:2;2078:61;2135:3;2126:6;2115:9;2111:22;2078:61;:::i;:::-;2070:6;2063:77;2193:2;2182:9;2178:18;2165:32;2149:48;;2222:2;2212:8;2209:16;2206:2;;;2238:1;2235;2228:12;2206:2;;2275:58;2329:3;2318:8;2307:9;2303:24;2275:58;:::i;:::-;2270:2;2262:6;2258:15;2251:83;;2386:2;2375:9;2371:18;2358:32;2399;2423:7;2399:32;:::i;:::-;2459:2;2447:15;;;;2440:32;1648:830;;-1:-1:-1;;1648:830:31:o;2483:1556::-;2544:5;2592:6;2580:9;2575:3;2571:19;2567:32;2564:2;;;2616:5;2609;2602:20;2564:2;2642:22;;:::i;:::-;2633:31;-1:-1:-1;2687:23:31;;-1:-1:-1;;;;;2759:14:31;;;2756:2;;;2786:1;2783;2776:12;2756:2;2813:45;2854:3;2845:6;2834:9;2830:22;2813:45;:::i;:::-;2806:5;2799:60;2912:2;2901:9;2897:18;2884:32;2868:48;;2941:2;2931:8;2928:16;2925:2;;;2957:1;2954;2947:12;2925:2;;2993:47;3036:3;3025:8;3014:9;3010:24;2993:47;:::i;:::-;2988:2;2981:5;2977:14;2970:71;;3073:38;3107:2;3096:9;3092:18;3073:38;:::i;:::-;3068:2;3061:5;3057:14;3050:62;3144:38;3178:2;3167:9;3163:18;3144:38;:::i;:::-;3139:2;3132:5;3128:14;3121:62;3216:38;3249:3;3238:9;3234:19;3216:38;:::i;:::-;3210:3;3203:5;3199:15;3192:63;3288:38;3321:3;3310:9;3306:19;3288:38;:::i;:::-;3282:3;3275:5;3271:15;3264:63;3360:38;3393:3;3382:9;3378:19;3360:38;:::i;:::-;3354:3;3347:5;3343:15;3336:63;3432:38;3465:3;3454:9;3450:19;3432:38;:::i;:::-;3426:3;3419:5;3415:15;3408:63;3490:3;3525:38;3559:2;3548:9;3544:18;3525:38;:::i;:::-;3509:14;;;3502:62;3583:3;3618:38;3637:18;;;3618:38;:::i;:::-;3602:14;;;3595:62;3676:3;3711:37;3729:18;;;3711:37;:::i;:::-;3695:14;;;3688:61;3768:3;3803:37;3821:18;;;3803:37;:::i;:::-;3787:14;;;3780:61;3860:3;3895:37;3913:18;;;3895:37;:::i;:::-;3879:14;;;3872:61;3952:3;3987:45;4013:18;;;3987:45;:::i;:::-;3971:14;;;3964:69;3975:5;2554:1485;-1:-1:-1;;2554:1485:31:o;4044:1596::-;4096:5;4144:6;4132:9;4127:3;4123:19;4119:32;4116:2;;;4168:5;4161;4154:20;4116:2;4194:22;;:::i;:::-;4185:31;;4239:27;4256:9;4239:27;:::i;:::-;4232:5;4225:42;4299:38;4333:2;4322:9;4318:18;4299:38;:::i;:::-;4294:2;4287:5;4283:14;4276:62;4370:38;4404:2;4393:9;4389:18;4370:38;:::i;:::-;4365:2;4358:5;4354:14;4347:62;4441:45;4482:2;4471:9;4467:18;4441:45;:::i;:::-;4436:2;4429:5;4425:14;4418:69;4520:46;4561:3;4550:9;4546:19;4520:46;:::i;:::-;4514:3;4507:5;4503:15;4496:71;4600:38;4633:3;4622:9;4618:19;4600:38;:::i;:::-;4594:3;4587:5;4583:15;4576:63;4672:36;4703:3;4692:9;4688:19;4672:36;:::i;:::-;4666:3;4659:5;4655:15;4648:61;4742:38;4775:3;4764:9;4760:19;4742:38;:::i;:::-;4736:3;4729:5;4725:15;4718:63;4800:3;4835:37;4868:2;4857:9;4853:18;4835:37;:::i;:::-;4819:14;;;4812:61;4892:3;4931:18;;;4918:32;-1:-1:-1;;;;;4999:14:31;;;4996:2;;;5026:1;5023;5016:12;4996:2;5062:45;5103:3;5094:6;5083:9;5079:22;5062:45;:::i;:::-;5057:2;5050:5;5046:14;5039:69;5127:3;5117:13;;5162:60;5218:2;5207:9;5203:18;5162:60;:::i;:::-;5157:2;5150:5;5146:14;5139:84;5242:3;5232:13;;5277:60;5333:2;5322:9;5318:18;5277:60;:::i;:::-;5272:2;5265:5;5261:14;5254:84;5357:3;5347:13;;5392:37;5425:2;5414:9;5410:18;5392:37;:::i;:::-;5387:2;5380:5;5376:14;5369:61;5449:3;5439:13;;5505:2;5494:9;5490:18;5477:32;5461:48;;5534:2;5524:8;5521:16;5518:2;;;5550:1;5547;5540:12;5518:2;;5586:47;5629:3;5618:8;5607:9;5603:24;5586:47;:::i;:::-;5581:2;5574:5;5570:14;5563:71;;;4106:1534;;;;:::o;5645:1352::-;5701:5;5749:6;5737:9;5732:3;5728:19;5724:32;5721:2;;;5773:5;5766;5759:20;5721:2;5799:17;;:::i;:::-;5790:26;-1:-1:-1;5839:23:31;;-1:-1:-1;;;;;5911:14:31;;;5908:2;;;5938:1;5935;5928:12;5908:2;5965:45;6006:3;5997:6;5986:9;5982:22;5965:45;:::i;:::-;5958:5;5951:60;6064:2;6053:9;6049:18;6036:32;6020:48;;6093:2;6083:8;6080:16;6077:2;;;6109:1;6106;6099:12;6077:2;;6145:47;6188:3;6177:8;6166:9;6162:24;6145:47;:::i;:::-;6140:2;6133:5;6129:14;6122:71;;6225:38;6259:2;6248:9;6244:18;6225:38;:::i;:::-;6220:2;6213:5;6209:14;6202:62;6296:38;6330:2;6319:9;6315:18;6296:38;:::i;:::-;6291:2;6284:5;6280:14;6273:62;6368:38;6401:3;6390:9;6386:19;6368:38;:::i;:::-;6362:3;6355:5;6351:15;6344:63;6440:38;6473:3;6462:9;6458:19;6440:38;:::i;:::-;6434:3;6427:5;6423:15;6416:63;6512:38;6545:3;6534:9;6530:19;6512:38;:::i;:::-;6506:3;6499:5;6495:15;6488:63;6584:38;6617:3;6606:9;6602:19;6584:38;:::i;:::-;6578:3;6571:5;6567:15;6560:63;6642:3;6677:37;6710:2;6699:9;6695:18;6677:37;:::i;:::-;6661:14;;;6654:61;6734:3;6769:37;6787:18;;;6769:37;:::i;:::-;6753:14;;;6746:61;6826:3;6861:37;6879:18;;;6861:37;:::i;:::-;6845:14;;;6838:61;6918:3;6953:37;6971:18;;;6953:37;:::i;7002:173::-;7070:20;;-1:-1:-1;;;;;7119:31:31;;7109:42;;7099:2;;7165:1;7162;7155:12;7180:132;7247:20;;7276:30;7247:20;7276:30;:::i;7317:156::-;7383:20;;7443:4;7432:16;;7422:27;;7412:2;;7463:1;7460;7453:12;7478:1521;7873:6;7881;7889;7897;7905;7913;7921;7929;7937;7945;7998:3;7986:9;7977:7;7973:23;7969:33;7966:2;;;8020:6;8012;8005:22;7966:2;8052:23;;-1:-1:-1;;;;;8124:14:31;;;8121:2;;;8156:6;8148;8141:22;8121:2;8184:56;8232:7;8223:6;8212:9;8208:22;8184:56;:::i;:::-;8174:66;;8293:2;8282:9;8278:18;8265:32;8249:48;;8322:2;8312:8;8309:16;8306:2;;;8343:6;8335;8328:22;8306:2;8371:58;8421:7;8410:8;8399:9;8395:24;8371:58;:::i;:::-;8361:68;;8482:2;8471:9;8467:18;8454:32;8438:48;;8511:2;8501:8;8498:16;8495:2;;;8532:6;8524;8517:22;8495:2;;8560:64;8616:7;8605:8;8594:9;8590:24;8560:64;:::i;:::-;8550:74;;;8643:38;8677:2;8666:9;8662:18;8643:38;:::i;:::-;7956:1043;;;;-1:-1:-1;7956:1043:31;;8728:3;8713:19;;8700:33;;8780:3;8765:19;;8752:33;;-1:-1:-1;8832:3:31;8817:19;;8804:33;;-1:-1:-1;8884:3:31;8869:19;;8856:33;;-1:-1:-1;8936:3:31;8921:19;;8908:33;;-1:-1:-1;8988:3:31;8973:19;;;8960:33;;-1:-1:-1;7956:1043:31;-1:-1:-1;;7956:1043:31:o;9004:1432::-;9367:6;9375;9383;9391;9399;9407;9415;9423;9431;9484:3;9472:9;9463:7;9459:23;9455:33;9452:2;;;9506:6;9498;9491:22;9452:2;9538:23;;-1:-1:-1;;;;;9610:14:31;;;9607:2;;;9642:6;9634;9627:22;9607:2;9670:56;9718:7;9709:6;9698:9;9694:22;9670:56;:::i;:::-;9660:66;;9779:2;9768:9;9764:18;9751:32;9735:48;;9808:2;9798:8;9795:16;9792:2;;;9829:6;9821;9814:22;9792:2;9857:58;9907:7;9896:8;9885:9;9881:24;9857:58;:::i;:::-;9847:68;;9968:2;9957:9;9953:18;9940:32;9924:48;;9997:2;9987:8;9984:16;9981:2;;;10018:6;10010;10003:22;9981:2;;10046:67;10105:7;10094:8;10083:9;10079:24;10046:67;:::i;:::-;10036:77;;;10132:38;10166:2;10155:9;10151:18;10132:38;:::i;:::-;9442:994;;;;-1:-1:-1;9442:994:31;;10217:3;10202:19;;10189:33;;10269:3;10254:19;;10241:33;;-1:-1:-1;10321:3:31;10306:19;;10293:33;;-1:-1:-1;10373:3:31;10358:19;;10345:33;;-1:-1:-1;10425:3:31;10410:19;;;10397:33;;-1:-1:-1;9442:994:31;-1:-1:-1;;9442:994:31:o;10441:1287::-;10802:6;10810;10818;10826;10834;10842;10850;10858;10866;10919:3;10907:9;10898:7;10894:23;10890:33;10887:2;;;10941:6;10933;10926:22;10887:2;10973:23;;-1:-1:-1;;;;;11045:14:31;;;11042:2;;;11077:6;11069;11062:22;11042:2;11105:56;11153:7;11144:6;11133:9;11129:22;11105:56;:::i;:::-;11095:66;;11214:2;11203:9;11199:18;11186:32;11170:48;;11243:2;11233:8;11230:16;11227:2;;;11264:6;11256;11249:22;11227:2;;11292:62;11346:7;11335:8;11324:9;11320:24;11292:62;:::i;:::-;11282:72;;;11373:38;11407:2;11396:9;11392:18;11373:38;:::i;:::-;10877:851;;;;-1:-1:-1;11363:48:31;;11458:2;11443:18;;11430:32;;-1:-1:-1;11509:3:31;11494:19;;11481:33;;11561:3;11546:19;;11533:33;;-1:-1:-1;11613:3:31;11598:19;;11585:33;;-1:-1:-1;11665:3:31;11650:19;;11637:33;;-1:-1:-1;11717:3:31;11702:19;;;11689:33;;-1:-1:-1;10877:851:31;-1:-1:-1;;10877:851:31:o;11733:259::-;11802:6;11855:2;11843:9;11834:7;11830:23;11826:32;11823:2;;;11876:6;11868;11861:22;11823:2;11913:9;11907:16;11932:30;11956:5;11932:30;:::i;11997:1066::-;12373:6;12366:14;12359:22;12354:3;12350:32;12345:3;12338:45;12320:3;12412:6;12406:13;12428:61;12482:6;12478:1;12473:3;12469:11;12462:4;12454:6;12450:17;12428:61;:::i;:::-;12549:13;;12508:16;;;;12571:62;12549:13;12620:1;12612:10;;12605:4;12593:17;;12571:62;:::i;:::-;12694:13;;12652:17;;;12716:62;12694:13;12765:1;12757:10;;12750:4;12738:17;;12716:62;:::i;:::-;12837:3;12878:16;;;-1:-1:-1;;;;;;12874:25:31;;;12870:1;12797:17;;;;12862:10;;;12855:45;;;;12932:16;;;;12928:25;;;12924:1;12916:10;;12909:45;-1:-1:-1;13016:3:31;12986:16;-1:-1:-1;;;;;;12982:47:31;12978:1;12970:10;;12963:67;-1:-1:-1;13054:2:31;13046:11;;12328:735;-1:-1:-1;;;;12328:735:31:o;13068:466::-;13243:3;13281:6;13275:13;13297:53;13343:6;13338:3;13331:4;13323:6;13319:17;13297:53;:::i;:::-;13413:13;;13372:16;;;;13435:57;13413:13;13372:16;13469:4;13457:17;;13435:57;:::i;:::-;13508:20;;13251:283;-1:-1:-1;;;;13251:283:31:o;13539:276::-;13670:3;13708:6;13702:13;13724:53;13770:6;13765:3;13758:4;13750:6;13746:17;13724:53;:::i;:::-;13793:16;;;;;13678:137;-1:-1:-1;;13678:137:31:o;13820:614::-;14100:3;14138:6;14132:13;14154:53;14200:6;14195:3;14188:4;14180:6;14176:17;14154:53;:::i;:::-;-1:-1:-1;;;14229:16:31;;;14254:18;;;14297:13;;14319:65;14297:13;14371:1;14360:13;;14353:4;14341:17;;14319:65;:::i;:::-;14404:20;14426:1;14400:28;;14108:326;-1:-1:-1;;;;14108:326:31:o;14825:958::-;-1:-1:-1;;;;;;15164:3:31;15198:16;;;15194:25;;15182:38;;-1:-1:-1;;;;;;15283:3:31;15253:16;;;15249:47;15292:1;15236:11;;15229:68;-1:-1:-1;;;;;;15360:2:31;15331:15;;;15327:45;15322:2;15313:12;;15306:67;15396:13;;-1:-1:-1;;15160:13:31;15418:62;15396:13;15468:2;15459:12;;15452:4;15440:17;;15418:62;:::i;:::-;15508:6;15503:3;15499:16;15489:26;;15566:2;15557:6;15552:3;15548:16;15544:25;15539:2;15535;15531:11;15524:46;15621:2;15612:6;15607:3;15603:16;15599:25;15594:2;15590;15586:11;15579:46;15656:6;15650:13;15634:29;;15672:63;15726:8;15721:2;15717;15713:11;15706:4;15698:6;15694:17;15672:63;:::i;:::-;15755:17;15774:2;15751:26;;15140:643;-1:-1:-1;;;;;;;;;15140:643:31:o;16500:383::-;16649:2;16638:9;16631:21;16612:4;16681:6;16675:13;16724:6;16719:2;16708:9;16704:18;16697:34;16740:66;16799:6;16794:2;16783:9;16779:18;16774:2;16766:6;16762:15;16740:66;:::i;:::-;16867:2;16846:15;-1:-1:-1;;16842:29:31;16827:45;;;;16874:2;16823:54;;16621:262;-1:-1:-1;;16621:262:31:o;18672:343::-;18874:2;18856:21;;;18913:2;18893:18;;;18886:30;-1:-1:-1;;;18947:2:31;18932:18;;18925:49;19006:2;18991:18;;18846:169::o;20072:343::-;20274:2;20256:21;;;20313:2;20293:18;;;20286:30;-1:-1:-1;;;20347:2:31;20332:18;;20325:49;20406:2;20391:18;;20246:169::o;22751:348::-;22953:2;22935:21;;;22992:2;22972:18;;;22965:30;-1:-1:-1;;;23026:2:31;23011:18;;23004:54;23090:2;23075:18;;22925:174::o;26092:354::-;26294:2;26276:21;;;26333:2;26313:18;;;26306:30;26372:32;26367:2;26352:18;;26345:60;26437:2;26422:18;;26266:180::o;27159:354::-;27361:2;27343:21;;;27400:2;27380:18;;;27373:30;27439:32;27434:2;27419:18;;27412:60;27504:2;27489:18;;27333:180::o;29337:352::-;29539:2;29521:21;;;29578:2;29558:18;;;29551:30;-1:-1:-1;;;29612:2:31;29597:18;;29590:58;29680:2;29665:18;;29511:178::o;29694:355::-;29896:2;29878:21;;;29935:2;29915:18;;;29908:30;29974:33;29969:2;29954:18;;29947:61;30040:2;30025:18;;29868:181::o;30415:356::-;30617:2;30599:21;;;30636:18;;;30629:30;30695:34;30690:2;30675:18;;30668:62;30762:2;30747:18;;30589:182::o;31135:404::-;31337:2;31319:21;;;31376:2;31356:18;;;31349:30;31415:34;31410:2;31395:18;;31388:62;-1:-1:-1;;;31481:2:31;31466:18;;31459:38;31529:3;31514:19;;31309:230::o;31896:403::-;32098:2;32080:21;;;32137:2;32117:18;;;32110:30;32176:34;32171:2;32156:18;;32149:62;-1:-1:-1;;;32242:2:31;32227:18;;32220:37;32289:3;32274:19;;32070:229::o;32304:255::-;32376:2;32370:9;32418:6;32406:19;;-1:-1:-1;;;;;32440:34:31;;32476:22;;;32437:62;32434:2;;;32502:18;;:::i;:::-;32538:2;32531:22;32350:209;:::o;32564:250::-;32631:2;32625:9;32673:6;32661:19;;-1:-1:-1;;;;;32695:34:31;;32731:22;;;32692:62;32689:2;;;32757:18;;:::i;32819:128::-;32859:3;32890:1;32886:6;32883:1;32880:13;32877:2;;;32896:18;;:::i;:::-;-1:-1:-1;32932:9:31;;32867:80::o;32952:236::-;32991:3;-1:-1:-1;;;;;33057:10:31;;;33087;;;33117:12;;;33109:21;;33106:2;;;33133:18;;:::i;33193:201::-;33233:1;-1:-1:-1;;;;;33298:10:31;;;;33317:2;;33334:18;;:::i;:::-;33372:10;;33368:20;;;;;33239:155;-1:-1:-1;;33239:155:31:o;33399:120::-;33439:1;33465;33455:2;;33470:18;;:::i;:::-;-1:-1:-1;33504:9:31;;33445:74::o;33524:199::-;33563:1;-1:-1:-1;;;;;33627:10:31;;;;33646:2;;33663:18;;:::i;33728:272::-;33768:7;-1:-1:-1;;;;;33839:10:31;;;33869;;;33902:11;;33895:19;33924:12;;;33916:21;;33891:47;33888:2;;;33941:18;;:::i;:::-;33981:13;;33780:220;-1:-1:-1;;;;33780:220:31:o;34005:168::-;34045:7;34111:1;34107;34103:6;34099:14;34096:1;34093:21;34088:1;34081:9;34074:17;34070:45;34067:2;;;34118:18;;:::i;:::-;-1:-1:-1;34158:9:31;;34057:116::o;34178:270::-;34217:7;-1:-1:-1;;;;;34287:10:31;;;34317;;;34350:11;;34343:19;34372:12;;;34364:21;;34339:47;34336:2;;;34389:18;;:::i;34453:125::-;34493:4;34521:1;34518;34515:8;34512:2;;;34526:18;;:::i;:::-;-1:-1:-1;34563:9:31;;34502:76::o;34583:229::-;34622:4;-1:-1:-1;;;;;34719:10:31;;;;34689;;34741:12;;;34738:2;;;34756:18;;:::i;:::-;34793:13;;34631:181;-1:-1:-1;;;34631:181:31:o;34817:258::-;34889:1;34899:113;34913:6;34910:1;34907:13;34899:113;;;34989:11;;;34983:18;34970:11;;;34963:39;34935:2;34928:10;34899:113;;;35030:6;35027:1;35024:13;35021:2;;;-1:-1:-1;;35065:1:31;35047:16;;35040:27;34870:205::o;35080:136::-;35119:3;35147:5;35137:2;;35156:18;;:::i;:::-;-1:-1:-1;;;35192:18:31;;35127:89::o;35221:380::-;35300:1;35296:12;;;;35343;;;35364:2;;35418:4;35410:6;35406:17;35396:27;;35364:2;35471;35463:6;35460:14;35440:18;35437:38;35434:2;;;35517:10;35512:3;35508:20;35505:1;35498:31;35552:4;35549:1;35542:15;35580:4;35577:1;35570:15;35434:2;;35276:325;;;:::o;35606:135::-;35645:3;-1:-1:-1;;35666:17:31;;35663:2;;;35686:18;;:::i;:::-;-1:-1:-1;35733:1:31;35722:13;;35653:88::o;35746:175::-;35783:3;35827:4;35820:5;35816:16;35856:4;35847:7;35844:17;35841:2;;;35864:18;;:::i;:::-;35913:1;35900:15;;35791:130;-1:-1:-1;;35791:130:31:o;35926:112::-;35958:1;35984;35974:2;;35989:18;;:::i;:::-;-1:-1:-1;36023:9:31;;35964:74::o;36043:127::-;36104:10;36099:3;36095:20;36092:1;36085:31;36135:4;36132:1;36125:15;36159:4;36156:1;36149:15;36175:127;36236:10;36231:3;36227:20;36224:1;36217:31;36267:4;36264:1;36257:15;36291:4;36288:1;36281:15;36307:127;36368:10;36363:3;36359:20;36356:1;36349:31;36399:4;36396:1;36389:15;36423:4;36420:1;36413:15;36439:129;-1:-1:-1;;;;;36513:30:31;;36503:41;;36493:2;;36558:1;36555;36548:12
Swarm Source
ipfs://ee7b2c74fdc947097f93238c12d3f8c0e5e56c04a156b57a959a68c3bd42edd0
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.