Skip to content

feat: integrate bridge tasks with arbitrum sdk #760

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

Merged
merged 1 commit into from
Dec 6, 2022
Merged
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
108 changes: 108 additions & 0 deletions cli/arbitrum.ts
Original file line number Diff line number Diff line change
@@ -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<L1ToL2MessageWriter> {
return (await getL1ToL2Message(
txHashOrReceipt,
l1Provider,
l2Provider,
signer,
)) as L1ToL2MessageWriter
}

export async function getL1ToL2MessageReader(
txHashOrReceipt: string | providers.TransactionReceipt,
l1Provider: Provider,
l2Provider: Provider,
): Promise<L1ToL2MessageReader> {
return await getL1ToL2Message(txHashOrReceipt, l1Provider, l2Provider)
}

export async function getL1ToL2MessageStatus(
txHashOrReceipt: string | providers.TransactionReceipt,
l1Provider: Provider,
l2Provider: Provider,
): Promise<L1ToL2MessageStatus> {
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<L1ToL2MessageWriter | L1ToL2MessageReader> {
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<L2ToL1MessageWriter> {
return (await getL2ToL1Message(
txHashOrReceipt,
l1Provider,
l2Provider,
signer,
)) as L2ToL1MessageWriter
}

export async function getL2ToL1MessageReader(
txHashOrReceipt: string | providers.TransactionReceipt,
l1Provider: Provider,
l2Provider: Provider,
): Promise<L2ToL1MessageReader> {
return await getL2ToL1Message(txHashOrReceipt, l1Provider, l2Provider)
}

export async function getL2ToL1MessageStatus(
txHashOrReceipt: string | providers.TransactionReceipt,
l1Provider: Provider,
l2Provider: Provider,
): Promise<L2ToL1MessageStatus> {
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]
}
17 changes: 10 additions & 7 deletions cli/commands/bridge/to-l1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions cli/commands/bridge/to-l2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -105,9 +106,12 @@ export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<v
// get l2 ticket status
if (txReceipt.status == 1) {
logger.info('Waiting for message to propagate to L2...')
const l1Receipt = new L1TransactionReceipt(txReceipt)
const l1ToL2Messages = await l1Receipt.getL1ToL2Messages(cli.wallet.connect(l2Provider))
const l1ToL2Message = l1ToL2Messages[0]
const l1ToL2Message = await getL1ToL2MessageWriter(
txReceipt,
cli.wallet.provider,
l2Provider,
cli.wallet,
)
try {
await checkAndRedeemMessage(l1ToL2Message)
} catch (e) {
Expand Down
58 changes: 44 additions & 14 deletions tasks/bridge/deposits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { L1ToL2MessageStatus } from '@arbitrum/sdk'
import { getL1ToL2MessageStatus } from '../../cli/arbitrum'

export const TASK_BRIDGE_DEPOSITS = 'bridge:deposits'

Expand All @@ -27,15 +29,20 @@ task(TASK_BRIDGE_DEPOSITS, 'List deposits initiated on L1GraphTokenGateway')
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.DepositInitiated(), 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.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)),
Expand All @@ -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 '❌'
}
}
50 changes: 38 additions & 12 deletions tasks/bridge/withdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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)),
Expand All @@ -45,23 +52,42 @@ 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' },
],
})

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 '❌'
}
}