Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class BlockchainEventRepository {
* The unique keys to check for. It is recommended these keys to be indexed columns, so that the query is faster.
* @param comparisonKeys - The keys to compare for changes.
*/
protected async saveAndHandleFinalisationBatch<Entity extends ObjectLiteral>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need this function to be able to share the storing function for Simple transfers for OFT and CCTP, see https://github.com/across-protocol/indexer/pull/433/files#diff-103d64d5e62515d16ca06086549eabe7dbc05b089ad0c69d013da5fc23918d84R52

public async saveAndHandleFinalisationBatch<Entity extends ObjectLiteral>(
entity: EntityTarget<Entity>,
data: Partial<Entity>[],
uniqueKeys: (keyof Entity)[],
Expand Down
9 changes: 9 additions & 0 deletions packages/indexer/src/data-indexing/adapter/oft/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ export interface SponsoredOFTSendLog extends ethers.providers.Log {
sig: string;
};
}

export interface ComposeDeliveredEvent extends ethers.Event {
args: [] & {
from: string;
to: string;
guid: string;
index: number;
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BigNumber, providers } from "ethers";
import { CHAIN_IDs } from "@across-protocol/constants";

export interface SimpleTransferFlowCompletedLog extends providers.Log {
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ import {
isHypercoreWithdraw,
} from "../adapter/cctp-v2/service";
import { entities, SaveQueryResult } from "@repo/indexer-database";
import {
formatAndSaveSimpleTransferFlowCompletedEvents,
getSimpleTransferFlowCompletedEventsFromTransactionReceipts,
} from "./hyperEvmExecutor";

export type EvmBurnEventsPair = {
depositForBurn: DepositForBurnEvent;
Expand Down Expand Up @@ -286,7 +290,7 @@ export class CCTPIndexerDataHandler implements IndexerDataHandler {
[];
if (sponsoredCCTPDstPeripheryAddress) {
simpleTransferFlowCompletedEvents =
this.getSimpleTransferFlowCompletedEventsFromTransactionReceipts(
getSimpleTransferFlowCompletedEventsFromTransactionReceipts(
filteredMessageReceivedTxReceipts,
sponsoredCCTPDstPeripheryAddress,
);
Expand Down Expand Up @@ -508,28 +512,6 @@ export class CCTPIndexerDataHandler implements IndexerDataHandler {
return events;
}

private getSimpleTransferFlowCompletedEventsFromTransactionReceipts(
transactionReceipts: Record<string, ethers.providers.TransactionReceipt>,
hyperEvmExecutorAddress: string,
) {
const events: SimpleTransferFlowCompletedLog[] = [];
for (const txHash of Object.keys(transactionReceipts)) {
const transactionReceipt = transactionReceipts[
txHash
] as providers.TransactionReceipt;
const simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedLog[] =
EventDecoder.decodeSimpleTransferFlowCompletedEvents(
transactionReceipt,
hyperEvmExecutorAddress,
);
if (simpleTransferFlowCompletedEvents.length > 0) {
events.push(...simpleTransferFlowCompletedEvents);
}
}

return events;
}

private getArbitraryActionsExecutedEventsFromTransactionReceipts(
transactionReceipts: Record<string, ethers.providers.TransactionReceipt>,
arbitraryEvmFlowExecutorAddress: string,
Expand Down Expand Up @@ -675,7 +657,8 @@ export class CCTPIndexerDataHandler implements IndexerDataHandler {
this.chainId,
blocksTimestamps,
),
this.cctpRepository.formatAndSaveSimpleTransferFlowCompletedEvents(
formatAndSaveSimpleTransferFlowCompletedEvents(
this.cctpRepository,
simpleTransferFlowCompletedEvents,
lastFinalisedBlock,
this.chainId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Logger } from "winston";
import { ethers, providers, Transaction } from "ethers";
import * as across from "@across-protocol/sdk";

import { DataSource, entities, SaveQueryResult } from "@repo/indexer-database";
import { entities, SaveQueryResult } from "@repo/indexer-database";

import { BlockRange } from "../model";
import { BlockRange, SimpleTransferFlowCompletedLog } from "../model";
import { IndexerDataHandler } from "./IndexerDataHandler";
import { O_ADAPTER_UPGRADEABLE_ABI } from "../adapter/oft/abis";
import {
Expand All @@ -18,26 +18,41 @@ import {
isEndpointIdSupported,
SPONSORED_OFT_SRC_PERIPHERY_ADDRESS,
} from "../adapter/oft/service";
import { OftTransferAggregator } from "./OftTransferAggregator";
import { EventDecoder } from "../../web3/EventDecoder";
import { fetchEvents } from "../../utils/contractUtils";
import {
formatAndSaveSimpleTransferFlowCompletedEvents,
getSimpleTransferFlowCompletedEventsFromTransactionReceipts,
} from "./hyperEvmExecutor";
import { CHAIN_IDs } from "@across-protocol/constants";

export type FetchEventsResult = {
oftSentEvents: OFTSentEvent[];
oftReceivedEvents: OFTReceivedEvent[];
sponsoredOFTSendEvents: SponsoredOFTSendLog[];
simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedLog[];
blocks: Record<string, providers.Block>;
};
export type StoreEventsResult = {
oftSentEvents: SaveQueryResult<entities.OFTSent>[];
oftReceivedEvents: SaveQueryResult<entities.OFTReceived>[];
sponsoredOFTSendEvents: SaveQueryResult<entities.SponsoredOFTSend>[];
simpleTransferFlowCompletedEvents: SaveQueryResult<entities.SimpleTransferFlowCompleted>[];
};

// Taken from https://hyperevmscan.io/tx/0xf72cfb2c0a9f781057cd4f7beca6fc6bd9290f1d73adef1142b8ac1b0ed7186c#eventlog#37
// TODO: Add testnet endpoint v2 address when applicable
export const ENDPOINT_V2_ADDRESS = "0x3a73033c0b1407574c76bdbac67f126f6b4a9aa9";

const DST_OFT_HANDLER_ADDRESS: { [key: number]: string } = {
// Taken from https://hyperevmscan.io/address/0x2beF20D17a17f6903017d27D1A35CC9Dc72b0888#code
[CHAIN_IDs.HYPEREVM]: "0x2beF20D17a17f6903017d27D1A35CC9Dc72b0888",
};

const SWAP_API_CALLDATA_MARKER = "73c0de";

export class OFTIndexerDataHandler implements IndexerDataHandler {
private isInitialized: boolean;
private oftTransferAggregator: OftTransferAggregator;

constructor(
private logger: Logger,
Expand Down Expand Up @@ -117,8 +132,10 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
O_ADAPTER_UPGRADEABLE_ABI,
this.provider,
);
const dstOftHandlerAddress = DST_OFT_HANDLER_ADDRESS[this.chainId];
const sponsoredOFTSrcPeripheryAddress =
SPONSORED_OFT_SRC_PERIPHERY_ADDRESS[this.chainId];

const [oftSentEvents, oftReceivedEvents] = await Promise.all([
oftAdapterContract.queryFilter(
"OFTSent",
Expand All @@ -131,19 +148,25 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
blockRange.to,
) as Promise<OFTReceivedEvent[]>,
]);
let blockHashes = [];
const oftSentTransactions = await this.getTransactions([
...new Set(oftSentEvents.map((event) => event.transactionHash)),
]);
const filteredOftSentEvents = await this.filterTransactionsFromSwapApi(
oftSentTransactions,
oftSentEvents,
);
blockHashes.push(...filteredOftSentEvents.map((event) => event.blockHash));

const filteredOftReceivedEvents =
await this.filterTransactionsForSupportedEndpointIds(oftReceivedEvents);
const filteredOftSentTransactionReceipts =
await this.getTransactionsReceipts([
...new Set(filteredOftSentEvents.map((event) => event.transactionHash)),
]);
blockHashes.push(
...filteredOftReceivedEvents.map((event) => event.blockHash),
);

let sponsoredOFTSendEvents: SponsoredOFTSendLog[] = [];
if (sponsoredOFTSrcPeripheryAddress) {
Expand All @@ -154,12 +177,31 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
);
}

const blocks = await this.getBlocks([
...new Set([
...filteredOftSentEvents.map((event) => event.blockHash),
...filteredOftReceivedEvents.map((event) => event.blockHash),
]),
]);
const simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedLog[] =
[];
if (dstOftHandlerAddress) {
const composeDeliveredEvents = await fetchEvents(
this.provider,
ENDPOINT_V2_ADDRESS,
"event ComposeDelivered(address from, address to, bytes32 guid, uint16 index)",
blockRange.from,
blockRange.to,
);
if (composeDeliveredEvents.length > 0) {
const transactionReceipts = await this.getTransactionsReceipts(
composeDeliveredEvents.map((event) => event.transactionHash),
);
const events =
getSimpleTransferFlowCompletedEventsFromTransactionReceipts(
transactionReceipts,
dstOftHandlerAddress,
);
simpleTransferFlowCompletedEvents.push(...events);
blockHashes.push(...events.map((event) => event.blockHash));
}
}

const blocks = await this.getBlocks([...new Set(blockHashes)]);
if (oftSentEvents.length > 0) {
this.logger.debug({
at: "Indexer#OFTIndexerDataHandler#fetchEventsByRange",
Expand All @@ -176,6 +218,7 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
oftSentEvents: filteredOftSentEvents,
oftReceivedEvents: filteredOftReceivedEvents,
sponsoredOFTSendEvents,
simpleTransferFlowCompletedEvents,
blocks,
};
}
Expand All @@ -185,13 +228,19 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
lastFinalisedBlock: number,
tokenAddress: string,
): Promise<StoreEventsResult> {
const { blocks, oftReceivedEvents, oftSentEvents, sponsoredOFTSendEvents } =
events;
const {
blocks,
oftReceivedEvents,
oftSentEvents,
sponsoredOFTSendEvents,
simpleTransferFlowCompletedEvents,
} = events;
const blocksTimestamps = this.getBlocksTimestamps(blocks);
const [
savedOftSentEvents,
savedOftReceivedEvents,
savedSponsoredOFTSendEvents,
savedSimpleTransferFlowCompletedEvents,
] = await Promise.all([
this.oftRepository.formatAndSaveOftSentEvents(
oftSentEvents,
Expand All @@ -213,12 +262,20 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {
this.chainId,
blocksTimestamps,
),
formatAndSaveSimpleTransferFlowCompletedEvents(
this.oftRepository,
simpleTransferFlowCompletedEvents,
lastFinalisedBlock,
this.chainId,
blocksTimestamps,
),
]);

return {
oftSentEvents: savedOftSentEvents,
oftReceivedEvents: savedOftReceivedEvents,
sponsoredOFTSendEvents: savedSponsoredOFTSendEvents,
simpleTransferFlowCompletedEvents: savedSimpleTransferFlowCompletedEvents,
};
}

Expand Down Expand Up @@ -317,13 +374,13 @@ export class OFTIndexerDataHandler implements IndexerDataHandler {

private getBlocksTimestamps(
blocks: Record<string, providers.Block>,
): Record<string, Date> {
): Record<number, Date> {
return Object.entries(blocks).reduce(
(acc, [blockHash, block]) => {
acc[blockHash] = new Date(block.timestamp * 1000);
acc[block.number] = new Date(block.timestamp * 1000);
return acc;
},
{} as Record<string, Date>,
{} as Record<number, Date>,
);
}
}
92 changes: 92 additions & 0 deletions packages/indexer/src/data-indexing/service/hyperEvmExecutor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ethers, providers } from "ethers";
import { SimpleTransferFlowCompletedLog } from "../model";
import { EventDecoder } from "../../web3/EventDecoder";
import { entities, SaveQueryResult } from "@repo/indexer-database";
import * as across from "@across-protocol/sdk";
import { BlockchainEventRepository } from "../../../../indexer-database/dist/src/utils";

/**
* Decodes and extracts `SimpleTransferFlowCompleted` events from a collection of transaction receipts.
* This function iterates over transaction receipts, decodes logs, and filters for `SimpleTransferFlowCompleted` events
* emitted by a specified HyperEVM executor contract.
*
* @param transactionReceipts A record of transaction receipts, indexed by their transaction hash.
* @param contractAddress The address of the HyperEVM executor contract to filter events from.
* @returns An array of decoded `SimpleTransferFlowCompletedLog` objects.
*/
export function getSimpleTransferFlowCompletedEventsFromTransactionReceipts(
transactionReceipts: Record<string, ethers.providers.TransactionReceipt>,
contractAddress: string,
) {
const events: SimpleTransferFlowCompletedLog[] = [];
for (const txHash of Object.keys(transactionReceipts)) {
const transactionReceipt = transactionReceipts[
txHash
] as providers.TransactionReceipt;
const simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedLog[] =
EventDecoder.decodeSimpleTransferFlowCompletedEvents(
transactionReceipt,
contractAddress,
);
if (simpleTransferFlowCompletedEvents.length > 0) {
events.push(...simpleTransferFlowCompletedEvents);
}
}

return events;
}

/**
* Formats and saves `SimpleTransferFlowCompleted` events to the database.
* This function maps the raw event data to the database entity format, marks them as finalized if they are within the finalized block range,
* and then saves them to the database in batches.
*
* @param repository The repository for database operations, specifically for saving blockchain events.
* @param simpleTransferFlowCompletedEvents An array of `SimpleTransferFlowCompletedLog` events to be processed.
* @param lastFinalisedBlock The last block number that is considered finalized.
* @param chainId The ID of the chain where these events were emitted.
* @param blockDates A record mapping block numbers to their corresponding `Date` objects.
* @param chunkSize The number of events to save in a single batch. Defaults to 100.
* @returns A promise that resolves to an array of `SaveQueryResult` for the saved events.
*/
export async function formatAndSaveSimpleTransferFlowCompletedEvents(
repository: BlockchainEventRepository,
simpleTransferFlowCompletedEvents: SimpleTransferFlowCompletedLog[],
lastFinalisedBlock: number,
chainId: number,
blockDates: Record<number, Date>,
chunkSize = 100,
) {
const formattedEvents: Partial<entities.SimpleTransferFlowCompleted>[] =
simpleTransferFlowCompletedEvents.map((event) => {
return {
blockNumber: event.blockNumber,
logIndex: event.logIndex,
transactionHash: event.transactionHash,
transactionIndex: event.transactionIndex,
blockTimestamp: blockDates[event.blockNumber]!,
chainId: chainId.toString(),
quoteNonce: event.args.quoteNonce,
finalRecipient: event.args.finalRecipient,
finalToken: event.args.finalToken.toString(),
evmAmountIn: event.args.evmAmountIn.toString(),
bridgingFeesIncurred: event.args.bridgingFeesIncurred.toString(),
evmAmountSponsored: event.args.evmAmountSponsored.toString(),
finalised: event.blockNumber <= lastFinalisedBlock,
};
});

const chunkedEvents = across.utils.chunk(formattedEvents, chunkSize);
const savedEvents = await Promise.all(
chunkedEvents.map((eventsChunk) =>
repository.saveAndHandleFinalisationBatch<entities.SimpleTransferFlowCompleted>(
entities.SimpleTransferFlowCompleted,
eventsChunk,
["chainId", "blockNumber", "transactionHash", "logIndex"],
[],
),
),
);
const result = savedEvents.flat();
return result;
}
Loading