Skip to content

Commit

Permalink
feat, test: support DutchV3
Browse files Browse the repository at this point in the history
  • Loading branch information
alanhwu committed Nov 21, 2024
1 parent 38e1990 commit 4cd9531
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 83 deletions.
4 changes: 2 additions & 2 deletions lib/entities/HardQuoteRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TradeType } from '@uniswap/sdk-core';
import { OrderType, UnsignedV2DutchOrder } from '@uniswap/uniswapx-sdk';
import { OrderType, UnsignedV2DutchOrder, UnsignedV3DutchOrder } from '@uniswap/uniswapx-sdk';
import { BigNumber, ethers, utils } from 'ethers';
import { v4 as uuidv4 } from 'uuid';

Expand All @@ -8,7 +8,7 @@ import { HardQuoteRequestBody } from '../handlers/hard-quote';
import { ProtocolVersion } from '../providers';

export class HardQuoteRequest {
public order: UnsignedV2DutchOrder;
public order: UnsignedV2DutchOrder | UnsignedV3DutchOrder;
private data: HardQuoteRequestBody;

public static fromHardRequestBody(body: HardQuoteRequestBody, orderType: OrderType): HardQuoteRequest {
Expand Down
215 changes: 136 additions & 79 deletions lib/handlers/hard-quote/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { KMSClient } from '@aws-sdk/client-kms';
import { TradeType } from '@uniswap/sdk-core';
import { KmsSigner } from '@uniswap/signer';
import { MetricLoggerUnit } from '@uniswap/smart-order-router';
import { CosignedV2DutchOrder, CosignerData, OrderType, UniswapXOrderParser } from '@uniswap/uniswapx-sdk';
import {
CosignedV2DutchOrder,
CosignedV3DutchOrder,
CosignerData,
OrderType,
UniswapXOrderParser,
UnsignedV2DutchOrder,
UnsignedV3DutchOrder,
V3CosignerData,
} from '@uniswap/uniswapx-sdk';
import { BigNumber, ethers } from 'ethers';
import Joi from 'joi';

import { POST_ORDER_ERROR_REASON } from '../../constants';
import { HardQuoteRequest, Metric, QuoteResponse } from '../../entities';
import { V2HardQuoteResponse } from '../../entities/V2HardQuoteResponse';
import { V3HardQuoteResponse } from '../../entities/V3HardQuoteResponse';
import { checkDefined } from '../../preconditions/preconditions';
import { ChainId } from '../../util/chains';
import { NoQuotesAvailable, OrderPostError, UnknownOrderCosignerError } from '../../util/errors';
Expand All @@ -22,7 +33,6 @@ import {
HardQuoteResponseData,
HardQuoteResponseDataJoi,
} from './schema';
import { V2HardQuoteResponse } from '../../entities/V2HardQuoteResponse';

const DEFAULT_EXCLUSIVITY_OVERRIDE_BPS = BigNumber.from(100); // non-exclusive fillers must override price by this much
const RESPONSE_LOG_TYPE = 'HardResponse';
Expand All @@ -46,18 +56,11 @@ export class QuoteHandler extends APIGLambdaHandler<
metric.putMetric(Metric.QUOTE_REQUESTED, 1, MetricLoggerUnit.Count);

const orderParser = new UniswapXOrderParser();
const orderType: OrderType = orderParser.getOrderTypeFromEncoded(requestBody.encodedInnerOrder, requestBody.tokenInChainId);

const orderType: OrderType = orderParser.getOrderTypeFromEncoded(
requestBody.encodedInnerOrder,
requestBody.tokenInChainId
);
const request = HardQuoteRequest.fromHardRequestBody(requestBody, orderType);


// if (orderType == OrderType.Dutch_V2) {

// } else if (orderType == OrderType.Dutch_V3) {

// } else {
// throw new Error('Unsupported order type');
// }
// re-create KmsClient every call to avoid clock skew issue
// https://github.com/aws/aws-sdk-js-v3/issues/6400
const kmsKeyId = checkDefined(process.env.KMS_KEY_ID, 'KMS_KEY_ID is not defined');
Expand All @@ -70,7 +73,6 @@ export class QuoteHandler extends APIGLambdaHandler<
log.error({ cosignerInReq: request.order.info.cosigner, expected: cosignerAddress }, 'Unknown cosigner');
throw new UnknownOrderCosignerError();
}

// Instead of decoding the order, we rely on frontend passing in the requestId
// from indicative quote
log.info({
Expand Down Expand Up @@ -102,17 +104,16 @@ export class QuoteHandler extends APIGLambdaHandler<
}
}

let cosignerData: CosignerData;
let cosignerData: CosignerData | V3CosignerData;
if (bestQuote) {
cosignerData = getCosignerData(request, bestQuote, orderType);
log.info({ bestQuote: bestQuote }, 'bestQuote');
} else {
cosignerData = getDefaultCosignerData(request);
cosignerData = getDefaultCosignerData(request, orderType);
log.info({ cosignerData: cosignerData }, 'open order with default cosignerData');
}
const cosignature = await cosigner.signDigest(request.order.cosignatureHash(cosignerData));
const cosignedOrder = CosignedV2DutchOrder.fromUnsignedOrder(request.order, cosignerData, cosignature);

const cosignedOrder = await createCosignedOrder(cosigner, request, cosignerData);
try {
metric.putMetric(Metric.QUOTE_POST_ATTEMPT, 1, MetricLoggerUnit.Count);
// if no quote and creating open order, create random new quoteId
Expand All @@ -125,7 +126,7 @@ export class QuoteHandler extends APIGLambdaHandler<
if (response.statusCode == 200 || response.statusCode == 201) {
metric.putMetric(Metric.QUOTE_200, 1, MetricLoggerUnit.Count);
metric.putMetric(Metric.QUOTE_LATENCY, Date.now() - start, MetricLoggerUnit.Milliseconds);
const hardResponse = new V2HardQuoteResponse(request, cosignedOrder);
const hardResponse = createHardQuoteResponse(request, cosignedOrder);
if (!bestQuote) {
// The RFQ responses are logged in getBestQuote()
// we log the Open Orders here
Expand Down Expand Up @@ -173,75 +174,70 @@ export class QuoteHandler extends APIGLambdaHandler<
}
}

export function getCosignerData(request: HardQuoteRequest, quote: QuoteResponse, orderType: OrderType): CosignerData {
if (orderType === OrderType.Dutch_V2) {
const decayStartTime = getDecayStartTime(request.tokenInChainId);
// default to open order with the original prices
let filler = ethers.constants.AddressZero;
let inputOverride = BigNumber.from(0);
const outputOverrides = request.order.info.outputs.map(() => BigNumber.from(0));
export function getCosignerData(
request: HardQuoteRequest,
quote: QuoteResponse,
orderType: OrderType
): CosignerData | V3CosignerData {
switch (orderType) {
case OrderType.Dutch_V2: {
const decayStartTime = getDecayStartTime(request.tokenInChainId);
let filler = ethers.constants.AddressZero;
let inputOverride = BigNumber.from(0);
const outputOverrides = request.order.info.outputs.map(() => BigNumber.from(0));

// if the quote is better, then increase amounts by the difference
if (request.type === TradeType.EXACT_INPUT) {
if (quote.amountOut.gt(request.totalOutputAmountStart)) {
const increase = quote.amountOut.sub(request.totalOutputAmountStart);
// give all the increase to the first (swapper) output
outputOverrides[0] = request.order.info.outputs[0].startAmount.add(increase);
if (quote.filler) {
filler = quote.filler;
if (request.type === TradeType.EXACT_INPUT) {
if (quote.amountOut.gt(request.totalOutputAmountStart)) {
const increase = quote.amountOut.sub(request.totalOutputAmountStart);
outputOverrides[0] = request.order.info.outputs[0].startAmount.add(increase);
if (quote.filler) {
filler = quote.filler;
}
}
}
} else {
if (quote.amountIn.lt(request.totalInputAmountStart)) {
inputOverride = quote.amountIn;
if (quote.filler) {
filler = quote.filler;
} else {
if (quote.amountIn.lt(request.totalInputAmountStart)) {
inputOverride = quote.amountIn;
if (quote.filler) {
filler = quote.filler;
}
}
}

const v2Data: CosignerData = {
decayStartTime,
decayEndTime: getDecayEndTime(request.tokenInChainId, decayStartTime),
exclusiveFiller: filler,
exclusivityOverrideBps: DEFAULT_EXCLUSIVITY_OVERRIDE_BPS,
inputOverride,
outputOverrides,
};
return v2Data;
}

return {
decayStartTime: decayStartTime,
decayEndTime: getDecayEndTime(request.tokenInChainId, decayStartTime),
exclusiveFiller: filler,
exclusivityOverrideBps: DEFAULT_EXCLUSIVITY_OVERRIDE_BPS,
inputOverride: inputOverride,
outputOverrides: outputOverrides,
};
}
// else if (orderType == OrderType.Dutch_V3) {
// return {
// decayStartBlock: 1,
// exclusiveFiller: ethers.constants.AddressZero,
// exclusivityOverrideBps: BigNumber.from(0),
// inputOverride: BigNumber.from(0),
// outputOverrides: request.order.info.outputs.map(() => BigNumber.from(0)),
// }
// }
else {
throw new Error('Unsupported order type');
case OrderType.Dutch_V3: {
const v3Data: V3CosignerData = {
decayStartBlock: 1,
exclusiveFiller: ethers.constants.AddressZero,
exclusivityOverrideBps: BigNumber.from(0),
inputOverride: BigNumber.from(0),
outputOverrides: request.order.info.outputs.map(() => BigNumber.from(0)),
};
return v3Data;
}

default:
throw new Error('Unsupported order type');
}
}

export function getDefaultCosignerData(request: HardQuoteRequest): CosignerData {
const decayStartTime = getDecayStartTime(request.tokenInChainId);
const filler = ethers.constants.AddressZero;
let inputOverride = BigNumber.from(0);
const outputOverrides = request.order.info.outputs.map(() => BigNumber.from(0));
if (request.type === TradeType.EXACT_INPUT) {
outputOverrides[0] = request.totalOutputAmountStart;
} else {
inputOverride = request.totalInputAmountStart;
export function getDefaultCosignerData(request: HardQuoteRequest, orderType: OrderType): CosignerData | V3CosignerData {
switch (orderType) {
case OrderType.Dutch_V2:
return getDefaultV2CosignerData(request);
case OrderType.Dutch_V3:
return getDefaultV3CosignerData(request);
default:
throw new Error('Unsupported order type');
}

return {
decayStartTime: decayStartTime,
decayEndTime: getDecayEndTime(request.tokenInChainId, decayStartTime),
exclusiveFiller: filler,
exclusivityOverrideBps: DEFAULT_EXCLUSIVITY_OVERRIDE_BPS,
inputOverride: inputOverride,
outputOverrides: outputOverrides,
};
}

function getDecayStartTime(chainId: number): number {
Expand All @@ -266,3 +262,64 @@ function getDecayEndTime(chainId: number, startTime: number): number {
return startTime + 30; // 30 seconds
}
}

function createHardQuoteResponse(
request: HardQuoteRequest,
order: CosignedV2DutchOrder | CosignedV3DutchOrder
): V2HardQuoteResponse | V3HardQuoteResponse {
if (order instanceof CosignedV2DutchOrder) {
return new V2HardQuoteResponse(request, order);
} else if (order instanceof CosignedV3DutchOrder) {
return new V3HardQuoteResponse(request, order);
}
throw new Error('Unsupported order type');
}

async function createCosignedOrder(
cosigner: KmsSigner,
request: HardQuoteRequest,
cosignerData: CosignerData | V3CosignerData,
): Promise<CosignedV2DutchOrder | CosignedV3DutchOrder> {
if (request.order instanceof UnsignedV2DutchOrder) {
const v2CosignerData = cosignerData as CosignerData;
const cosignature = await cosigner.signDigest(request.order.cosignatureHash(v2CosignerData));
return CosignedV2DutchOrder.fromUnsignedOrder(request.order, v2CosignerData, cosignature);
} else if (request.order instanceof UnsignedV3DutchOrder) {
const v3CosignerData = cosignerData as V3CosignerData;
const cosignature = await cosigner.signDigest(request.order.cosignatureHash(v3CosignerData));
return CosignedV3DutchOrder.fromUnsignedOrder(request.order, v3CosignerData, cosignature);
} else {
throw new Error('Unsupported order type');
}
}

function getDefaultV2CosignerData(request: HardQuoteRequest): CosignerData {
const decayStartTime = getDecayStartTime(request.tokenInChainId);
const filler = ethers.constants.AddressZero;
let inputOverride = BigNumber.from(0);
const outputOverrides = request.order.info.outputs.map(() => BigNumber.from(0));
if (request.type === TradeType.EXACT_INPUT) {
outputOverrides[0] = request.totalOutputAmountStart;
} else {
inputOverride = request.totalInputAmountStart;
}

return {
decayStartTime: decayStartTime,
decayEndTime: getDecayEndTime(request.tokenInChainId, decayStartTime),
exclusiveFiller: filler,
exclusivityOverrideBps: DEFAULT_EXCLUSIVITY_OVERRIDE_BPS,
inputOverride: inputOverride,
outputOverrides: outputOverrides,
};
}

function getDefaultV3CosignerData(request: HardQuoteRequest): V3CosignerData {
return {
decayStartBlock: 1,
exclusiveFiller: ethers.constants.AddressZero,
exclusivityOverrideBps: BigNumber.from(0),
inputOverride: BigNumber.from(0),
outputOverrides: request.order.info.outputs.map(() => BigNumber.from(0)),
};
}
4 changes: 2 additions & 2 deletions test/handlers/hard-quote/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KMSClient } from '@aws-sdk/client-kms';
import { TradeType } from '@uniswap/sdk-core';
import { CosignedV2DutchOrder, OrderType, UnsignedV2DutchOrder, UnsignedV2DutchOrderInfo } from '@uniswap/uniswapx-sdk';
import { CosignedV2DutchOrder, CosignerData, OrderType, UnsignedV2DutchOrder, UnsignedV2DutchOrderInfo } from '@uniswap/uniswapx-sdk';
import { createMetricsLogger } from 'aws-embedded-metrics';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
// import axios from 'axios';
Expand Down Expand Up @@ -303,7 +303,7 @@ describe('Quote handler', () => {
it('updates decay times reasonably', async () => {
const request = await getRequest(getOrder({ cosigner: cosignerWallet.address }));
const now = Math.floor(Date.now() / 1000);
const cosignerData = getCosignerData(new HardQuoteRequest(request, OrderType.Dutch_V2), getQuoteResponse({}), OrderType.Dutch_V2);
const cosignerData: CosignerData = getCosignerData(new HardQuoteRequest(request, OrderType.Dutch_V2), getQuoteResponse({}), OrderType.Dutch_V2) as CosignerData;
expect(cosignerData.decayStartTime).toBeGreaterThan(now);
expect(cosignerData.decayStartTime).toBeLessThan(now + 1000);
expect(cosignerData.decayEndTime).toBeGreaterThan(cosignerData.decayStartTime);
Expand Down
Loading

0 comments on commit 4cd9531

Please sign in to comment.