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] | |||
---|---|---|---|---|---|---|---|---|---|
0x6f9550069a823656b6d613f55c0c196137b3da6fd473b984b68f964d2032bcef | 0x613f7761 | 21255673 | 194 days 5 hrs ago | 0x8af97264482b59c7aa11010907710dee6d8d8c6c | IN | Create: LiquidityPools | 0 MATIC | 0.10686555 |
[ Download CSV Export ]
Contract Name:
LiquidityPools
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: 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 { 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 { 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; /** * 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
[{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
613f7761003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361061006c5760003560e01c8063089f7adc14610071578063467b41fb146100935780638d39e3a8146100c95780639cffaf0f14610109578063ba9a7a5614610136578063f2090c5214610157575b600080fd5b81801561007d57600080fd5b5061009161008c3660046135cf565b610177565b005b81801561009f57600080fd5b506100b36100ae36600461349f565b6101a1565b6040516100c09190613ae4565b60405180910390f35b8180156100d557600080fd5b506100e96100e4366004613445565b610285565b604080516001600160401b039384168152929091166020830152016100c0565b81801561011557600080fd5b50610129610124366004613590565b610541565b6040516100c09190613b9d565b61013f6103e881565b6040516001600160401b0390911681526020016100c0565b81801561016357600080fd5b50610091610172366004613507565b610621565b600061018589898989610648565b905061019688888787858888610956565b505050505050505050565b6101a961302a565b4285610160015110156101d75760405162461bcd60e51b81526004016101ce906139dd565b60405180910390fd5b60006101e286610ab0565b90506000808281526002808a0160205260409091205460ff169081111561021957634e487b7160e01b600052602160045260246000fd5b146102365760405162461bcd60e51b81526004016101ce90613a04565b60008181526002880160205260409020805460ff191660011790556060860151608087015160a088015160c089015160e08a015161027a94939291908a8a8a610b65565b979650505050505050565b6000806000610295888888610bfb565b60018101549091506000906102b690869033906001600160a01b0316610c75565b6040805160c081018252845460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b9004909116608082015260018401546001600160a01b031660a082015290915061032d9082610cf1565b8354919550935060009061034c908690600160481b900460ff16610dc9565b8354909150600090610369908690600160901b900460ff16610dc9565b84549091508690859060019061038e90849061010090046001600160401b0316613e21565b92506101000a8154816001600160401b0302191690836001600160401b031602179055508484600001600a8282829054906101000a90046001600160401b03166103d89190613e21565b82546001600160401b039182166101009390930a92830291909202199091161790555060018401546001600160a01b03166337209efe3361041a866012610dc9565b8585336040518663ffffffff1660e01b815260040161043d95949392919061387e565b600060405180830381600087803b15801561045757600080fd5b505af115801561046b573d6000803e3d6000fd5b5050604051636ce5768960e11b81526001600160a01b038b16925063d9caed12915061049f9033908e90879060040161385a565b600060405180830381600087803b1580156104b957600080fd5b505af11580156104cd573d6000803e3d6000fd5b5050604051636ce5768960e11b81526001600160a01b038b16925063d9caed1291506105019033908d90869060040161385a565b600060405180830381600087803b15801561051b57600080fd5b505af115801561052f573d6000803e3d6000fd5b50505050505050509550959350505050565b61054961306d565b42856101400151101561056e5760405162461bcd60e51b81526004016101ce906139dd565b600061057986610e5d565b90506000808281526002808a0160205260409091205460ff16908111156105b057634e487b7160e01b600052602160045260246000fd5b146105cd5760405162461bcd60e51b81526004016101ce90613a04565b60008181526002880160205260408120805460ff19166001179055608087015160a08801516105fd918a91610eef565b90506106158760600151828960c00151898989610f61565b98975050505050505050565b600061062e878787610fc0565b905061063e828787878786611237565b5050505050505050565b60008161077057600061065a85610e5d565b600081815260028801602052604081205491925060ff909116908660200151600181111561069857634e487b7160e01b600052602160045260246000fd5b14156106e25760018160028111156106c057634e487b7160e01b600052602160045260246000fd5b146106dd5760405162461bcd60e51b81526004016101ce906138ae565b610751565b600081600281111561070457634e487b7160e01b600052602160045260246000fd5b146107215760405162461bcd60e51b81526004016101ce906138e4565b61073582876101600151886060015161145f565b6107515760405162461bcd60e51b81526004016101ce90613982565b50600090815260028681016020526040909120805460ff191690911790555b60006107858685600001518660200151610bfb565b60018101546040805160c081018252835460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b900490911660808201526001600160a01b0390911660a082018190529350909150610805908690869061148f565b60608401518154829060019061082a90849061010090046001600160401b0316613e21565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550836080015181600001600a8282829054906101000a90046001600160401b03166108789190613e21565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550816001600160a01b03166337209efe86606001516108bf87604001516012610dc9565b606088015185546108da9190600160481b900460ff16610dc9565b608089015186546108f59190600160901b900460ff16610dc9565b8a61012001516040518663ffffffff1660e01b815260040161091b95949392919061387e565b600060405180830381600087803b15801561093557600080fd5b505af1158015610949573d6000803e3d6000fd5b5050505050949350505050565b600080610967838a8a898b8a6117ce565b915091506109736130b7565b6001600160401b03831615610a0a57885161098f908690611a25565b9050876001600160a01b031663d9caed128b61012001518b600001516109b9878660600151610dc9565b6040518463ffffffff1660e01b81526004016109d79392919061385a565b600060405180830381600087803b1580156109f157600080fd5b505af1158015610a05573d6000803e3d6000fd5b505050505b6001600160401b03821615610aa4576020890151610a29908690611a25565b9050876001600160a01b031663d9caed128b61012001518b60200151610a53868660600151610dc9565b6040518463ffffffff1660e01b8152600401610a719392919061385a565b600060405180830381600087803b158015610a8b57600080fd5b505af1158015610a9f573d6000803e3d6000fd5b505050505b50505050505050505050565b805160009060ff16600314610ad75760405162461bcd60e51b81526004016101ce90613aad565b8151600083602001516001811115610aff57634e487b7160e01b600052602160045260246000fd5b6040808601516060870151608088015160a089015160c08a015160e08b01516101008c01516101208d01516101408e01516101608f01519951610b489d9c9b9a906020016137a5565b604051602081830303815290604052805190602001209050919050565b610b6d61302a565b610b756130b7565b610b7f848a611a25565b604081015183529050610b958a82898887611c3f565b60608501526001600160401b039081166040850152166020830152610bba8489611a25565b604081015160808401529050610bd38a82888887611c3f565b60e08501526001600160401b0390811660c08501521660a08301525098975050505050505050565b6001600160a01b0380831660009081526001850160209081526040808320938516835292905220805460ff16610c6e5760405162461bcd60e51b81526020600482015260186024820152772737903837b7b6103337b91030b2323932b9b9903830b4b960411b60448201526064016101ce565b9392505050565b600080610c83858585611d1d565b805461010090046001600160401b03169250905081610cdb5760405162461bcd60e51b8152602060048201526014602482015273139bc818985b185b98d948199bdc88185cdcd95d60621b60448201526064016101ce565b8054610100600160481b03191690559392505050565b6000806000610d758560a001516001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610d3657600080fd5b505afa158015610d4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d6e9190613679565b6012611e20565b9050610d8685602001518583611ec1565b9250610db0838660200151610d9b9190613e21565b610da487611ed0565b6305f5e1006001611f07565b8560600151610dbf9190613e21565b9150509250929050565b600060208260ff161115610def5760405162461bcd60e51b81526004016101ce90613a66565b60088260ff161115610e2b57610e06600883613e49565b610e1190600a613d11565b610e24906001600160401b038516613dbc565b9050610e57565b610e36826008613e49565b610e4190600a613d11565b610e54906001600160401b038516613c94565b90505b92915050565b805160009060ff16600314610e845760405162461bcd60e51b81526004016101ce90613aad565b8151600183602001516001811115610eac57634e487b7160e01b600052602160045260246000fd5b6040808601516060870151608088015160a089015160c08a015160e08b01516101008c01516101208d01516101408e01519851610b489c9b9a9990602001613709565b6001600160a01b0380831660009081526020858152604080832085851684529091529020541680610c6e5760405162461bcd60e51b815260206004820152601c60248201527b2737902628103a37b5b2b7103337b91030b2323932b9b9903830b4b960211b60448201526064016101ce565b610f6961306d565b6000610f758488611a25565b604081015160208401526001600160a01b03881683529050610f9a8882888887611c3f565b60808501526001600160401b039081166060850152166040830152509695505050505050565b600080610fcc84610ab0565b600081815260028701602052604081205491925060ff909116908560200151600181111561100a57634e487b7160e01b600052602160045260246000fd5b141561105457600181600281111561103257634e487b7160e01b600052602160045260246000fd5b1461104f5760405162461bcd60e51b81526004016101ce906138ae565b6110c3565b600081600281111561107657634e487b7160e01b600052602160045260246000fd5b146110935760405162461bcd60e51b81526004016101ce906138e4565b6110a782866101800151876060015161145f565b6110c35760405162461bcd60e51b81526004016101ce90613982565b5060009081526002858101602090815260408320805460ff19169092179091558351908401516110f4918791610bfb565b60018101546040805160c081018252835460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b900490911660808201526001600160a01b0390911660a0820181905293509091506111749085908590611f98565b61117e81846122c0565b816001600160a01b0316638cb75cad85606001516111a186604001516012610dc9565b60a087015185546111bc9190600160481b900460ff16610dc9565b60c088015186546111d79190600160901b900460ff16610dc9565b8961014001516040518663ffffffff1660e01b81526004016111fd95949392919061387e565b600060405180830381600087803b15801561121757600080fd5b505af115801561122b573d6000803e3d6000fd5b50505050509392505050565b60008061124d8888606001518860000151611d1d565b6060870151815491925090829060019061127690849061010090046001600160401b0316613e21565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506112a988868860000151611d1d565b90508560a0015186606001516112bf9190613e21565b815482906001906112df90849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506113168888606001518860200151611d1d565b6080870151815491925090829060019061133f90849061010090046001600160401b0316613e21565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555061137288868860200151611d1d565b90508560c0015186608001516113889190613e21565b815482906001906113a890849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550836001600160a01b03168761014001516001600160a01b0316141561144c576113fa88886060015185611d1d565b6040870151815491925090829060019061142390849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550611454565b856040015191505b509695505050505050565b6000816001600160a01b031661147d61147786612574565b856125ae565b6001600160a01b031614949350505050565b82608001516001600160a01b031682600001516001600160a01b03161480156114d157508260a001516001600160a01b031682602001516001600160a01b0316145b8061151957508260a001516001600160a01b031682600001516001600160a01b0316148015611519575082608001516001600160a01b031682602001516001600160a01b0316145b6115355760405162461bcd60e51b81526004016101ce906139ad565b600082606001516001600160401b031611801561155f5750600082608001516001600160401b0316115b6115ab5760405162461bcd60e51b815260206004820181905260248201527f47726f7373207175616e746974696573206d757374206265206e6f6e7a65726f60448201526064016101ce565b60008084608001516001600160a01b031684600001516001600160a01b0316146115df578461010001518560e001516115eb565b8460e001518561010001515b91509150600080611600848660400151611e20565b61160e848760800151611e20565b91509150816001600160401b031686606001516001600160401b031610156116485760405162461bcd60e51b81526004016101ce9061394f565b806001600160401b031686608001516001600160401b0316101561167e5760405162461bcd60e51b81526004016101ce9061391b565b61168d8760c001516012611e20565b6001600160401b031686604001516001600160401b0316146116ec5760405162461bcd60e51b8152602060048201526018602482015277125b9d985b1a59081b1a5c5d5a591a5d1e48189d5c9b995960421b60448201526064016101ce565b600080611706886040015188610cf190919063ffffffff16565b6001600160401b031691506001600160401b031691508188606001516001600160401b0316146117705760405162461bcd60e51b8152602060048201526015602482015274496e76616c69642062617365207175616e7469747960581b60448201526064016101ce565b8088608001516001600160401b0316146117c55760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642071756f7465207175616e7469747960501b60448201526064016101ce565b6101968861265a565b6000806000846001600160a01b03168861012001516001600160a01b03161415611857576118058989606001518960000151611d1d565b60a0880151815491925090829060019061182e90849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555061185f565b8660a0015192505b61186e89878960000151611d1d565b90508660a0015187606001516118849190613e21565b815482906001906118a490849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550846001600160a01b03168861012001516001600160a01b0316141561194c576118fa8989606001518960200151611d1d565b60c0880151815491925090829060019061192390849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550611954565b8660c0015191505b61196389878960200151611d1d565b90508660c0015187608001516119799190613e21565b8154829060019061199990849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506119cc89896060015186611d1d565b604088015181549192509082906001906119f590849061010090046001600160401b0316613e21565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555050965096945050505050565b611a2d6130b7565b6001600160a01b038216611ad057610e24836002018054611a4d90613e6c565b80601f0160208091040260200160405190810160405280929190818152602001828054611a7990613e6c565b8015611ac65780601f10611a9b57610100808354040283529160200191611ac6565b820191906000526020600020905b815481529060010190602001808311611aa957829003601f168201915b5050505050612712565b6001600160a01b03808316600090815260208581526040808320815160c081018352815460ff81161515825261010090049095169285019290925260018201805493949391840191611b2190613e6c565b80601f0160208091040260200160405190810160405280929190818152602001828054611b4d90613e6c565b8015611b9a5780601f10611b6f57610100808354040283529160200191611b9a565b820191906000526020600020905b815481529060010190602001808311611b7d57829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015280519091508015611be7575080608001515b610e545760405162461bcd60e51b8152602060048201526024808201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f72206164646044820152637265737360e01b60648201526084016101ce565b6000806000611c52868860600151611e20565b92506000836001600160401b031611611ca35760405162461bcd60e51b81526020600482015260136024820152725175616e7469747920697320746f6f206c6f7760681b60448201526064016101ce565b6000611cb3848960600151610dc9565b60208901519091506001600160a01b0316611cdc57611cd786896020015183612751565b611cec565b611cec89896020015188846129b0565b6020880151611cff9086908b9087612b9e565b9250611d0f838960600151610dc9565b915050955095509592505050565b6001600160a01b038083166000908152602085815260408083209385168352929052908120805460ff16158015611d60575060018501546001600160a01b031615155b15611e1857600185015460405163dbb3653560e01b81526001600160a01b03868116600483015285811660248301529091169063dbb365359060440160206040518083038186803b158015611db457600080fd5b505afa158015611dc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dec9190613691565b815460ff196001600160401b039290921661010002919091166001600160481b03199091161760011781555b949350505050565b600060208260ff161115611e465760405162461bcd60e51b81526004016101ce90613a66565b600060088360ff161115611e7b57611e5f600884613e49565b611e6a90600a613d11565b611e749085613c94565b9050611e9e565b611e86836008613e49565b611e9190600a613d11565b611e9b9085613dbc565b90505b600160401b8110610e545760405162461bcd60e51b81526004016101ce90613a2f565b6000611e188484846000611f07565b600081602001516001600160401b031660001415611ef057506000919050565b610e576305f5e10083606001518460200151611ec1565b600080611f206001600160401b03808716908816613dbc565b90506000611f376001600160401b03861683613c94565b9050838015611f5857506000611f566001600160401b03871684613ea1565b115b15611f6b57611f68600182613c51565b90505b600160401b8110611f8e5760405162461bcd60e51b81526004016101ce90613a2f565b9695505050505050565b82608001516001600160a01b031682600001516001600160a01b0316148015611fda57508260a001516001600160a01b031682602001516001600160a01b0316145b8061202257508260a001516001600160a01b031682600001516001600160a01b0316148015612022575082608001516001600160a01b031682602001516001600160a01b0316145b61203e5760405162461bcd60e51b81526004016101ce906139ad565b60008060008086608001516001600160a01b031686600001516001600160a01b031614612080578661012001518760e001518861010001518960c00151612097565b8661010001518760c001518861012001518960e001515b93509350935093506000806000806120b3888a60400151611e20565b6120c1888b60400151611e20565b6120cf888c60800151611e20565b6120dd888d60800151611e20565b9350935093509350836001600160401b03168a606001516001600160401b0316101561211b5760405162461bcd60e51b81526004016101ce9061394f565b826001600160401b03168a606001516001600160401b031611156121815760405162461bcd60e51b815260206004820152601e60248201527f446573697265642062617365207175616e74697479206578636565646564000060448201526064016101ce565b816001600160401b03168a608001516001600160401b031610156121b75760405162461bcd60e51b81526004016101ce9061391b565b806001600160401b03168a608001516001600160401b0316111561221d5760405162461bcd60e51b815260206004820152601f60248201527f446573697265642071756f7465207175616e746974792065786365656465640060448201526064016101ce565b60008a604001516001600160401b0316118015612263575060a08a015160c08b015161224a918b91612c05565b6001600160401b03168a604001516001600160401b0316145b6122aa5760405162461bcd60e51b8152602060048201526018602482015277125b9d985b1a59081b1a5c5d5a591a5d1e481b5a5b9d195960421b60448201526064016101ce565b6122b38a61265a565b5050505050505050505050565b6040805160c081018252835460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b9004909116608082015260018301546001600160a01b031660a082015260009061233690611ed0565b60a0830151845491925090849060019061235f90849061010090046001600160401b0316613c69565b92506101000a8154816001600160401b0302191690836001600160401b031602179055508160c0015183600001600a8282829054906101000a90046001600160401b03166123ad9190613c69565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550806001600160401b03166000141561245b576040805160c081018252845460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b9004909116608082015260018401546001600160a01b031660a082015261245690612cdf565b505050565b82546305f5e1006101009091046001600160401b031610801590612495575082546305f5e100600160501b9091046001600160401b031610155b15612456576040805160c081018252845460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b9004909116608082015260018401546001600160a01b031660a082015260009061251090611ed0565b9050806001600160401b0316826001600160401b03161461256e5760405162461bcd60e51b8152602060048201526018602482015277506f6f6c2070726963652063616e6e6f74206368616e676560401b60448201526064016101ce565b50505050565b6040517b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6020820152603c8101829052600090605c01610b48565b6000806000808451604114156125d85750505060208201516040830151606084015160001a61264e565b8451604014156126065750505060408201516020830151906001600160ff1b0381169060ff1c601b0161264e565b60405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016101ce565b611f8e86828585612d99565b61267361266682612f33565b82606001516107d0612f49565b6126b45760405162461bcd60e51b815260206004820152601260248201527145786365737369766520626173652066656560701b60448201526064016101ce565b6126cd6126c082612f7c565b82608001516107d0612f49565b61270f5760405162461bcd60e51b81526020600482015260136024820152724578636573736976652071756f74652066656560681b60448201526064016101ce565b50565b61271a6130b7565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b6001600160a01b0382166127c6576040516001600160a01b0384169082156108fc029083906000818181858888f193505050506124565760405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b60448201526064016101ce565b6040516370a0823160e01b81526000906001600160a01b038416906370a08231906127f5908790600401613846565b60206040518083038186803b15801561280d57600080fd5b505afa158015612821573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128459190613679565b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018590529192509084169063a9059cbb90604401600060405180830381600087803b15801561289357600080fd5b505af11580156128a7573d6000803e3d6000fd5b50506040516370a0823160e01b8152600092506001600160a01b03861691506370a08231906128da908890600401613846565b60206040518083038186803b1580156128f257600080fd5b505afa158015612906573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061292a9190613679565b9050826129378383613e0a565b146129a95760405162461bcd60e51b81526020600482015260486024820152600080516020613f2283398151915260448201527f207375636365737320776974686f75742065787065637465642062616c616e6360648201526765206368616e676560c01b608482015260a4016101ce565b5050505050565b6040516370a0823160e01b81526000906001600160a01b038516906370a08231906129df908690600401613846565b60206040518083038186803b1580156129f757600080fd5b505afa158015612a0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a2f9190613679565b6040516323b872dd60e01b81529091506001600160a01b038516906323b872dd90612a629088908790879060040161385a565b600060405180830381600087803b158015612a7c57600080fd5b505af1158015612a90573d6000803e3d6000fd5b50506040516370a0823160e01b8152600092506001600160a01b03871691506370a0823190612ac3908790600401613846565b60206040518083038186803b158015612adb57600080fd5b505afa158015612aef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b139190613679565b905082612b208383613e0a565b14612b965760405162461bcd60e51b815260206004820152604c6024820152600080516020613f2283398151915260448201527f46726f6d207375636365737320776974686f757420657870656374656420626160648201526b6c616e6365206368616e676560a01b608482015260a4016101ce565b505050505050565b600080612bac868686611d1d565b805490915083908290600190612bd190849061010090046001600160401b0316613c69565b82546001600160401b0391821661010093840a90810290830219909116179092559254929092049091169695505050505050565b6000808460a001516001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b158015612c4557600080fd5b505afa158015612c59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c7d9190613679565b905080612caa57612ca2612c9d6001600160401b03808616908716613dbc565b612f92565b915050610c6e565b6000612cb7826012611e20565b9050611f8e612ccb82878960200151611ec1565b612cda83878a60600151611ec1565b613002565b60008082606001516001600160401b031683602001516001600160401b03161115612d135782606001518360200151612d1e565b826020015183606001515b91509150806001600160401b03166305f5e1006001600160401b0316836001600160401b0316612d4e9190613dbc565b10156124565760405162461bcd60e51b815260206004820152601a6024820152794578636565646564206d6178207265736572766520726174696f60301b60448201526064016101ce565b60006fa2a8918ca85bafe22016d0b997e4df60600160ff1b03821115612e0c5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016101ce565b8360ff16601b1480612e2157508360ff16601c145b612e785760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b60648201526084016101ce565b6040805160008082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa158015612ecc573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116612f2a5760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b60448201526064016101ce565b95945050505050565b60008160a001518260600151610e579190613e21565b60008083612f5961271087613ddb565b612f639190613ca8565b6001600160401b03808516911611159150509392505050565b60008160c001518260800151610e579190613e21565b60006003821115612ff35750806000612fac600283613c94565b612fb7906001613c51565b90505b81811015612fed57905080600281612fd28186613c94565b612fdc9190613c51565b612fe69190613c94565b9050612fba565b50919050565b8115612ffd575060015b919050565b6000816001600160401b0316836001600160401b0316106130235781610e54565b5090919050565b60408051610100810182526060808252600060208301819052928201839052808201839052608082015260a0810182905260c0810182905260e081019190915290565b6040518060a0016040528060006001600160a01b031681526020016060815260200160006001600160401b0316815260200160006001600160401b03168152602001600081525090565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b8035612ffd81613ef7565b600082601f830112613108578081fd5b81356001600160401b038082111561312257613122613ee1565b604051601f8301601f19908116603f0116810190828211818310171561314a5761314a613ee1565b81604052838152866020858801011115613162578485fd5b8360208701602083013792830160200193909352509392505050565b803560028110612ffd57600080fd5b60006101a0828403121561319f578081fd5b6131a7613c05565b90506131b282613434565b81526131c06020830161317e565b60208201526131d160408301613412565b60408201526131e2606083016130ed565b60608201526131f3608083016130ed565b608082015261320460a083016130ed565b60a082015260c082013560c082015260e082013560e08201526101008083013581830152506101208083013581830152506101406132438184016130ed565b908201526101608281013590820152610180808301356001600160401b0381111561326d57600080fd5b613279858286016130f8565b82840152505092915050565b600060e08284031215613296578081fd5b60405160e081016001600160401b03811182821017156132b8576132b8613ee1565b60405290508082356132c981613ef7565b815260208301356132d981613ef7565b602082015260408301356132ec81613f0c565b604082015260608301356132ff81613f0c565b606082015261331060808401613429565b608082015261332160a08401613429565b60a082015261333260c08401613429565b60c08201525092915050565b60006101808284031215613350578081fd5b613358613c2e565b905061336382613434565b81526133716020830161317e565b602082015261338260408301613412565b6040820152613393606083016130ed565b60608201526133a4608083016130ed565b60808201526133b560a083016130ed565b60a082015260c082013560c082015260e082013560e08201526101008083013581830152506101206133e88184016130ed565b908201526101408281013590820152610160808301356001600160401b0381111561326d57600080fd5b80356001600160801b0381168114612ffd57600080fd5b8035612ffd81613f0c565b803560ff81168114612ffd57600080fd5b600080600080600060a0868803121561345c578081fd5b85359450602086013561346e81613ef7565b9350604086013561347e81613ef7565b9250606086013561348e81613ef7565b949793965091946080013592915050565b600080600080600060a086880312156134b6578081fd5b8535945060208601356001600160401b038111156134d2578182fd5b6134de8882890161318d565b94505060408601356134ef81613ef7565b94979396509394606081013594506080013592915050565b6000806000806000806101808789031215613520578384fd5b8635955060208701356001600160401b0381111561353c578485fd5b61354889828a0161318d565b9550506135588860408901613285565b935061012087013561356981613ef7565b925061014087013561357a81613ef7565b8092505061016087013590509295509295509295565b600080600080600060a086880312156135a7578283fd5b8535945060208601356001600160401b038111156135c3578384fd5b6134de8882890161333e565b6000806000806000806000806101c0898b0312156135eb578586fd5b8835975060208901356001600160401b03811115613607578687fd5b6136138b828c0161333e565b9750506136238a60408b01613285565b95506101208901358015158114613638578283fd5b945061014089013561364981613ef7565b935061016089013561365a81613ef7565b979a9699509497939692959294505050610180820135916101a0013590565b60006020828403121561368a578081fd5b5051919050565b6000602082840312156136a2578081fd5b8151610e5481613f0c565b60601b6001600160601b0319169052565b60008151808452815b818110156136e3576020818501810151868301820152016136c7565b818111156136f45782602083870101525b50601f01601f19169290920160200192915050565b6001600160f81b031960f88e811b821683528d811b821660018401528c901b1660028201526001600160801b031960808b901b1660038201526000613751601383018b6136ad565b61375e602783018a6136ad565b61376b603b8301896136ad565b86604f83015285606f83015284608f83015261378a60af8301856136ad565b5060c381019190915260e3019b9a5050505050505050505050565b6001600160f81b031960f88f811b821683528e811b821660018401528d901b1660028201526001600160801b031960808c901b1660038201526137eb601382018b6136ad565b6137f8602782018a6136ad565b613805603b8201896136ad565b86604f82015285606f82015284608f8201528360af82015261382a60cf8201846136ad565b60e3810191909152610103019c9b505050505050505050505050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b039586168152602081019490945260408401929092526060830152909116608082015260a00190565b6020808252601c908201527b2737ba1032bc32b1baba30b1363290333937b69037b716b1b430b4b760211b604082015260600190565b6020808252601d908201527f4e6f742065786563757461626c652066726f6d206f66662d636861696e000000604082015260600190565b6020808252601a9082015279135a5b881c5d5bdd19481c5d585b9d1a5d1e481b9bdd081b595d60321b604082015260600190565b602080825260199082015278135a5b8818985cd9481c5d585b9d1a5d1e481b9bdd081b595d603a1b604082015260600190565b602080825260119082015270496e76616c6964207369676e617475726560781b604082015260600190565b602080825260169082015275082e6e6cae840c2c8c8e4cae6e640dad2e6dac2e8c6d60531b604082015260600190565b6020808252600d908201526c125111560e8811561412549151609a1b604082015260600190565b602080825260119082015270105b1c9958591e481a5b9a5d1a585d1959607a1b604082015260600190565b6020808252601d908201527f506970207175616e74697479206f766572666c6f77732075696e743634000000604082015260600190565b60208082526027908201527f41737365742063616e6e6f742068617665206d6f7265207468616e20333220646040820152666563696d616c7360c81b606082015260800190565b6020808252601e908201527f5369676e617475726520686173682076657273696f6e20696e76616c69640000604082015260600190565b6020815260008251610100806020850152613b036101208501836136be565b915060018060401b0360208601511660408501526040850151613b3160608601826001600160401b03169052565b50606085015160808501526080850151601f198584030160a0860152613b5783826136be565b92505060a0850151613b7460c08601826001600160401b03169052565b5060c08501516001600160401b03811660e08601525060e0949094015192909301919091525090565b602080825282516001600160a01b03168282015282015160a06040830152600090613bcb60c08401826136be565b60408501516001600160401b03908116606086810191909152860151166080808601919091529094015160a0909301929092525090919050565b6040516101a081016001600160401b0381118282101715613c2857613c28613ee1565b60405290565b60405161018081016001600160401b0381118282101715613c2857613c28613ee1565b60008219821115613c6457613c64613eb5565b500190565b60006001600160401b03828116848216808303821115613c8b57613c8b613eb5565b01949350505050565b600082613ca357613ca3613ecb565b500490565b60006001600160401b0383811680613cc257613cc2613ecb565b92169190910492915050565b600181815b80851115613d09578160001904821115613cef57613cef613eb5565b80851615613cfc57918102915b93841c9390800290613cd3565b509250929050565b6000610e5460ff841683600082613d2a57506001610e57565b81613d3757506000610e57565b8160018114613d4d5760028114613d5757613d73565b6001915050610e57565b60ff841115613d6857613d68613eb5565b50506001821b610e57565b5060208310610133831016604e8410600b8410161715613d96575081810a610e57565b613da08383613cce565b8060001904821115613db457613db4613eb5565b029392505050565b6000816000190483118215151615613dd657613dd6613eb5565b500290565b60006001600160401b0382811684821681151582840482111615613e0157613e01613eb5565b02949350505050565b600082821015613e1c57613e1c613eb5565b500390565b60006001600160401b0383811690831681811015613e4157613e41613eb5565b039392505050565b600060ff821660ff841680821015613e6357613e63613eb5565b90039392505050565b600181811c90821680613e8057607f821691505b60208210811415612fed57634e487b7160e01b600052602260045260246000fd5b600082613eb057613eb0613ecb565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b038116811461270f57600080fd5b6001600160401b038116811461270f57600080fdfe546f6b656e20636f6e74726163742072657475726e6564207472616e73666572a264697066735822122018a573e81d7aede27430a13b0d89aa8e369aaa6de387214f77cf2d6c6e5ce7e064736f6c63430008040033
Deployed ByteCode Sourcemap
1250:14258:18:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6275:695;;;;;;;;;;-1:-1:-1;6275:695:18;;;;;:::i;:::-;;:::i;:::-;;1800:904;;;;;;;;;;-1:-1:-1;1800:904:18;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;9390:1947;;;;;;;;;;-1:-1:-1;9390:1947:18;;;;;:::i;:::-;;:::i;:::-;;;;-1:-1:-1;;;;;32081:15:26;;;32063:34;;32133:15;;;;32128:2;32113:18;;32106:43;31999:18;9390:1947:18;31981:174:26;5204:1067:18;;;;;;;;;;-1:-1:-1;5204:1067:18;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;1724:48::-;;1767:5;1724:48;;;;;-1:-1:-1;;;;;31805:31:26;;;31787:50;;31775:2;31760:18;1724:48:18;31742:101:26;2708:628:18;;;;;;;;;;-1:-1:-1;2708:628:18;;;;;:::i;:::-;;:::i;6275:695::-;6610:46;6665:118;6711:4;6725:7;6742:9;6761:14;6665:36;:118::i;:::-;6610:173;;6790:175;6827:7;6842:9;6859;6876;6893:22;6923:13;6944:15;6790:29;:175::i;:::-;6275:695;;;;;;;;;:::o;1800:904::-;2035:37;;:::i;:::-;2109:15;2088:8;:17;;;:36;;2080:62;;;;-1:-1:-1;;;2080:62:18;;;;;;;:::i;:::-;;;;;;;;;2149:12;2164:42;2197:8;2164:32;:42::i;:::-;2149:57;-1:-1:-1;2249:33:18;2227:18;;;;:12;;;;:18;;;;;;;;;;:55;;;;;-1:-1:-1;;;2227:55:18;;;;;;;;;;2212:103;;;;-1:-1:-1;;;2212:103:18;;;;;;;:::i;:::-;2321:18;;;;:12;;;:18;;;;;:51;;-1:-1:-1;;2321:51:18;2342:30;2321:51;;;2493:15;;;;2518;;;;2543;;;;2568:23;;;;2601;;;;2448:251;;2493:15;2518;2543;2568:23;2634:9;2653:13;2676:15;2448:35;:251::i;:::-;2435:264;1800:904;-1:-1:-1;;;;;;;1800:904:18:o;9390:1947::-;9620:36;9664:37;9716:26;9751:108;9794:4;9808:16;9834:17;9751:33;:108::i;:::-;9970:27;;;;9716:143;;-1:-1:-1;9866:28:18;;9903:103;;:15;;9942:10;;-1:-1:-1;;;;;9970:27:18;9903:29;:103::i;:::-;10120:48;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;10120:48:18;;;;;;;;;-1:-1:-1;;;10120:48:18;;;;;;;;;;;-1:-1:-1;;;10120:48:18;;;;;;;;;-1:-1:-1;;;10120:48:18;;;;;;;;;;;;;-1:-1:-1;;;;;10120:48:18;;;;;9866:140;;-1:-1:-1;10120:71:18;;9866:140;10120:48;:71::i;:::-;10335:22;;10054:137;;-1:-1:-1;10054:137:18;-1:-1:-1;10197:43:18;;10249:116;;10054:137;;-1:-1:-1;;;10335:22:18;;;;10249:37;:116::i;:::-;10511:23;;10197:168;;-1:-1:-1;10371:44:18;;10424:118;;10471:30;;-1:-1:-1;;;10511:23:18;;;;10424:37;:118::i;:::-;10576:60;;10371:171;;-1:-1:-1;10607:29:18;;10576:60;;:27;;:60;;10607:29;;10576:60;;;-1:-1:-1;;;;;10576:60:18;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;10576:60:18;;;;;-1:-1:-1;;;;;10576:60:18;;;;;;10674:30;10642:4;:28;;;:62;;;;;;;;;;-1:-1:-1;;;;;10642:62:18;;;;;:::i;:::-;;;-1:-1:-1;;;;;10642:62:18;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;10745:27:18;;;-1:-1:-1;;;;;10745:27:18;:32;10785:10;10803:126;10850:21;391:2:5;10803:37:18;:126::i;:::-;10937:35;10980:36;11024:10;10745:295;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;11088:118:18;;-1:-1:-1;;;11088:118:18;;-1:-1:-1;;;;;11088:18:18;;;-1:-1:-1;11088:18:18;;-1:-1:-1;11088:118:18;;11122:10;;11141:16;;11165:35;;11088:118;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;11212:120:18;;-1:-1:-1;;;11212:120:18;;-1:-1:-1;;;;;11212:18:18;;;-1:-1:-1;11212:18:18;;-1:-1:-1;11212:120:18;;11246:10;;11265:17;;11290:36;;11212:120;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9390:1947;;;;;;;;;;;;:::o;5204:1067::-;5440:36;;:::i;:::-;5512:15;5492:7;:16;;;:35;;5484:61;;;;-1:-1:-1;;;5484:61:18;;;;;;;:::i;:::-;5552:12;5567:40;5599:7;5567:31;:40::i;:::-;5552:55;-1:-1:-1;5650:33:18;5628:18;;;;:12;;;;:18;;;;;;;;;;:55;;;;;-1:-1:-1;;;5628:55:18;;;;;;;;;;5613:103;;;;-1:-1:-1;;;5613:103:18;;;;;;;:::i;:::-;5722:18;;;;:12;;;:18;;;;;:51;;-1:-1:-1;;5722:51:18;5743:30;5722:51;;;5938:14;;;;5964;;;;5868:120;;5722:12;;5868:42;:120::i;:::-;5812:184;;6075:191;6118:7;:14;;;6142:22;6174:7;:17;;;6201:9;6220:13;6243:15;6075:33;:191::i;:::-;6062:204;5204:1067;-1:-1:-1;;;;;;;;5204:1067:18:o;2708:628::-;2974:46;3029:64;3067:4;3073:8;3083:9;3029:37;:64::i;:::-;2974:119;-1:-1:-1;3184:147:18;:15;3229:8;3245:9;3262;3279:16;2974:119;3184:37;:147::i;:::-;;2708:628;;;;;;;:::o;6974:2388::-;7176:46;7650:14;7645:716;;7676:12;7691:40;7723:7;7691:31;:40::i;:::-;7741:26;7770:18;;;:12;;;:18;;;;;;7676:55;;-1:-1:-1;7770:18:18;;;;;7803:7;:19;;;:57;;;;;;-1:-1:-1;;;7803:57:18;;;;;;;;;;7799:494;;;7904:30;7895:5;:39;;;;;;-1:-1:-1;;;7895:39:18;;;;;;;;;;7874:116;;;;-1:-1:-1;;;7874:116:18;;;;;;;:::i;:::-;7799:494;;;8049:33;8040:5;:42;;;;;;-1:-1:-1;;;8040:42:18;;;;;;;;;;8019:120;;;;-1:-1:-1;;;8019:120:18;;;;;;;:::i;:::-;8172:65;8197:4;8203:7;:17;;;8222:7;:14;;;8172:24;:65::i;:::-;8151:131;;;;-1:-1:-1;;;8151:131:18;;;;;;;:::i;:::-;-1:-1:-1;8302:18:18;;;;8323:29;8302:12;;;:18;;;;;;:50;;-1:-1:-1;;8302:50:18;;;;;;7645:716;8373:26;8408:128;8451:4;8465:9;:26;;;8501:9;:27;;;8408:33;:128::i;:::-;8567:27;;;;8601:110;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;8567:27:18;8601:110;;;;;;;;-1:-1:-1;;;8601:110:18;;;;;;;;;;;-1:-1:-1;;;8601:110:18;;;;;;;;;-1:-1:-1;;;8601:110:18;;;;;;;;;-1:-1:-1;;;;;8567:27:18;;;8601:110;;;;;;8567:27;-1:-1:-1;8373:163:18;;-1:-1:-1;8601:110:18;;8669:7;;8684:9;;8601:60;:110::i;:::-;8776:33;;;;8745:64;;;;:27;;:64;;8776:33;;8745:64;;;-1:-1:-1;;;;;8745:64:18;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;8745:64:18;;;;;-1:-1:-1;;;;;8745:64:18;;;;;;8847:9;:34;;;8815:4;:28;;;:66;;;;;;;;;;-1:-1:-1;;;;;8815:66:18;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;8815:66:18;;;;;-1:-1:-1;;;;;8815:66:18;;;;;;8888:22;-1:-1:-1;;;;;8888:27:18;;8923:7;:14;;;8945:130;8992:9;:25;;;391:2:5;8945:37:18;:130::i;:::-;9130:33;;;;9173:22;;9083:120;;9130:33;-1:-1:-1;;;9173:22:18;;;;9083:37;:120::i;:::-;9258:34;;;;9302:23;;9211:122;;9258:34;-1:-1:-1;;;9302:23:18;;;;9211:37;:122::i;:::-;9341:7;:10;;;8888:469;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6974:2388;;;;;;;:::o;3566:1316:25:-;3907:36;;4003:163;:15;4053:7;4070:9;4089;4116;4136:22;4003:40;:163::i;:::-;3899:267;;;;4173:18;;:::i;:::-;-1:-1:-1;;;;;4201:33:25;;;4197:336;;4285:26;;4252:60;;:13;;:32;:60::i;:::-;4244:68;;4320:9;-1:-1:-1;;;;;4320:18:25;;4348:7;:10;;;4368:9;:26;;;4404:114;4453:29;4494:5;:14;;;4404:37;:114::i;:::-;4320:206;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4197:336;-1:-1:-1;;;;;4542:34:25;;;4538:340;;4627:27;;;;4594:61;;:13;;:32;:61::i;:::-;4586:69;;4663:9;-1:-1:-1;;;;;4663:18:25;;4691:7;:10;;;4711:9;:27;;;4748:115;4797:30;4839:5;:14;;;4748:37;:115::i;:::-;4663:208;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4538:340;3566:1316;;;;;;;;;;:::o;671:741:11:-;807:29;;775:7;;807:63;;1340:1:5;807:63:11;792:124;;;;-1:-1:-1;;;792:124:11;;;;;;;:::i;:::-;983:29;;;1077:8;:20;;;1071:27;;;;;;-1:-1:-1;;;1071:27:11;;;;;;;;;1110:14;;;;;1136:15;;;;1163;;;;1190;;;;1217:23;;;;1252;;;;1287:19;;;;1318;;;;1349:11;;;;1372:17;;;;955:444;;;;;;;1372:17;955:444;;;:::i;:::-;;;;;;;;;;;;;936:471;;;;;;923:484;;671:741;;;:::o;1167:1099:7:-;1481:44;;:::i;:::-;1533:18;;:::i;:::-;1566:40;:13;1599:6;1566:32;:40::i;:::-;1634:12;;;;1612:34;;1558:48;-1:-1:-1;1792:114:7;1812:6;1558:48;1839:21;1868:9;1885:15;1792:12;:114::i;:::-;1740:43;;;1652:254;-1:-1:-1;;;;;1652:254:7;;;1695:37;;;1652:254;;1660:27;;;1652:254;1921:40;:13;1954:6;1921:32;:40::i;:::-;1989:12;;;;1967:19;;;:34;1913:48;-1:-1:-1;2147:114:7;2167:6;1913:48;2194:21;2223:9;2240:15;2147:12;:114::i;:::-;2095:43;;;2007:254;-1:-1:-1;;;;;2007:254:7;;;2050:37;;;2007:254;;2015:27;;;2007:254;-1:-1:-1;2015:6:7;1167:1099;-1:-1:-1;;;;;;;;1167:1099:7:o;13703:315:18:-;-1:-1:-1;;;;;13901:39:18;;;13860:26;13901:39;;;:21;;;:39;;;;;;;;:58;;;;;;;;;13973:11;;;;13965:48;;;;-1:-1:-1;;;13965:48:18;;20361:2:26;13965:48:18;;;20343:21:26;20400:2;20380:18;;;20373:30;-1:-1:-1;;;20419:18:26;;;20412:54;20483:18;;13965:48:18;20333:174:26;13965:48:18;13703:315;;;;;:::o;5840:427:4:-;5957:36;6001:23;6041:57;6071:4;6077:6;6085:12;6041:29;:57::i;:::-;6136:21;;;;;-1:-1:-1;;;;;6136:21:4;;-1:-1:-1;6136:21:4;-1:-1:-1;6136:21:4;6164:66;;;;-1:-1:-1;;;6164:66:4;;29500:2:26;6164:66:4;;;29482:21:26;29539:2;29519:18;;;29512:30;-1:-1:-1;;;29558:18:26;;;29551:50;29618:18;;6164:66:4;29472:170:26;6164:66:4;6237:25;;-1:-1:-1;;;;;;6237:25:4;;;5840:427;;-1:-1:-1;;;5840:427:4:o;802:1095:17:-;959:36;1003:37;1055:27;1091:163;1153:4;:27;;;-1:-1:-1;;;;;1138:56:17;;:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;391:2:5;1091:37:17;:163::i;:::-;1055:199;;1382:125;1417:4;:27;;;1452:21;1481:20;1382:27;:125::i;:::-;1350:157;;1689:203;1756:29;1726:4;:27;;;:59;;;;:::i;:::-;1795:37;1827:4;1795:31;:37::i;:::-;1285:5:5;1880:4:17;1689:27;:203::i;:::-;1652:4;:28;;;:240;;;;:::i;:::-;1613:279;;802:1095;;;;;;:::o;196:470:3:-;301:7;343:2;326:13;:19;;;;318:71;;;;-1:-1:-1;;;318:71:3;;;;;;;:::i;:::-;504:1;488:13;:17;;;484:105;;;563:17;579:1;563:13;:17;:::i;:::-;549:32;;557:2;549:32;:::i;:::-;522:60;;-1:-1:-1;;;;;522:23:3;;:60;:::i;:::-;515:67;;;;484:105;642:17;646:13;642:1;:17;:::i;:::-;628:32;;636:2;628:32;:::i;:::-;601:60;;-1:-1:-1;;;;;601:23:3;;:60;:::i;:::-;594:67;;196:470;;;;;:::o;1416:685:11:-;1549:28;;1517:7;;1549:62;;1340:1:5;1549:62:11;1534:123;;;;-1:-1:-1;;;1534:123:11;;;;;;;:::i;:::-;1724:28;;1770:27;1816:7;:19;;;1810:26;;;;;;-1:-1:-1;;;1810:26:11;;;;;;;;;1848:13;;;;;1873:14;;;;1899;;;;1925;;;;1951:17;;;;1980:18;;;;2010;;;;2040:10;;;;2062:16;;;;1696:392;;;;;;;2062:16;1696:392;;;:::i;14022:405:18:-;-1:-1:-1;;;;;14245:45:18;;;14166:46;14245:45;;;;;;;;;;;:65;;;;;;;;;;;;14331:47;14316:106;;;;-1:-1:-1;;;14316:106:18;;26833:2:26;14316:106:18;;;26815:21:26;26872:2;26852:18;;;26845:30;-1:-1:-1;;;26891:18:26;;;26884:58;26959:18;;14316:106:18;26805:178:26;2270:757:7;2542:43;;:::i;:::-;2593:18;2620:56;:13;2653:22;2620:32;:56::i;:::-;2703:12;;;;2682:18;;;:33;-1:-1:-1;;;;;2721:44:7;;;;2593:83;-1:-1:-1;2909:113:7;2929:6;2593:83;2956:20;2984:9;3001:15;2909:12;:113::i;:::-;2858:42;;;2772:250;-1:-1:-1;;;;;2772:250:7;;;2814:36;;;2772:250;;2780:26;;;2772:250;-1:-1:-1;2780:6:7;2270:757;-1:-1:-1;;;;;;2270:757:7:o;3340:1834:18:-;3520:46;3582:12;3597:42;3630:8;3597:32;:42::i;:::-;3647:26;3676:18;;;:12;;;:18;;;;;;3582:57;;-1:-1:-1;3676:18:18;;;;;3707:8;:20;;;:58;;;;;;-1:-1:-1;;;3707:58:18;;;;;;;;;;3703:469;;;3805:30;3796:5;:39;;;;;;-1:-1:-1;;;3796:39:18;;;;;;;;;;3777:110;;;;-1:-1:-1;;;3777:110:18;;;;;;;:::i;:::-;3703:469;;;3940:33;3931:5;:42;;;;;;-1:-1:-1;;;3931:42:18;;;;;;;;;;3912:114;;;;-1:-1:-1;;;3912:114:18;;;;;;;:::i;:::-;4055:67;4080:4;4086:8;:18;;;4106:8;:15;;;4055:24;:67::i;:::-;4036:127;;;;-1:-1:-1;;;4036:127:18;;;;;;;:::i;:::-;-1:-1:-1;4179:18:18;;;;4200:29;4179:12;;;:18;;;;;;;:50;;-1:-1:-1;;4179:50:18;;;;;;;4334:26;;4370:27;;;;4277:128;;4179:12;;4277:33;:128::i;:::-;4436:27;;;;4470:112;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;4436:27:18;4470:112;;;;;;;;-1:-1:-1;;;4470:112:18;;;;;;;;;;;-1:-1:-1;;;4470:112:18;;;;;;;;;-1:-1:-1;;;4470:112:18;;;;;;;;;-1:-1:-1;;;;;4436:27:18;;;4470:112;;;;;;4436:27;-1:-1:-1;4242:163:18;;-1:-1:-1;4470:112:18;;4539:8;;4555:9;;4470:61;:112::i;:::-;4589:62;4635:4;4641:9;4589:45;:62::i;:::-;4702:22;-1:-1:-1;;;;;4702:27:18;;4737:8;:15;;;4760:130;4807:9;:25;;;391:2:5;4760:37:18;:130::i;:::-;4945:31;;;;4986:22;;4898:118;;4945:31;-1:-1:-1;;;4986:22:18;;;;4898:37;:118::i;:::-;5071:32;;;;5113:23;;5024:120;;5071:32;-1:-1:-1;;;5113:23:18;;;;5024:37;:120::i;:::-;5152:8;:11;;;4702:467;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3340:1834;;;;;;:::o;6296:1659:4:-;6566:28;6626:23;6658:112;6697:4;6711:8;:15;;;6736:9;:26;;;6658:29;:112::i;:::-;6801:33;;;;6776:58;;;;-1:-1:-1;6801:33:4;6776:58;;:21;;:58;;6801:33;;6776:58;;;-1:-1:-1;;;;;6776:58:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;6776:58:4;;;;;-1:-1:-1;;;;;6776:58:4;;;;;;6874:98;6911:4;6923:9;6940;:26;;;6874:29;:98::i;:::-;6864:108;;7051:9;:31;;;7009:9;:33;;;:73;;;;:::i;:::-;6978:104;;;;:21;;:104;;;;;;;-1:-1:-1;;;;;6978:104:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;6978:104:4;;;;;-1:-1:-1;;;;;6978:104:4;;;;;;7125:105;7162:4;7174:8;:15;;;7197:9;:27;;;7125:29;:105::i;:::-;7261:34;;;;7236:59;;;;-1:-1:-1;7261:34:4;7236:59;;:21;;:59;;7261:34;;7236:59;;;-1:-1:-1;;;;;7236:59:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;7236:59:4;;;;;-1:-1:-1;;;;;7236:59:4;;;;;;7336:99;7373:4;7385:9;7402;:27;;;7336:29;:99::i;:::-;7326:109;;7515:9;:32;;;7472:9;:34;;;:75;;;;:::i;:::-;7441:106;;;;:21;;:106;;;;;;;-1:-1:-1;;;;;7441:106:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;7441:106:4;;;;;-1:-1:-1;;;;;7441:106:4;;;;;;7663:16;-1:-1:-1;;;;;7648:31:4;:8;:11;;;-1:-1:-1;;;;;7648:31:4;;7644:307;;;7699:117;7738:4;7752:8;:15;;;7785:22;7699:29;:117::i;:::-;7849:25;;;;7824:50;;;;-1:-1:-1;7849:25:4;7824:50;;:21;;:50;;7849:25;;7824:50;;;-1:-1:-1;;;;;7824:50:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;7824:50:4;;;;;-1:-1:-1;;;;;7824:50:4;;;;;;7644:307;;;7919:9;:25;;;7895:49;;7644:307;6296:1659;;;;;;;;;:::o;421:219:11:-;540:4;629:6;-1:-1:-1;;;;;565:70:11;:60;579:34;608:4;579:28;:34::i;:::-;615:9;565:13;:60::i;:::-;-1:-1:-1;;;;;565:70:11;;;421:219;-1:-1:-1;;;;421:219:11:o;3058:2065:16:-;3278:7;:14;;;-1:-1:-1;;;;;3248:44:16;:9;:26;;;-1:-1:-1;;;;;3248:44:16;;:101;;;;;3335:7;:14;;;-1:-1:-1;;;;;3304:45:16;:9;:27;;;-1:-1:-1;;;;;3304:45:16;;3248:101;3247:220;;;;3393:7;:14;;;-1:-1:-1;;;;;3363:44:16;:9;:26;;;-1:-1:-1;;;;;3363:44:16;;:103;;;;;3452:7;:14;;;-1:-1:-1;;;;;3421:45:16;:9;:27;;;-1:-1:-1;;;;;3421:45:16;;3363:103;3231:275;;;;-1:-1:-1;;;3231:275:16;;;;;;;:::i;:::-;3564:1;3528:9;:33;;;-1:-1:-1;;;;;3528:37:16;;:87;;;;;3614:1;3577:9;:34;;;-1:-1:-1;;;;;3577:38:16;;3528:87;3513:150;;;;-1:-1:-1;;;3513:150:16;;28305:2:26;3513:150:16;;;28287:21:26;;;28324:18;;;28317:30;28383:34;28363:18;;;28356:62;28435:18;;3513:150:16;28277:182:26;3513:150:16;3671:27;3700:28;3768:7;:14;;;-1:-1:-1;;;;;3738:44:16;:9;:26;;;-1:-1:-1;;;;;3738:44:16;;:146;;3845:7;:18;;;3865:7;:18;;;3738:146;;;3794:7;:18;;;3814:7;:18;;;3738:146;3670:214;;;;3891:14;3907:15;3942:112;3991:19;4022:4;:22;;;3942:37;:112::i;:::-;4064:114;4113:20;4145:4;:23;;;4064:37;:114::i;:::-;3890:296;;;;4245:7;-1:-1:-1;;;;;4208:44:16;:9;:33;;;-1:-1:-1;;;;;4208:44:16;;;4193:100;;;;-1:-1:-1;;;4193:100:16;;;;;;;:::i;:::-;4352:8;-1:-1:-1;;;;;4314:46:16;:9;:34;;;-1:-1:-1;;;;;4314:46:16;;;4299:103;;;;-1:-1:-1;;;4299:103:16;;;;;;;:::i;:::-;4461:128;4510:7;:17;;;391:2:5;4461:37:16;:128::i;:::-;-1:-1:-1;;;;;4424:165:16;:9;:25;;;-1:-1:-1;;;;;4424:165:16;;4409:220;;;;-1:-1:-1;;;4409:220:16;;29147:2:26;4409:220:16;;;29129:21:26;29186:2;29166:18;;;29159:30;-1:-1:-1;;;29205:18:26;;;29198:54;29269:18;;4409:220:16;29119:174:26;4409:220:16;4644:39;4691:40;4740:68;4782:9;:25;;;4740:4;:41;;:68;;;;:::i;:::-;-1:-1:-1;;;;;4636:172:16;;;-1:-1:-1;;;;;4636:172:16;;;4867:31;4830:9;:33;;;-1:-1:-1;;;;;4830:68:16;;4815:120;;;;-1:-1:-1;;;4815:120:16;;22473:2:26;4815:120:16;;;22455:21:26;22512:2;22492:18;;;22485:30;-1:-1:-1;;;22531:18:26;;;22524:51;22592:18;;4815:120:16;22445:171:26;4815:120:16;4994:32;4956:9;:34;;;-1:-1:-1;;;;;4956:70:16;;4941:123;;;;-1:-1:-1;;;4941:123:16;;22122:2:26;4941:123:16;;;22104:21:26;22161:2;22141:18;;;22134:30;-1:-1:-1;;;22180:18:26;;;22173:52;22242:18;;4941:123:16;22094:172:26;4941:123:16;5071:47;5108:9;5071:36;:47::i;7959:2186:4:-;8245:36;8289:37;8341:23;8515:16;-1:-1:-1;;;;;8501:30:4;:7;:10;;;-1:-1:-1;;;;;8501:30:4;;8497:365;;;8580:119;8621:4;8637:7;:14;;;8663:9;:26;;;8580:29;:119::i;:::-;8734:31;;;;8709:56;;;;-1:-1:-1;8734:31:4;8709:56;;:21;;:56;;8734:31;;8709:56;;;-1:-1:-1;;;;;8709:56:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;8709:56:4;;;;;-1:-1:-1;;;;;8709:56:4;;;;;;8497:365;;;8822:9;:31;;;8790:63;;8497:365;8905:106;8944:4;8958:9;8977;:26;;;8905:29;:106::i;:::-;8895:116;;9096:9;:31;;;9052:9;:33;;;:75;;;;:::i;:::-;9019:108;;;;:21;;:108;;;;;;;-1:-1:-1;;;;;9019:108:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;9019:108:4;;;;;-1:-1:-1;;;;;9019:108:4;;;;;;9285:16;-1:-1:-1;;;;;9271:30:4;:7;:10;;;-1:-1:-1;;;;;9271:30:4;;9267:370;;;9351:120;9392:4;9408:7;:14;;;9434:9;:27;;;9351:29;:120::i;:::-;9506:32;;;;9481:57;;;;-1:-1:-1;9506:32:4;9481:57;;:21;;:57;;9506:32;;9481:57;;;-1:-1:-1;;;;;9481:57:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;9481:57:4;;;;;-1:-1:-1;;;;;9481:57:4;;;;;;9267:370;;;9596:9;:32;;;9563:65;;9267:370;9681:107;9720:4;9734:9;9753;:27;;;9681:29;:107::i;:::-;9671:117;;9874:9;:32;;;9829:9;:34;;;:77;;;;:::i;:::-;9796:110;;;;:21;;:110;;;;;;;-1:-1:-1;;;;;9796:110:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;9796:110:4;;;;;-1:-1:-1;;;;;9796:110:4;;;;;;9960:116;9999:4;10013:7;:14;;;10045:22;9960:29;:116::i;:::-;10109:25;;;;10084:50;;;;-1:-1:-1;10109:25:4;10084:50;;:21;;:50;;10109:25;;10084:50;;;-1:-1:-1;;;;;10084:50:4;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;10084:50:4;;;;;-1:-1:-1;;;;;10084:50:4;;;;;;7959:2186;;;;;;;;;;:::o;6291:411:1:-;6398:12;;:::i;:::-;-1:-1:-1;;;;;6424:28:1;;6420:91;;6469:35;6481:4;:22;;6469:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;:35::i;6420:91::-;-1:-1:-1;;;;;6538:34:1;;;6517:18;6538:34;;;;;;;;;;;6517:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:18;;:55;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;6517:55:1;;;-1:-1:-1;;6517:55:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;6517:55:1;;;;;;6593:12;;;;-1:-1:-1;6593:33:1;;;;;6609:5;:17;;;6593:33;6578:100;;;;-1:-1:-1;;;6578:100:1;;26428:2:26;6578:100:1;;;26410:21:26;26467:2;26447:18;;;26440:30;26506:34;26486:18;;;26479:62;-1:-1:-1;;;26557:18:26;;;26550:34;26601:19;;6578:100:1;26400:226:26;3031:2009:7;3247:21;3276:31;3315:38;3385:93;3430:20;3458:5;:14;;;3385:37;:93::i;:::-;3368:110;;3509:1;3492:14;-1:-1:-1;;;;;3492:18:7;;3484:50;;;;-1:-1:-1;;;3484:50:7;;21774:2:26;3484:50:7;;;21756:21:26;21813:2;21793:18;;;21786:30;-1:-1:-1;;;21832:18:26;;;21825:49;21891:18;;3484:50:7;21746:169:26;3484:50:7;3901:49;3959:69;3997:14;4013:5;:14;;;3959:37;:69::i;:::-;4083:18;;;;3901:127;;-1:-1:-1;;;;;;4083:34:7;4079:632;;4226:149;4277:9;4298:5;:18;;;4326:41;4226:25;:149::i;:::-;4079:632;;;4529:175;4566:6;4589:5;:18;;;4634:9;4655:41;4529:27;:175::i;:::-;4853:18;;;;4799:100;;:15;;4839:6;;4879:14;4799:32;:100::i;:::-;4772:127;;4938:97;4983:24;5015:5;:14;;;4938:37;:97::i;:::-;4905:130;;3031:2009;;;;;;;;;;:::o;10166:510:4:-;-1:-1:-1;;;;;10353:38:4;;;10298:15;10353:38;;;;;;;;;;;:52;;;;;;;;;;;10417:18;;;;10416:19;:68;;;;-1:-1:-1;10447:20:4;;;;-1:-1:-1;;;;;10447:20:4;10439:45;;10416:68;10412:239;;;10518:20;;;;:93;;-1:-1:-1;;;10518:93:4;;-1:-1:-1;;;;;15260:15:26;;;10518:93:4;;;15242:34:26;15312:15;;;15292:18;;;15285:43;10518:20:4;;;;:47;;15177:18:26;;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;10166:510;-1:-1:-1;;;;10166:510:4:o;670:671:3:-;782:6;823:2;806:13;:19;;;;798:71;;;;-1:-1:-1;;;798:71:3;;;;;;;:::i;:::-;876:22;1012:1;996:13;:17;;;992:239;;;1094:17;1110:1;1094:13;:17;:::i;:::-;1080:32;;1088:2;1080:32;:::i;:::-;1048:65;;:20;:65;:::i;:::-;1023:90;;992:239;;;1205:17;1209:13;1205:1;:17;:::i;:::-;1191:32;;1199:2;1191:32;:::i;:::-;1159:65;;:20;:65;:::i;:::-;1134:90;;992:239;-1:-1:-1;;;1244:14:3;:22;1236:64;;;;-1:-1:-1;;;1236:64:3;;;;;;;:::i;84:291:20:-;225:6;252:118;284:12;306:16;332:15;357:5;252:22;:118::i;338:352:17:-;441:6;461:4;:27;;;-1:-1:-1;;;;;461:32:17;492:1;461:32;457:61;;;-1:-1:-1;510:1:17;;338:352;-1:-1:-1;338:352:17:o;457:61::-;537:148;1285:5:5;612:4:17;:28;;;650:4;:27;;;537;:148::i;379:454:20:-;538:6;;571:40;-1:-1:-1;;;;;571:40:20;;;;:21;;:40;:::i;:::-;552:59;-1:-1:-1;617:14:20;634:26;-1:-1:-1;;;;;634:26:20;;552:59;634:26;:::i;:::-;617:43;;670:7;:41;;;;-1:-1:-1;710:1:20;681:26;-1:-1:-1;;;;;681:26:20;;:8;:26;:::i;:::-;:30;670:41;666:73;;;721:11;731:1;721:11;;:::i;:::-;;;666:73;-1:-1:-1;;;752:6:20;:14;744:56;;;;-1:-1:-1;;;744:56:20;;;;;;;:::i;:::-;821:6;379:454;-1:-1:-1;;;;;;379:454:20:o;715:2339:16:-;938:8;:15;;;-1:-1:-1;;;;;908:45:16;:9;:26;;;-1:-1:-1;;;;;908:45:16;;:103;;;;;996:8;:15;;;-1:-1:-1;;;;;965:46:16;:9;:27;;;-1:-1:-1;;;;;965:46:16;;908:103;907:224;;;;1055:8;:15;;;-1:-1:-1;;;;;1025:45:16;:9;:26;;;-1:-1:-1;;;;;1025:45:16;;:105;;;;;1115:8;:15;;;-1:-1:-1;;;;;1084:46:16;:9;:27;;;-1:-1:-1;;;;;1084:46:16;;1025:105;891:279;;;;-1:-1:-1;;;891:279:16;;;;;;;:::i;:::-;1185:27;1220:31;1259:28;1295:32;1372:8;:15;;;-1:-1:-1;;;;;1342:45:16;:9;:26;;;-1:-1:-1;;;;;1342:45:16;;:351;;1563:8;:19;;;1594:8;:23;;;1629:8;:19;;;1660:8;:23;;;1342:351;;;1410:8;:19;;;1441:8;:23;;;1476:8;:19;;;1507:8;:23;;;1342:351;1177:516;;;;;;;;1700:14;1716;1732:15;1749;1784:112;1833:19;1864:4;:22;;;1784:37;:112::i;:::-;1906:116;1955:23;1990:4;:22;;;1906:37;:116::i;:::-;2032:114;2081:20;2113:4;:23;;;2032:37;:114::i;:::-;2156:118;2205:24;2241:4;:23;;;2156:37;:118::i;:::-;1699:583;;;;;;;;2341:7;-1:-1:-1;;;;;2304:44:16;:9;:33;;;-1:-1:-1;;;;;2304:44:16;;;2289:100;;;;-1:-1:-1;;;2289:100:16;;;;;;;:::i;:::-;2447:7;-1:-1:-1;;;;;2410:44:16;:9;:33;;;-1:-1:-1;;;;;2410:44:16;;;2395:105;;;;-1:-1:-1;;;2395:105:16;;21415:2:26;2395:105:16;;;21397:21:26;21454:2;21434:18;;;21427:30;21493:32;21473:18;;;21466:60;21543:18;;2395:105:16;21387:180:26;2395:105:16;2559:8;-1:-1:-1;;;;;2521:46:16;:9;:34;;;-1:-1:-1;;;;;2521:46:16;;;2506:103;;;;-1:-1:-1;;;2506:103:16;;;;;;;:::i;:::-;2668:8;-1:-1:-1;;;;;2630:46:16;:9;:34;;;-1:-1:-1;;;;;2630:46:16;;;2615:108;;;;-1:-1:-1;;;2615:108:16;;18086:2:26;2615:108:16;;;18068:21:26;18125:2;18105:18;;;18098:30;18164:33;18144:18;;;18137:61;18215:18;;2615:108:16;18058:181:26;2615:108:16;2773:1;2745:9;:25;;;-1:-1:-1;;;;;2745:29:16;;:210;;;;-1:-1:-1;2870:31:16;;;;2913:32;;;;2823:132;;:4;;:35;:132::i;:::-;-1:-1:-1;;;;;2786:169:16;:9;:25;;;-1:-1:-1;;;;;2786:169:16;;2745:210;2730:265;;;;-1:-1:-1;;;2730:265:16;;26075:2:26;2730:265:16;;;26057:21:26;26114:2;26094:18;;;26087:30;-1:-1:-1;;;26133:18:26;;;26126:54;26197:18;;2730:265:16;26047:174:26;2730:265:16;3002:47;3039:9;3002:36;:47::i;:::-;715:2339;;;;;;;;;;;:::o;14431:1075:18:-;14605:36;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;14605:36:18;;;;;;;;;-1:-1:-1;;;14605:36:18;;;;;;;;;;;-1:-1:-1;;;14605:36:18;;;;;;;;;-1:-1:-1;;;14605:36:18;;;;;;;;;;;;;-1:-1:-1;;;;;14605:36:18;;;;;14583:19;;14605:38;;:36;:38::i;:::-;14709:31;;;;14678:62;;14583:60;;-1:-1:-1;14709:31:18;14678:62;;:27;;:62;;14709:31;;14678:62;;;-1:-1:-1;;;;;14678:62:18;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;14678:62:18;;;;;-1:-1:-1;;;;;14678:62:18;;;;;;14778:9;:32;;;14746:4;:28;;;:64;;;;;;;;;;-1:-1:-1;;;;;14746:64:18;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;14746:64:18;;;;;-1:-1:-1;;;;;14746:64:18;;;;;;15009:12;-1:-1:-1;;;;;15009:17:18;15025:1;15009:17;15005:497;;;15130:42;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;15130:42:18;;;;;;;;;-1:-1:-1;;;15130:42:18;;;;;;;;;;;-1:-1:-1;;;15130:42:18;;;;;;;;;-1:-1:-1;;;15130:42:18;;;;;;;;;;;;;-1:-1:-1;;;;;15130:42:18;;;;;;;:36;:42::i;:::-;14431:1075;;;:::o;15005:497::-;15196:27;;883:5:5;15196:27:18;;;;-1:-1:-1;;;;;15196:27:18;:70;;;;:151;;-1:-1:-1;15276:28:18;;883:5:5;-1:-1:-1;;;15276:28:18;;;-1:-1:-1;;;;;15276:28:18;:71;;15196:151;15185:317;;;15384:36;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;15384:36:18;;;;;;;;;-1:-1:-1;;;15384:36:18;;;;;;;;;;;-1:-1:-1;;;15384:36:18;;;;;;;;;-1:-1:-1;;;15384:36:18;;;;;;;;;;;;;-1:-1:-1;;;;;15384:36:18;;;;;15362:19;;15384:38;;:36;:38::i;:::-;15362:60;;15454:12;-1:-1:-1;;;;;15438:28:18;:12;-1:-1:-1;;;;;15438:28:18;;15430:65;;;;-1:-1:-1;;;15430:65:18;;17733:2:26;15430:65:18;;;17715:21:26;17772:2;17752:18;;;17745:30;-1:-1:-1;;;17791:18:26;;;17784:54;17855:18;;15430:65:18;17705:174:26;15430:65:18;15185:317;14431:1075;;;:::o;4360:265:8:-;4559:58;;-1:-1:-1;;;4559:58:8;;;11499:80:26;11595:12;;;11588:28;;;4429:7:8;;11632:12:26;;4559:58:8;11489:161:26;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;;20001:2:26;2425:41:8;;;19983:21:26;20040:2;20020:18;;;20013:30;20079:33;20059:18;;;20052:61;20130:18;;2425:41:8;19973:181:26;1895:582:8;2494:22;2502:4;2508:1;2511;2514;2494:7;:22::i;5127:568:16:-;5258:170;5298:42;:9;:40;:42::i;:::-;5350:9;:33;;;553:8:5;5258:30:16;:170::i;:::-;5243:219;;;;-1:-1:-1;;;5243:219:16;;21068:2:26;5243:219:16;;;21050:21:26;21107:2;21087:18;;;21080:30;-1:-1:-1;;;21126:18:26;;;21119:48;21184:18;;5243:219:16;21040:168:26;5243:219:16;5483:172;5523:43;:9;:41;:43::i;:::-;5576:9;:34;;;553:8:5;5483:30:16;:172::i;:::-;5468:222;;;;-1:-1:-1;;;5468:222:16;;22823:2:26;5468:222:16;;;22805:21:26;22862:2;22842:18;;;22835:30;-1:-1:-1;;;22881:18:26;;;22874:49;22940:18;;5468:222:16;22795:169:26;5468:222:16;5127:568;:::o;8061:179:1:-;8149:12;;:::i;:::-;-1:-1:-1;8178:57:1;;;;;;;;8184:4;8178:57;;;-1:-1:-1;8178:57:1;;;;;;;;;;;;;8223:2;8178:57;;;;;;;;;;;;;;;;8061:179::o;1326:782:2:-;-1:-1:-1;;;;;1460:21:2;;1456:648;;1508:43;;-1:-1:-1;;;;;1508:21:2;;;:43;;;;;1530:20;;1508:43;;;;1530:20;1508:21;:43;;;;;;;1491:99;;;;-1:-1:-1;;;1491:99:2;;27190:2:26;1491:99:2;;;27172:21:26;27229:2;27209:18;;;27202:30;-1:-1:-1;;;27248:18:26;;;27241:49;27307:18;;1491:99:2;27162:169:26;1456:648:2;1635:41;;-1:-1:-1;;;1635:41:2;;1611:21;;-1:-1:-1;;;;;1635:23:2;;;;;:41;;1659:16;;1635:41;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1793:62;;-1:-1:-1;;;1793:62:2;;-1:-1:-1;;;;;14943:32:26;;;1793:62:2;;;14925:51:26;14992:18;;;14985:34;;;1611:65:2;;-1:-1:-1;1793:22:2;;;;;;14898:18:26;;1793:62:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1887:41:2;;-1:-1:-1;;;1887:41:2;;1864:20;;-1:-1:-1;;;;;;1887:23:2;;;-1:-1:-1;1887:23:2;;:41;;1911:16;;1887:41;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1864:64;-1:-1:-1;1985:20:2;1953:28;1968:13;1864:64;1953:28;:::i;:::-;:52;1936:161;;;;-1:-1:-1;;;1936:161:2;;28666:2:26;1936:161:2;;;28648:21:26;28705:2;28685:18;;;28678:30;-1:-1:-1;;;;;;;;;;;28724:18:26;;;28717:62;28815:34;28795:18;;;28788:62;-1:-1:-1;;;28866:19:26;;;28859:39;28915:19;;1936:161:2;28638:302:26;1936:161:2;1456:648;;1326:782;;;:::o;615:588::-;775:26;;-1:-1:-1;;;775:26:2;;751:21;;-1:-1:-1;;;;;775:22:2;;;;;:26;;798:2;;775:26;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;918:59;;-1:-1:-1;;;918:59:2;;751:50;;-1:-1:-1;;;;;;918:25:2;;;;;:59;;944:6;;952:2;;956:20;;918:59;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1007:26:2;;-1:-1:-1;;;1007:26:2;;984:20;;-1:-1:-1;;;;;;1007:22:2;;;-1:-1:-1;1007:22:2;;:26;;1030:2;;1007:26;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;984:49;-1:-1:-1;1086:20:2;1054:28;1069:13;984:49;1054:28;:::i;:::-;:52;1039:159;;;;-1:-1:-1;;;1039:159:2;;18446:2:26;1039:159:2;;;18428:21:26;18485:2;18465:18;;;18458:30;-1:-1:-1;;;;;;;;;;;18504:18:26;;;18497:62;18595:34;18575:18;;;18568:62;-1:-1:-1;;;18646:19:26;;;18639:43;18699:19;;1039:159:2;18418:306:26;1039:159:2;615:588;;;;;;:::o;1001:352:4:-;1148:23;1179;1211:57;1241:4;1247:6;1255:12;1211:29;:57::i;:::-;1274:39;;;;-1:-1:-1;1299:14:4;;1274:39;;:21;;:39;;1299:14;;1274:39;;;-1:-1:-1;;;;;1274:39:4;;:::i;:::-;;;-1:-1:-1;;;;;1274:39:4;;;;;;;;;;;;;;;;;;;;;1327:21;;;;;;;;;;1001:352;-1:-1:-1;;;;;;1001:352:4:o;1994:1094:17:-;2155:28;2191:31;2246:4;:27;;;-1:-1:-1;;;;;2231:56:17;;:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2191:98;-1:-1:-1;2368:28:17;2364:215;;2511:60;2521:49;-1:-1:-1;;;;;2521:49:17;;;;:27;;:49;:::i;:::-;2511:9;:60::i;:::-;2489:83;;;;;2364:215;2585:27;2621:128;2668:23;391:2:5;2621:37:17;:128::i;:::-;2585:164;;2769:314;2787:138;2826:20;2858:18;2888:4;:27;;;2787;:138::i;:::-;2935:140;2974:20;3006:19;3037:4;:28;;;2935:27;:140::i;:::-;2769:8;:314::i;3021:493:24:-;3103:21;3126;3188:4;:28;;;-1:-1:-1;;;;;3157:59:24;:4;:27;;;-1:-1:-1;;;;;3157:59:24;;;: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:24;720:5:5;-1:-1:-1;;;;;3377:64:24;3385:14;-1:-1:-1;;;;;3377:23:24;:64;;;;:::i;:::-;:90;;3362:147;;;;-1:-1:-1;;;3362:147:24;;25720:2:26;3362:147:24;;;25702:21:26;25759:2;25739:18;;;25732:30;-1:-1:-1;;;25778:18:26;;;25771:56;25844:18;;3362:147:24;25692:176:26;2656:1414:8;2741:7;-1:-1:-1;;;;;3642:80:8;;;3634:127;;;;-1:-1:-1;;;3634:127:8;;23517:2:26;3634:127:8;;;23499:21:26;23556:2;23536:18;;;23529:30;23595:34;23575:18;;;23568:62;-1:-1:-1;;;23646:18:26;;;23639:32;23688:19;;3634:127:8;23489:224:26;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;;24613:2:26;3771:65:8;;;24595:21:26;24652:2;24632:18;;;24625:30;24691:34;24671:18;;;24664:62;-1:-1:-1;;;24742:18:26;;;24735:32;24784:19;;3771:65:8;24585:224:26;3771:65:8;3948:24;;;3931:14;3948:24;;;;;;;;;17002:25:26;;;17075:4;17063:17;;17043:18;;;17036:45;;;;17097:18;;;17090:34;;;17140:18;;;17133:34;;;3948:24:8;;16974:19:26;;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;;17380:2:26;3982:57:8;;;17362:21:26;17419:2;17399:18;;;17392:30;-1:-1:-1;;;17438:18:26;;;17431:54;17502:18;;3982:57:8;17352:174:26;3982:57:8;4057:6;2656:1414;-1:-1:-1;;;;;2656:1414:8:o;251:198:15:-;364:6;418:4;:26;;;387:4;:28;;;:57;;;;:::i;5719:224:24:-;5824:4;;5899:5;5861:34;264:9:5;5861:3:24;:34;:::i;:::-;5860:44;;;;:::i;:::-;-1:-1:-1;;;;;5917:21:24;;;;;;;;-1:-1:-1;;5719:224:24;;;;;:::o;535:201:15:-;649:6;704:4;:27;;;672:4;:29;;;:59;;;;:::i;935:241:20:-;983:9;1008:1;1004;:5;1000:172;;;-1:-1:-1;1023:1:20;1032:9;1044:5;1048:1;1023;1044:5;:::i;:::-;:9;;1052:1;1044:9;:::i;:::-;1032:21;;1061:67;1072:1;1068;:5;1061:67;;;1089:1;-1:-1:-1;1089:1:20;1118;1089;1105:5;1089:1;1105;:5;:::i;:::-;:9;;;;:::i;:::-;1104:15;;;;:::i;:::-;1100:19;;1061:67;;;1000:172;935:241;;;:::o;1000:172::-;1144:6;;1140:32;;-1:-1:-1;1164:1:20;1140:32;935:241;;;:::o;837:94::-;893:8;917:1;-1:-1:-1;;;;;913:5:20;:1;-1:-1:-1;;;;;913:5:20;;:13;;925:1;913:13;;;-1:-1:-1;921:1:20;;909:17;-1:-1:-1;837:94:20:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:134:26:-;82:20;;111:31;82:20;111:31;:::i;153:738::-;195:5;248:3;241:4;233:6;229:17;225:27;215:2;;270:5;263;256:20;215:2;297:20;;-1:-1:-1;;;;;366:10:26;;;363:2;;;379:18;;:::i;:::-;454:2;448:9;422:2;508:13;;-1:-1:-1;;504:22:26;;;528:2;500:31;496:40;484:53;;;552:18;;;572:22;;;549:46;546:2;;;598:18;;:::i;:::-;638:10;634:2;627:22;673:2;665:6;658:18;719:3;712:4;707:2;699:6;695:15;691:26;688:35;685:2;;;740:5;733;726:20;685:2;808;801:4;793:6;789:17;782:4;774:6;770:17;757:54;831:15;;;848:4;827:26;820:41;;;;-1:-1:-1;835:6:26;205:686;-1:-1:-1;;;205:686:26:o;896:167::-;988:20;;1037:1;1027:12;;1017:2;;1053:1;1050;1043:12;1068:1326;1132:5;1180:6;1168:9;1163:3;1159:19;1155:32;1152:2;;;1204:5;1197;1190:20;1152:2;1230:22;;:::i;:::-;1221:31;;1275:27;1292:9;1275:27;:::i;:::-;1268:5;1261:42;1335:62;1393:2;1382:9;1378:18;1335:62;:::i;:::-;1330:2;1323:5;1319:14;1312:86;1430:38;1464:2;1453:9;1449:18;1430:38;:::i;:::-;1425:2;1418:5;1414:14;1407:62;1501:38;1535:2;1524:9;1520:18;1501:38;:::i;:::-;1496:2;1489:5;1485:14;1478:62;1573:39;1607:3;1596:9;1592:19;1573:39;:::i;:::-;1567:3;1560:5;1556:15;1549:64;1646:39;1680:3;1669:9;1665:19;1646:39;:::i;:::-;1640:3;1633:5;1629:15;1622:64;1747:3;1736:9;1732:19;1719:33;1713:3;1706:5;1702:15;1695:58;1814:3;1803:9;1799:19;1786:33;1780:3;1773:5;1769:15;1762:58;1839:3;1902:2;1891:9;1887:18;1874:32;1869:2;1862:5;1858:14;1851:56;;1926:3;1989:2;1978:9;1974:18;1961:32;1956:2;1949:5;1945:14;1938:56;;2013:3;2048:38;2082:2;2071:9;2067:18;2048:38;:::i;:::-;2032:14;;;2025:62;2106:3;2154:18;;;2141:32;2125:14;;;2118:56;2193:3;2232:18;;;2219:32;-1:-1:-1;;;;;2263:30:26;;2260:2;;;2306:1;2303;2296:12;2260:2;2342:45;2383:3;2374:6;2363:9;2359:22;2342:45;:::i;:::-;2337:2;2330:5;2326:14;2319:69;;;1142:1252;;;;:::o;2399:1138::-;2470:5;2518:4;2506:9;2501:3;2497:19;2493:30;2490:2;;;2540:5;2533;2526:20;2490:2;2577;2571:9;2619:4;2607:17;;-1:-1:-1;;;;;2639:34:26;;2675:22;;;2636:62;2633:2;;;2701:18;;:::i;:::-;2737:2;2730:22;2770:6;-1:-1:-1;2770:6:26;2800:23;;2832:33;2800:23;2832:33;:::i;:::-;2874:23;;2949:2;2934:18;;2921:32;2962:33;2921:32;2962:33;:::i;:::-;3023:2;3011:15;;3004:32;3088:2;3073:18;;3060:32;3101;3060;3101;:::i;:::-;3161:2;3149:15;;3142:32;3226:2;3211:18;;3198:32;3239;3198;3239;:::i;:::-;3299:2;3287:15;;3280:32;3346:38;3379:3;3364:19;;3346:38;:::i;:::-;3340:3;3332:6;3328:16;3321:64;3419:38;3452:3;3441:9;3437:19;3419:38;:::i;:::-;3413:3;3405:6;3401:16;3394:64;3492:38;3525:3;3514:9;3510:19;3492:38;:::i;:::-;3486:3;3478:6;3474:16;3467:64;;2480:1057;;;;:::o;3542:1233::-;3605:5;3653:6;3641:9;3636:3;3632:19;3628:32;3625:2;;;3677:5;3670;3663:20;3625:2;3703:17;;:::i;:::-;3694:26;;3743:27;3760:9;3743:27;:::i;:::-;3736:5;3729:42;3803:62;3861:2;3850:9;3846:18;3803:62;:::i;:::-;3798:2;3791:5;3787:14;3780:86;3898:38;3932:2;3921:9;3917:18;3898:38;:::i;:::-;3893:2;3886:5;3882:14;3875:62;3969:38;4003:2;3992:9;3988:18;3969:38;:::i;:::-;3964:2;3957:5;3953:14;3946:62;4041:39;4075:3;4064:9;4060:19;4041:39;:::i;:::-;4035:3;4028:5;4024:15;4017:64;4114:39;4148:3;4137:9;4133:19;4114:39;:::i;:::-;4108:3;4101:5;4097:15;4090:64;4215:3;4204:9;4200:19;4187:33;4181:3;4174:5;4170:15;4163:58;4282:3;4271:9;4267:19;4254:33;4248:3;4241:5;4237:15;4230:58;4307:3;4370:2;4359:9;4355:18;4342:32;4337:2;4330:5;4326:14;4319:56;;4394:3;4429:38;4463:2;4452:9;4448:18;4429:38;:::i;:::-;4413:14;;;4406:62;4487:3;4535:18;;;4522:32;4506:14;;;4499:56;4574:3;4613:18;;;4600:32;-1:-1:-1;;;;;4644:30:26;;4641:2;;;4687:1;4684;4677:12;4780:173;4848:20;;-1:-1:-1;;;;;4897:31:26;;4887:42;;4877:2;;4943:1;4940;4933:12;4958:132;5025:20;;5054:30;5025:20;5054:30;:::i;5095:156::-;5161:20;;5221:4;5210:16;;5200:27;;5190:2;;5241:1;5238;5231:12;5256:748;5422:6;5430;5438;5446;5454;5507:3;5495:9;5486:7;5482:23;5478:33;5475:2;;;5529:6;5521;5514:22;5475:2;5570:9;5557:23;5547:33;;5630:2;5619:9;5615:18;5602:32;5643:31;5668:5;5643:31;:::i;:::-;5693:5;-1:-1:-1;5750:2:26;5735:18;;5722:32;5763:33;5722:32;5763:33;:::i;:::-;5815:7;-1:-1:-1;5874:2:26;5859:18;;5846:32;5887:33;5846:32;5887:33;:::i;:::-;5465:539;;;;-1:-1:-1;5465:539:26;;5993:3;5978:19;5965:33;;5465:539;-1:-1:-1;;5465:539:26:o;6009:822::-;6235:6;6243;6251;6259;6267;6320:3;6308:9;6299:7;6295:23;6291:33;6288:2;;;6342:6;6334;6327:22;6288:2;6370:23;;;-1:-1:-1;6444:2:26;6429:18;;6416:32;-1:-1:-1;;;;;6460:30:26;;6457:2;;;6508:6;6500;6493:22;6457:2;6536:68;6596:7;6587:6;6576:9;6572:22;6536:68;:::i;:::-;6526:78;;;6654:2;6643:9;6639:18;6626:32;6667:31;6692:5;6667:31;:::i;:::-;6278:553;;;;-1:-1:-1;6717:5:26;;6769:2;6754:18;;6741:32;;-1:-1:-1;6820:3:26;6805:19;6792:33;;6278:553;-1:-1:-1;;6278:553:26:o;6836:1002::-;7069:6;7077;7085;7093;7101;7109;7162:3;7150:9;7141:7;7137:23;7133:33;7130:2;;;7184:6;7176;7169:22;7130:2;7212:23;;;-1:-1:-1;7286:2:26;7271:18;;7258:32;-1:-1:-1;;;;;7302:30:26;;7299:2;;;7350:6;7342;7335:22;7299:2;7378:68;7438:7;7429:6;7418:9;7414:22;7378:68;:::i;:::-;7368:78;;;7465:71;7528:7;7523:2;7512:9;7508:18;7465:71;:::i;:::-;7455:81;;7586:3;7575:9;7571:19;7558:33;7600:31;7625:5;7600:31;:::i;:::-;7650:5;-1:-1:-1;7707:3:26;7692:19;;7679:33;7721;7679;7721;:::i;:::-;7773:7;7763:17;;;7827:3;7816:9;7812:19;7799:33;7789:43;;7120:718;;;;;;;;:::o;7843:820::-;8068:6;8076;8084;8092;8100;8153:3;8141:9;8132:7;8128:23;8124:33;8121:2;;;8175:6;8167;8160:22;8121:2;8203:23;;;-1:-1:-1;8277:2:26;8262:18;;8249:32;-1:-1:-1;;;;;8293:30:26;;8290:2;;;8341:6;8333;8326:22;8290:2;8369:67;8428:7;8419:6;8408:9;8404:22;8369:67;:::i;8668:1291::-;8959:6;8967;8975;8983;8991;8999;9007;9015;9068:3;9056:9;9047:7;9043:23;9039:33;9036:2;;;9090:6;9082;9075:22;9036:2;9118:23;;;-1:-1:-1;9192:2:26;9177:18;;9164:32;-1:-1:-1;;;;;9208:30:26;;9205:2;;;9256:6;9248;9241:22;9205:2;9284:67;9343:7;9334:6;9323:9;9319:22;9284:67;:::i;:::-;9274:77;;;9370:71;9433:7;9428:2;9417:9;9413:18;9370:71;:::i;:::-;9360:81;;9491:3;9480:9;9476:19;9463:33;9539:5;9532:13;9525:21;9518:5;9515:32;9505:2;;9566:6;9558;9551:22;9505:2;9594:5;-1:-1:-1;9651:3:26;9636:19;;9623:33;9665;9623;9665;:::i;:::-;9717:7;-1:-1:-1;9776:3:26;9761:19;;9748:33;9790;9748;9790;:::i;:::-;9026:933;;;;-1:-1:-1;9026:933:26;;;;;;9842:7;;-1:-1:-1;;;9896:3:26;9881:19;;9868:33;;9948:3;9933:19;9920:33;;9026:933::o;9964:194::-;10034:6;10087:2;10075:9;10066:7;10062:23;10058:32;10055:2;;;10108:6;10100;10093:22;10055:2;-1:-1:-1;10136:16:26;;10045:113;-1:-1:-1;10045:113:26:o;10163:259::-;10232:6;10285:2;10273:9;10264:7;10260:23;10256:32;10253:2;;;10306:6;10298;10291:22;10253:2;10343:9;10337:16;10362:30;10386:5;10362:30;:::i;10427:125::-;10533:2;10505:14;-1:-1:-1;;;;;;10501:44:26;10489:57;;10479:73::o;10557:476::-;10599:3;10637:5;10631:12;10664:6;10659:3;10652:19;10689:3;10701:162;10715:6;10712:1;10709:13;10701:162;;;10777:4;10833:13;;;10829:22;;10823:29;10805:11;;;10801:20;;10794:59;10730:12;10701:162;;;10881:6;10878:1;10875:13;10872:2;;;10947:3;10940:4;10931:6;10926:3;10922:16;10918:27;10911:40;10872:2;-1:-1:-1;11015:2:26;10994:15;-1:-1:-1;;10990:29:26;10981:39;;;;11022:4;10977:50;;10607:426;-1:-1:-1;;10607:426:26:o;11655:1117::-;-1:-1:-1;;;;;;12112:3:26;12146:16;;;12142:25;;12130:38;;12201:16;;;12197:25;;12193:1;12184:11;;12177:46;12256:16;;;12252:25;12248:1;12239:11;;12232:46;-1:-1:-1;;;;;;12341:3:26;12311:16;;;12307:47;12303:1;12294:11;;12287:68;-1:-1:-1;12364:48:26;12408:2;12399:12;;12391:6;12364:48;:::i;:::-;12421;12465:2;12460:3;12456:12;12448:6;12421:48;:::i;:::-;12478;12522:2;12517:3;12513:12;12505:6;12478:48;:::i;:::-;12556:6;12551:2;12546:3;12542:12;12535:28;12594:6;12588:3;12583;12579:13;12572:29;12632:6;12626:3;12621;12617:13;12610:29;12648:50;12693:3;12688;12684:13;12675:7;12648:50;:::i;:::-;-1:-1:-1;12723:3:26;12714:13;;12707:30;;;;12762:3;12753:13;;12088:684;-1:-1:-1;;;;;;;;;;;12088:684:26:o;12777:1141::-;-1:-1:-1;;;;;;13271:3:26;13249:16;;;13245:36;;13233:49;;13315:16;;;13311:36;;13307:1;13298:11;;13291:57;13381:16;;;13377:36;13373:1;13364:11;;13357:57;-1:-1:-1;;;;;;11137:3:26;11108:15;;;11104:46;13459:1;13450:11;;11092:59;13471:48;13515:2;13510:3;13506:12;13498:6;13471:48;:::i;:::-;13528;13572:2;13567:3;13563:12;13555:6;13528:48;:::i;:::-;13585;13629:2;13624:3;13620:12;13612:6;13585:48;:::i;:::-;13663:6;13658:2;13653:3;13649:12;13642:28;13701:6;13695:3;13690;13686:13;13679:29;13739:6;13733:3;13728;13724:13;13717:29;13777:7;13771:3;13766;13762:13;13755:30;13794:50;13839:3;13834;13830:13;13821:7;13794:50;:::i;:::-;13869:3;13860:13;;13853:30;;;;13908:3;13899:13;;13223:695;-1:-1:-1;;;;;;;;;;;;13223:695:26:o;13923:203::-;-1:-1:-1;;;;;14087:32:26;;;;14069:51;;14057:2;14042:18;;14024:102::o;14347:391::-;-1:-1:-1;;;;;14621:15:26;;;14603:34;;14673:15;;;;14668:2;14653:18;;14646:43;14720:2;14705:18;;14698:34;;;;14553:2;14538:18;;14520:218::o;15719:519::-;-1:-1:-1;;;;;16034:15:26;;;16016:34;;16081:2;16066:18;;16059:34;;;;16124:2;16109:18;;16102:34;;;;16167:2;16152:18;;16145:34;16216:15;;;16210:3;16195:19;;16188:44;15965:3;15950:19;;15932:306::o;18729:352::-;18931:2;18913:21;;;18970:2;18950:18;;;18943:30;-1:-1:-1;;;19004:2:26;18989:18;;18982:58;19072:2;19057:18;;18903:178::o;19086:353::-;19288:2;19270:21;;;19327:2;19307:18;;;19300:30;19366:31;19361:2;19346:18;;19339:59;19430:2;19415:18;;19260:179::o;19444:350::-;19646:2;19628:21;;;19685:2;19665:18;;;19658:30;-1:-1:-1;;;19719:2:26;19704:18;;19697:56;19785:2;19770:18;;19618:176::o;20512:349::-;20714:2;20696:21;;;20753:2;20733:18;;;20726:30;-1:-1:-1;;;20787:2:26;20772:18;;20765:55;20852:2;20837:18;;20686:175::o;22969:341::-;23171:2;23153:21;;;23210:2;23190:18;;;23183:30;-1:-1:-1;;;23244:2:26;23229:18;;23222:47;23301:2;23286:18;;23143:167::o;23718:346::-;23920:2;23902:21;;;23959:2;23939:18;;;23932:30;-1:-1:-1;;;23993:2:26;23978:18;;23971:52;24055:2;24040:18;;23892:172::o;24069:337::-;24271:2;24253:21;;;24310:2;24290:18;;;24283:30;-1:-1:-1;;;24344:2:26;24329:18;;24322:43;24397:2;24382:18;;24243:163::o;24814:341::-;25016:2;24998:21;;;25055:2;25035:18;;;25028:30;-1:-1:-1;;;25089:2:26;25074:18;;25067:47;25146:2;25131:18;;24988:167::o;25160:353::-;25362:2;25344:21;;;25401:2;25381:18;;;25374:30;25440:31;25435:2;25420:18;;25413:59;25504:2;25489:18;;25334:179::o;27336:403::-;27538:2;27520:21;;;27577:2;27557:18;;;27550:30;27616:34;27611:2;27596:18;;27589:62;-1:-1:-1;;;27682:2:26;27667:18;;27660:37;27729:3;27714:19;;27510:229::o;27744:354::-;27946:2;27928:21;;;27985:2;27965:18;;;27958:30;28024:32;28019:2;28004:18;;27997:60;28089:2;28074:18;;27918:180::o;29647:1191::-;29880:2;29869:9;29862:21;29843:4;29918:6;29912:13;29944:6;29986:2;29981;29970:9;29966:18;29959:30;30012:52;30059:3;30048:9;30044:19;30030:12;30012:52;:::i;:::-;29998:66;;30144:1;30140;30136:2;30132:10;30128:18;30122:2;30114:6;30110:15;30104:22;30100:47;30095:2;30084:9;30080:18;30073:75;30197:2;30189:6;30185:15;30179:22;30210:53;30259:2;30248:9;30244:18;30228:14;-1:-1:-1;;;;;11227:30:26;11215:43;;11205:59;30210:53;;30318:2;30310:6;30306:15;30300:22;30294:3;30283:9;30279:19;30272:51;30372:3;30364:6;30360:16;30354:23;30446:2;30442:7;30430:9;30422:6;30418:22;30414:36;30408:3;30397:9;30393:19;30386:65;30474:41;30508:6;30492:14;30474:41;:::i;:::-;30460:55;;;30564:3;30556:6;30552:16;30546:23;30578:54;30627:3;30616:9;30612:19;30596:14;-1:-1:-1;;;;;11227:30:26;11215:43;;11205:59;30578:54;-1:-1:-1;30681:3:26;30669:16;;30663:23;-1:-1:-1;;;;;11227:30:26;;30744:3;30729:19;;11215:43;-1:-1:-1;30803:3:26;30791:16;;;;30785:23;30765:18;;;;30758:51;;;;-1:-1:-1;30826:6:26;29852:986::o;30843:787::-;31074:2;31056:21;;;31117:13;;-1:-1:-1;;;;;31113:39:26;31093:18;;;31086:67;31188:15;;31182:22;31140:3;31235:2;31220:18;;31213:32;-1:-1:-1;;31268:52:26;31315:3;31300:19;;31182:22;31268:52;:::i;:::-;31369:2;31357:15;;31351:22;-1:-1:-1;;;;;31446:23:26;;;31441:2;31426:18;;;31419:51;;;;31517:15;;31511:22;31507:31;31501:3;31486:19;;;31479:60;;;;31583:16;;;31577:23;31570:4;31555:20;;;31548:53;;;;-1:-1:-1;31254:66:26;;31046:584;-1:-1:-1;31046:584:26:o;32160:255::-;32232:2;32226:9;32274:6;32262:19;;-1:-1:-1;;;;;32296:34:26;;32332:22;;;32293:62;32290:2;;;32358:18;;:::i;:::-;32394:2;32387:22;32206:209;:::o;32420:250::-;32487:2;32481:9;32529:6;32517:19;;-1:-1:-1;;;;;32551:34:26;;32587:22;;;32548:62;32545:2;;;32613:18;;:::i;32675:128::-;32715:3;32746:1;32742:6;32739:1;32736:13;32733:2;;;32752:18;;:::i;:::-;-1:-1:-1;32788:9:26;;32723:80::o;32808:236::-;32847:3;-1:-1:-1;;;;;32913:10:26;;;32943;;;32973:12;;;32965:21;;32962:2;;;32989:18;;:::i;:::-;33025:13;;32855:189;-1:-1:-1;;;;32855:189:26:o;33049:120::-;33089:1;33115;33105:2;;33120:18;;:::i;:::-;-1:-1:-1;33154:9:26;;33095:74::o;33174:199::-;33213:1;-1:-1:-1;;;;;33277:10:26;;;;33296:2;;33313:18;;:::i;:::-;33351:10;;33347:20;;;;;33219:154;-1:-1:-1;;33219:154:26:o;33378:422::-;33467:1;33510:5;33467:1;33524:270;33545:7;33535:8;33532:21;33524:270;;;33604:4;33600:1;33596:6;33592:17;33586:4;33583:27;33580:2;;;33613:18;;:::i;:::-;33663:7;33653:8;33649:22;33646:2;;;33683:16;;;;33646:2;33762:22;;;;33722:15;;;;33524:270;;;33528:3;33442:358;;;;;:::o;33805:140::-;33863:5;33892:47;33933:4;33923:8;33919:19;33913:4;33999:5;34029:8;34019:2;;-1:-1:-1;34070:1:26;34084:5;;34019:2;34118:4;34108:2;;-1:-1:-1;34155:1:26;34169:5;;34108:2;34200:4;34218:1;34213:59;;;;34286:1;34281:130;;;;34193:218;;34213:59;34243:1;34234:10;;34257:5;;;34281:130;34318:3;34308:8;34305:17;34302:2;;;34325:18;;:::i;:::-;-1:-1:-1;;34381:1:26;34367:16;;34396:5;;34193:218;;34495:2;34485:8;34482:16;34476:3;34470:4;34467:13;34463:36;34457:2;34447:8;34444:16;34439:2;34433:4;34430:12;34426:35;34423:77;34420:2;;;-1:-1:-1;34532:19:26;;;34564:5;;34420:2;34611:34;34636:8;34630:4;34611:34;:::i;:::-;34681:6;34677:1;34673:6;34669:19;34660:7;34657:32;34654:2;;;34692:18;;:::i;:::-;34730:20;;34009:747;-1:-1:-1;;;34009:747:26:o;34761:168::-;34801:7;34867:1;34863;34859:6;34855:14;34852:1;34849:21;34844:1;34837:9;34830:17;34826:45;34823:2;;;34874:18;;:::i;:::-;-1:-1:-1;34914:9:26;;34813:116::o;34934:270::-;34973:7;-1:-1:-1;;;;;35043:10:26;;;35073;;;35106:11;;35099:19;35128:12;;;35120:21;;35095:47;35092:2;;;35145:18;;:::i;:::-;35185:13;;34985:219;-1:-1:-1;;;;34985:219:26:o;35209:125::-;35249:4;35277:1;35274;35271:8;35268:2;;;35282:18;;:::i;:::-;-1:-1:-1;35319:9:26;;35258:76::o;35339:229::-;35378:4;-1:-1:-1;;;;;35475:10:26;;;;35445;;35497:12;;;35494:2;;;35512:18;;:::i;:::-;35549:13;;35387:181;-1:-1:-1;;;35387:181:26:o;35573:195::-;35611:4;35648;35645:1;35641:12;35680:4;35677:1;35673:12;35705:3;35700;35697:12;35694:2;;;35712:18;;:::i;:::-;35749:13;;;35620:148;-1:-1:-1;;;35620:148:26:o;35773:380::-;35852:1;35848:12;;;;35895;;;35916:2;;35970:4;35962:6;35958:17;35948:27;;35916:2;36023;36015:6;36012:14;35992:18;35989:38;35986:2;;;36069:10;36064:3;36060:20;36057:1;36050:31;36104:4;36101:1;36094:15;36132:4;36129:1;36122:15;36158:112;36190:1;36216;36206:2;;36221:18;;:::i;:::-;-1:-1:-1;36255:9:26;;36196:74::o;36275:127::-;36336:10;36331:3;36327:20;36324:1;36317:31;36367:4;36364:1;36357:15;36391:4;36388:1;36381:15;36407:127;36468:10;36463:3;36459:20;36456:1;36449:31;36499:4;36496:1;36489:15;36523:4;36520:1;36513:15;36539:127;36600:10;36595:3;36591:20;36588:1;36581:31;36631:4;36628:1;36621:15;36655:4;36652:1;36645:15;36671:131;-1:-1:-1;;;;;36746:31:26;;36736:42;;36726:2;;36792:1;36789;36782:12;36807:129;-1:-1:-1;;;;;36881:30:26;;36871:41;;36861:2;;36926:1;36923;36916:12
Swarm Source
ipfs://18a573e81d7aede27430a13b0d89aa8e369aaa6de387214f77cf2d6c6e5ce7e0
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.