UNPKG

quickswap-smart-order-router

Version:
481 lines 45.7 kB
import { BigNumber } from '@ethersproject/bignumber'; import { encodeRouteToPath } from '@uniswap/v3-sdk'; import retry from 'async-retry'; import _ from 'lodash'; import stats from 'stats-lite'; import { IQuoterV2__factory } from '../../types/v3/factories/IQuoterV2__factory'; import { ChainId, metric, MetricLoggerUnit } from '../../util'; import { QUOTER_V2_ADDRESS } from '../../util/addresses'; import { log } from '../../util/log'; 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 quotes for V3. For V3, quotes are computed on-chain using * the 'QuoterV2' smart 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 V3QuoteProvider */ export class V3QuoteProvider { /** * Creates an instance of V3QuoteProvider. * * @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. */ constructor(chainId, provider, // Only supports Uniswap Multicall as it needs the gas limitting functionality. multicall2Provider, retryOptions = { retries: DEFAULT_BATCH_RETRIES, minTimeout: 25, maxTimeout: 250, }, batchParams = { multicallChunk: 150, gasLimitPerCall: 1000000, quoteMinSuccessRate: 0.2, }, gasErrorFailureOverride = { gasLimitOverride: 1500000, multicallChunk: 100, }, successRateFailureOverrides = { gasLimitOverride: 1300000, multicallChunk: 110, }, blockNumberConfig = { baseBlockOffset: 0, rollback: { enabled: false }, }, 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; const quoterAddress = quoterAddressOverride ? quoterAddressOverride : QUOTER_V2_ADDRESS; if (!quoterAddress) { throw new Error(`No address for Uniswap QuoterV2 Contract on chain id: ${chainId}`); } this.quoterAddress = 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; 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: (_a = _providerConfig === null || _providerConfig === void 0 ? void 0 : _providerConfig.blockNumber) !== null && _a !== void 0 ? _a : originalBlockNumber + baseBlockOffset, }; const inputs = _(routes) .flatMap((route) => { const encodedRoute = encodeRouteToPath(route, functionName == 'quoteExactOutput' // For exactOut must be true to ensure the routes are reversed. ); 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}].`); 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.quoterAddress, contractInterface: IQuoterV2__factory.createInterface(), functionName, functionParams: inputs, providerConfig, additionalConfig: { gasLimitPerCallOverride: gasLimitOverride, }, }); const successRateError = this.validateSuccessRate(results.results, haveRetriedForSuccessRate); 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('QuoteBlockConflictErrorRetry', 1, MetricLoggerUnit.Count); haveRetriedForBlockConflictError = true; } retryAll = true; } else if (error instanceof ProviderBlockHeaderError) { if (!haveRetriedForBlockHeader) { metric.putMetric('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('QuoteTimeoutRetry', 1, MetricLoggerUnit.Count); haveRetriedForTimeout = true; } } else if (error instanceof ProviderGasError) { if (!haveRetriedForOutOfGas) { metric.putMetric('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('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('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) { // TODO: Work with Arbitrum to find a solution for making large multicalls with gas limits that always // successfully. // // On Arbitrum we can not set a gas limit for every call in the multicall and guarantee that // we will not run out of gas on the node. This is because they have a different way of accounting // for gas, that seperates storage and compute gas costs, and we can not cover both in a single limit. // // To work around this and avoid throwing errors when really we just couldn't get a quote, we catch this // case and return 0 quotes found. if ((this.chainId == ChainId.ARBITRUM_ONE || this.chainId == ChainId.ARBITRUM_RINKEBY) && _.every(failedQuoteStates, (failedQuoteState) => failedQuoteState.reason instanceof ProviderGasError) && attemptNumber == this.retryOptions.retries) { log.error(`Failed to get quotes on Arbitrum due to provider gas error issue. Overriding error to return 0 quotes.`); return { results: [], blockNumber: BigNumber.from(0), approxGasUsedPerSuccessCall: 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); metric.putMetric('QuoteApproxGasUsedPerSuccessfulCall', approxGasUsedPerSuccessCall, MetricLoggerUnit.Count); metric.putMetric('QuoteNumRetryLoops', finalAttemptNumber - 1, MetricLoggerUnit.Count); metric.putMetric('QuoteTotalCallsToProvider', totalCallsMade, MetricLoggerUnit.Count); metric.putMetric('QuoteExpectedCallsToProvider', expectedCallsMade, MetricLoggerUnit.Count); metric.putMetric('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) { 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) => { 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: null, initializedTicksCrossedList: null, }; } return { amount, quote: quoteResult.result[0], sqrtPriceX96AfterList: quoteResult.result[1], initializedTicksCrossedList: quoteResult.result[2], gasEstimate: quoteResult.result[3], }; }); 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 quotes for V3 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) { 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}`); return; } return new SuccessRateError(`Quote success rate below threshold of ${quoteMinSuccessRate}: ${successRate}`); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVvdGUtcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3YzL3F1b3RlLXByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNwRCxPQUFPLEtBQWtDLE1BQU0sYUFBYSxDQUFDO0FBQzdELE9BQU8sQ0FBQyxNQUFNLFFBQVEsQ0FBQztBQUN2QixPQUFPLEtBQUssTUFBTSxZQUFZLENBQUM7QUFFL0IsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNkNBQTZDLENBQUM7QUFDakYsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDL0QsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFekQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3JDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQThCbEQsTUFBTSxPQUFPLGtCQUFtQixTQUFRLEtBQUs7SUFBN0M7O1FBQ1MsU0FBSSxHQUFHLG9CQUFvQixDQUFDO0lBQ3JDLENBQUM7Q0FBQTtBQUNELE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxLQUFLO0lBQTNDOztRQUNTLFNBQUksR0FBRyxrQkFBa0IsQ0FBQztJQUNuQyxDQUFDO0NBQUE7QUFFRCxNQUFNLE9BQU8sd0JBQXlCLFNBQVEsS0FBSztJQUFuRDs7UUFDUyxTQUFJLEdBQUcsMEJBQTBCLENBQUM7SUFDM0MsQ0FBQztDQUFBO0FBRUQsTUFBTSxPQUFPLG9CQUFxQixTQUFRLEtBQUs7SUFBL0M7O1FBQ1MsU0FBSSxHQUFHLHNCQUFzQixDQUFDO0lBQ3ZDLENBQUM7Q0FBQTtBQUVEOzs7Ozs7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