Skip to content

Commit 938471c

Browse files
committed
ft_watcher: track fill vaas for cctp execution and fill accounts for local execution
Signed-off-by: bingyuyap <bingyu.yap.21@gmail.com>
1 parent 63aaa73 commit 938471c

File tree

8 files changed

+53
-16
lines changed

8 files changed

+53
-16
lines changed

common/src/solana.ts

+10
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,13 @@ export async function makeRpcCall(
117117
export function decodeBase64Data(encodedData: string) {
118118
return new Uint8Array(Buffer.from(encodedData, 'base64'));
119119
}
120+
121+
const SOLANA_SEQ_LOG = 'Program log: Sequence: ';
122+
export function parseWormholeSequenceFromLogs(logs: string[]): number | null {
123+
for (const log of logs) {
124+
if (log.startsWith(SOLANA_SEQ_LOG)) {
125+
return parseInt(log.replace(SOLANA_SEQ_LOG, ''), 10);
126+
}
127+
}
128+
return null;
129+
}

database/fast-transfer-schema.sql

+6-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ CREATE TABLE fast_transfer_executions (
5858
execution_payer VARCHAR(255),
5959
execution_tx_hash VARCHAR(255),
6060
execution_slot BIGINT,
61-
execution_time TIMESTAMP
61+
execution_time TIMESTAMP,
62+
-- fill_id can be a vaa id (cctp) or solana account pubkey (local)
63+
fill_id VARCHAR(255),
6264
);
6365

6466
-- Settlement is created when the settlement is created in the `settleFastTransfer`
@@ -96,11 +98,12 @@ CREATE TABLE auction_history_mapping (
9698

9799
-- Redeem Swaps table to track the final swap before funds reach the user's account
98100
CREATE TABLE redeem_swaps (
99-
fill_vaa_id VARCHAR(255) PRIMARY KEY,
101+
-- fill_id can be a vaa id (cctp) or solana account pubkey (local)
102+
fill_id VARCHAR(255) PRIMARY KEY,
100103
tx_hash VARCHAR(255) NOT NULL,
101104
recipient VARCHAR(255) NOT NULL,
102105
output_token VARCHAR(255) NOT NULL,
103106
output_amount BIGINT NOT NULL,
104107
relaying_fee BIGINT NOT NULL,
105-
timestamp TIMESTAMP NOT NULL
108+
redeem_time TIMESTAMP NOT NULL
106109
);

watcher/src/fastTransfer/swapLayer/parser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ class SwapLayerParser {
6161
recipient: swapEvent.args.recipient,
6262
output_amount: BigInt(swapEvent.args.outputAmount.toString()),
6363
output_token: swapEvent.args.outputToken,
64-
timestamp: new Date(blockTime * 1000),
64+
redeem_time: new Date(blockTime * 1000),
6565
relaying_fee: BigInt(swapEvent.args.relayingFee.toString()),
66-
fill_vaa_id: fillVaaId,
66+
fill_id: fillVaaId,
6767
};
6868
}
6969

watcher/src/fastTransfer/types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export type FastTransferExecutionInfo = {
5555
execution_time: Date;
5656
execution_tx_hash: string;
5757
execution_slot: bigint;
58+
// fill_id can be a vaa id (cctp) or a Solana public key
59+
fill_id: string;
5860
};
5961

6062
export type FastTransferSettledInfo = {
@@ -149,6 +151,6 @@ export type RedeemSwap = {
149151
output_token: string;
150152
output_amount: bigint;
151153
relaying_fee: bigint;
152-
timestamp: Date;
153-
fill_vaa_id: string;
154+
redeem_time: Date;
155+
fill_id: string;
154156
};

watcher/src/watchers/FTEVMWatcher.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { AXIOS_CONFIG_JSON, RPCS_BY_CHAIN } from '../consts';
88
import { makeBlockKey } from '../databases/utils';
99
import TokenRouterParser from '../fastTransfer/tokenRouter/parser';
1010
import SwapLayerParser from '../fastTransfer/swapLayer/parser';
11-
import { MarketOrder, RedeemSwap } from '../fastTransfer/types';
1211
import { Block } from './EVMWatcher';
1312
import { BigNumber } from 'ethers';
1413
import axios from 'axios';
@@ -151,7 +150,7 @@ export class FTEVMWatcher extends Watcher {
151150
}
152151

153152
if (swapLayerResults.length) {
154-
await this.saveBatch(swapLayerResults, 'redeem_swaps', 'fill_vaa_id', fromBlock, toBlock);
153+
await this.saveBatch(swapLayerResults, 'redeem_swaps', 'fill_id', fromBlock, toBlock);
155154
}
156155

157156
// we do not need to compare the lastBlockTime from tokenRouter and swapLayer as they both use toBlock

watcher/src/watchers/FTSolanaWatcher.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ import {
4141
AuctionUpdatedEvent,
4242
} from '../fastTransfer/types';
4343
import knex, { Knex } from 'knex';
44-
import { assertEnvironmentVariable } from '@wormhole-foundation/wormhole-monitor-common';
44+
import {
45+
assertEnvironmentVariable,
46+
parseWormholeSequenceFromLogs,
47+
} from '@wormhole-foundation/wormhole-monitor-common';
4548
import { getLogger } from '../utils/logger';
4649
import base58 from 'bs58';
4750
import {
@@ -503,7 +506,8 @@ export class FTSolanaWatcher extends SolanaWatcher {
503506
tx: VersionedTransactionResponse,
504507
payerAccount: PublicKey,
505508
auctionAccountPubkey: PublicKey,
506-
fastVaaAccount: PublicKey
509+
fastVaaAccount: PublicKey,
510+
fillVaaId: string
507511
): Promise<{ id: FastTransferId; info: FastTransferExecutionInfo }> {
508512
const vaaAccount = await VaaAccount.fetch(
509513
this.matchingEngineProgram.program.provider.connection,
@@ -542,6 +546,7 @@ export class FTSolanaWatcher extends SolanaWatcher {
542546
execution_time: new Date(tx.blockTime! * 1000),
543547
execution_tx_hash: tx.transaction.signatures[0],
544548
execution_slot: BigInt(tx.slot),
549+
fill_id: fillVaaId,
545550
},
546551
};
547552
}
@@ -556,14 +561,28 @@ export class FTSolanaWatcher extends SolanaWatcher {
556561
throw new Error('Insufficient account key indexes for parseExecuteFastOrderCctp');
557562
}
558563
const payerAccountPubkey = accountKeys[accountKeyIndexes[0]];
564+
const custodianAccountPubkey = accountKeys[accountKeyIndexes[3]];
559565
const fastVaaAccountPubkey = accountKeys[accountKeyIndexes[4]];
560566
const auctionAccountPubkey = accountKeys[accountKeyIndexes[5]];
567+
const seq = parseWormholeSequenceFromLogs(res.meta?.logMessages || []);
568+
569+
if (!seq) {
570+
throw new Error('Cannot find fill sequnece');
571+
}
572+
573+
// in `execute_fast_order_cctp` (see: https://github.com/wormhole-foundation/example-liquidity-layer/blob/63f2b423026ac1ad8b099bc3ecd44ba4fc64aae2/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs#L158)
574+
// custodian is used as the emitter
575+
const emitterInHex = custodianAccountPubkey.toBuffer().toString('hex');
576+
577+
// matching engine only exists in Solana. This only checks for Solana matching engine anyways. So we can be sure that emitter chain is always 1
578+
const fillVaaId = `1/${emitterInHex}/${seq}`;
561579

562580
const { id, info } = await this.computeExecutionData(
563581
res,
564582
payerAccountPubkey,
565583
auctionAccountPubkey,
566-
fastVaaAccountPubkey
584+
fastVaaAccountPubkey,
585+
fillVaaId
567586
);
568587

569588
await this.saveFastTransferInfo('fast_transfer_executions', info);
@@ -583,18 +602,20 @@ export class FTSolanaWatcher extends SolanaWatcher {
583602
) {
584603
const accountKeys = res.transaction.message.getAccountKeys().staticAccountKeys;
585604
const accountKeyIndexes = instruction.accountKeyIndexes;
586-
if (accountKeyIndexes.length < 4) {
605+
if (accountKeyIndexes.length < 5) {
587606
throw new Error('Insufficient account key indexes for parseExecuteFastOrderLocal');
588607
}
589608
const payerAccountPubkey = accountKeys[accountKeyIndexes[0]];
590609
const fastVaaAccountPubkey = accountKeys[accountKeyIndexes[2]];
591610
const auctionAccountPubkey = accountKeys[accountKeyIndexes[3]];
611+
const fastFillAccountPubkey = accountKeys[accountKeyIndexes[4]];
592612

593613
const { id, info } = await this.computeExecutionData(
594614
res,
595615
payerAccountPubkey,
596616
auctionAccountPubkey,
597-
fastVaaAccountPubkey
617+
fastVaaAccountPubkey,
618+
fastFillAccountPubkey.toBase58()
598619
);
599620

600621
await this.saveFastTransferInfo('fast_transfer_executions', info);

watcher/src/watchers/__tests__/FTEVMWatcher.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ describe('SwapLayerParser', () => {
123123
output_token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
124124
output_amount: BigInt('20000000000'),
125125
relaying_fee: BigInt('10447500'),
126-
timestamp: new Date(mockBlock.timestamp * 1000),
127-
fill_vaa_id: '1/cb0406e59555bf0371b7c4fff1812a11a8d92dad02ad422062971d61dcce2cd0/2',
126+
redeem_time: new Date(mockBlock.timestamp * 1000),
127+
fill_id: '1/cb0406e59555bf0371b7c4fff1812a11a8d92dad02ad422062971d61dcce2cd0/2',
128128
});
129129
});
130130
});

watcher/src/watchers/__tests__/FTSolanaWatcher.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ test('should parse executeFastOrderLocal', async () => {
129129
execution_time: new Date('2024-05-23T15:58:23.000Z'),
130130
execution_tx_hash: txHash,
131131
fast_vaa_hash: 'fd99d2d20f7458cae97de7d7bcf94cbdc5ac734264fa495bf01f1748e28039da',
132+
fill_id: 'B2qkDPs1gPh69uvKN5mbtHRAFtiMaZCe4vu6Wp3yaaJ1',
132133
});
133134
});
134135

@@ -156,6 +157,7 @@ test('should parse executeFastOrderCctp', async () => {
156157
execution_slot: 301864332n,
157158
execution_time: new Date('2024-05-28T03:15:19.000Z'),
158159
fast_vaa_hash: '14a5187e40e4fd2b2950cd8332b4142259757f4cbf7cffcb7cc95249df8415b9',
160+
fill_id: '1/3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077/7970',
159161
});
160162
});
161163

0 commit comments

Comments
 (0)