From d3f0a5eabf8543c913b2e85023ef341ef275a355 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 15 May 2024 15:43:24 +0100 Subject: [PATCH 1/7] Pull auction params from client --- lib/config/ChainConfigManager.ts | 5 +++++ lib/entities/context/DutchQuoteContext.ts | 9 +++++++-- lib/entities/quote/DutchQuote.ts | 4 ++-- lib/entities/quote/DutchQuoteFactory.ts | 2 +- lib/entities/quote/DutchV1Quote.ts | 2 +- lib/entities/quote/DutchV2Quote.ts | 2 +- lib/entities/request/DutchV1Request.ts | 6 ++++++ lib/entities/request/DutchV2Request.ts | 4 ++++ lib/util/validator.ts | 4 ++++ test/integ/base.test.ts | 9 +-------- test/integ/quote-relay.test.ts | 4 ++-- 11 files changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index d176b1f0..c1113d8d 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -76,6 +76,11 @@ export abstract class ChainConfigManager { [ChainId.ARBITRUM_ONE]: { routingTypes: { [RoutingType.CLASSIC]: {}, + [RoutingType.DUTCH_V2]: { + skipRFQ: true, + priceImprovementBps: 5, + deadlineBufferSecs: 20, + }, }, alarmEnabled: true, }, diff --git a/lib/entities/context/DutchQuoteContext.ts b/lib/entities/context/DutchQuoteContext.ts index a4ce739c..e5521926 100644 --- a/lib/entities/context/DutchQuoteContext.ts +++ b/lib/entities/context/DutchQuoteContext.ts @@ -137,7 +137,7 @@ export class DutchQuoteContext implements QuoteContext { } const quoteConfig = ChainConfigManager.getQuoteConfig(quote.chainId, this.request.routingType); - if (quoteConfig.skipRFQ) { + if (quoteConfig.skipRFQ || this.request.config.forceSyntheticQuotes) { this.log.info('RFQ Orders are disabled in config'); return null; } @@ -203,7 +203,12 @@ export class DutchQuoteContext implements QuoteContext { const quoteConfig = ChainConfigManager.getQuoteConfig(chainId, this.request.routingType); // if the useSyntheticQuotes override is not set by client or server and we're not skipping RFQ, return null // if we are skipping RFQ, we need a synthetic quote - if (!this.request.config.useSyntheticQuotes && !syntheticStatus.syntheticEnabled && !quoteConfig.skipRFQ) { + if ( + !this.request.config.useSyntheticQuotes && + !this.request.config.forceSyntheticQuotes && + !syntheticStatus.syntheticEnabled && + !quoteConfig.skipRFQ + ) { this.log.info('Synthetic not enabled, skipping synthetic'); return null; } diff --git a/lib/entities/quote/DutchQuote.ts b/lib/entities/quote/DutchQuote.ts index ea80d8e2..77f762e8 100644 --- a/lib/entities/quote/DutchQuote.ts +++ b/lib/entities/quote/DutchQuote.ts @@ -85,7 +85,7 @@ export abstract class DutchQuote implements IQuote public readonly createdAt: string; public derived: DutchQuoteDerived; public routingType: RoutingType.DUTCH_LIMIT | RoutingType.DUTCH_V2; - public abstract readonly defaultDeadlienBufferInSecs: number; + public abstract readonly defaultDeadlineBufferInSecs: number; // Add 1bps price improvmement to favor Dutch public static defaultPriceImprovementBps = 1; @@ -170,7 +170,7 @@ export abstract class DutchQuote implements IQuote return this.request.config.deadlineBufferSecs; } const quoteConfig = ChainConfigManager.getQuoteConfig(this.chainId, this.request.routingType); - return quoteConfig.deadlineBufferSecs ?? this.defaultDeadlienBufferInSecs; + return this.request.config.deadlineBufferSecs ?? quoteConfig.deadlineBufferSecs ?? this.defaultDeadlineBufferInSecs; } public get portionAmountOutStart(): BigNumber { diff --git a/lib/entities/quote/DutchQuoteFactory.ts b/lib/entities/quote/DutchQuoteFactory.ts index d510049b..b5845eeb 100644 --- a/lib/entities/quote/DutchQuoteFactory.ts +++ b/lib/entities/quote/DutchQuoteFactory.ts @@ -63,7 +63,7 @@ export class DutchQuoteFactory { const priceImprovedStartAmounts = DutchQuote.applyPriceImprovement( { amountIn: quote.amountInGasAdjusted, amountOut: quote.amountOutGasAdjusted }, request.info.type, - quoteConfig.priceImprovementBps + request.config.priceImprovementBps ?? quoteConfig.priceImprovementBps ); const startAmounts = DutchQuote.applyPreSwapGasAdjustment(priceImprovedStartAmounts, quote); diff --git a/lib/entities/quote/DutchV1Quote.ts b/lib/entities/quote/DutchV1Quote.ts index 73ac7cc4..939bf9b0 100644 --- a/lib/entities/quote/DutchV1Quote.ts +++ b/lib/entities/quote/DutchV1Quote.ts @@ -16,7 +16,7 @@ import { generateRandomNonce } from '../../util/nonce'; export class DutchV1Quote extends DutchQuote implements IQuote { public routingType: RoutingType.DUTCH_LIMIT = RoutingType.DUTCH_LIMIT; - public readonly defaultDeadlienBufferInSecs: number = DEFAULT_DEADLINE_BUFFER_SECS; + public readonly defaultDeadlineBufferInSecs: number = DEFAULT_DEADLINE_BUFFER_SECS; public toJSON(): DutchQuoteDataJSON { return { diff --git a/lib/entities/quote/DutchV2Quote.ts b/lib/entities/quote/DutchV2Quote.ts index 886ad134..2f03edbd 100644 --- a/lib/entities/quote/DutchV2Quote.ts +++ b/lib/entities/quote/DutchV2Quote.ts @@ -32,7 +32,7 @@ export type DutchV2QuoteDataJSON = SharedOrderQuoteDataJSON & { export class DutchV2Quote extends DutchQuote implements IQuote { public readonly routingType: RoutingType.DUTCH_V2 = RoutingType.DUTCH_V2; - public readonly defaultDeadlienBufferInSecs: number = DEFAULT_V2_DEADLINE_BUFFER_SECS; + public readonly defaultDeadlineBufferInSecs: number = DEFAULT_V2_DEADLINE_BUFFER_SECS; public toJSON(): DutchV2QuoteDataJSON { return { diff --git a/lib/entities/request/DutchV1Request.ts b/lib/entities/request/DutchV1Request.ts index db2f3377..67b2b4f5 100644 --- a/lib/entities/request/DutchV1Request.ts +++ b/lib/entities/request/DutchV1Request.ts @@ -17,6 +17,8 @@ export interface DutchConfig { deadlineBufferSecs?: number; useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + forceSyntheticQuotes?: boolean; + priceImprovementBps?: number; } export interface DutchQuoteRequestInfo extends QuoteRequestInfo { @@ -32,6 +34,8 @@ export interface DutchConfigJSON { deadlineBufferSecs?: number; useSyntheticQuotes?: boolean; gasAdjustmentBps?: number; + forceSyntheticQuotes?: boolean; + priceImprovementBps?: number; } export class DutchV1Request implements QuoteRequest { @@ -52,6 +56,8 @@ export class DutchV1Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, + forceSyntheticQuotes: body.forceSyntheticQuotes, + priceImprovementBps: body.priceImprovementBps, } ); } diff --git a/lib/entities/request/DutchV2Request.ts b/lib/entities/request/DutchV2Request.ts index d8af8b90..a9eae0d6 100644 --- a/lib/entities/request/DutchV2Request.ts +++ b/lib/entities/request/DutchV2Request.ts @@ -8,6 +8,8 @@ export interface DutchV2Config { deadlineBufferSecs?: number; useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + forceSyntheticQuotes?: boolean; + priceImprovementBps?: number; } export interface DutchV2ConfigJSON extends Omit { @@ -30,6 +32,8 @@ export class DutchV2Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, + forceSyntheticQuotes: body.forceSyntheticQuotes, + priceImprovementBps: body.priceImprovementBps, } ); } diff --git a/lib/util/validator.ts b/lib/util/validator.ts index 2256aa58..fd443a19 100644 --- a/lib/util/validator.ts +++ b/lib/util/validator.ts @@ -111,6 +111,8 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), + forceSyntheticQuotes: Joi.boolean().optional(), + priceImprovementBps: FieldValidator.bps.optional(), }); // extends a classic request config, but requires a gasToken and has optional parameters for the fee auction @@ -131,5 +133,7 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), + forceSyntheticQuotes: Joi.boolean().optional(), + priceImprovementBps: FieldValidator.bps.optional(), }); } diff --git a/test/integ/base.test.ts b/test/integ/base.test.ts index 2abd7532..5609782c 100644 --- a/test/integ/base.test.ts +++ b/test/integ/base.test.ts @@ -1,14 +1,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { ZERO } from '@uniswap/router-sdk'; import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk-core'; -import { - DAI_MAINNET, - parseAmount, - USDC_MAINNET, - USDT_MAINNET, - WBTC_MAINNET, - WETH9, -} from '@uniswap/smart-order-router'; +import { DAI_MAINNET, parseAmount, USDC_MAINNET, USDT_MAINNET, WBTC_MAINNET, WETH9 } from '@uniswap/smart-order-router'; import { fail } from 'assert'; import axiosStatic, { AxiosRequestConfig, AxiosResponse } from 'axios'; import axiosRetry from 'axios-retry'; diff --git a/test/integ/quote-relay.test.ts b/test/integ/quote-relay.test.ts index c32b5856..7bdf9024 100644 --- a/test/integ/quote-relay.test.ts +++ b/test/integ/quote-relay.test.ts @@ -114,7 +114,7 @@ describe.skip('relayQuote', function () { expect(order.info.universalRouterCalldata).to.not.be.undefined; }); - it('missing gasToken in request config' , async () => { + it('missing gasToken in request config', async () => { const quoteReq: QuoteRequestBodyJSON = { requestId: 'id', tokenIn: USDC_MAINNET.address, @@ -140,7 +140,7 @@ describe.skip('relayQuote', function () { errorCode: 'VALIDATION_ERROR', }, }); - }) + }); }); }); } From a6471d8fe07c8efe651d9aca8b8c932bc7220cae Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 16 May 2024 12:48:09 +0100 Subject: [PATCH 2/7] Update field name and add tests --- lib/config/ChainConfigManager.ts | 4 +- lib/entities/context/DutchQuoteContext.ts | 6 +- lib/entities/request/DutchV1Request.ts | 6 +- lib/entities/request/DutchV2Request.ts | 4 +- lib/util/validator.ts | 4 +- .../lib/config/ChainConfigManager.test.ts | 176 +++++++++++++++++- .../context/DutchQuoteContext.test.ts | 76 ++++++++ 7 files changed, 255 insertions(+), 21 deletions(-) diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index c1113d8d..2ccd7bf1 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -9,7 +9,7 @@ type IntentOverrides = { }; export type DutchOverrides = IntentOverrides & { - skipRFQ?: boolean; + forceOpenOrders?: boolean; priceImprovementBps?: number; }; @@ -77,7 +77,7 @@ export abstract class ChainConfigManager { routingTypes: { [RoutingType.CLASSIC]: {}, [RoutingType.DUTCH_V2]: { - skipRFQ: true, + forceOpenOrders: true, priceImprovementBps: 5, deadlineBufferSecs: 20, }, diff --git a/lib/entities/context/DutchQuoteContext.ts b/lib/entities/context/DutchQuoteContext.ts index e5521926..76c97b64 100644 --- a/lib/entities/context/DutchQuoteContext.ts +++ b/lib/entities/context/DutchQuoteContext.ts @@ -137,7 +137,7 @@ export class DutchQuoteContext implements QuoteContext { } const quoteConfig = ChainConfigManager.getQuoteConfig(quote.chainId, this.request.routingType); - if (quoteConfig.skipRFQ || this.request.config.forceSyntheticQuotes) { + if (quoteConfig.forceOpenOrders || this.request.config.forceOpenOrders) { this.log.info('RFQ Orders are disabled in config'); return null; } @@ -205,9 +205,9 @@ export class DutchQuoteContext implements QuoteContext { // if we are skipping RFQ, we need a synthetic quote if ( !this.request.config.useSyntheticQuotes && - !this.request.config.forceSyntheticQuotes && + !this.request.config.forceOpenOrders && !syntheticStatus.syntheticEnabled && - !quoteConfig.skipRFQ + !quoteConfig.forceOpenOrders ) { this.log.info('Synthetic not enabled, skipping synthetic'); return null; diff --git a/lib/entities/request/DutchV1Request.ts b/lib/entities/request/DutchV1Request.ts index 67b2b4f5..43d5bb74 100644 --- a/lib/entities/request/DutchV1Request.ts +++ b/lib/entities/request/DutchV1Request.ts @@ -17,7 +17,7 @@ export interface DutchConfig { deadlineBufferSecs?: number; useSyntheticQuotes: boolean; gasAdjustmentBps?: number; - forceSyntheticQuotes?: boolean; + forceOpenOrders?: boolean; priceImprovementBps?: number; } @@ -34,7 +34,7 @@ export interface DutchConfigJSON { deadlineBufferSecs?: number; useSyntheticQuotes?: boolean; gasAdjustmentBps?: number; - forceSyntheticQuotes?: boolean; + forceOpenOrders?: boolean; priceImprovementBps?: number; } @@ -56,7 +56,7 @@ export class DutchV1Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, - forceSyntheticQuotes: body.forceSyntheticQuotes, + forceOpenOrders: body.forceOpenOrders, priceImprovementBps: body.priceImprovementBps, } ); diff --git a/lib/entities/request/DutchV2Request.ts b/lib/entities/request/DutchV2Request.ts index a9eae0d6..3f59bbd7 100644 --- a/lib/entities/request/DutchV2Request.ts +++ b/lib/entities/request/DutchV2Request.ts @@ -8,7 +8,7 @@ export interface DutchV2Config { deadlineBufferSecs?: number; useSyntheticQuotes: boolean; gasAdjustmentBps?: number; - forceSyntheticQuotes?: boolean; + forceOpenOrders?: boolean; priceImprovementBps?: number; } @@ -32,7 +32,7 @@ export class DutchV2Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, - forceSyntheticQuotes: body.forceSyntheticQuotes, + forceOpenOrders: body.forceOpenOrders, priceImprovementBps: body.priceImprovementBps, } ); diff --git a/lib/util/validator.ts b/lib/util/validator.ts index fd443a19..0366f379 100644 --- a/lib/util/validator.ts +++ b/lib/util/validator.ts @@ -111,7 +111,7 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), - forceSyntheticQuotes: Joi.boolean().optional(), + forceOpenOrders: Joi.boolean().optional(), priceImprovementBps: FieldValidator.bps.optional(), }); @@ -133,7 +133,7 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), - forceSyntheticQuotes: Joi.boolean().optional(), + forceOpenOrders: Joi.boolean().optional(), priceImprovementBps: FieldValidator.bps.optional(), }); } diff --git a/test/unit/lib/config/ChainConfigManager.test.ts b/test/unit/lib/config/ChainConfigManager.test.ts index d69ae5f8..cd3d3597 100644 --- a/test/unit/lib/config/ChainConfigManager.test.ts +++ b/test/unit/lib/config/ChainConfigManager.test.ts @@ -322,7 +322,7 @@ describe('ChainConfigManager', () => { expect(req).toBeDefined(); }); - it('skipRFQ prevents RFQ from running', async () => { + it('forceOpenOrders prevents RFQ from running', async () => { const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; for (const routingType of routingTypes) { // First show that RFQ is used by default @@ -392,7 +392,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [routingType]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -414,7 +414,7 @@ describe('ChainConfigManager', () => { } }); - it('skipRFQ forces synthetic quote', async () => { + it('forceOpenOrders forces synthetic quote', async () => { const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; for (const routingType of routingTypes) { setChainConfigManager( @@ -423,7 +423,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [routingType]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -434,7 +434,7 @@ describe('ChainConfigManager', () => { [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], } ); - // Setting useSyntheticQuotes: false should be overridden by skipRFQ + // Setting useSyntheticQuotes: false should be overridden by forceOpenOrders let req, context, rfqQuote; switch (routingType) { case RoutingType.DUTCH_LIMIT: { @@ -490,7 +490,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { priceImprovementBps: 1, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -554,7 +554,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { priceImprovementBps: 10, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -585,7 +585,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [RoutingType.DUTCH_LIMIT]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -624,6 +624,83 @@ describe('ChainConfigManager', () => { expect(quote.amountOutStart).toEqual(expectedOut); }); + it('Client specified BPS override is given priority when set', async () => { + const clientBps = 100; + const serverBps = 10; + const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; + for (const routingType of routingTypes) { + setChainConfigManager( + { + [ChainId.MAINNET]: { + routingTypes: { + [RoutingType.CLASSIC]: {}, + [routingType]: { + priceImprovementBps: serverBps, + forceOpenOrders: true, + }, + }, + alarmEnabled: false, + }, + }, + { + [RoutingType.DUTCH_LIMIT]: [RoutingType.CLASSIC], + [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], + } + ); + + let req, context, rfqQuote; + switch (routingType) { + case RoutingType.DUTCH_LIMIT: { + req = makeDutchRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: undefined, priceImprovementBps: clientBps } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.DUTCH_V2: { + req = makeDutchV2Request( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: undefined, priceImprovementBps: clientBps } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchV2QuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + default: + throw new Error('Unknown routing type'); + } + + const classicQuote = createClassicQuote( + { quote: AMOUNT_LARGE, quoteGasAdjusted: AMOUNT_LARGE_GAS_ADJUSTED }, + { type: 'EXACT_INPUT', tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET } + ); + + const quote = await context.resolve({ + [req.key()]: rfqQuote, + [context.classicKey]: classicQuote, + }); + if (quote?.routingType != routingType) { + throw new Error(`Unexpected routing type in quote ${quote?.routingType}`); + } + expect(quote.quoteType).toEqual(QuoteType.SYNTHETIC); + // Client BPS is used + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(AMOUNT_LARGE_GAS_ADJUSTED) + .mul(BPS + clientBps) + .div(BPS) + .toString() + ); + } + }); + it('stdAuctionPeriodSecs is used when set', async () => { setChainConfigManager( { @@ -743,7 +820,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { deadlineBufferSecs: 9999, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -811,5 +888,86 @@ describe('ChainConfigManager', () => { expect(quote.deadlineBufferSecs).toEqual(9999); } }); + + it('Client specified deadlineBufferSecs is given priority when set', async () => { + const clientDeadline = 999; + const serverDeadline = 9999; + const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2, RoutingType.RELAY]; + for (const routingType of routingTypes) { + setChainConfigManager( + { + [ChainId.MAINNET]: { + routingTypes: { + [RoutingType.CLASSIC]: {}, + [routingType]: { + deadlineBufferSecs: serverDeadline, + forceOpenOrders: true, + }, + }, + alarmEnabled: false, + }, + }, + { + [RoutingType.DUTCH_LIMIT]: [RoutingType.CLASSIC], + [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], + } + ); + + let req, context, rfqQuote; + switch (routingType) { + case RoutingType.DUTCH_LIMIT: { + req = makeDutchRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: clientDeadline } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.DUTCH_V2: { + req = makeDutchV2Request( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: clientDeadline } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchV2QuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.RELAY: { + req = makeRelayRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { deadlineBufferSecs: clientDeadline } + ); + context = new RelayQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createRelayQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + default: + throw new Error('Unknown routing type'); + } + + const classicQuote = createClassicQuote( + { quote: AMOUNT_LARGE, quoteGasAdjusted: AMOUNT_LARGE_GAS_ADJUSTED }, + { type: 'EXACT_INPUT', tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET } + ); + const quote = await context.resolve({ + [req.key()]: rfqQuote, + [context.classicKey]: classicQuote, + }); + if (quote?.routingType != routingType) { + throw new Error(`Unexpected routing type in quote ${quote?.routingType}`); + } + expect(quote.deadlineBufferSecs).toEqual(clientDeadline); + } + }); }); }); diff --git a/test/unit/lib/entities/context/DutchQuoteContext.test.ts b/test/unit/lib/entities/context/DutchQuoteContext.test.ts index bd871f44..7bcd004b 100644 --- a/test/unit/lib/entities/context/DutchQuoteContext.test.ts +++ b/test/unit/lib/entities/context/DutchQuoteContext.test.ts @@ -335,6 +335,82 @@ describe('DutchQuoteContext', () => { expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual(filler); }); + it('uses synthetic if worse with forceOpenOrders=true and switch=false', async () => { + // Show that the test first will prefer RFQ + let request = makeDutchRequest({}, { forceOpenOrders: false }); + let context = new DutchQuoteContext(logger, request, makeProviders(false)); + const filler = '0x1111111111111111111111111111111111111111'; + const rfqQuote = createDutchQuote({ amountOut: '10000000000', filler }, 'EXACT_INPUT'); + expect(rfqQuote.filler).toEqual(filler); + const classicQuote = createClassicQuote( + { quote: '5000000000', quoteGasAdjusted: '4999500000' }, + { type: 'EXACT_INPUT' } + ); + context.dependencies(); + + let quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual(filler); + expect(quote?.amountOut.toString()).toEqual('10000000000'); + + // Setting forceOpenOrders will skip the RFQ + request = makeDutchRequest({}, { forceOpenOrders: true }); + context = new DutchQuoteContext(logger, request, makeProviders(false)); + context.dependencies(); + + quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual( + '0x0000000000000000000000000000000000000000' + ); + // Synthetic starts at quoteGasAdjusted + 1bp + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(4999500000) + .mul(BPS + DutchQuote.defaultPriceImprovementBps) + .div(BPS) + .toString() + ); + }); + + it('uses priceImprovementBps when present in request', async () => { + const bpsImprovement = 10; + const request = makeDutchRequest({}, { forceOpenOrders: true, priceImprovementBps: bpsImprovement }); + const context = new DutchQuoteContext(logger, request, makeProviders(false)); + const filler = '0x1111111111111111111111111111111111111111'; + const rfqQuote = createDutchQuote({ amountOut: '10000000000', filler }, 'EXACT_INPUT'); + expect(rfqQuote.filler).toEqual(filler); + const classicQuote = createClassicQuote( + { quote: '5000000000', quoteGasAdjusted: '4999500000' }, + { type: 'EXACT_INPUT' } + ); + context.dependencies(); + + const quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual( + '0x0000000000000000000000000000000000000000' + ); + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(4999500000) + .mul(BPS + bpsImprovement) + .div(BPS) + .toString() + ); + }); + it('uses synthetic if rfq quote is at least 300% better than classic; EXACT_IN', async () => { const context = new DutchQuoteContext(logger, QUOTE_REQUEST_DL, makeProviders(false)); const filler = '0x1111111111111111111111111111111111111111'; From a796e6c5d02ec238cd8d6ba063370823a5061776 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 16 May 2024 12:53:42 +0100 Subject: [PATCH 3/7] Remove Arbitrum change and add comments --- lib/config/ChainConfigManager.ts | 7 +------ lib/entities/request/DutchV1Request.ts | 2 ++ lib/entities/request/DutchV2Request.ts | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index 2ccd7bf1..c1efeb28 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -75,12 +75,7 @@ export abstract class ChainConfigManager { }, [ChainId.ARBITRUM_ONE]: { routingTypes: { - [RoutingType.CLASSIC]: {}, - [RoutingType.DUTCH_V2]: { - forceOpenOrders: true, - priceImprovementBps: 5, - deadlineBufferSecs: 20, - }, + [RoutingType.CLASSIC]: {} }, alarmEnabled: true, }, diff --git a/lib/entities/request/DutchV1Request.ts b/lib/entities/request/DutchV1Request.ts index 43d5bb74..6fc37c48 100644 --- a/lib/entities/request/DutchV1Request.ts +++ b/lib/entities/request/DutchV1Request.ts @@ -15,8 +15,10 @@ export interface DutchConfig { startTimeBufferSecs?: number; auctionPeriodSecs?: number; deadlineBufferSecs?: number; + // Setting true will include an Open Order in the quote comparison useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + // Setting true will force an Open Order and skip RFQ forceOpenOrders?: boolean; priceImprovementBps?: number; } diff --git a/lib/entities/request/DutchV2Request.ts b/lib/entities/request/DutchV2Request.ts index 3f59bbd7..8dab10b9 100644 --- a/lib/entities/request/DutchV2Request.ts +++ b/lib/entities/request/DutchV2Request.ts @@ -6,8 +6,10 @@ import { DutchQuoteRequestInfo } from './DutchV1Request'; export interface DutchV2Config { swapper: string; deadlineBufferSecs?: number; + // Setting true will include an Open Order in the quote comparison useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + // Setting true will force an Open Order and skip RFQ forceOpenOrders?: boolean; priceImprovementBps?: number; } From 61ade4b050dc29c0326f63b21ec8c89e3dd14b13 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Thu, 16 May 2024 17:31:39 +0100 Subject: [PATCH 4/7] Gate RFQ on flag and bump SDK version --- lib/config/ChainConfigManager.ts | 3 ++- lib/providers/quoters/RfqQuoter.ts | 6 +++++ package.json | 2 +- yarn.lock | 36 ++++++++++++++++++------------ 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index c1efeb28..b72bdfea 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -75,7 +75,8 @@ export abstract class ChainConfigManager { }, [ChainId.ARBITRUM_ONE]: { routingTypes: { - [RoutingType.CLASSIC]: {} + [RoutingType.CLASSIC]: {}, + [RoutingType.DUTCH_V2]: {}, }, alarmEnabled: true, }, diff --git a/lib/providers/quoters/RfqQuoter.ts b/lib/providers/quoters/RfqQuoter.ts index 7f97f139..12998fb9 100644 --- a/lib/providers/quoters/RfqQuoter.ts +++ b/lib/providers/quoters/RfqQuoter.ts @@ -11,6 +11,7 @@ import { log } from '../../util/log'; import { metrics } from '../../util/metrics'; import { generateRandomNonce } from '../../util/nonce'; import { Quoter, QuoterType } from './index'; +import { ChainConfigManager } from '../../config/ChainConfigManager'; export class RfqQuoter implements Quoter { static readonly type: QuoterType.UNISWAPX_RFQ; @@ -19,6 +20,11 @@ export class RfqQuoter implements Quoter { constructor(private rfqUrl: string, private serviceUrl: string, private paramApiKey: string) {} async quote(request: DutchQuoteRequest): Promise { + // Skip RFQ for forced Open Orders + const quoteConfig = ChainConfigManager.getQuoteConfig(request.info.tokenInChainId, request.routingType); + if (quoteConfig.forceOpenOrders || request.config.forceOpenOrders) { + return null; + } const swapper = request.config.swapper; const now = Date.now(); const portionEnabled = frontendAndUraEnablePortion(request.info.sendPortionEnabled); diff --git a/package.json b/package.json index e672a0ba..40fdf5fa 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@uniswap/router-sdk": "^1.9.0", "@uniswap/sdk-core": "^4.2.0", "@uniswap/smart-order-router": "^3.26.0", - "@uniswap/uniswapx-sdk": "^2.0.1-alpha.10", + "@uniswap/uniswapx-sdk": "^2.1.0-beta.4", "@uniswap/universal-router-sdk": "^1.9.0", "aws-cdk-lib": "2.85.0", "aws-embedded-metrics": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 0fdd8511..604a6738 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2070,6 +2070,14 @@ ethers "^5.3.1" tiny-invariant "^1.3.1" +"@uniswap/permit2-sdk@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@uniswap/permit2-sdk/-/permit2-sdk-1.2.1.tgz#7f562d96a26de0538508ba9ea2b2f02150572bf2" + integrity sha512-3eEVW6d1tepF9L4XLGhrGoVPKFBe/kQYRCvkzMmYu32amEeV2L8sg5TBJhrVZu+cu0srX7rh3nhOucNdMVOGzg== + dependencies: + ethers "^5.7.0" + tiny-invariant "^1.1.0" + "@uniswap/router-sdk@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.9.0.tgz#f35432ddc400ddb83985adc840c5898e2bc008cd" @@ -2081,10 +2089,10 @@ "@uniswap/v2-sdk" "^4.3.0" "@uniswap/v3-sdk" "^3.11.0" -"@uniswap/sdk-core@^4.0.3": - version "4.0.6" - resolved "https://registry.npmjs.org/@uniswap/sdk-core/-/sdk-core-4.0.6.tgz" - integrity sha512-6GzCVfnOiJtvo91zlF/VjnC2OEbBRThVclzrh7+Zmo8dBovXwSlXwqn3RkSWACn/XEOzAKH70TficfOWm6mWJA== +"@uniswap/sdk-core@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.2.0.tgz#9930f133baec9c1118d891ebf8fcba7f7efc153d" + integrity sha512-yXAMLHZRYYuh6KpN2nOlLTYBjGiopmI9WUB4Z0tyNkW4ZZub54cUt22eibpGbZAhRAMxclox9IPIs6wwrM3soQ== dependencies: "@ethersproject/address" "^5.0.2" big.js "^5.2.2" @@ -2093,10 +2101,10 @@ tiny-invariant "^1.1.0" toformat "^2.0.0" -"@uniswap/sdk-core@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.2.0.tgz#9930f133baec9c1118d891ebf8fcba7f7efc153d" - integrity sha512-yXAMLHZRYYuh6KpN2nOlLTYBjGiopmI9WUB4Z0tyNkW4ZZub54cUt22eibpGbZAhRAMxclox9IPIs6wwrM3soQ== +"@uniswap/sdk-core@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.0.0.tgz#7accd8c7372b14811777cbd1d92133d4de57c683" + integrity sha512-m5KmkqvvEz5rurRHdG3zGJappi1Crxet3B2dpgDFgq5MKoKd6qCbeChRkoQjTTJpY1MLGCsXj8ZtZ0/arisvLQ== dependencies: "@ethersproject/address" "^5.0.2" big.js "^5.2.2" @@ -2165,15 +2173,15 @@ resolved "https://registry.npmjs.org/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz" integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg== -"@uniswap/uniswapx-sdk@^2.0.1-alpha.10": - version "2.0.1-alpha.10" - resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-2.0.1-alpha.10.tgz#7ea770031fd59bb92121ad6c44c1a8710055b995" - integrity sha512-nDWJ9qLFBLId2lxJ8TMy15HIBlzQe2yFE6LEJSzEF1T5EGDFv1G/ioKQSm2LJg4cO11UfVouHKsn6rwYS6P9wA== +"@uniswap/uniswapx-sdk@^2.1.0-beta.4": + version "2.1.0-beta.4" + resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-2.1.0-beta.4.tgz#052bd0d3601e4e14eb76e744f4ab164b952756c9" + integrity sha512-kIbWoGiMrrpq265/99+zOBOlHP/t22lLYKW5C7yKT0HiSM+t671Nl6hvUftRkdf2J7CribOBH1koJyWjkMikOA== dependencies: "@ethersproject/bytes" "^5.7.0" "@ethersproject/providers" "^5.7.0" - "@uniswap/permit2-sdk" "^1.2.0" - "@uniswap/sdk-core" "^4.0.3" + "@uniswap/permit2-sdk" "^1.2.1" + "@uniswap/sdk-core" "^5.0.0" ethers "^5.7.0" "@uniswap/universal-router-sdk@^1.8.1": From 892123c57dd47cb22e55ca1a12a5a31977d46268 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 22 May 2024 17:57:43 +0100 Subject: [PATCH 5/7] Update comment --- lib/entities/context/DutchQuoteContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entities/context/DutchQuoteContext.ts b/lib/entities/context/DutchQuoteContext.ts index 76c97b64..f95f1966 100644 --- a/lib/entities/context/DutchQuoteContext.ts +++ b/lib/entities/context/DutchQuoteContext.ts @@ -202,7 +202,7 @@ export class DutchQuoteContext implements QuoteContext { const chainId = classicQuote.request.info.tokenInChainId; const quoteConfig = ChainConfigManager.getQuoteConfig(chainId, this.request.routingType); // if the useSyntheticQuotes override is not set by client or server and we're not skipping RFQ, return null - // if we are skipping RFQ, we need a synthetic quote + // if we are forcing Open Orders, we need a synthetic quote if ( !this.request.config.useSyntheticQuotes && !this.request.config.forceOpenOrders && From 4cf362117a74c81a6bed51d54ebe1aee61ba1916 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 22 May 2024 21:57:02 +0100 Subject: [PATCH 6/7] Small cleanup --- lib/entities/quote/DutchQuote.ts | 3 --- lib/providers/quoters/RfqQuoter.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/entities/quote/DutchQuote.ts b/lib/entities/quote/DutchQuote.ts index 77f762e8..3011bcd0 100644 --- a/lib/entities/quote/DutchQuote.ts +++ b/lib/entities/quote/DutchQuote.ts @@ -166,9 +166,6 @@ export abstract class DutchQuote implements IQuote // The number of seconds from endTime that the order should expire public get deadlineBufferSecs(): number { - if (this.request.config.deadlineBufferSecs !== undefined) { - return this.request.config.deadlineBufferSecs; - } const quoteConfig = ChainConfigManager.getQuoteConfig(this.chainId, this.request.routingType); return this.request.config.deadlineBufferSecs ?? quoteConfig.deadlineBufferSecs ?? this.defaultDeadlineBufferInSecs; } diff --git a/lib/providers/quoters/RfqQuoter.ts b/lib/providers/quoters/RfqQuoter.ts index 12998fb9..a3408666 100644 --- a/lib/providers/quoters/RfqQuoter.ts +++ b/lib/providers/quoters/RfqQuoter.ts @@ -3,6 +3,7 @@ import { ID_TO_CHAIN_ID, WRAPPED_NATIVE_CURRENCY } from '@uniswap/smart-order-ro import { BigNumber } from 'ethers'; import axios from './helpers'; +import { ChainConfigManager } from '../../config/ChainConfigManager'; import { BPS, frontendAndUraEnablePortion, NATIVE_ADDRESS, RoutingType } from '../../constants'; import { DutchQuoteRequest, Quote } from '../../entities'; import { DutchQuoteFactory } from '../../entities/quote/DutchQuoteFactory'; @@ -11,7 +12,6 @@ import { log } from '../../util/log'; import { metrics } from '../../util/metrics'; import { generateRandomNonce } from '../../util/nonce'; import { Quoter, QuoterType } from './index'; -import { ChainConfigManager } from '../../config/ChainConfigManager'; export class RfqQuoter implements Quoter { static readonly type: QuoterType.UNISWAPX_RFQ; From c89853b7d3e08437425c61b00bf4fc256a648b50 Mon Sep 17 00:00:00 2001 From: Cody Born Date: Wed, 22 May 2024 23:24:26 +0100 Subject: [PATCH 7/7] Set default deadlineBufferSecs to 60 --- lib/config/ChainConfigManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index b72bdfea..304f9de8 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -76,7 +76,9 @@ export abstract class ChainConfigManager { [ChainId.ARBITRUM_ONE]: { routingTypes: { [RoutingType.CLASSIC]: {}, - [RoutingType.DUTCH_V2]: {}, + [RoutingType.DUTCH_V2]: { + deadlineBufferSecs: 60 + }, }, alarmEnabled: true, },