Skip to content

Commit

Permalink
Mark TWAP orders as unknown if getting part orders fails
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Jul 24, 2024
1 parent b4f1447 commit 88c9d4e
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 40 deletions.
105 changes: 105 additions & 0 deletions src/routes/transactions/mappers/common/twap-order.mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,111 @@ describe('TwapOrderMapper', () => {
});
});

it('should map an unknown executed TWAP order', async () => {
configurationService.set('swaps.maxNumberOfParts', 2);

// We instantiate in tests to be able to set maxNumberOfParts
const mapper = new TwapOrderMapper(
configurationService,
mockLoggingService,
swapOrderHelper,
mockSwapsRepository,
composableCowDecoder,
gpv2OrderHelper,
twapOrderHelper,
new SwapAppsHelper(configurationService, allowedApps),
);

/**
* @see https://sepolia.etherscan.io/address/0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74
*/
const chainId = '11155111';
const owner = '0x31eaC7F0141837B266De30f4dc9aF15629Bd5381';
const data =
'0x0d0d9800000000000000000000000000000000000000000000000000000000000000008000000000000000000000000052ed56da04309aca4c3fecc595298d80c2f16bac000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006cf1e9ca41f7611def408122793c358a3d11e5a500000000000000000000000000000000000000000000000000000019011f294a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000be72e441bf55620febc26715db68d3494213d8cb000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b1400000000000000000000000031eac7f0141837b266de30f4dc9af15629bd538100000000000000000000000000000000000000000000000b941d039eed310b36000000000000000000000000000000000000000000000000087bbc924df9167e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000007080000000000000000000000000000000000000000000000000000000000000000f7be7261f56698c258bf75f888d68a00c85b22fb21958b9009c719eb88aebda00000000000000000000000000000000000000000000000000000000000000000';
const executionDate = new Date(1718288040000);

/**
* @see https://explorer.cow.fi/sepolia/orders/0xdaabe82f86545c66074b5565962e96758979ae80124aabef05e0585149d30f7931eac7f0141837b266de30f4dc9af15629bd5381666b05af?tab=overview
*/
const buyToken = tokenBuilder()
.with('address', getAddress('0xfff9976782d46cc05630d1f6ebab18b2324d6b14'))
.build();
const sellToken = tokenBuilder()
.with('address', getAddress('0xbe72e441bf55620febc26715db68d3494213d8cb'))
.build();
const fullAppData = JSON.parse(fakeJson());

// Note: this seems to happen on TWAP with parts above the limit of parts
mockSwapsRepository.getOrder.mockRejectedValue(
new Error('Order not found'),
);
mockTokenRepository.getToken.mockImplementation(async ({ address }) => {
// We only need mock part1 addresses as all parts use the same tokens
switch (address) {
case buyToken.address: {
return Promise.resolve(buyToken);
}
case sellToken.address: {
return Promise.resolve(sellToken);
}
default: {
return Promise.reject(new Error(`Token not found: ${address}`));
}
}
});
mockSwapsRepository.getFullAppData.mockResolvedValue({ fullAppData });

const result = await mapper.mapTwapOrder(chainId, owner, {
data,
executionDate,
});

expect(result).toEqual({
buyAmount: '1222579021996502268',
buyToken: {
address: buyToken.address,
decimals: buyToken.decimals,
logoUri: buyToken.logoUri,
name: buyToken.name,
symbol: buyToken.symbol,
trusted: buyToken.trusted,
},
class: 'limit',
durationOfPart: {
durationType: 'AUTO',
},
executedBuyAmount: null,
executedSellAmount: null,
executedSurplusFee: null,
fullAppData,
humanDescription: null,
kind: 'sell',
minPartLimit: '611289510998251134',
numberOfParts: '2',
status: 'unknown',
owner: '0x31eaC7F0141837B266De30f4dc9aF15629Bd5381',
partSellAmount: '213586875483862141750',
receiver: '0x31eaC7F0141837B266De30f4dc9aF15629Bd5381',
richDecodedInfo: null,
sellAmount: '427173750967724283500',
sellToken: {
address: sellToken.address,
decimals: sellToken.decimals,
logoUri: sellToken.logoUri,
name: sellToken.name,
symbol: sellToken.symbol,
trusted: sellToken.trusted,
},
startTime: {
startType: 'AT_MINING_TIME',
},
timeBetweenParts: 1800,
type: 'TwapOrder',
validUntil: 1718291639,
});
});

it('should map a TWAP order, up to a limit', async () => {
configurationService.set('swaps.maxNumberOfParts', 1);

Expand Down
104 changes: 64 additions & 40 deletions src/routes/transactions/mappers/common/twap-order.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
SwapAppsHelper,
SwapAppsHelperModule,
} from '@/routes/transactions/helpers/swap-apps.helper';
import { GPv2OrderParameters } from '@/domain/swaps/contracts/decoders/gp-v2-decoder.helper';

@Injectable()
export class TwapOrderMapper {
Expand Down Expand Up @@ -99,52 +100,28 @@ export class TwapOrderMapper {
: twapParts
: [];

const orders: Array<KnownOrder> = [];

for (const part of partsToFetch) {
const partFullAppData = await this.swapsRepository.getFullAppData(
chainId,
part.appData,
);

if (!this.swapAppsHelper.isAppAllowed(partFullAppData)) {
throw new Error(
`Unsupported App: ${partFullAppData.fullAppData?.appCode}`,
);
}

const orderUid = this.gpv2OrderHelper.computeOrderUid({
chainId,
owner: safeAddress,
order: part,
});
const order = await this.swapsRepository
.getOrder(chainId, orderUid)
.catch(() => {
this.loggingService.warn(
`Error getting orderUid ${orderUid} from SwapsRepository`,
);
});

if (!order || order.kind == OrderKind.Unknown) {
continue;
}

if (!this.swapAppsHelper.isAppAllowed(order)) {
throw new Error(`Unsupported App: ${order.fullAppData?.appCode}`);
}
const orders = await this.getOrdersForParts({
partsToFetch,
chainId,
safeAddress,
});

orders.push(order as KnownOrder);
}
const status = !orders ? OrderStatus.Unknown : this.getOrderStatus(orders);

const executedSellAmount: TwapOrderInfo['executedSellAmount'] =
hasAbundantParts ? null : this.getExecutedSellAmount(orders).toString();
hasAbundantParts || !orders
? null
: this.getExecutedSellAmount(orders).toString();

const executedBuyAmount: TwapOrderInfo['executedBuyAmount'] =
hasAbundantParts ? null : this.getExecutedBuyAmount(orders).toString();
hasAbundantParts || !orders
? null
: this.getExecutedBuyAmount(orders).toString();

const executedSurplusFee: TwapOrderInfo['executedSurplusFee'] =
hasAbundantParts ? null : this.getExecutedSurplusFee(orders).toString();
hasAbundantParts || !orders
? null
: this.getExecutedSurplusFee(orders).toString();

const [sellToken, buyToken] = await Promise.all([
this.swapOrderHelper.getToken({
Expand All @@ -158,7 +135,7 @@ export class TwapOrderMapper {
]);

return new TwapOrderTransactionInfo({
status: this.getOrderStatus(orders),
status,
kind: twapOrderData.kind,
class: twapOrderData.class,
validUntil: Math.max(...twapParts.map((order) => order.validTo)),
Expand Down Expand Up @@ -195,6 +172,53 @@ export class TwapOrderMapper {
});
}

private async getOrdersForParts(args: {
partsToFetch: Array<GPv2OrderParameters>;
chainId: string;
safeAddress: `0x${string}`;
}): Promise<Array<KnownOrder> | null> {
const orders: Array<KnownOrder> = [];

for (const part of args.partsToFetch) {
const partFullAppData = await this.swapsRepository.getFullAppData(
args.chainId,
part.appData,
);

if (!this.swapAppsHelper.isAppAllowed(partFullAppData)) {
throw new Error(
`Unsupported App: ${partFullAppData.fullAppData?.appCode}`,
);
}

const orderUid = this.gpv2OrderHelper.computeOrderUid({
chainId: args.chainId,
owner: args.safeAddress,
order: part,
});
const order = await this.swapsRepository
.getOrder(args.chainId, orderUid)
.catch(() => {
this.loggingService.warn(
`Error getting orderUid ${orderUid} from SwapsRepository`,
);
});

if (!order || order.kind == OrderKind.Unknown) {
// Without every order it's not possible to determine status or executed amounts/fees
return null;
}

if (!this.swapAppsHelper.isAppAllowed(order)) {
throw new Error(`Unsupported App: ${order.fullAppData?.appCode}`);
}

orders.push(order as KnownOrder);
}

return orders;
}

private getOrderStatus(
orders: Array<Awaited<ReturnType<typeof this.swapOrderHelper.getOrder>>>,
): OrderStatus {
Expand Down

0 comments on commit 88c9d4e

Please sign in to comment.