-
Notifications
You must be signed in to change notification settings - Fork 210
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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({ | ||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can run those 2 queries in parallel with Promise.all There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||
} |
There was a problem hiding this comment.
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([{...}]