From 604fcb1b19a39f114875cfd86d50f1d9d6c5ef7b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 6 Nov 2023 13:19:48 -0500 Subject: [PATCH 1/3] log opposing quote iff real quote is valid --- lib/entities/QuoteRequest.ts | 9 ++++++++ lib/quoters/WebhookQuoter.ts | 11 ++++++++- test/handlers/quote/handler.test.ts | 36 +++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/entities/QuoteRequest.ts b/lib/entities/QuoteRequest.ts index b26bf923..e96fb21a 100644 --- a/lib/entities/QuoteRequest.ts +++ b/lib/entities/QuoteRequest.ts @@ -89,6 +89,15 @@ export class QuoteRequest { ...(this.quoteId && { quoteId: this.quoteId }), }; } + + public toOpposingRequest(): QuoteRequest { + const opposingJSON = this.toOpposingCleanJSON(); + return new QuoteRequest({ + ...opposingJSON, + amount: BigNumber.from(opposingJSON.amount), + type: TradeType[opposingJSON.type as keyof typeof TradeType], + }); +} public get requestId(): string { return this.data.requestId; diff --git a/lib/quoters/WebhookQuoter.ts b/lib/quoters/WebhookQuoter.ts index 98dbb393..4d78fa20 100644 --- a/lib/quoters/WebhookQuoter.ts +++ b/lib/quoters/WebhookQuoter.ts @@ -96,7 +96,7 @@ export class WebhookQuoter implements Quoter { ...(!!headers && { headers }), }; - const [hookResponse] = await Promise.all([ + const [hookResponse, opposite] = await Promise.all([ axios.post(endpoint, cleanRequest, axiosConfig), axios.post(endpoint, opposingCleanRequest, axiosConfig), ]); @@ -170,6 +170,15 @@ export class WebhookQuoter implements Quoter { request.requestId } for endpoint ${endpoint} successful quote: ${request.amount.toString()} -> ${quote.toString()}}` ); + + //iff valid quote, log the opposing side as well + const opposingRequest = request.toOpposingRequest(); + const opposingResponse = QuoteResponse.fromRFQ(opposingRequest, opposite.data, opposingRequest.type).response; + this.log.info({ + eventType: 'QuoteResponse', + body: { ...opposingResponse.toLog(), offerer: opposingResponse.swapper }, + }); + return response; } catch (e) { metric.putMetric(Metric.RFQ_FAIL_ERROR, 1, MetricLoggerUnit.Count); diff --git a/test/handlers/quote/handler.test.ts b/test/handlers/quote/handler.test.ts index 49c3923b..08e46e6b 100644 --- a/test/handlers/quote/handler.test.ts +++ b/test/handlers/quote/handler.test.ts @@ -229,6 +229,19 @@ describe('Quote handler', () => { quoteId: QUOTE_ID, }, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + amountOut: amountIn.mul(3).toString(), + requestId: request.requestId, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + amountIn: request.amount, + swapper: request.swapper, + chainId: request.tokenInChainId, + quoteId: QUOTE_ID, + }, + }); }); const response: APIGatewayProxyResult = await getQuoteHandler(quoters).handler( @@ -274,6 +287,16 @@ describe('Quote handler', () => { ...responseFromRequest(request, { amountOut: amountIn.mul(2).toString() }), }, }); + }).mockImplementationOnce((_endpoint, _req, options: any) => { + expect(options.headers['X-Authentication']).toEqual('1234'); + const res = responseFromRequest(request, { amountOut: amountIn.mul(3).toString() }); + return Promise.resolve({ + data: { + ...res, + tokenIn: res.tokenOut, + tokenOut: res.tokenIn, + }, + }); }); const response: APIGatewayProxyResult = await getQuoteHandler(quoters).handler( @@ -404,6 +427,19 @@ describe('Quote handler', () => { quoteId: QUOTE_ID, }, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + amountOut: amountIn.div(2).toString(), + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + amountIn: request.amount, + swapper: request.swapper, + chainId: request.tokenInChainId, + requestId: request.requestId, + quoteId: QUOTE_ID, + }, + }); }); const response: APIGatewayProxyResult = await getQuoteHandler(quoters).handler( From 27f9fd0509d616d90c0616203247f1012a8f9e21 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 6 Nov 2023 13:31:33 -0500 Subject: [PATCH 2/3] fix unit tests --- test/entities/QuoteRequest.test.ts | 15 ++++++++ test/providers/quoters/WebhookQuoter.test.ts | 40 ++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/test/entities/QuoteRequest.test.ts b/test/entities/QuoteRequest.test.ts index 546edb0f..b6912eb8 100644 --- a/test/entities/QuoteRequest.test.ts +++ b/test/entities/QuoteRequest.test.ts @@ -53,4 +53,19 @@ describe('QuoteRequest', () => { numOutputs: 1, }); }); + + it('toOpposingRequest', async () => { + const opposingRequest = request.toOpposingRequest(); + expect(opposingRequest.toCleanJSON()).toEqual({ + tokenInChainId: CHAIN_ID, + tokenOutChainId: CHAIN_ID, + requestId: REQUEST_ID, + tokenIn: TOKEN_OUT, + tokenOut: TOKEN_IN, + amount: ethers.utils.parseEther('1').toString(), + swapper: SWAPPER, + type: 'EXACT_OUTPUT', + numOutputs: 1, + }); + }); }); diff --git a/test/providers/quoters/WebhookQuoter.test.ts b/test/providers/quoters/WebhookQuoter.test.ts index d498a9b3..8649e5b0 100644 --- a/test/providers/quoters/WebhookQuoter.test.ts +++ b/test/providers/quoters/WebhookQuoter.test.ts @@ -69,6 +69,14 @@ describe('WebhookQuoter tests', () => { return Promise.resolve({ data: quote, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + ...quote, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + } + }); }); const response = await webhookQuoter.quote(request); @@ -82,6 +90,14 @@ describe('WebhookQuoter tests', () => { return Promise.resolve({ data: quote, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + ...quote, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + } + }); }); await webhookQuoter.quote(request); @@ -168,6 +184,14 @@ describe('WebhookQuoter tests', () => { return Promise.resolve({ data: quote, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + ...quote, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + } + }); }); const response = await webhookQuoter.quote(request); @@ -202,6 +226,14 @@ describe('WebhookQuoter tests', () => { return Promise.resolve({ data: quote, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + ...quote, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + } + }); }); const response = await webhookQuoter.quote(request); @@ -230,6 +262,14 @@ describe('WebhookQuoter tests', () => { return Promise.resolve({ data: quote, }); + }).mockImplementationOnce((_endpoint, _req, _options) => { + return Promise.resolve({ + data: { + ...quote, + tokenIn: request.tokenOut, + tokenOut: request.tokenIn, + } + }); }); const response = await quoter.quote(request); From 6505b310ed9a74cdd6ef619ebde5ccaf85f439e9 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 6 Nov 2023 16:04:38 -0500 Subject: [PATCH 3/3] add sanity checks before logging opposing quote --- lib/quoters/WebhookQuoter.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/quoters/WebhookQuoter.ts b/lib/quoters/WebhookQuoter.ts index 4d78fa20..5a01093c 100644 --- a/lib/quoters/WebhookQuoter.ts +++ b/lib/quoters/WebhookQuoter.ts @@ -173,11 +173,13 @@ export class WebhookQuoter implements Quoter { //iff valid quote, log the opposing side as well const opposingRequest = request.toOpposingRequest(); - const opposingResponse = QuoteResponse.fromRFQ(opposingRequest, opposite.data, opposingRequest.type).response; - this.log.info({ - eventType: 'QuoteResponse', - body: { ...opposingResponse.toLog(), offerer: opposingResponse.swapper }, - }); + const opposingResponse = QuoteResponse.fromRFQ(opposingRequest, opposite.data, opposingRequest.type); + if (opposingResponse && !isNonQuote(opposingRequest, opposite, opposingResponse.response) && !opposingResponse.validation.error) { + this.log.info({ + eventType: 'QuoteResponse', + body: { ...opposingResponse.response.toLog(), offerer: opposingResponse.response.swapper }, + }); + } return response; } catch (e) {