From 71373870cd0352601be99813233fa5dce44ac24a Mon Sep 17 00:00:00 2001 From: amateima <89395931+amateima@users.noreply.github.com> Date: Mon, 6 May 2024 13:13:59 +0300 Subject: [PATCH] feat: add support for swap events (#389) --- migrations/1714647751484-Deposit.ts | 23 ++ src/modules/deposit/model/deposit.entity.ts | 13 ++ src/modules/deposit/utils.ts | 10 + .../adapter/messaging/BlocksEventsConsumer.ts | 18 +- .../web3/model/swap-and-bridge-events.ts | 13 ++ .../web3/services/EthProvidersService.ts | 14 ++ .../web3/services/abi/SwapAndBridge.json | 218 ++++++++++++++++++ 7 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 migrations/1714647751484-Deposit.ts create mode 100644 src/modules/web3/model/swap-and-bridge-events.ts create mode 100644 src/modules/web3/services/abi/SwapAndBridge.json diff --git a/migrations/1714647751484-Deposit.ts b/migrations/1714647751484-Deposit.ts new file mode 100644 index 00000000..6025e0be --- /dev/null +++ b/migrations/1714647751484-Deposit.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Deposit1714647751484 implements MigrationInterface { + name = "Deposit1714647751484"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "deposit" ADD "swapTokenAddress" character varying`); + await queryRunner.query(`ALTER TABLE "deposit" ADD "swapTokenId" integer`); + await queryRunner.query(`ALTER TABLE "deposit" ADD "swapTokenAmount" numeric`); + await queryRunner.query(` + ALTER TABLE "deposit" + ADD CONSTRAINT "FK_deposit_swapTokenId" + FOREIGN KEY ("swapTokenId") REFERENCES "token"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "deposit" DROP CONSTRAINT "FK_deposit_swapTokenId"`); + await queryRunner.query(`ALTER TABLE "deposit" DROP COLUMN "swapTokenAmount"`); + await queryRunner.query(`ALTER TABLE "deposit" DROP COLUMN "swapTokenId"`); + await queryRunner.query(`ALTER TABLE "deposit" DROP COLUMN "swapTokenAddress"`); + } +} diff --git a/src/modules/deposit/model/deposit.entity.ts b/src/modules/deposit/model/deposit.entity.ts index c6757103..1fcf494b 100644 --- a/src/modules/deposit/model/deposit.entity.ts +++ b/src/modules/deposit/model/deposit.entity.ts @@ -216,6 +216,19 @@ export class Deposit { @Column({ nullable: true }) relayer?: string; + @Column({ nullable: true }) + swapTokenAddress?: string; + + @Column({ nullable: true }) + swapTokenId?: number; + + @ManyToOne(() => Token) + @JoinColumn([{ name: "swapTokenId", referencedColumnName: "id", foreignKeyConstraintName: "FK_deposit_swapTokenId" }]) + swapToken?: Token; + + @Column({ type: "decimal", nullable: true }) + swapTokenAmount?: string; + @CreateDateColumn() createdAt: Date; diff --git a/src/modules/deposit/utils.ts b/src/modules/deposit/utils.ts index e41d24a7..8bc107fc 100644 --- a/src/modules/deposit/utils.ts +++ b/src/modules/deposit/utils.ts @@ -47,5 +47,15 @@ export function formatDeposit(deposit: Deposit) { } : undefined, fillDeadline: deposit.fillDeadline || null, + swapToken: deposit.swapToken + ? { + address: deposit.swapToken.address, + chainId: deposit.swapToken.chainId, + symbol: deposit.swapToken.symbol, + decimals: deposit.swapToken.decimals, + } + : undefined, + swapTokenAmount: deposit.swapTokenAmount, + swapTokenAddress: deposit.swapTokenAddress, }; } diff --git a/src/modules/scraper/adapter/messaging/BlocksEventsConsumer.ts b/src/modules/scraper/adapter/messaging/BlocksEventsConsumer.ts index 0a81dffd..4427bc04 100644 --- a/src/modules/scraper/adapter/messaging/BlocksEventsConsumer.ts +++ b/src/modules/scraper/adapter/messaging/BlocksEventsConsumer.ts @@ -29,6 +29,8 @@ import { FilledV3RelayEvent, RequestedSpeedUpV3DepositEvent, } from "../../../web3/model"; +import SwapAndBridgeAbi from "../../../web3/services/abi/SwapAndBridge.json"; +import { SwapBeforeBridgeEvent } from "../../../web3/model/swap-and-bridge-events"; import { AppConfig } from "../../../configuration/configuration.service"; import { splitBlockRanges } from "../../utils"; import { AcrossContractsVersion } from "../../../web3/model/across-version"; @@ -380,13 +382,21 @@ export class BlocksEventsConsumer { } = event.args; const wei = BigNumber.from(10).pow(18); const feePct = inputAmount.eq(0) ? BigNumber.from(0) : wei.sub(outputAmount.mul(wei).div(inputAmount)); + // const txReceipt = await this.providers.getCachedTransactionReceipt(chainId, transactionHash); + const txReceipt = await this.providers.getProvider(chainId).getTransactionReceipt(transactionHash); + const swapBeforeBridgeEvents = this.providers.parseTransactionReceiptLogs( + txReceipt, + "SwapBeforeBridge", + SwapAndBridgeAbi, + ) as unknown as SwapBeforeBridgeEvent[]; + const swapEvent = swapBeforeBridgeEvents.length > 0 ? swapBeforeBridgeEvents[0] : undefined; + const swapToken = swapEvent ? await this.providers.getCachedToken(chainId, swapEvent.args.swapToken) : undefined; let trueDepositor = depositor; let exclusivityDeadlineDate = undefined; if (exclusivityDeadline > 0) exclusivityDeadlineDate = new Date(exclusivityDeadline * 1000); if (depositor === SPOKE_POOL_VERIFIER_CONTRACT_ADDRESS) { - const tx = await this.providers.getCachedTransactionReceipt(chainId, transactionHash); - trueDepositor = tx.from; + trueDepositor = txReceipt.from; } return this.depositRepository.create({ @@ -412,6 +422,10 @@ export class BlocksEventsConsumer { exclusivityDeadline: exclusivityDeadlineDate, relayer, message, + // swap event properties + swapTokenId: swapToken?.id, + swapTokenAmount: swapEvent?.args.swapTokenAmount.toString(), + swapTokenAddress: swapEvent?.args.swapToken, }); } diff --git a/src/modules/web3/model/swap-and-bridge-events.ts b/src/modules/web3/model/swap-and-bridge-events.ts new file mode 100644 index 00000000..02c63f5d --- /dev/null +++ b/src/modules/web3/model/swap-and-bridge-events.ts @@ -0,0 +1,13 @@ +import { BigNumber, Event } from "ethers"; + +export interface SwapBeforeBridgeEvent extends Event { + args: [string, string, string, BigNumber, BigNumber, string, BigNumber] & { + exchange: string; + swapToken: string; + acrossInputToken: string; + swapTokenAmount: BigNumber; + acrossInputAmount: BigNumber; + acrossOutputToken: string; + acrossOutputAmount: BigNumber; + }; +} diff --git a/src/modules/web3/services/EthProvidersService.ts b/src/modules/web3/services/EthProvidersService.ts index 0107d814..a82e48bc 100644 --- a/src/modules/web3/services/EthProvidersService.ts +++ b/src/modules/web3/services/EthProvidersService.ts @@ -152,6 +152,20 @@ export class EthProvidersService { return cachedReceipt; } + public parseTransactionReceiptLogs(receipt: ethers.providers.TransactionReceipt, eventName: string, abi: any) { + const events: ethers.utils.LogDescription[] = []; + + for (const log of receipt.logs) { + const contractInterface = new ethers.utils.Interface(abi); + const parsedLog = contractInterface.parseLog(log); + if (parsedLog && parsedLog.name === eventName) { + events.push(parsedLog); + } + } + + return events; + } + private setProviders() { const supportedChainIds = Object.keys(this.appConfig.values.web3.providers); diff --git a/src/modules/web3/services/abi/SwapAndBridge.json b/src/modules/web3/services/abi/SwapAndBridge.json new file mode 100644 index 00000000..3c54366f --- /dev/null +++ b/src/modules/web3/services/abi/SwapAndBridge.json @@ -0,0 +1,218 @@ +[ + { + "inputs": [ + { + "internalType": "contract V3SpokePoolInterface", + "name": "_spokePool", + "type": "address" + }, + { + "internalType": "address", + "name": "_exchange", + "type": "address" + }, + { + "internalType": "bytes4[]", + "name": "_allowedSelectors", + "type": "bytes4[]" + }, + { + "internalType": "contract IERC20", + "name": "_swapToken", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "_acrossInputToken", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidFunctionSelector", + "type": "error" + }, + { + "inputs": [], + "name": "LeftoverSrcTokens", + "type": "error" + }, + { + "inputs": [], + "name": "MinimumExpectedInputAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ACROSS_INPUT_TOKEN", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXCHANGE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SPOKE_POOL", + "outputs": [ + { + "internalType": "contract V3SpokePoolInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SWAP_TOKEN", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "name": "allowedSelectors", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "routerCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "swapTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minExpectedInputTokenAmount", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "outputToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "outputAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "destinationChainid", + "type": "uint256" + }, + { + "internalType": "address", + "name": "exclusiveRelayer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "quoteTimestamp", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "fillDeadline", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "exclusivityDeadline", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "internalType": "struct SwapAndBridgeBase.DepositData", + "name": "depositData", + "type": "tuple" + } + ], + "name": "swapAndBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]