Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make bridge tasks prettier and display more info for L2 to L1 #783

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
88 changes: 58 additions & 30 deletions tasks/bridge/deposits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cliOpts } from '../../cli/defaults'
import { ethers } from 'ethers'
import { Table } from 'console-table-printer'
import { L1ToL2MessageStatus } from '@arbitrum/sdk'
import { getL1ToL2MessageStatus } from '../../cli/arbitrum'
import { getL1ToL2MessageStatus, getL1ToL2MessageReader } from '../../cli/arbitrum'

export const TASK_BRIDGE_DEPOSITS = 'bridge:deposits'

Expand All @@ -16,66 +16,89 @@ task(TASK_BRIDGE_DEPOSITS, 'List deposits initiated on L1GraphTokenGateway')
)
.addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description)
.addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description)
.addOptionalParam('startBlock', 'Start block for the search')
.addOptionalParam('endBlock', 'End block for the search')
.addOptionalParam('l1StartBlock', 'Start block on L1 for the search')
.addOptionalParam('l1EndBlock', 'End block on L1 for the search')
.addOptionalParam('l2StartBlock', 'Start block on L2 for the search')
.setAction(async (taskArgs, hre) => {
console.log('> L1GraphTokenGateway deposits')
console.time('runtime')
console.log('> GRT Bridge deposits <\n')

const graph = hre.graph(taskArgs)
const gateway = graph.l1.contracts.L1GraphTokenGateway
console.log(`Tracking 'DepositInitiated' events on ${gateway.address}`)
const l2Gateway = graph.l2.contracts.L2GraphTokenGateway
const l1Gateway = graph.l1.contracts.L1GraphTokenGateway
const l1StartBlock = taskArgs.l1StartBlock ? parseInt(taskArgs.l1StartBlock) : 0
const l1EndBlock = taskArgs.l1EndBlock ? parseInt(taskArgs.l1EndBlock) : 'latest'
const l2StartBlock = taskArgs.l2StartBlock ? parseInt(taskArgs.l2StartBlock) : 0

const startBlock = taskArgs.startBlock ? parseInt(taskArgs.startBlock) : 0
const endBlock = taskArgs.endBlock ? parseInt(taskArgs.endBlock) : 'latest'
console.log(`Searching blocks from block ${startBlock} to block ${endBlock}`)
console.log(
`Tracking 'DepositInitiated' events on L1GraphTokenGateway (${l1Gateway.address}) from block ${l1StartBlock} to block ${l1EndBlock}`,
)
console.log(
`Tracking 'DepositFinalized' events on L2GraphTokenGateway (${l2Gateway.address}) from block ${l2StartBlock} onwards`,
)

const events = await Promise.all(
const depositInitiatedEvents = 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),
),
})),
await l1Gateway.queryFilter(l1Gateway.filters.DepositInitiated(), l1StartBlock, l1EndBlock)
).map(async (e) => {
const retryableTicket = await getL1ToL2MessageReader(
e.transactionHash,
graph.l1.provider,
graph.l2.provider,
)

return {
l1Tx: `Block ${e.blockNumber} (${new Date(
(await graph.l1.provider.getBlock(e.blockNumber)).timestamp * 1000,
).toLocaleString()}) ${e.transactionHash}`,
l2Tx: `${retryableTicket.retryableCreationId}`, // Can't get block data because of arb node rate limit
amount: prettyBigNumber(e.args.amount),
status: emojifyRetryableStatus(
await getL1ToL2MessageStatus(e.transactionHash, graph.l1.provider, graph.l2.provider),
),
}
}),
)

const total = events.reduce(
const total = depositInitiatedEvents.reduce(
(acc, e) => acc.add(ethers.utils.parseEther(e.amount)),
ethers.BigNumber.from(0),
)
console.log(
`Found ${events.length} deposits with a total of ${ethers.utils.formatEther(total)} GRT`,
`Found ${depositInitiatedEvents.length} deposits with a total of ${prettyBigNumber(
total,
)} GRT`,
)

console.log(
'L1 to L2 message status reference: 🚧 = not yet created, ❌ = creation failed, ⚠️ = funds deposited on L2, ✅ = redeemed, ⌛ = expired',
'\nL1 to L2 message status reference: 🚧 = not yet created, ❌ = creation failed, ⚠️ = funds deposited on L2, ✅ = redeemed, ⌛ = expired',
)

printEvents(events)
printEvents(depositInitiatedEvents)
console.timeEnd('runtime')
})

function printEvents(events: any[]) {
const tablePrinter = new Table({
charLength: { '🚧': 2, '✅': 2, '⚠️': 1, '⌛': 2, '❌': 2 },
columns: [
{ name: 'status', color: 'green', alignment: 'center' },
{ name: 'blockNumber', color: 'green', alignment: 'center' },
{ name: 'l1Tx', color: 'green', alignment: 'center', maxLen: 72, title: 'L1 transaction' },
{
name: 'tx',
name: 'l2Tx',
color: 'green',
alignment: 'center',
maxLen: 88,
maxLen: 72,
title: 'L2 retryable ticket creation',
},
{ name: 'amount', color: 'green', alignment: 'center' },
{ name: 'amount', color: 'green' },
],
})

events.map((e) => tablePrinter.addRow(e))
events.map((e) => {
tablePrinter.addRow(e)
tablePrinter.addRow({}) // For table padding
})
tablePrinter.printTable()
}

Expand All @@ -95,3 +118,8 @@ function emojifyRetryableStatus(status: L1ToL2MessageStatus): string {
return '❌'
}
}

// Format BigNumber to 2 decimal places
function prettyBigNumber(amount: ethers.BigNumber): string {
return (+ethers.utils.formatEther(amount)).toFixed(2)
}
140 changes: 105 additions & 35 deletions tasks/bridge/withdrawals.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { task } from 'hardhat/config'
import { cliOpts } from '../../cli/defaults'
import { ethers } from 'ethers'
import { BigNumber, ethers } from 'ethers'
import { Table } from 'console-table-printer'
import { L2ToL1MessageStatus } from '@arbitrum/sdk'
import { getL2ToL1MessageStatus } from '../../cli/arbitrum'
import { keccak256 } from 'ethers/lib/utils'

export const TASK_BRIDGE_WITHDRAWALS = 'bridge:withdrawals'

Expand All @@ -16,66 +17,130 @@ task(TASK_BRIDGE_WITHDRAWALS, 'List withdrawals initiated on L2GraphTokenGateway
)
.addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description)
.addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description)
.addOptionalParam('startBlock', 'Start block for the search')
.addOptionalParam('endBlock', 'End block for the search')
.addOptionalParam('l1StartBlock', 'Start block on L1 for the search')
.addOptionalParam('l2StartBlock', 'Start block on L2 for the search')
.addOptionalParam('l2EndBlock', 'End block on L2 for the search')
.setAction(async (taskArgs, hre) => {
console.log('> L2GraphTokenGateway withdrawals')
console.time('runtime')
console.log('> GRT Bridge withdrawals <\n')

const graph = hre.graph(taskArgs)
const gateway = graph.l2.contracts.L2GraphTokenGateway
console.log(`Tracking 'WithdrawalInitiated' events on ${gateway.address}`)
const l2Gateway = graph.l2.contracts.L2GraphTokenGateway
const l1Gateway = graph.l1.contracts.L1GraphTokenGateway
const l1StartBlock = taskArgs.l1StartBlock ? parseInt(taskArgs.l1StartBlock) : 0
const l2StartBlock = taskArgs.l2StartBlock ? parseInt(taskArgs.l2StartBlock) : 0
const l2EndBlock = taskArgs.l2EndBlock ? parseInt(taskArgs.l2EndBlock) : 'latest'

const startBlock = taskArgs.startBlock ? parseInt(taskArgs.startBlock) : 0
const endBlock = taskArgs.endBlock ? parseInt(taskArgs.endBlock) : 'latest'
console.log(`Searching blocks from block ${startBlock} to block ${endBlock}`)
console.log(
`Tracking 'WithdrawalInitiated' events on L2GraphTokenGateway (${l2Gateway.address}) from block ${l2StartBlock} to block ${l2EndBlock}`,
)
console.log(
`Tracking 'WithdrawalFinalized' events on L1GraphTokenGateway (${l1Gateway.address}) from block ${l1StartBlock} onwards`,
)

const events = await Promise.all(
let totalGRTClaimed = ethers.BigNumber.from(0)
let totalGRTConfirmed = ethers.BigNumber.from(0)
let totalGRTUnconfirmed = ethers.BigNumber.from(0)

const withdrawalFinalizedEvents = 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),
),
})),
await l1Gateway.queryFilter(l1Gateway.filters.WithdrawalFinalized(), l1StartBlock)
).map(async (e) => {
const receipt = await e.getTransactionReceipt()
const outBoxTransactionExecutedEvent = receipt.logs.find(
(log) =>
log.topics[0] ===
keccak256(
ethers.utils.toUtf8Bytes(
'OutBoxTransactionExecuted(address,address,uint256,uint256)',
),
),
)

return {
blockNumber: e.blockNumber,
transactionHash: e.transactionHash,
transactionIndex: outBoxTransactionExecutedEvent
? BigNumber.from(outBoxTransactionExecutedEvent.data)
: null,
}
}),
)

const total = events.reduce(
(acc, e) => acc.add(ethers.utils.parseEther(e.amount)),
ethers.BigNumber.from(0),
const withdrawalInitiatedEvents = await Promise.all(
(
await l2Gateway.queryFilter(
l2Gateway.filters.WithdrawalInitiated(),
l2StartBlock,
l2EndBlock,
)
).map(async (e) => {
const status = await getL2ToL1MessageStatus(
e.transactionHash,
graph.l1.provider,
graph.l2.provider,
)
if (status === L2ToL1MessageStatus.EXECUTED)
totalGRTClaimed = totalGRTClaimed.add(e.args.amount)
if (status === L2ToL1MessageStatus.CONFIRMED)
totalGRTConfirmed = totalGRTConfirmed.add(e.args.amount)
if (status === L2ToL1MessageStatus.UNCONFIRMED)
totalGRTUnconfirmed = totalGRTUnconfirmed.add(e.args.amount)

// Find L1 event
const l1Event = withdrawalFinalizedEvents.find((ev) =>
ev.transactionIndex.eq(e.args.l2ToL1Id),
)

return {
l2Tx: `Block ${e.blockNumber} (${new Date(
(await graph.l2.provider.getBlock(e.blockNumber)).timestamp * 1000,
).toLocaleString()}) ${e.transactionHash}`,
l1Tx: l1Event
? `Block ${l1Event.blockNumber} (${new Date(
(await graph.l1.provider.getBlock(l1Event.blockNumber)).timestamp * 1000,
).toLocaleString()}) ${l1Event.transactionHash}`
: '-',
amount: prettyBigNumber(e.args.amount),
status: emojifyL2ToL1Status(status),
}
}),
)

console.log(
`\nFound ${withdrawalInitiatedEvents.length} withdrawals for a total of ${prettyBigNumber(
totalGRTClaimed.add(totalGRTConfirmed).add(totalGRTUnconfirmed),
)} GRT`,
)
console.log(`- Total GRT claimed on L1 (executed): ${prettyBigNumber(totalGRTClaimed)} GRT`)
console.log(
`Found ${events.length} withdrawals for a total of ${ethers.utils.formatEther(total)} GRT`,
`- Total GRT claimable on L1 (confirmed): ${prettyBigNumber(totalGRTConfirmed)} GRT`,
)
console.log(`- Total GRT on transit (unconfirmed): ${prettyBigNumber(totalGRTUnconfirmed)} GRT`)

console.log(
'L2 to L1 message status reference: 🚧 = unconfirmed, ⚠️ = confirmed, ✅ = executed',
'\nL2 to L1 message status reference: 🚧 = unconfirmed, ⚠️ = confirmed, ✅ = executed',
)

printEvents(events)
printEvents(withdrawalInitiatedEvents)
console.timeEnd('runtime')
})

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: 'tx',
color: 'green',
alignment: 'center',
maxLen: 88,
},
{ name: 'l2Tx', color: 'green', alignment: 'center', maxLen: 72, title: 'L2 transaction' },
{ name: 'l1Tx', color: 'green', alignment: 'center', maxLen: 72, title: 'L1 transaction' },
{ name: 'amount', color: 'green' },
],
})

events.map((e) => tablePrinter.addRow(e))
events.map((e) => {
tablePrinter.addRow(e)
tablePrinter.addRow({}) // For table padding
})
tablePrinter.printTable()
}

Expand All @@ -91,3 +156,8 @@ function emojifyL2ToL1Status(status: L2ToL1MessageStatus): string {
return '❌'
}
}

// Format BigNumber to 2 decimal places
function prettyBigNumber(amount: ethers.BigNumber): string {
return (+ethers.utils.formatEther(amount)).toFixed(2)
}