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: fetchDepositsByParentTxHashFromEventLog #2175

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,112 @@
import { ChainId } from '../../networks'
import { fetchDepositsByParentTxHashFromEventLog } from '../fetchDepositsByParentTxHashFromEventLog'

describe('fetchDepositsByParentTxHashFromEventLog', () => {
it('fetches nitro ETH deposit from event log when child chain provided is correct', async () => {
const result = await fetchDepositsByParentTxHashFromEventLog({
childChainId: ChainId.ArbitrumSepolia,
txHash:
'0xcfc3ec69ae6cbe89a8cbd0c242add626b3254540c23189d302c9fcd359630ce7'
})

expect(result.ethDeposits).toHaveLength(1)
expect(result.tokenDepositRetryables).toHaveLength(0)
expect(result.ethDeposits).toEqual(
expect.arrayContaining([
expect.objectContaining({
assetName: 'ETH',
assetType: 'ETH',
blockNumber: 7271901,
childChainId: 421614,
destination: '0xfd5735380689a53e6b048e980f34cb94be9fd0c7',
direction: 'deposit',
isClassic: false,
l1NetworkID: '11155111',
l2NetworkID: '421614',
parentChainId: 11155111,
parentToChildMsgData: {
childTxId:
'0xed613c2399692ed2704f3551ffcb952c1fe2a19f856a4ad2fd4bfd047fc2f580',
fetchingUpdate: false,
retryableCreationTxID:
'0xed613c2399692ed2704f3551ffcb952c1fe2a19f856a4ad2fd4bfd047fc2f580',
status: 3
},
sender: '0xfd5735380689a53e6b048e980f34cb94be9fd0c7',
source: 'event_logs',
status: 'success',
timestampCreated: '1734111816000',
timestampResolved: '1734112122000',
txID: '0xcfc3ec69ae6cbe89a8cbd0c242add626b3254540c23189d302c9fcd359630ce7',
type: 'deposit-l1',
value: '1.0'
})
])
)
})

it('fetches nitro token deposit from event log when child chain provided is correct', async () => {
const result = await fetchDepositsByParentTxHashFromEventLog({
childChainId: ChainId.ArbitrumOne,
txHash:
'0x583bea616664fa32f68d25822bb64f18c8186221c35919a567b3de5eb9c1ae7e'
})

expect(result.ethDeposits).toHaveLength(0)
expect(result.tokenDepositRetryables).toHaveLength(1)
expect(result.tokenDepositRetryables).toEqual(
expect.arrayContaining([
expect.objectContaining({
Copy link
Contributor

Choose a reason for hiding this comment

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

you can call directly expect(result.tokenDepositRetryables).toEqual([{...}]

assetName: 'WBTC',
assetType: 'ERC20',
blockNumber: 21502346,
childChainId: 42161,
destination: '0x07aE8551Be970cB1cCa11Dd7a11F47Ae82e70E67',
direction: 'deposit',
isClassic: false,
l1NetworkID: '1',
l2NetworkID: '42161',
parentChainId: 1,
parentToChildMsgData: {
childTxId:
'0x80e82a211400023ad140d88caeb049bc7b629930b8a030e027565e09c73c0db4',
fetchingUpdate: false,
retryableCreationTxID:
'0xdfd64b76cc4daa23f5ea989b1bc84d3a553dd8bdedc737da19289c5ff6f36a67',
status: 4
},
sender: '0x07aE8551Be970cB1cCa11Dd7a11F47Ae82e70E67',
source: 'event_logs',
status: 'success',
timestampCreated: '1735405559000',
timestampResolved: '1735405934000',
tokenAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
txID: '0x583bea616664fa32f68d25822bb64f18c8186221c35919a567b3de5eb9c1ae7e',
type: 'deposit',
value: '2.25871265',
value2: undefined
})
])
)
})

it('fetches no deposits from event log when child chain provided is wrong', async () => {
const result1 = await fetchDepositsByParentTxHashFromEventLog({
childChainId: ChainId.ArbitrumOne,
txHash:
'0xcfc3ec69ae6cbe89a8cbd0c242add626b3254540c23189d302c9fcd359630ce7'
})

expect(result1.ethDeposits).toHaveLength(0)
expect(result1.tokenDepositRetryables).toHaveLength(0)

const result2 = await fetchDepositsByParentTxHashFromEventLog({
childChainId: ChainId.ArbitrumSepolia,
txHash:
'0x583bea616664fa32f68d25822bb64f18c8186221c35919a567b3de5eb9c1ae7e'
})

expect(result2.ethDeposits).toHaveLength(0)
expect(result2.tokenDepositRetryables).toHaveLength(0)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { providers, utils } from 'ethers'
import { L1ERC20Gateway__factory } from '@arbitrum/sdk/dist/lib/abi/factories/L1ERC20Gateway__factory'
import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory'
import {
ArbitrumNetwork,
EthDepositMessage,
getArbitrumNetwork,
ParentToChildMessageReader,
ParentToChildMessageReaderClassic,
ParentTransactionReceipt
} from '@arbitrum/sdk'
import { parseTypedLogs } from '@arbitrum/sdk/dist/lib/dataEntities/event'

import { getProviderForChainId } from '@/token-bridge-sdk/utils'
import { updateAdditionalDepositData } from './helpers'
import { AssetType } from '../../hooks/arbTokenBridge.types'
import { Transaction } from '../../types/Transactions'
import { fetchNativeCurrency } from '../../hooks/useNativeCurrency'

export interface ParentToChildMessagesAndDepositMessages {
tokenDepositRetryables: ParentToChildMessageReader[]
tokenDepositRetryablesClassic: ParentToChildMessageReaderClassic[]
ethDeposits: EthDepositMessage[]
}

async function getParentTxReceipt(
txHash: string,
parentChainId: number
): Promise<ParentTransactionReceipt | undefined> {
try {
const parentProvider = getProviderForChainId(Number(parentChainId))
Copy link
Contributor

Choose a reason for hiding this comment

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

parentChainId should already be a number according to type on L28. If we expect to receive other type, we should update L28. Otherwise we can remove the casting here.


const receipt = await parentProvider.getTransactionReceipt(txHash)
if (receipt) {
return new ParentTransactionReceipt(receipt)
}
} catch (e) {
console.warn(`Cannot get tx receipt from parent chain ${parentChainId}`)
}
}

export type FetchDepositTxFromEventLogResult = {
receiver: string
sender: string
timestampCreated: string
transactionHash: string
type: 'EthDeposit' | 'TokenDeposit'
isClassic: boolean
id: string
ethValue?: string
tokenAmount?: string
blockCreatedAt: string
l1Token?: {
symbol: string
decimals: number
id: string
name: string
registeredAtBlock: string
}
}

const getDepositInitiatedLogs = (logs: providers.Log[]) => {
return parseTypedLogs(L1ERC20Gateway__factory, logs, 'DepositInitiated')
}

async function getChildChainMessages(
childChain: ArbitrumNetwork,
parentTxReceipt: ParentTransactionReceipt
) {
const childChainId = childChain.chainId
const childProvider = getProviderForChainId(childChainId)

// Check if any parentToChild msg is sent to the inbox of this child chain
const logFromChildChainInbox = parentTxReceipt.logs.filter(
log =>
log.address.toLowerCase() === childChain.ethBridge.inbox.toLowerCase()
)

if (logFromChildChainInbox.length === 0) {
return
}

const isClassic = await parentTxReceipt.isClassic(childProvider)

const parentToChildMessagesClassic = isClassic
? await parentTxReceipt.getParentToChildMessagesClassic(childProvider)
: ([] as ParentToChildMessageReaderClassic[])

const parentToChildMessages = isClassic
? ([] as ParentToChildMessageReader[])
: await parentTxReceipt.getParentToChildMessages(childProvider)
Copy link
Contributor

Choose a reason for hiding this comment

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

We can run those 2 queries in parallel with Promise.all

Copy link
Contributor

Choose a reason for hiding this comment

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

We can also add the third one below on L93


const ethDeposits = await parentTxReceipt.getEthDeposits(childProvider)

return {
tokenDepositRetryables: parentToChildMessages,
tokenDepositRetryablesClassic: parentToChildMessagesClassic,
ethDeposits
}
}

/**
* Get event logs for ParentToChild deposit transactions by tx hash.
* @param childChainId
* @param txHash The uniqueId indexed field was removed in nitro and a hash indexed field was added.
* For pre-nitro events the value passed in here will be used to find events with the same uniqueId.
* For post nitro events it will be used to find events with the same hash.
* @returns Any classic and nitro events that match the provided filters.
*/
export async function fetchDepositsByParentTxHashFromEventLog({
childChainId,
txHash
}: {
childChainId: number
txHash: string
}): Promise<{
ethDeposits: Transaction[]
tokenDepositRetryables: Transaction[]
}> {
if (!txHash) {
return {
ethDeposits: [],
tokenDepositRetryables: []
}
}
const childChain = getArbitrumNetwork(childChainId)
const parentChainId = childChain.parentChainId
const parentProvider = getProviderForChainId(parentChainId)

const parentTxReceipt = await getParentTxReceipt(txHash, parentChainId)

if (!parentTxReceipt) {
return {
ethDeposits: [],
tokenDepositRetryables: []
}
}

const messages = await getChildChainMessages(childChain, parentTxReceipt)

if (typeof messages === 'undefined') {
return {
ethDeposits: [],
tokenDepositRetryables: []
}
}

const tokenDepositRetryables: Transaction[] = await Promise.all(
messages.tokenDepositRetryables
.map(async tokenDepositMessage => {
const depositsInitiatedLogs = getDepositInitiatedLogs(
parentTxReceipt.logs
)

const firstDepositInitiatedLog = depositsInitiatedLogs[0]
if (typeof firstDepositInitiatedLog === 'undefined') {
return
}

const parentErc20Address = firstDepositInitiatedLog.l1Token
const tokenAmount = firstDepositInitiatedLog._amount.toString()

let tokenDepositData:
| {
tokenAmount: string | undefined
l1Token: {
symbol: string
decimals: number
id: string
name: string
}
}
| undefined

try {
const erc20 = ERC20__factory.connect(
parentErc20Address,
parentProvider
)
const [name, symbol, decimals] = await Promise.all([
erc20.name(),
erc20.symbol(),
erc20.decimals()
])
tokenDepositData = {
tokenAmount,
l1Token: {
symbol,
decimals,
id: parentErc20Address,
name
}
}
} catch (e) {
console.log('failed to fetch token data', e)
}

const receiverFromLog = firstDepositInitiatedLog._to

const timestamp = (
await parentProvider.getBlock(parentTxReceipt.blockNumber)
).timestamp

const depositTx: Transaction = {
destination: receiverFromLog,
sender: parentTxReceipt.from,
timestampCreated: timestamp.toString(),
type: 'deposit',
isClassic: false,
txID: parentTxReceipt.transactionHash,
value:
typeof tokenDepositData !== 'undefined' &&
typeof tokenDepositData.tokenAmount !== 'undefined'
? utils.formatUnits(
tokenDepositData.tokenAmount,
tokenDepositData.l1Token.decimals
)
: null,
blockNumber: parentTxReceipt.blockNumber,
direction: 'deposit',
source: 'event_logs',
assetType: AssetType.ERC20,
assetName: tokenDepositData?.l1Token.symbol ?? '',
tokenAddress: parentErc20Address,
parentChainId,
childChainId,
l1NetworkID: parentChainId.toString(),
l2NetworkID: childChainId.toString()
}

return await updateAdditionalDepositData({
depositTx,
parentProvider,
childProvider: tokenDepositMessage.childProvider
})
})
.filter(
(eventLogResult): eventLogResult is Promise<Transaction> =>
typeof eventLogResult !== 'undefined'
)
)

const ethDeposits: Transaction[] = await Promise.all(
messages.ethDeposits.map(async depositMessage => {
const timestamp = (
await parentProvider.getBlock(parentTxReceipt.blockNumber)
).timestamp
const childProvider = getProviderForChainId(depositMessage.childChainId)
const nativeCurrency = await fetchNativeCurrency({
provider: childProvider
})
const depositTx: Transaction = {
destination: depositMessage.to.toLowerCase(),
sender: parentTxReceipt.from.toLowerCase(),
timestampCreated: timestamp.toString(),
txID: parentTxReceipt.transactionHash,
type: 'deposit-l1',
isClassic: false,
value: utils.formatUnits(
depositMessage.value.toString(),
nativeCurrency.decimals
),
blockNumber: parentTxReceipt.blockNumber,
direction: 'deposit',
source: 'event_logs',
parentChainId,
childChainId: depositMessage.childChainId,
l1NetworkID: parentChainId.toString(),
l2NetworkID: depositMessage.childChainId.toString(),
assetType: AssetType.ETH,
assetName: nativeCurrency.symbol
}

return await updateAdditionalDepositData({
depositTx,
parentProvider,
childProvider
})
})
)

return { ethDeposits, tokenDepositRetryables }
}
Loading