UNPKG

@sky-mavis/smart-order-router

Version:
488 lines 52.7 kB
import { BigNumber } from '@ethersproject/bignumber'; import { MIXED_ROUTE_QUOTER_V1_ADDRESSES, QUOTER_ADDRESSES } from '@sky-mavis/katana-core'; import { encodeMixedRouteToPath, MixedRouteSDK, Protocol } from '@uniswap/router-sdk'; import { encodeRouteToPath } from '@uniswap/v3-sdk'; import retry from 'async-retry'; import _ from 'lodash'; import stats from 'stats-lite'; import { V2Route } from '../routers/router'; import { IMixedRouteQuoterV1__factory } from '../types/other/factories/IMixedRouteQuoterV1__factory'; import { IQuoterV2__factory } from '../types/v3/factories/IQuoterV2__factory'; import { ID_TO_NETWORK_NAME, metric, MetricLoggerUnit } from '../util'; import { log } from '../util/log'; import { DEFAULT_BATCH_PARAMS, DEFAULT_BLOCK_NUMBER_CONFIGS, DEFAULT_GAS_ERROR_FAILURE_OVERRIDES, DEFAULT_RETRY_OPTIONS, DEFAULT_SUCCESS_RATE_FAILURE_OVERRIDES, } from '../util/onchainQuoteProviderConfigs'; import { routeToString } from '../util/routes'; export class BlockConflictError extends Error { constructor() { super(...arguments); this.name = 'BlockConflictError'; } } export class SuccessRateError extends Error { constructor() { super(...arguments); this.name = 'SuccessRateError'; } } export class ProviderBlockHeaderError extends Error { constructor() { super(...arguments); this.name = 'ProviderBlockHeaderError'; } } export class ProviderTimeoutError extends Error { constructor() { super(...arguments); this.name = 'ProviderTimeoutError'; } } /** * This error typically means that the gas used by the multicall has * exceeded the total call gas limit set by the node provider. * * This can be resolved by modifying BatchParams to request fewer * quotes per call, or to set a lower gas limit per quote. * * @export * @class ProviderGasError */ export class ProviderGasError extends Error { constructor() { super(...arguments); this.name = 'ProviderGasError'; } } const DEFAULT_BATCH_RETRIES = 2; /** * Computes on chain quotes for swaps. For pure V3 routes, quotes are computed on-chain using * the 'QuoterV2' smart contract. For exactIn mixed and V2 routes, quotes are computed using the 'MixedRouteQuoterV1' contract * This is because computing quotes off-chain would require fetching all the tick data for each pool, which is a lot of data. * * To minimize the number of requests for quotes we use a Multicall contract. Generally * the number of quotes to fetch exceeds the maximum we can fit in a single multicall * while staying under gas limits, so we also batch these quotes across multiple multicalls. * * The biggest challenge with the quote provider is dealing with various gas limits. * Each provider sets a limit on the amount of gas a call can consume (on Infura this * is approximately 10x the block max size), so we must ensure each multicall does not * exceed this limit. Additionally, each quote on V3 can consume a large number of gas if * the pool lacks liquidity and the swap would cause all the ticks to be traversed. * * To ensure we don't exceed the node's call limit, we limit the gas used by each quote to * a specific value, and we limit the number of quotes in each multicall request. Users of this * class should set BatchParams such that multicallChunk * gasLimitPerCall is less than their node * providers total gas limit per call. * * @export * @class OnChainQuoteProvider */ export class OnChainQuoteProvider { /** * Creates an instance of OnChainQuoteProvider. * * @param chainId The chain to get quotes for. * @param provider The web 3 provider. * @param multicall2Provider The multicall provider to use to get the quotes on-chain. * Only supports the Uniswap Multicall contract as it needs the gas limitting functionality. * @param retryOptions The retry options for each call to the multicall. * @param batchParams The parameters for each batched call to the multicall. * @param gasErrorFailureOverride The gas and chunk parameters to use when retrying a batch that failed due to out of gas. * @param successRateFailureOverrides The parameters for retries when we fail to get quotes. * @param blockNumberConfig Parameters for adjusting which block we get quotes from, and how to handle block header not found errors. * @param [quoterAddressOverride] Overrides the address of the quoter contract to use. * @param metricsPrefix metrics prefix to differentiate between different instances of the quote provider. */ constructor({ chainId, provider, multicall2Provider, metricsPrefix = (chainId, useMixedRouteQuoter, optimisticCachedRoutes) => useMixedRouteQuoter ? `ChainId_${chainId}_MixedQuoter_OptimisticCachedRoutes${optimisticCachedRoutes}_` : `ChainId_${chainId}_V3Quoter_OptimisticCachedRoutes${optimisticCachedRoutes}_`, retryOptions = DEFAULT_RETRY_OPTIONS, batchParams = DEFAULT_BATCH_PARAMS, gasErrorFailureOverride = DEFAULT_GAS_ERROR_FAILURE_OVERRIDES, successRateFailureOverrides = DEFAULT_SUCCESS_RATE_FAILURE_OVERRIDES, blockNumberConfig = DEFAULT_BLOCK_NUMBER_CONFIGS, quoterAddressOverride, }) { this.chainId = chainId; this.provider = provider; this.multicall2Provider = multicall2Provider; this.retryOptions = retryOptions; this.batchParams = batchParams; this.gasErrorFailureOverride = gasErrorFailureOverride; this.successRateFailureOverrides = successRateFailureOverrides; this.blockNumberConfig = blockNumberConfig; this.quoterAddressOverride = quoterAddressOverride; this.metricsPrefix = metricsPrefix; } getQuoterAddress(useMixedRouteQuoter) { if (this.quoterAddressOverride) { const quoterAddress = this.quoterAddressOverride(useMixedRouteQuoter); if (!quoterAddress) { throw new Error(`No address for the quoter contract on chain id: ${this.chainId}`); } return quoterAddress; } const quoterAddress = useMixedRouteQuoter ? MIXED_ROUTE_QUOTER_V1_ADDRESSES[this.chainId] : QUOTER_ADDRESSES[this.chainId]; if (!quoterAddress) { throw new Error(`No address for the quoter contract on chain id: ${this.chainId}`); } return quoterAddress; } async getQuotesManyExactIn(amountIns, routes, providerConfig) { return this.getQuotesManyData(amountIns, routes, 'quoteExactInput', providerConfig); } async getQuotesManyExactOut(amountOuts, routes, providerConfig) { return this.getQuotesManyData(amountOuts, routes, 'quoteExactOutput', providerConfig); } async getQuotesManyData(amounts, routes, functionName, _providerConfig) { var _a, _b; const useMixedRouteQuoter = routes.some(route => route.protocol === Protocol.V2) || routes.some(route => route.protocol === Protocol.MIXED); const optimisticCachedRoutes = (_a = _providerConfig === null || _providerConfig === void 0 ? void 0 : _providerConfig.optimisticCachedRoutes) !== null && _a !== void 0 ? _a : false; /// Validate that there are no incorrect routes / function combinations this.validateRoutes(routes, functionName, useMixedRouteQuoter); let multicallChunk = this.batchParams.multicallChunk; let gasLimitOverride = this.batchParams.gasLimitPerCall; const { baseBlockOffset, rollback } = this.blockNumberConfig; // Apply the base block offset if provided const originalBlockNumber = await this.provider.getBlockNumber(); const providerConfig = { ..._providerConfig, blockNumber: (_b = _providerConfig === null || _providerConfig === void 0 ? void 0 : _providerConfig.blockNumber) !== null && _b !== void 0 ? _b : originalBlockNumber + baseBlockOffset, }; const inputs = _(routes) .flatMap(route => { const encodedRoute = route.protocol === Protocol.V3 ? encodeRouteToPath(route, functionName == 'quoteExactOutput') : encodeMixedRouteToPath(route instanceof V2Route ? new MixedRouteSDK(route.pairs, route.input, route.output) : route); const routeInputs = amounts.map(amount => [ encodedRoute, `0x${amount.quotient.toString(16)}`, ]); return routeInputs; }) .value(); const normalizedChunk = Math.ceil(inputs.length / Math.ceil(inputs.length / multicallChunk)); const inputsChunked = _.chunk(inputs, normalizedChunk); let quoteStates = _.map(inputsChunked, inputChunk => { return { status: 'pending', inputs: inputChunk, }; }); log.info(`About to get ${inputs.length} quotes in chunks of ${normalizedChunk} [${_.map(inputsChunked, i => i.length).join(',')}] ${gasLimitOverride ? `with a gas limit override of ${gasLimitOverride}` : ''} and block number: ${await providerConfig.blockNumber} [Original before offset: ${originalBlockNumber}].`); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteBatchSize`, inputs.length, MetricLoggerUnit.Count); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteBatchSize_${ID_TO_NETWORK_NAME(this.chainId)}`, inputs.length, MetricLoggerUnit.Count); const startTime = Date.now(); let haveRetriedForSuccessRate = false; let haveRetriedForBlockHeader = false; let blockHeaderRetryAttemptNumber = 0; let haveIncrementedBlockHeaderFailureCounter = false; let blockHeaderRolledBack = false; let haveRetriedForBlockConflictError = false; let haveRetriedForOutOfGas = false; let haveRetriedForTimeout = false; let haveRetriedForUnknownReason = false; let finalAttemptNumber = 1; const expectedCallsMade = quoteStates.length; let totalCallsMade = 0; const { results: quoteResults, blockNumber, approxGasUsedPerSuccessCall, } = await retry(async (_bail, attemptNumber) => { haveIncrementedBlockHeaderFailureCounter = false; finalAttemptNumber = attemptNumber; const [success, failed, pending] = this.partitionQuotes(quoteStates); log.info(`Starting attempt: ${attemptNumber}. Currently ${success.length} success, ${failed.length} failed, ${pending.length} pending. Gas limit override: ${gasLimitOverride} Block number override: ${providerConfig.blockNumber}.`); quoteStates = await Promise.all(_.map(quoteStates, async (quoteState, idx) => { if (quoteState.status == 'success') { return quoteState; } // QuoteChunk is pending or failed, so we try again const { inputs } = quoteState; try { totalCallsMade = totalCallsMade + 1; const results = await this.multicall2Provider.callSameFunctionOnContractWithMultipleParams({ address: this.getQuoterAddress(useMixedRouteQuoter), contractInterface: useMixedRouteQuoter ? IMixedRouteQuoterV1__factory.createInterface() : IQuoterV2__factory.createInterface(), functionName, functionParams: inputs, providerConfig, additionalConfig: { gasLimitPerCallOverride: gasLimitOverride, }, }); const successRateError = this.validateSuccessRate(results.results, haveRetriedForSuccessRate, useMixedRouteQuoter, optimisticCachedRoutes); if (successRateError) { return { status: 'failed', inputs, reason: successRateError, results, }; } return { status: 'success', inputs, results, }; } catch (err) { // Error from providers have huge messages that include all the calldata and fill the logs. // Catch them and rethrow with shorter message. if (err.message.includes('header not found')) { return { status: 'failed', inputs, reason: new ProviderBlockHeaderError(err.message.slice(0, 500)), }; } if (err.message.includes('timeout')) { return { status: 'failed', inputs, reason: new ProviderTimeoutError(`Req ${idx}/${quoteStates.length}. Request had ${inputs.length} inputs. ${err.message.slice(0, 500)}`), }; } if (err.message.includes('out of gas')) { return { status: 'failed', inputs, reason: new ProviderGasError(err.message.slice(0, 500)), }; } return { status: 'failed', inputs, reason: new Error(`Unknown error from provider: ${err.message.slice(0, 500)}`), }; } })); const [successfulQuoteStates, failedQuoteStates, pendingQuoteStates] = this.partitionQuotes(quoteStates); if (pendingQuoteStates.length > 0) { throw new Error('Pending quote after waiting for all promises.'); } let retryAll = false; const blockNumberError = this.validateBlockNumbers(successfulQuoteStates, inputsChunked.length, gasLimitOverride); // If there is a block number conflict we retry all the quotes. if (blockNumberError) { retryAll = true; } const reasonForFailureStr = _.map(failedQuoteStates, failedQuoteState => failedQuoteState.reason.name).join(', '); if (failedQuoteStates.length > 0) { log.info(`On attempt ${attemptNumber}: ${failedQuoteStates.length}/${quoteStates.length} quotes failed. Reasons: ${reasonForFailureStr}`); for (const failedQuoteState of failedQuoteStates) { const { reason: error } = failedQuoteState; log.info({ error }, `[QuoteFetchError] Attempt ${attemptNumber}. ${error.message}`); if (error instanceof BlockConflictError) { if (!haveRetriedForBlockConflictError) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteBlockConflictErrorRetry`, 1, MetricLoggerUnit.Count); haveRetriedForBlockConflictError = true; } retryAll = true; } else if (error instanceof ProviderBlockHeaderError) { if (!haveRetriedForBlockHeader) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteBlockHeaderNotFoundRetry`, 1, MetricLoggerUnit.Count); haveRetriedForBlockHeader = true; } // Ensure that if multiple calls fail due to block header in the current pending batch, // we only count once. if (!haveIncrementedBlockHeaderFailureCounter) { blockHeaderRetryAttemptNumber = blockHeaderRetryAttemptNumber + 1; haveIncrementedBlockHeaderFailureCounter = true; } if (rollback.enabled) { const { rollbackBlockOffset, attemptsBeforeRollback } = rollback; if (blockHeaderRetryAttemptNumber >= attemptsBeforeRollback && !blockHeaderRolledBack) { log.info(`Attempt ${attemptNumber}. Have failed due to block header ${blockHeaderRetryAttemptNumber - 1} times. Rolling back block number by ${rollbackBlockOffset} for next retry`); providerConfig.blockNumber = providerConfig.blockNumber ? (await providerConfig.blockNumber) + rollbackBlockOffset : (await this.provider.getBlockNumber()) + rollbackBlockOffset; retryAll = true; blockHeaderRolledBack = true; } } } else if (error instanceof ProviderTimeoutError) { if (!haveRetriedForTimeout) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteTimeoutRetry`, 1, MetricLoggerUnit.Count); haveRetriedForTimeout = true; } } else if (error instanceof ProviderGasError) { if (!haveRetriedForOutOfGas) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteOutOfGasExceptionRetry`, 1, MetricLoggerUnit.Count); haveRetriedForOutOfGas = true; } gasLimitOverride = this.gasErrorFailureOverride.gasLimitOverride; multicallChunk = this.gasErrorFailureOverride.multicallChunk; retryAll = true; } else if (error instanceof SuccessRateError) { if (!haveRetriedForSuccessRate) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteSuccessRateRetry`, 1, MetricLoggerUnit.Count); haveRetriedForSuccessRate = true; // Low success rate can indicate too little gas given to each call. gasLimitOverride = this.successRateFailureOverrides.gasLimitOverride; multicallChunk = this.successRateFailureOverrides.multicallChunk; retryAll = true; } } else { if (!haveRetriedForUnknownReason) { metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteUnknownReasonRetry`, 1, MetricLoggerUnit.Count); haveRetriedForUnknownReason = true; } } } } if (retryAll) { log.info(`Attempt ${attemptNumber}. Resetting all requests to pending for next attempt.`); const normalizedChunk = Math.ceil(inputs.length / Math.ceil(inputs.length / multicallChunk)); const inputsChunked = _.chunk(inputs, normalizedChunk); quoteStates = _.map(inputsChunked, inputChunk => { return { status: 'pending', inputs: inputChunk, }; }); } if (failedQuoteStates.length > 0) { throw new Error(`Failed to get ${failedQuoteStates.length} quotes. Reasons: ${reasonForFailureStr}`); } const callResults = _.map(successfulQuoteStates, quoteState => quoteState.results); return { results: _.flatMap(callResults, result => result.results), blockNumber: BigNumber.from(callResults[0].blockNumber), approxGasUsedPerSuccessCall: stats.percentile(_.map(callResults, result => result.approxGasUsedPerSuccessCall), 100), }; }, { retries: DEFAULT_BATCH_RETRIES, ...this.retryOptions, }); const routesQuotes = this.processQuoteResults(quoteResults, routes, amounts, BigNumber.from(gasLimitOverride)); const endTime = Date.now(); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteLatency`, endTime - startTime, MetricLoggerUnit.Milliseconds); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteApproxGasUsedPerSuccessfulCall`, approxGasUsedPerSuccessCall, MetricLoggerUnit.Count); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteNumRetryLoops`, finalAttemptNumber - 1, MetricLoggerUnit.Count); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteTotalCallsToProvider`, totalCallsMade, MetricLoggerUnit.Count); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteExpectedCallsToProvider`, expectedCallsMade, MetricLoggerUnit.Count); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteNumRetriedCalls`, totalCallsMade - expectedCallsMade, MetricLoggerUnit.Count); const [successfulQuotes, failedQuotes] = _(routesQuotes) .flatMap((routeWithQuotes) => routeWithQuotes[1]) .partition(quote => quote.quote != null) .value(); log.info(`Got ${successfulQuotes.length} successful quotes, ${failedQuotes.length} failed quotes. Took ${finalAttemptNumber - 1} attempt loops. Total calls made to provider: ${totalCallsMade}. Have retried for timeout: ${haveRetriedForTimeout}`); return { routesWithQuotes: routesQuotes, blockNumber, }; } partitionQuotes(quoteStates) { const successfulQuoteStates = _.filter(quoteStates, (quoteState) => quoteState.status == 'success'); const failedQuoteStates = _.filter(quoteStates, (quoteState) => quoteState.status == 'failed'); const pendingQuoteStates = _.filter(quoteStates, (quoteState) => quoteState.status == 'pending'); return [successfulQuoteStates, failedQuoteStates, pendingQuoteStates]; } processQuoteResults(quoteResults, routes, amounts, gasLimit) { const routesQuotes = []; const quotesResultsByRoute = _.chunk(quoteResults, amounts.length); const debugFailedQuotes = []; for (let i = 0; i < quotesResultsByRoute.length; i++) { const route = routes[i]; const quoteResults = quotesResultsByRoute[i]; const quotes = _.map(quoteResults, (quoteResult, index) => { var _a; const amount = amounts[index]; if (!quoteResult.success) { const percent = (100 / amounts.length) * (index + 1); const amountStr = amount.toFixed(Math.min(amount.currency.decimals, 2)); const routeStr = routeToString(route); debugFailedQuotes.push({ route: routeStr, percent, amount: amountStr, }); return { amount, quote: null, sqrtPriceX96AfterList: null, gasEstimate: (_a = quoteResult.gasUsed) !== null && _a !== void 0 ? _a : null, gasLimit: gasLimit, initializedTicksCrossedList: null, }; } return { amount, quote: quoteResult.result[0], sqrtPriceX96AfterList: quoteResult.result[1], initializedTicksCrossedList: quoteResult.result[2], gasEstimate: quoteResult.result[3], gasLimit: gasLimit, }; }); routesQuotes.push([route, quotes]); } // For routes and amounts that we failed to get a quote for, group them by route // and batch them together before logging to minimize number of logs. const debugChunk = 80; _.forEach(_.chunk(debugFailedQuotes, debugChunk), (quotes, idx) => { const failedQuotesByRoute = _.groupBy(quotes, q => q.route); const failedFlat = _.mapValues(failedQuotesByRoute, f => _(f) .map(f => `${f.percent}%[${f.amount}]`) .join(',')); log.info({ failedQuotes: _.map(failedFlat, (amounts, routeStr) => `${routeStr} : ${amounts}`), }, `Failed on chain quotes for routes Part ${idx}/${Math.ceil(debugFailedQuotes.length / debugChunk)}`); }); return routesQuotes; } validateBlockNumbers(successfulQuoteStates, totalCalls, gasLimitOverride) { if (successfulQuoteStates.length <= 1) { return null; } const results = _.map(successfulQuoteStates, quoteState => quoteState.results); const blockNumbers = _.map(results, result => result.blockNumber); const uniqBlocks = _(blockNumbers) .map(blockNumber => blockNumber.toNumber()) .uniq() .value(); if (uniqBlocks.length == 1) { return null; } /* if ( uniqBlocks.length == 2 && Math.abs(uniqBlocks[0]! - uniqBlocks[1]!) <= 1 ) { return null; } */ return new BlockConflictError(`Quotes returned from different blocks. ${uniqBlocks}. ${totalCalls} calls were made with gas limit ${gasLimitOverride}`); } validateSuccessRate(allResults, haveRetriedForSuccessRate, useMixedRouteQuoter, optimisticCachedRoutes) { const numResults = allResults.length; const numSuccessResults = allResults.filter(result => result.success).length; const successRate = (1.0 * numSuccessResults) / numResults; const { quoteMinSuccessRate } = this.batchParams; if (successRate < quoteMinSuccessRate) { if (haveRetriedForSuccessRate) { log.info(`Quote success rate still below threshold despite retry. Continuing. ${quoteMinSuccessRate}: ${successRate}`); metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteRetriedSuccessRateLow`, successRate, MetricLoggerUnit.Percent); return; } metric.putMetric(`${this.metricsPrefix(this.chainId, useMixedRouteQuoter, optimisticCachedRoutes)}QuoteSuccessRateLow`, successRate, MetricLoggerUnit.Percent); return new SuccessRateError(`Quote success rate below threshold of ${quoteMinSuccessRate}: ${successRate}`); } } /** * Throw an error for incorrect routes / function combinations * @param routes Any combination of V3, V2, and Mixed routes. * @param functionName * @param useMixedRouteQuoter true if there are ANY V2Routes or MixedRoutes in the routes parameter */ validateRoutes(routes, functionName, useMixedRouteQuoter) { /// We do not send any V3Routes to new qutoer becuase it is not deployed on chains besides mainnet if (routes.some(route => route.protocol === Protocol.V3) && useMixedRouteQuoter) { throw new Error(`Cannot use mixed route quoter with V3 routes`); } /// We cannot call quoteExactOutput with V2 or Mixed routes if (functionName === 'quoteExactOutput' && useMixedRouteQuoter) { throw new Error('Cannot call quoteExactOutput with V2 or Mixed routes'); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib24tY2hhaW4tcXVvdGUtcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJvdmlkZXJzL29uLWNoYWluLXF1b3RlLXByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVyRCxPQUFPLEVBQVcsK0JBQStCLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNwRyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3RGLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3BELE9BQU8sS0FBa0MsTUFBTSxhQUFhLENBQUM7QUFDN0QsT0FBTyxDQUFDLE1BQU0sUUFBUSxDQUFDO0FBQ3ZCLE9BQU8sS0FBSyxNQUFNLFlBQVksQ0FBQztBQUUvQixPQUFPLEVBQWMsT0FBTyxFQUFXLE1BQU0sbUJBQW1CLENBQUM7QUFDakUsT0FBTyxFQUFFLDRCQUE0QixFQUFFLE1BQU0sdURBQXVELENBQUM7QUFDckcsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFDOUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUV2RSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2xDLE9BQU8sRUFDTCxvQkFBb0IsRUFDcEIsNEJBQTRCLEVBQzVCLG1DQUFtQyxFQUNuQyxxQkFBcUIsRUFDckIsc0NBQXNDLEdBQ3ZDLE1BQU0scUNBQXFDLENBQUM7QUFDN0MsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBa0MvQyxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsS0FBSztJQUE3Qzs7UUFDUyxTQUFJLEdBQUcsb0JBQW9CLENB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