From 9e6cfbbba525bc142c90c2cab44beca382baad85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Mon, 5 Dec 2022 13:22:15 -0300 Subject: [PATCH] feat: integrate bridge tasks with arbitrum sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TomΓ‘s Migone --- cli/arbitrum.ts | 108 +++++++++++++++++++++++++++++++++++ cli/commands/bridge/to-l1.ts | 17 +++--- cli/commands/bridge/to-l2.ts | 10 +++- tasks/bridge/deposits.ts | 58 ++++++++++++++----- tasks/bridge/withdrawals.ts | 50 ++++++++++++---- 5 files changed, 207 insertions(+), 36 deletions(-) create mode 100644 cli/arbitrum.ts diff --git a/cli/arbitrum.ts b/cli/arbitrum.ts new file mode 100644 index 000000000..727a19fa7 --- /dev/null +++ b/cli/arbitrum.ts @@ -0,0 +1,108 @@ +import { + L1ToL2MessageReader, + L1ToL2MessageStatus, + L1ToL2MessageWriter, + L1TransactionReceipt, + L2ToL1MessageReader, + L2ToL1MessageStatus, + L2ToL1MessageWriter, + L2TransactionReceipt, +} from '@arbitrum/sdk' +import { providers, Signer } from 'ethers' +import { Provider } from '@ethersproject/abstract-provider' + +// L1 -> L2 +export async function getL1ToL2MessageWriter( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, + signer: Signer, +): Promise { + return (await getL1ToL2Message( + txHashOrReceipt, + l1Provider, + l2Provider, + signer, + )) as L1ToL2MessageWriter +} + +export async function getL1ToL2MessageReader( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, +): Promise { + return await getL1ToL2Message(txHashOrReceipt, l1Provider, l2Provider) +} + +export async function getL1ToL2MessageStatus( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, +): Promise { + const message = await getL1ToL2Message(txHashOrReceipt, l1Provider, l2Provider) + return await message.status() +} + +async function getL1ToL2Message( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, + signer?: Signer, +): Promise { + const txReceipt = + typeof txHashOrReceipt === 'string' + ? await l1Provider.getTransactionReceipt(txHashOrReceipt) + : txHashOrReceipt + const l2SignerOrProvider = signer ? signer.connect(l2Provider) : l2Provider + const l1Receipt = new L1TransactionReceipt(txReceipt) + const l1ToL2Messages = await l1Receipt.getL1ToL2Messages(l2SignerOrProvider) + return l1ToL2Messages[0] +} + +// L2 -> L1 +export async function getL2ToL1MessageWriter( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, + signer: Signer, +): Promise { + return (await getL2ToL1Message( + txHashOrReceipt, + l1Provider, + l2Provider, + signer, + )) as L2ToL1MessageWriter +} + +export async function getL2ToL1MessageReader( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, +): Promise { + return await getL2ToL1Message(txHashOrReceipt, l1Provider, l2Provider) +} + +export async function getL2ToL1MessageStatus( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, +): Promise { + const message = await getL2ToL1Message(txHashOrReceipt, l1Provider, l2Provider) + return await message.status(l2Provider) +} + +async function getL2ToL1Message( + txHashOrReceipt: string | providers.TransactionReceipt, + l1Provider: Provider, + l2Provider: Provider, + signer?: Signer, +) { + const txReceipt = + typeof txHashOrReceipt === 'string' + ? await l2Provider.getTransactionReceipt(txHashOrReceipt) + : txHashOrReceipt + const l1SignerOrProvider = signer ? signer.connect(l1Provider) : l1Provider + const l2Receipt = new L2TransactionReceipt(txReceipt) + const l2ToL1Messages = await l2Receipt.getL2ToL1Messages(l1SignerOrProvider) + return l2ToL1Messages[0] +} diff --git a/cli/commands/bridge/to-l1.ts b/cli/commands/bridge/to-l1.ts index 0913aa2e1..93c6b1f53 100644 --- a/cli/commands/bridge/to-l1.ts +++ b/cli/commands/bridge/to-l1.ts @@ -10,6 +10,7 @@ import { BigNumber } from 'ethers' import { JsonRpcProvider } from '@ethersproject/providers' import { providers } from 'ethers' import { L2GraphToken } from '../../../build/types/L2GraphToken' +import { getL2ToL1MessageReader, getL2ToL1MessageWriter } from '../../arbitrum' const FOURTEEN_DAYS_IN_SECONDS = 24 * 3600 * 14 @@ -99,10 +100,11 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom 'outboundTransfer(address,address,uint256,bytes)', params, ) + + const l2ToL1Message = await getL2ToL1MessageReader(receipt, cli.wallet.provider, l2Provider) const l2Receipt = new L2TransactionReceipt(receipt) - const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0] - const ethBlockNum = l2ToL1Message.getFirstExecutableBlock(l2Provider) + const ethBlockNum = await l2ToL1Message.getFirstExecutableBlock(l2Provider) if (ethBlockNum === null) { logger.info(`L2 to L1 message can or already has been executed. If not finalized call`) } else { @@ -157,11 +159,12 @@ export const finishSendToL1 = async ( txHash = allEvents[allEvents.length - 1].transactionHash } logger.info(`Getting receipt from transaction ${txHash}`) - const receipt = await l2Provider.getTransactionReceipt(txHash) - - const l2Receipt = new L2TransactionReceipt(receipt) - logger.info(`Getting L2 to L1 message...`) - const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0] + const l2ToL1Message = await getL2ToL1MessageWriter( + txHash, + cli.wallet.provider, + l2Provider, + cli.wallet, + ) if (wait) { const retryDelayMs = cliArgs.retryDelaySeconds ? cliArgs.retryDelaySeconds * 1000 : 60000 diff --git a/cli/commands/bridge/to-l2.ts b/cli/commands/bridge/to-l2.ts index 01b3f65bc..acd9363af 100644 --- a/cli/commands/bridge/to-l2.ts +++ b/cli/commands/bridge/to-l2.ts @@ -6,6 +6,7 @@ import { loadEnv, CLIArgs, CLIEnvironment } from '../../env' import { logger } from '../../logging' import { getProvider, sendTransaction, toGRT, ensureAllowance, toBN } from '../../network' import { chainIdIsL2, estimateRetryableTxGas } from '../../cross-chain' +import { getL1ToL2MessageWriter } from '../../arbitrum' const logAutoRedeemReason = (autoRedeemRec) => { if (autoRedeemRec == null) { @@ -105,9 +106,12 @@ export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise ({ - blockNumber: e.blockNumber, - transactionHash: e.transactionHash, - from: e.args.from, - to: e.args.to, - amount: ethers.utils.formatEther(e.args.amount), - })) + const events = await Promise.all( + ( + await gateway.queryFilter(gateway.filters.DepositInitiated(), startBlock, endBlock) + ).map(async (e) => ({ + blockNumber: `${e.blockNumber} (${new Date( + (await graph.l1.provider.getBlock(e.blockNumber)).timestamp * 1000, + ).toLocaleString()})`, + tx: `${e.transactionHash} ${e.args.from} -> ${e.args.to}`, + amount: ethers.utils.formatEther(e.args.amount), + status: emojifyRetryableStatus( + await getL1ToL2MessageStatus(e.transactionHash, graph.l1.provider, graph.l2.provider), + ), + })), + ) const total = events.reduce( (acc, e) => acc.add(ethers.utils.parseEther(e.amount)), @@ -45,23 +52,46 @@ task(TASK_BRIDGE_DEPOSITS, 'List deposits initiated on L1GraphTokenGateway') `Found ${events.length} deposits with a total of ${ethers.utils.formatEther(total)} GRT`, ) + console.log( + 'L1 to L2 message status reference: 🚧 = not yet created, ❌ = creation failed, ⚠️ = funds deposited on L2, βœ… = redeemed, βŒ› = expired', + ) + printEvents(events) }) function printEvents(events: any[]) { const tablePrinter = new Table({ + charLength: { '🚧': 2, 'βœ…': 2, '⚠️': 1, 'βŒ›': 2, '❌': 2 }, columns: [ - { name: 'blockNumber', color: 'green' }, + { name: 'status', color: 'green', alignment: 'center' }, + { name: 'blockNumber', color: 'green', alignment: 'center' }, { - name: 'transactionHash', + name: 'tx', color: 'green', + alignment: 'center', + maxLen: 88, }, - { name: 'from', color: 'green' }, - { name: 'to', color: 'green' }, - { name: 'amount', color: 'green' }, + { name: 'amount', color: 'green', alignment: 'center' }, ], }) events.map((e) => tablePrinter.addRow(e)) tablePrinter.printTable() } + +function emojifyRetryableStatus(status: L1ToL2MessageStatus): string { + switch (status) { + case L1ToL2MessageStatus.NOT_YET_CREATED: + return '🚧' + case L1ToL2MessageStatus.CREATION_FAILED: + return '❌' + case L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2: + return '⚠️ ' + case L1ToL2MessageStatus.REDEEMED: + return 'βœ…' + case L1ToL2MessageStatus.EXPIRED: + return 'βŒ›' + default: + return '❌' + } +} diff --git a/tasks/bridge/withdrawals.ts b/tasks/bridge/withdrawals.ts index cf6d4e36b..ea1cb402a 100644 --- a/tasks/bridge/withdrawals.ts +++ b/tasks/bridge/withdrawals.ts @@ -2,6 +2,8 @@ import { task } from 'hardhat/config' import { cliOpts } from '../../cli/defaults' import { ethers } from 'ethers' import { Table } from 'console-table-printer' +import { L2ToL1MessageStatus } from '@arbitrum/sdk' +import { getL2ToL1MessageStatus } from '../../cli/arbitrum' export const TASK_BRIDGE_WITHDRAWALS = 'bridge:withdrawals' @@ -27,15 +29,20 @@ task(TASK_BRIDGE_WITHDRAWALS, 'List withdrawals initiated on L2GraphTokenGateway const endBlock = taskArgs.endBlock ? parseInt(taskArgs.endBlock) : 'latest' console.log(`Searching blocks from block ${startBlock} to block ${endBlock}`) - const events = ( - await gateway.queryFilter(gateway.filters.WithdrawalInitiated(), startBlock, endBlock) - ).map((e) => ({ - blockNumber: e.blockNumber, - transactionHash: e.transactionHash, - from: e.args.from, - to: e.args.to, - amount: ethers.utils.formatEther(e.args.amount), - })) + const events = await Promise.all( + ( + await gateway.queryFilter(gateway.filters.WithdrawalInitiated(), startBlock, endBlock) + ).map(async (e) => ({ + blockNumber: `${e.blockNumber} (${new Date( + (await graph.l2.provider.getBlock(e.blockNumber)).timestamp * 1000, + ).toLocaleString()})`, + tx: `${e.transactionHash} ${e.args.from} -> ${e.args.to}`, + amount: ethers.utils.formatEther(e.args.amount), + status: emojifyL2ToL1Status( + await getL2ToL1MessageStatus(e.transactionHash, graph.l1.provider, graph.l2.provider), + ), + })), + ) const total = events.reduce( (acc, e) => acc.add(ethers.utils.parseEther(e.amount)), @@ -45,19 +52,25 @@ task(TASK_BRIDGE_WITHDRAWALS, 'List withdrawals initiated on L2GraphTokenGateway `Found ${events.length} withdrawals for a total of ${ethers.utils.formatEther(total)} GRT`, ) + console.log( + 'L2 to L1 message status reference: 🚧 = unconfirmed, ⚠️ = confirmed, βœ… = executed', + ) + printEvents(events) }) function printEvents(events: any[]) { const tablePrinter = new Table({ + charLength: { '🚧': 2, 'βœ…': 2, '⚠️': 1, '❌': 2 }, columns: [ + { name: 'status', color: 'green', alignment: 'center' }, { name: 'blockNumber', color: 'green' }, { - name: 'transactionHash', + name: 'tx', color: 'green', + alignment: 'center', + maxLen: 88, }, - { name: 'from', color: 'green' }, - { name: 'to', color: 'green' }, { name: 'amount', color: 'green' }, ], }) @@ -65,3 +78,16 @@ function printEvents(events: any[]) { events.map((e) => tablePrinter.addRow(e)) tablePrinter.printTable() } + +function emojifyL2ToL1Status(status: L2ToL1MessageStatus): string { + switch (status) { + case L2ToL1MessageStatus.UNCONFIRMED: + return '🚧' + case L2ToL1MessageStatus.CONFIRMED: + return '⚠️ ' + case L2ToL1MessageStatus.EXECUTED: + return 'βœ…' + default: + return '❌' + } +}