@@ -34,7 +34,6 @@ import {
3434 fetchTokenInfo ,
3535 getCachedBlockForTimestamp ,
3636 getCurrentTime ,
37- getNetworkName ,
3837 isDefined ,
3938 mapAsync ,
4039 paginatedEventQuery ,
@@ -75,17 +74,11 @@ type HubPoolEvent =
7574 | "RootBundleExecuted"
7675 | "CrossChainContractsSet" ;
7776
78- type L1TokensToDestinationTokens = {
79- [ l1Token : string ] : { [ destinationChainId : number ] : Address } ;
80- } ;
81-
8277export type LpFeeRequest = Pick < Deposit , "originChainId" | "inputToken" | "inputAmount" | "quoteTimestamp" > & {
8378 paymentChainId ?: number ;
8479} ;
8580
8681export class HubPoolClient extends BaseAbstractClient {
87- // L1Token -> destinationChainId -> destinationToken
88- protected l1TokensToDestinationTokens : L1TokensToDestinationTokens = { } ;
8982 protected l1Tokens : L1TokenInfo [ ] = [ ] ; // L1Tokens and their associated info.
9083 // @dev `token` here is a 20-byte hex sting
9184 protected lpTokens : { [ token : string ] : LpToken } = { } ;
@@ -192,87 +185,65 @@ export class HubPoolClient extends BaseAbstractClient {
192185 l1Token : EvmAddress ,
193186 destinationChainId : number ,
194187 latestHubBlock = Number . MAX_SAFE_INTEGER
195- ) : Address {
188+ ) : Address | undefined {
196189 if ( ! this . l1TokensToDestinationTokensWithBlock ?. [ l1Token . toNative ( ) ] ?. [ destinationChainId ] ) {
197- const chain = getNetworkName ( destinationChainId ) ;
198- const { symbol } = this . l1Tokens . find ( ( { address } ) => address . eq ( l1Token ) ) ?? { symbol : l1Token . toString ( ) } ;
199- throw new Error ( `Could not find SpokePool mapping for ${ symbol } on ${ chain } and L1 token ${ l1Token } ` ) ;
190+ return undefined ;
200191 }
201192 // Find the last mapping published before the target block.
202- const l2Token : DestinationTokenWithBlock | undefined = sortEventsDescending (
203- this . l1TokensToDestinationTokensWithBlock [ l1Token . toNative ( ) ] [ destinationChainId ]
204- ) . find ( ( mapping : DestinationTokenWithBlock ) => mapping . blockNumber <= latestHubBlock ) ;
205- if ( ! l2Token ) {
206- const chain = getNetworkName ( destinationChainId ) ;
207- const { symbol } = this . l1Tokens . find ( ( { address } ) => address . eq ( l1Token ) ) ?? { symbol : l1Token . toString ( ) } ;
208- throw new Error (
209- `Could not find SpokePool mapping for ${ symbol } on ${ chain } at or before HubPool block ${ latestHubBlock } !`
210- ) ;
211- }
212- return l2Token . l2Token ;
193+ const l2Token : DestinationTokenWithBlock | undefined = this . l1TokensToDestinationTokensWithBlock [
194+ l1Token . toNative ( )
195+ ] [ destinationChainId ] . find ( ( mapping : DestinationTokenWithBlock ) => mapping . blockNumber <= latestHubBlock ) ;
196+
197+ return ! isDefined ( l2Token ) || l2Token . l2Token . isZeroAddress ( ) ? undefined : l2Token . l2Token ;
213198 }
214199
215200 // Returns the latest L1 token to use for an L2 token as of the input hub block.
216201 getL1TokenForL2TokenAtBlock (
217202 l2Token : Address ,
218203 destinationChainId : number ,
219204 latestHubBlock = Number . MAX_SAFE_INTEGER
220- ) : EvmAddress {
221- const l2Tokens = Object . keys ( this . l1TokensToDestinationTokensWithBlock )
222- . filter ( ( l1Token ) => this . l2TokenEnabledForL1Token ( EvmAddress . from ( l1Token ) , destinationChainId ) )
223- . map ( ( l1Token ) => {
224- // Return all matching L2 token mappings that are equal to or earlier than the target block.
225- // @dev Since tokens on L2s (like Solana) can have 32 byte addresses, filter on the lower 20 bytes of the token only.
226- return this . l1TokensToDestinationTokensWithBlock [ l1Token ] [ destinationChainId ] . filter (
227- ( dstTokenWithBlock ) =>
228- dstTokenWithBlock . l2Token . truncateToBytes20 ( ) === l2Token . truncateToBytes20 ( ) &&
229- dstTokenWithBlock . blockNumber <= latestHubBlock
230- ) ;
231- } )
232- . flat ( ) ;
233- if ( l2Tokens . length === 0 ) {
234- const chain = getNetworkName ( destinationChainId ) ;
235- throw new Error (
236- `Could not find HubPool mapping for ${ l2Token } on ${ chain } at or before HubPool block ${ latestHubBlock } !`
205+ ) : EvmAddress | undefined {
206+ const l2Tokens = Object . keys ( this . l1TokensToDestinationTokensWithBlock ) . flatMap ( ( l1Token ) => {
207+ // Get the latest L2 token mapping for the given L1 token.
208+ // @dev Since tokens on L2s (like Solana) can have 32 byte addresses, filter on the lower 20 bytes of the token only.
209+ const sortedL2Tokens = sortEventsDescending (
210+ ( this . l1TokensToDestinationTokensWithBlock [ l1Token ] [ destinationChainId ] ?? [ ] ) . filter (
211+ ( dstTokenWithBlock ) => dstTokenWithBlock . blockNumber <= latestHubBlock
212+ )
237213 ) ;
238- }
239- // Find the last mapping published before the target block.
240- return sortEventsDescending ( l2Tokens ) [ 0 ] . l1Token ;
214+ // If the latest L2 token mapping is equal to the target L2 token, return it.
215+ return sortedL2Tokens . length > 0 && sortedL2Tokens [ 0 ] . l2Token . truncateToBytes20 ( ) === l2Token . truncateToBytes20 ( )
216+ ? sortedL2Tokens [ 0 ]
217+ : [ ] ;
218+ } ) ;
219+
220+ return l2Tokens . length === 0 ? undefined : sortEventsDescending ( l2Tokens ) [ 0 ] . l1Token ;
241221 }
242222
243223 protected getL1TokenForDeposit (
244224 deposit : Pick < DepositWithBlock , "originChainId" | "inputToken" | "quoteBlockNumber" >
245- ) : EvmAddress {
225+ ) : EvmAddress | undefined {
246226 // L1-->L2 token mappings are set via PoolRebalanceRoutes which occur on mainnet,
247227 // so we use the latest token mapping. This way if a very old deposit is filled, the relayer can use the
248228 // latest L2 token mapping to find the L1 token counterpart.
249229 return this . getL1TokenForL2TokenAtBlock ( deposit . inputToken , deposit . originChainId , deposit . quoteBlockNumber ) ;
250230 }
251231
252232 l2TokenEnabledForL1Token ( l1Token : EvmAddress , destinationChainId : number ) : boolean {
253- return this . l1TokensToDestinationTokens ?. [ l1Token . toNative ( ) ] ?. [ destinationChainId ] != undefined ;
233+ return this . l2TokenEnabledForL1TokenAtBlock ( l1Token , destinationChainId , Number . MAX_SAFE_INTEGER ) ;
254234 }
255235
256236 l2TokenEnabledForL1TokenAtBlock ( l1Token : EvmAddress , destinationChainId : number , hubBlockNumber : number ) : boolean {
257237 // Find the last mapping published before the target block.
258238 const l2Token : DestinationTokenWithBlock | undefined = sortEventsDescending (
259239 this . l1TokensToDestinationTokensWithBlock ?. [ l1Token . toNative ( ) ] ?. [ destinationChainId ] ?? [ ]
260240 ) . find ( ( mapping : DestinationTokenWithBlock ) => mapping . blockNumber <= hubBlockNumber ) ;
261- return l2Token !== undefined ;
241+ return isDefined ( l2Token ) && ! l2Token . l2Token . isZeroAddress ( ) ;
262242 }
263243
264244 l2TokenHasPoolRebalanceRoute ( l2Token : Address , l2ChainId : number , hubPoolBlock = this . latestHeightSearched ) : boolean {
265- return Object . values ( this . l1TokensToDestinationTokensWithBlock ) . some ( ( destinationTokenMapping ) => {
266- return Object . entries ( destinationTokenMapping ) . some ( ( [ _l2ChainId , setPoolRebalanceRouteEvents ] ) => {
267- return setPoolRebalanceRouteEvents . some ( ( e ) => {
268- return (
269- e . blockNumber <= hubPoolBlock &&
270- e . l2Token . truncateToBytes20 ( ) === l2Token . truncateToBytes20 ( ) &&
271- Number ( _l2ChainId ) === l2ChainId
272- ) ;
273- } ) ;
274- } ) ;
275- } ) ;
245+ const l1Token = this . getL1TokenForL2TokenAtBlock ( l2Token , l2ChainId , hubPoolBlock ) ;
246+ return isDefined ( l1Token ) ;
276247 }
277248
278249 /**
@@ -401,11 +372,11 @@ export class HubPoolClient extends BaseAbstractClient {
401372 const hubPoolTokens : { [ k : string ] : EvmAddress } = { } ;
402373 const getHubPoolToken = ( deposit : LpFeeRequest , quoteBlockNumber : number ) : EvmAddress | undefined => {
403374 const tokenKey = `${ deposit . originChainId } -${ deposit . inputToken } ` ;
404- if ( this . l2TokenHasPoolRebalanceRoute ( deposit . inputToken , deposit . originChainId , quoteBlockNumber ) ) {
405- return ( hubPoolTokens [ tokenKey ] ??= this . getL1TokenForDeposit ( { ...deposit , quoteBlockNumber } ) ) ;
375+ const l1Token = this . getL1TokenForDeposit ( { ...deposit , quoteBlockNumber } ) ;
376+ if ( ! isDefined ( l1Token ) ) {
377+ return undefined ;
406378 }
407-
408- return undefined ;
379+ return ( hubPoolTokens [ tokenKey ] ??= l1Token ) ;
409380 } ;
410381
411382 // Filter hubPoolTokens for duplicates by reverting to their native string
@@ -553,14 +524,14 @@ export class HubPoolClient extends BaseAbstractClient {
553524 // Resolve both SpokePool tokens back to their respective HubPool tokens and verify that they match.
554525 const l1TokenA = this . getL1TokenForL2TokenAtBlock ( tokenA , chainIdA , hubPoolBlock ) ;
555526 const l1TokenB = this . getL1TokenForL2TokenAtBlock ( tokenB , chainIdB , hubPoolBlock ) ;
556- if ( ! l1TokenA . eq ( l1TokenB ) ) {
527+ if ( ! isDefined ( l1TokenA ) || ! isDefined ( l1TokenB ) || ! l1TokenA . eq ( l1TokenB ) ) {
557528 return false ;
558529 }
559530
560531 // Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
561532 const _tokenA = this . getL2TokenForL1TokenAtBlock ( l1TokenA , chainIdA , hubPoolBlock ) ;
562533 const _tokenB = this . getL2TokenForL1TokenAtBlock ( l1TokenB , chainIdB , hubPoolBlock ) ;
563- return tokenA . eq ( _tokenA ) && tokenB . eq ( _tokenB ) ;
534+ return isDefined ( _tokenA ) && isDefined ( _tokenB ) && tokenA . eq ( _tokenA ) && tokenB . eq ( _tokenB ) ;
564535 }
565536
566537 getSpokeActivationBlockForChain ( chainId : number ) : number {
@@ -1001,24 +972,22 @@ export class HubPoolClient extends BaseAbstractClient {
1001972 destinationToken = svmUsdc ;
1002973 }
1003974
1004- // If the destination token is set to the zero address in an event, then this means Across should no longer
1005- // rebalance to this chain.
1006- if ( ! destinationToken . isZeroAddress ( ) ) {
1007- assign ( this . l1TokensToDestinationTokens , [ args . l1Token , args . destinationChainId ] , destinationToken ) ;
1008- assign (
1009- this . l1TokensToDestinationTokensWithBlock ,
1010- [ args . l1Token , args . destinationChainId ] ,
1011- [
1012- {
1013- l1Token : EvmAddress . from ( args . l1Token ) ,
1014- l2Token : destinationToken ,
1015- blockNumber : args . blockNumber ,
1016- txnIndex : args . txnIndex ,
1017- logIndex : args . logIndex ,
1018- txnRef : args . txnRef ,
1019- } ,
1020- ]
1021- ) ;
975+ const newRoute : DestinationTokenWithBlock = {
976+ l1Token : EvmAddress . from ( args . l1Token ) ,
977+ l2Token : destinationToken ,
978+ blockNumber : args . blockNumber ,
979+ txnIndex : args . txnIndex ,
980+ logIndex : args . logIndex ,
981+ txnRef : args . txnRef ,
982+ } ;
983+ if ( this . l1TokensToDestinationTokensWithBlock [ args . l1Token ] ?. [ args . destinationChainId ] ) {
984+ // Events are most likely coming in descending orders already but just in case we sort them again.
985+ this . l1TokensToDestinationTokensWithBlock [ args . l1Token ] [ args . destinationChainId ] = sortEventsDescending ( [
986+ ...this . l1TokensToDestinationTokensWithBlock [ args . l1Token ] [ args . destinationChainId ] ,
987+ newRoute ,
988+ ] ) ;
989+ } else {
990+ assign ( this . l1TokensToDestinationTokensWithBlock , [ args . l1Token , args . destinationChainId ] , [ newRoute ] ) ;
1022991 }
1023992 }
1024993 }
0 commit comments