From 5b816cb5fee77cfabcdcd6d1648dac15f36ede00 Mon Sep 17 00:00:00 2001 From: fmrsabino <3332770+fmrsabino@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:02:21 +0100 Subject: [PATCH 1/2] Add filledPercentage to SwapOrderTransactionInfo --- .../entities/swap-order-info.entity.ts | 7 +++ .../mappers/common/swap-order.mapper.spec.ts | 59 +++++++++++++++++++ .../mappers/common/swap-order.mapper.ts | 25 ++++++++ 3 files changed, 91 insertions(+) diff --git a/src/routes/transactions/entities/swap-order-info.entity.ts b/src/routes/transactions/entities/swap-order-info.entity.ts index 535290a856..89acc2eec5 100644 --- a/src/routes/transactions/entities/swap-order-info.entity.ts +++ b/src/routes/transactions/entities/swap-order-info.entity.ts @@ -51,12 +51,16 @@ export abstract class SwapOrderTransactionInfo extends TransactionInfo { @ApiProperty({ description: 'The timestamp when the order expires' }) expiresTimestamp: number; + @ApiProperty({ description: 'The filled percentage of the order' }) + filledPercentage: string; + protected constructor(args: { status: 'open' | 'fulfilled' | 'cancelled' | 'expired'; orderKind: 'buy' | 'sell'; sellToken: TokenInfo; buyToken: TokenInfo; expiresTimestamp: number; + filledPercentage: string; }) { super(TransactionInfoType.SwapOrder, null, null); this.type = TransactionInfoType.SwapOrder; @@ -65,6 +69,7 @@ export abstract class SwapOrderTransactionInfo extends TransactionInfo { this.sellToken = args.sellToken; this.buyToken = args.buyToken; this.expiresTimestamp = args.expiresTimestamp; + this.filledPercentage = args.filledPercentage; } } @@ -94,6 +99,7 @@ export class FulfilledSwapOrderTransactionInfo extends SwapOrderTransactionInfo expiresTimestamp: number; surplusFeeLabel: string | null; executionPriceLabel: string; + filledPercentage: string; }) { super({ ...args, status: 'fulfilled' }); this.status = 'fulfilled'; @@ -120,6 +126,7 @@ export class DefaultSwapOrderTransactionInfo extends SwapOrderTransactionInfo { buyToken: TokenInfo; expiresTimestamp: number; limitPriceLabel: string; + filledPercentage: string; }) { super(args); this.status = args.status; diff --git a/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts b/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts index 33e56e2e4d..b8b8cb5c46 100644 --- a/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts +++ b/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts @@ -106,6 +106,7 @@ describe('Swap Order Mapper tests', () => { symbol: buyToken.symbol, }, expiresTimestamp: order.validTo, + filledPercentage: expect.any(String), surplusLabel: expectedSurplus, executionPriceLabel: expectedExecutionPrice, humanDescription: null, @@ -157,6 +158,7 @@ describe('Swap Order Mapper tests', () => { symbol: buyToken.symbol, }, expiresTimestamp: order.validTo, + filledPercentage: expect.any(String), limitPriceLabel: expectedLimitPriceDescription, humanDescription: null, richDecodedInfo: null, @@ -380,4 +382,61 @@ describe('Swap Order Mapper tests', () => { ).toHaveBeenCalledWith(transaction, dataSize, chainId, null, null); }, ); + + it.each([ + // [executedAmount, amount, expectedFilledPercentage] + [1000, 1000, '100.00'], + [0, 1000, '0.00'], + [500, 1000, '50.00'], + [350, 1050, '33.33'], + ])( + 'should calculate the filled percentage correctly for buy orders', + async (executedAmount, amount, expected) => { + const chainId = faker.string.numeric(); + const transaction = multisigTransactionBuilder().build(); + const buyToken = tokenBuilder().with('decimals', 0).build(); + const sellToken = tokenBuilder().build(); + let order = orderBuilder() + .with( + 'status', + faker.helpers.arrayElement([ + 'open', + 'fulfilled', + 'cancelled', + 'expired', + ]), + ) + .build(); + if (order.kind === 'buy') { + order = { + ...order, + executedBuyAmount: BigInt(executedAmount), + buyAmount: BigInt(amount), + }; + } else if (order.kind === 'sell') { + order = { + ...order, + executedSellAmount: BigInt(executedAmount), + sellAmount: BigInt(amount), + }; + } else { + throw new Error('Invalid order kind'); + } + setPreSignatureDecoderMock.getOrderUid.mockReturnValue( + order.uid as `0x${string}`, + ); + swapsRepositoryMock.getOrder.mockResolvedValue(order); + tokenRepositoryMock.getToken.mockImplementation(({ address }) => { + if (address === order.buyToken) return Promise.resolve(buyToken); + if (address === order.sellToken) return Promise.resolve(sellToken); + return Promise.reject(new Error(`Token ${address} not found.`)); + }); + + const result = await target.mapSwapOrder(chainId, transaction, 0); + + expect(result).toMatchObject({ + filledPercentage: expected, + }); + }, + ); }); diff --git a/src/routes/transactions/mappers/common/swap-order.mapper.ts b/src/routes/transactions/mappers/common/swap-order.mapper.ts index feafd2875d..f1cec97501 100644 --- a/src/routes/transactions/mappers/common/swap-order.mapper.ts +++ b/src/routes/transactions/mappers/common/swap-order.mapper.ts @@ -169,6 +169,29 @@ export class SwapOrderMapper { return `1 ${sellToken.token.symbol} = ${ratio} ${buyToken.token.symbol}`; } + /** + * Returns the filled percentage of an order. + * The percentage is calculated as the ratio of the executed amount to the total amount. + * + * @param order - The order to calculate the filled percentage for. + * @private + */ + private _getFilledPercentage(order: Order): string { + let executed: number; + let total: number; + if (order.kind === 'buy') { + executed = Number(order.executedBuyAmount); + total = Number(order.buyAmount); + } else if (order.kind === 'sell') { + executed = Number(order.executedSellAmount); + total = Number(order.sellAmount); + } else { + throw new Error('Unknown order kind'); + } + + return ((executed / total) * 100).toFixed(2).toString(); + } + private _mapFulfilledOrderStatus(args: { buyToken: TokenAmount; sellToken: TokenAmount; @@ -194,6 +217,7 @@ export class SwapOrderMapper { args.sellToken, args.buyToken, ), + filledPercentage: this._getFilledPercentage(args.order), }); } @@ -228,6 +252,7 @@ export class SwapOrderMapper { buyToken: args.buyToken.toTokenInfo(), expiresTimestamp: args.order.validTo, limitPriceLabel: this._getLimitPriceLabel(args.sellToken, args.buyToken), + filledPercentage: this._getFilledPercentage(args.order), }); } } From 7c939f5a5ebab7af689bd6e372923f188ce5a215 Mon Sep 17 00:00:00 2001 From: fmrsabino <3332770+fmrsabino@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:45:40 +0100 Subject: [PATCH 2/2] PR comments --- .../entities/swap-order-info.entity.ts | 5 ++++- .../mappers/common/swap-order.mapper.spec.ts | 16 +++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/routes/transactions/entities/swap-order-info.entity.ts b/src/routes/transactions/entities/swap-order-info.entity.ts index 89acc2eec5..80a7b60d89 100644 --- a/src/routes/transactions/entities/swap-order-info.entity.ts +++ b/src/routes/transactions/entities/swap-order-info.entity.ts @@ -51,7 +51,10 @@ export abstract class SwapOrderTransactionInfo extends TransactionInfo { @ApiProperty({ description: 'The timestamp when the order expires' }) expiresTimestamp: number; - @ApiProperty({ description: 'The filled percentage of the order' }) + @ApiProperty({ + description: 'The filled percentage of the order', + examples: ['0.00', '50.75', '100.00'], + }) filledPercentage: string; protected constructor(args: { diff --git a/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts b/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts index b8b8cb5c46..0d735e375a 100644 --- a/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts +++ b/src/routes/transactions/mappers/common/swap-order.mapper.spec.ts @@ -396,7 +396,7 @@ describe('Swap Order Mapper tests', () => { const transaction = multisigTransactionBuilder().build(); const buyToken = tokenBuilder().with('decimals', 0).build(); const sellToken = tokenBuilder().build(); - let order = orderBuilder() + const order = orderBuilder() .with( 'status', faker.helpers.arrayElement([ @@ -408,17 +408,11 @@ describe('Swap Order Mapper tests', () => { ) .build(); if (order.kind === 'buy') { - order = { - ...order, - executedBuyAmount: BigInt(executedAmount), - buyAmount: BigInt(amount), - }; + order['executedBuyAmount'] = BigInt(executedAmount); + order['buyAmount'] = BigInt(amount); } else if (order.kind === 'sell') { - order = { - ...order, - executedSellAmount: BigInt(executedAmount), - sellAmount: BigInt(amount), - }; + order['executedSellAmount'] = BigInt(executedAmount); + order['sellAmount'] = BigInt(amount); } else { throw new Error('Invalid order kind'); }