diff --git a/package.json b/package.json index 870722d6c4..90ef46bbfd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@across-protocol/constants": "^3.1.68", - "@across-protocol/contracts": "^4.1.0", + "@across-protocol/contracts": "^4.1.3", "@across-protocol/sdk": "4.3.47", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", diff --git a/src/adapter/bridges/BaseBridgeAdapter.ts b/src/adapter/bridges/BaseBridgeAdapter.ts index 31a768f5a6..5e169973e5 100644 --- a/src/adapter/bridges/BaseBridgeAdapter.ts +++ b/src/adapter/bridges/BaseBridgeAdapter.ts @@ -8,6 +8,8 @@ import { isDefined, EvmAddress, Address, + getHubPoolAddress, + getSpokePoolAddressEvm, } from "../../utils"; import { SortableEvent } from "../../interfaces"; @@ -25,6 +27,8 @@ export type BridgeEvent = SortableEvent & { export type BridgeEvents = { [l2Token: string]: BridgeEvent[] }; export abstract class BaseBridgeAdapter { + protected readonly hubPoolAddress: EvmAddress; + protected readonly spokePoolAddress: EvmAddress; protected l1Bridge: Contract; protected l2Bridge: Contract; public gasToken: EvmAddress | undefined; @@ -34,7 +38,10 @@ export abstract class BaseBridgeAdapter { protected hubChainId: number, protected l1Signer: Signer, public l1Gateways: EvmAddress[] - ) {} + ) { + this.hubPoolAddress = getHubPoolAddress(hubChainId); + this.spokePoolAddress = getSpokePoolAddressEvm(l2chainId); + } abstract constructL1ToL2Txn( toAddress: Address, diff --git a/src/adapter/bridges/BinanceCEXBridge.ts b/src/adapter/bridges/BinanceCEXBridge.ts index 7aaf7bc092..55bf726e55 100644 --- a/src/adapter/bridges/BinanceCEXBridge.ts +++ b/src/adapter/bridges/BinanceCEXBridge.ts @@ -14,11 +14,14 @@ import { floatToBN, CHAIN_IDs, compareAddressesSimple, - isContractDeployedToAddress, winston, + chainIsProd, + chainIsEvm, } from "../../utils"; +import { utils as ethersUtils } from "ethers"; import { BaseBridgeAdapter, BridgeTransactionDetails, BridgeEvents } from "./BaseBridgeAdapter"; import ERC20_ABI from "../../common/abi/MinimalERC20.json"; +import { getAllDeployedAddresses } from "@across-protocol/contracts"; export class BinanceCEXBridge extends BaseBridgeAdapter { // Only store the promise in the constructor and evaluate the promise in async blocks. @@ -27,6 +30,10 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { protected tokenSymbol: string; protected l2Provider: Provider; + // Since this is a CEX rebalancing adapter, it will never be used to rebalance contract funds. This means we can _always_ return an empty set + // of bridge events if the monitored address is an Across smart contract and avoid querying the API needlessly. + private addressIgnorelist: Set; + constructor( l2chainId: number, hubChainId: number, @@ -54,6 +61,8 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { // Cast the input Signer | Provider to a Provider. this.l2Provider = l2SignerOrProvider instanceof Signer ? l2SignerOrProvider.provider : l2SignerOrProvider; + + this.initAddressIgnorelist(this.hubPoolAddress); } async constructL1ToL2Txn( @@ -85,10 +94,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { _toAddress: EvmAddress, eventConfig: EventSearchConfig ): Promise { - // Since this is a CEX rebalancing adapter, it will never be used to rebalance contract funds. This means we can _always_ return an empty set - // of bridge events if the monitored address is a contract on L1 or L2 and avoid querying the API needlessly. - const isL1OrL2Contract = await this.isL1OrL2Contract(fromAddress); - if (isL1OrL2Contract) { + if (this.addressIgnorelist.has(fromAddress.toNative())) { return {}; } assert(l1Token.toNative() === this.getL1Bridge().address); @@ -137,10 +143,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { toAddress: EvmAddress, eventConfig: EventSearchConfig ): Promise { - // Since this is a CEX rebalancing adapter, it will never be used to rebalance contract funds. This means we can _always_ return an empty set - // of bridge events if the monitored address is a contract on L1 or L2 and avoid querying the API needlessly. - const isL1OrL2Contract = await this.isL1OrL2Contract(toAddress); - if (isL1OrL2Contract) { + if (this.addressIgnorelist.has(toAddress.toNative())) { return {}; } // We must typecast the l2 signer or provider into specifically an ethers Provider type so we can call `getTransactionReceipt` and `getBlockByNumber` on it. @@ -179,15 +182,19 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { }; } - private async isL1OrL2Contract(address: EvmAddress): Promise { - const [isL1Contract, isL2Contract] = await Promise.all([ - isContractDeployedToAddress(address.toNative(), this.l1Signer.provider), - isContractDeployedToAddress(address.toNative(), this.l2Provider), - ]); - return isL1Contract || isL2Contract; - } - protected async getBinanceClient() { return (this.binanceApiClient ??= await this.binanceApiClientPromise); } + + // Init ignore list with all SpokePool addresses from Evm chains and a HubPool address. + initAddressIgnorelist(hubPoolAddress: EvmAddress) { + const acrossSpokesAndHub = new Set( + getAllDeployedAddresses("SpokePool") + .filter((contractInfo) => chainIsEvm(contractInfo.chainId) && chainIsProd(contractInfo.chainId)) + .map((contractInfo) => ethersUtils.getAddress(contractInfo.address)) + ); + acrossSpokesAndHub.add(hubPoolAddress.toNative()); + + this.addressIgnorelist = acrossSpokesAndHub; + } } diff --git a/src/adapter/bridges/HyperlaneXERC20Bridge.ts b/src/adapter/bridges/HyperlaneXERC20Bridge.ts index c284197320..c6a5ac0bb9 100644 --- a/src/adapter/bridges/HyperlaneXERC20Bridge.ts +++ b/src/adapter/bridges/HyperlaneXERC20Bridge.ts @@ -92,6 +92,7 @@ export class HyperlaneXERC20Bridge extends BaseBridgeAdapter { return { contract: this.l1Bridge, method: "transferRemote", + // ! todo make this more robust by adding a test for this address formatting args: [this.dstDomainId, toAddress.toBytes32(), amount], value: fee, }; diff --git a/src/adapter/bridges/OFTBridge.ts b/src/adapter/bridges/OFTBridge.ts index 2afe88ce58..c8a01cf3c7 100644 --- a/src/adapter/bridges/OFTBridge.ts +++ b/src/adapter/bridges/OFTBridge.ts @@ -13,12 +13,11 @@ import { Address, toBytes32, toWei, - isContractDeployedToAddress, winston, } from "../../utils"; import { processEvent } from "../utils"; import { CHAIN_IDs, PUBLIC_NETWORKS } from "@across-protocol/constants"; -import { CONTRACT_ADDRESSES, IOFT_ABI_FULL } from "../../common"; +import { IOFT_ABI_FULL } from "../../common"; export type SendParamStruct = { dstEid: BigNumberish; @@ -61,7 +60,6 @@ export class OFTBridge extends BaseBridgeAdapter { // Cap the messaging fee to prevent excessive costs private static readonly FEE_CAP = toWei("0.1"); // 0.1 ether - private readonly hubPoolAddress: string; public readonly dstTokenAddress: string; private readonly dstChainEid: number; private tokenDecimals?: number; @@ -90,8 +88,6 @@ export class OFTBridge extends BaseBridgeAdapter { super(dstChainId, hubChainId, hubSigner, [route.hubChainIOFTAddress]); - this.hubPoolAddress = CONTRACT_ADDRESSES[hubChainId]?.hubPool?.address; - assert(isDefined(this.hubPoolAddress), `Hub pool address not found for chain ${hubChainId}`); this.dstTokenAddress = this.resolveL2TokenAddress(hubTokenAddress); this.dstChainEid = getOFTEidForChainId(dstChainId); this.l1Bridge = new Contract(route.hubChainIOFTAddress.toNative(), IOFT_ABI_FULL, hubSigner); @@ -181,11 +177,11 @@ export class OFTBridge extends BaseBridgeAdapter { } // Return no events if the query is for hubPool - if (fromAddress.eq(EvmAddress.from(this.hubPoolAddress))) { + if (fromAddress.eq(this.hubPoolAddress)) { return {}; } - const isSpokePool = await isContractDeployedToAddress(toAddress.toNative(), this.l2Bridge.provider); + const isSpokePool = this.spokePoolAddress.eq(toAddress); const fromHubEvents = await paginatedEventQuery( this.l1Bridge, this.l1Bridge.filters.OFTSent( @@ -219,7 +215,7 @@ export class OFTBridge extends BaseBridgeAdapter { } // Return no events if the query is for hubPool - if (fromAddress.eq(EvmAddress.from(this.hubPoolAddress))) { + if (fromAddress.eq(this.hubPoolAddress)) { return {}; } diff --git a/src/adapter/bridges/OpStackWethBridge.ts b/src/adapter/bridges/OpStackWethBridge.ts index 859166f354..17ab209ebe 100644 --- a/src/adapter/bridges/OpStackWethBridge.ts +++ b/src/adapter/bridges/OpStackWethBridge.ts @@ -10,22 +10,26 @@ import { TOKEN_SYMBOLS_MAP, EvmAddress, winston, + chainIsEvm, + chainIsProd, } from "../../utils"; +import { utils as ethersUtils } from "ethers"; import { CONTRACT_ADDRESSES } from "../../common"; import { Log } from "../../interfaces"; import { matchL2EthDepositAndWrapEvents, processEvent } from "../utils"; -import { utils } from "@across-protocol/sdk"; import { BridgeTransactionDetails, BaseBridgeAdapter, BridgeEvents } from "./BaseBridgeAdapter"; import WETH_ABI from "../../common/abi/Weth.json"; +import { getAllDeployedAddresses } from "@across-protocol/contracts"; export class OpStackWethBridge extends BaseBridgeAdapter { protected atomicDepositor: Contract; protected l2Weth: Contract; protected l1Weth: EvmAddress; - private readonly hubPoolAddress: string; private readonly l2Gas = 200000; + private allEvmSpokePools: Set; + constructor( l2chainId: number, hubChainId: number, @@ -55,7 +59,8 @@ export class OpStackWethBridge extends BaseBridgeAdapter { this.l2Weth = new Contract(TOKEN_SYMBOLS_MAP.WETH.addresses[l2chainId], WETH_ABI, l2SignerOrProvider); this.l1Weth = EvmAddress.from(TOKEN_SYMBOLS_MAP.WETH.addresses[this.hubChainId]); - this.hubPoolAddress = CONTRACT_ADDRESSES[this.hubChainId]?.hubPool?.address; + + this.initAllEvmSpokePools(hubChainId); } async constructL1ToL2Txn( @@ -93,25 +98,24 @@ export class OpStackWethBridge extends BaseBridgeAdapter { // to actually filter on. So we make some simplifying assumptions: // - For our tracking purposes, the ETHDepositInitiated `fromAddress` will be the // AtomicDepositor if the fromAddress is an EOA. - const isContract = await this.isHubChainContract(fromAddress); - const isL2ChainContract = await this.isL2ChainContract(fromAddress); + const isAcrossSpokePool = this.isAcrossSpokePool(fromAddress); + const isHubPool = this.hubPoolAddress.eq(fromAddress); + const isEOA = !isAcrossSpokePool && !isHubPool; // Since we can only index on the `fromAddress` for the ETHDepositInitiated event, we can't support // monitoring the spoke pool address - if (isL2ChainContract || (isContract && fromAddress.toNative() !== this.hubPoolAddress)) { + if (isAcrossSpokePool) { return this.convertEventListToBridgeEvents([]); } const events = await paginatedEventQuery( this.getL1Bridge(), - this.getL1Bridge().filters.ETHDepositInitiated( - isContract ? fromAddress.toNative() : this.atomicDepositor.address - ), + this.getL1Bridge().filters.ETHDepositInitiated(isHubPool ? fromAddress.toNative() : this.atomicDepositor.address), eventConfig ); // If EOA sent the ETH via the AtomicDepositor, then remove any events where the // toAddress is not the EOA so we don't get confused with other users using the AtomicDepositor - if (!isContract) { + if (isEOA) { return this.convertEventListToBridgeEvents(events.filter((event) => event.args._to === fromAddress.toNative())); } return this.convertEventListToBridgeEvents(events); @@ -123,16 +127,16 @@ export class OpStackWethBridge extends BaseBridgeAdapter { toAddress: EvmAddress, eventConfig: EventSearchConfig ): Promise { - // Check if the sender is a contract on the L1 network. - const isContract = await this.isHubChainContract(fromAddress); + const isAcrossSpokePool = this.isAcrossSpokePool(fromAddress); + const isHubPool = this.hubPoolAddress.eq(fromAddress); + const isEOA = !isAcrossSpokePool && !isHubPool; // See above for why we don't want to monitor the spoke pool contract. - const isL2ChainContract = await this.isL2ChainContract(fromAddress); - if (isL2ChainContract) { + if (isAcrossSpokePool) { return this.convertEventListToBridgeEvents([]); } - if (!isContract) { + if (isEOA) { // When bridging WETH to OP stack chains from an EOA, ETH is bridged via the AtomicDepositor contract // and received as ETH on L2. The InventoryClient is built to abstract this subtlety and // assumes that WETH is being rebalanced from L1 to L2. Therefore, L1 to L2 ETH transfers sent from an EOA @@ -160,7 +164,7 @@ export class OpStackWethBridge extends BaseBridgeAdapter { } else { // Since we can only index on the `fromAddress` for the DepositFinalized event, we can't support // monitoring the spoke pool address - if (fromAddress.toNative() !== this.hubPoolAddress) { + if (!isHubPool) { return this.convertEventListToBridgeEvents([]); } @@ -174,11 +178,21 @@ export class OpStackWethBridge extends BaseBridgeAdapter { } } - async isHubChainContract(address: EvmAddress): Promise { - return utils.isContractDeployedToAddress(address.toNative(), this.getL1Bridge().provider); + isAcrossSpokePool(address: EvmAddress) { + return this.allEvmSpokePools.has(address.toNative()); } - async isL2ChainContract(address: EvmAddress): Promise { - return utils.isContractDeployedToAddress(address.toNative(), this.getL2Bridge().provider); + + initAllEvmSpokePools(hubChainId: number) { + const hubIsProd = chainIsProd(hubChainId); + const evmSpokePools = getAllDeployedAddresses("SpokePool") + .filter( + (contractInfo) => + chainIsEvm(contractInfo.chainId) && + (hubIsProd ? chainIsProd(contractInfo.chainId) : !chainIsProd(contractInfo.chainId)) + ) + .map((contractInfo) => ethersUtils.getAddress(contractInfo.address)); + + this.allEvmSpokePools = new Set(evmSpokePools); } private queryL2WrapEthEvents( diff --git a/src/adapter/bridges/ScrollERC20Bridge.ts b/src/adapter/bridges/ScrollERC20Bridge.ts index a74536cd99..b631341aa7 100644 --- a/src/adapter/bridges/ScrollERC20Bridge.ts +++ b/src/adapter/bridges/ScrollERC20Bridge.ts @@ -7,7 +7,6 @@ import { Provider, toWei, fixedPointAdjustment, - isContractDeployedToAddress, bnZero, compareAddressesSimple, TOKEN_SYMBOLS_MAP, @@ -29,8 +28,7 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter { protected feeMultiplier = toWei(1.5); protected readonly scrollGasPriceOracle: Contract; - protected readonly scrollGatewayRouter; - protected readonly hubPoolAddress; + protected readonly scrollGatewayRouter: Contract; constructor( l2chainId: number, hubChainId: number, @@ -54,8 +52,6 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter { this.scrollGatewayRouter = new Contract(l1Address, l1Abi, l1Signer); this.scrollGasPriceOracle = new Contract(gasPriceOracleAddress, gasPriceOracleAbi, l1Signer); - - this.hubPoolAddress = CONTRACT_ADDRESSES[hubChainId].hubPool.address; } async constructL1ToL2Txn( @@ -86,8 +82,9 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter { toAddress: EvmAddress, eventConfig: EventSearchConfig ): Promise { - const isL2Contract = await isContractDeployedToAddress(toAddress.toNative(), this.l2Bridge.provider); - const monitoredFromAddress = isL2Contract ? this.hubPoolAddress : fromAddress.toNative(); + // For a SpokePool from chainId associated with this Bridge object, return all transfers (hubpool-addr -> spokepool-addr) + const isAssociatedSpokePool = toAddress.eq(this.spokePoolAddress); + const monitoredFromAddress = isAssociatedSpokePool ? this.hubPoolAddress.toNative() : fromAddress.toNative(); const l1Bridge = this.getL1Bridge(); const events = await paginatedEventQuery( @@ -111,8 +108,9 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter { toAddress: EvmAddress, eventConfig: EventSearchConfig ): Promise { - const isL2Contract = await isContractDeployedToAddress(toAddress.toNative(), this.l2Bridge.provider); - const monitoredFromAddress = isL2Contract ? this.hubPoolAddress : fromAddress.toNative(); + const isAssociatedSpokePool = toAddress.eq(this.spokePoolAddress); + // For SpokePool from chainId associated with our Bridge object, return all transfers (hubpool-addr -> spokepool-addr) + const monitoredFromAddress = isAssociatedSpokePool ? this.hubPoolAddress.toNative() : fromAddress.toNative(); const l2Bridge = this.getL2Bridge(); const events = await paginatedEventQuery( diff --git a/src/adapter/bridges/SnxOptimismBridge.ts b/src/adapter/bridges/SnxOptimismBridge.ts index 67a4748a23..96304abbe3 100644 --- a/src/adapter/bridges/SnxOptimismBridge.ts +++ b/src/adapter/bridges/SnxOptimismBridge.ts @@ -5,7 +5,6 @@ import { EventSearchConfig, Signer, Provider, - isContractDeployedToAddress, EvmAddress, winston, } from "../../utils"; @@ -60,8 +59,8 @@ export class SnxOptimismBridge extends BaseBridgeAdapter { } // If `toAddress` is a contract on L2, then assume the contract is the spoke pool, and further assume that the sender // is the hub pool. - const isSpokePool = await this.isL2ChainContract(toAddress); - fromAddress = isSpokePool ? hubPoolAddress : fromAddress; + const isAssociatedSpokePool = this.spokePoolAddress.eq(toAddress); + fromAddress = isAssociatedSpokePool ? hubPoolAddress : fromAddress; const events = await paginatedEventQuery( this.getL1Bridge(), this.getL1Bridge().filters.DepositInitiated(fromAddress.toNative()), @@ -95,8 +94,4 @@ export class SnxOptimismBridge extends BaseBridgeAdapter { } return new Contract(hubPoolContractData.address, hubPoolContractData.abi, this.l1Signer); } - - private isL2ChainContract(address: EvmAddress): Promise { - return isContractDeployedToAddress(address.toNative(), this.getL2Bridge().provider); - } } diff --git a/src/adapter/bridges/ZKStackBridge.ts b/src/adapter/bridges/ZKStackBridge.ts index c4bef04829..b575d1227b 100644 --- a/src/adapter/bridges/ZKStackBridge.ts +++ b/src/adapter/bridges/ZKStackBridge.ts @@ -8,7 +8,6 @@ import { TOKEN_SYMBOLS_MAP, compareAddressesSimple, paginatedEventQuery, - isContractDeployedToAddress, EvmAddress, isDefined, bnZero, @@ -146,8 +145,8 @@ export class ZKStackBridge extends BaseBridgeAdapter { eventConfig: EventSearchConfig ): Promise { // Logic changes based on whether we are sending tokens to the spoke pool or to an EOA. - const isL2Contract = await this._isContract(toAddress.toNative(), this.getL2Bridge().provider!); - const annotatedFromAddress = isL2Contract ? this.hubPool.address : fromAddress.toNative(); + const isAssociatedSpokePool = this.spokePoolAddress.eq(toAddress); + const annotatedFromAddress = isAssociatedSpokePool ? this.hubPool.address : fromAddress.toNative(); const bridgingCustomGasToken = isDefined(this.gasToken) && this.gasToken.eq(l1Token); let processedEvents; if (!bridgingCustomGasToken) { @@ -164,7 +163,7 @@ export class ZKStackBridge extends BaseBridgeAdapter { .filter((event) => compareAddressesSimple(event.args.receiver, toAddress.toNative())) .map((e) => processEvent(e, "amount")); } else { - if (isL2Contract) { + if (isAssociatedSpokePool) { const rawEvents = await paginatedEventQuery(this.hubPool, this.hubPool.filters.TokensRelayed(), eventConfig); processedEvents = rawEvents .filter( @@ -203,7 +202,7 @@ export class ZKStackBridge extends BaseBridgeAdapter { const l2Token = this.resolveL2TokenAddress(l1Token); // Similar to the query, if we are sending to the spoke pool, we must assume that the sender is the hubPool, // so we add a special case for this reason. - const isSpokePool = await isContractDeployedToAddress(toAddress.toNative(), this.l2Bridge.provider); + const isAssociatedSpokePool = this.spokePoolAddress.eq(toAddress); const bridgingCustomGasToken = isDefined(this.gasToken) && this.gasToken.eq(l1Token); let processedEvents; if (!bridgingCustomGasToken) { @@ -222,7 +221,7 @@ export class ZKStackBridge extends BaseBridgeAdapter { } else { // We are bridging the native token so we need to query transfer events from the aliased senders. let events; - if (isSpokePool) { + if (isAssociatedSpokePool) { events = await paginatedEventQuery( this.nativeToken, this.nativeToken.filters.Transfer(zksync.utils.applyL1ToL2Alias(this.hubPool.address), toAddress.toNative()), @@ -280,10 +279,6 @@ export class ZKStackBridge extends BaseBridgeAdapter { return l2Gas; } - async _isContract(address: string, provider: Provider): Promise { - return isContractDeployedToAddress(address, provider); - } - protected override resolveL2TokenAddress(l1Token: EvmAddress): string { // ZkStack chains may or may not have native USDC, but all will have only one USDC "type" supported by Across. If there is an entry in the TOKEN_SYMBOLS_MAP for USDC, then use this, otherwise, bubble up the resolution. if ( diff --git a/src/adapter/l2Bridges/BaseL2BridgeAdapter.ts b/src/adapter/l2Bridges/BaseL2BridgeAdapter.ts index 1d12590682..6eb7cad26e 100644 --- a/src/adapter/l2Bridges/BaseL2BridgeAdapter.ts +++ b/src/adapter/l2Bridges/BaseL2BridgeAdapter.ts @@ -1,9 +1,21 @@ import { AugmentedTransaction } from "../../clients/TransactionClient"; -import { BigNumber, Contract, EventSearchConfig, Provider, Signer, EvmAddress, Address } from "../../utils"; +import { + BigNumber, + Contract, + EventSearchConfig, + Provider, + Signer, + EvmAddress, + Address, + getHubPoolAddress, + getSpokePoolAddressEvm, +} from "../../utils"; export abstract class BaseL2BridgeAdapter { protected l2Bridge: Contract; protected l1Bridge: Contract; + protected readonly hubPoolAddress: EvmAddress; + protected readonly spokePoolAddress: EvmAddress; constructor( protected l2chainId: number, @@ -11,7 +23,10 @@ export abstract class BaseL2BridgeAdapter { protected l2Signer: Signer, protected l1Provider: Provider | Signer, protected l1Token: EvmAddress - ) {} + ) { + this.hubPoolAddress = getHubPoolAddress(hubChainId); + this.spokePoolAddress = getSpokePoolAddressEvm(l2chainId); + } abstract constructWithdrawToL1Txns( toAddress: Address, diff --git a/src/adapter/l2Bridges/HyperlaneXERC20Bridge.ts b/src/adapter/l2Bridges/HyperlaneXERC20Bridge.ts index f70e33d6f8..94bb3f23e1 100644 --- a/src/adapter/l2Bridges/HyperlaneXERC20Bridge.ts +++ b/src/adapter/l2Bridges/HyperlaneXERC20Bridge.ts @@ -16,24 +16,17 @@ import { getTokenInfo, getNetworkName, createFormatFunction, - isContractDeployedToAddress, } from "../../utils"; import { PUBLIC_NETWORKS, HYPERLANE_NO_DOMAIN_ID } from "@across-protocol/constants"; import { BaseL2BridgeAdapter } from "./BaseL2BridgeAdapter"; import HYPERLANE_ROUTER_ABI from "../../common/abi/IHypXERC20Router.json"; -import { - CONTRACT_ADDRESSES, - HYPERLANE_ROUTERS, - HYPERLANE_DEFAULT_FEE_CAP, - HYPERLANE_FEE_CAP_OVERRIDES, -} from "../../common"; +import { HYPERLANE_ROUTERS, HYPERLANE_DEFAULT_FEE_CAP, HYPERLANE_FEE_CAP_OVERRIDES } from "../../common"; import { AugmentedTransaction } from "../../clients/TransactionClient"; export class HyperlaneXERC20BridgeL2 extends BaseL2BridgeAdapter { readonly l2Token: EvmAddress; private readonly originDomainId: number; private readonly destinationDomainId: number; - private readonly hubPoolAddress: string; constructor( l2chainId: number, @@ -73,8 +66,6 @@ export class HyperlaneXERC20BridgeL2 extends BaseL2BridgeAdapter { this.destinationDomainId != HYPERLANE_NO_DOMAIN_ID, `Hyperlane domain id not set for chain ${hubChainId}. Set it first before using HyperlaneXERC20BridgeL2` ); - this.hubPoolAddress = CONTRACT_ADDRESSES[hubChainId]?.hubPool?.address; - assert(isDefined(this.hubPoolAddress), `HubPool address not found for hubChainId ${hubChainId}`); } async constructWithdrawToL1Txns( @@ -106,6 +97,7 @@ export class HyperlaneXERC20BridgeL2 extends BaseL2BridgeAdapter { method: "transferRemote", unpermissioned: false, nonMulticall: true, + // ! todo make this more robust by adding a test for this address formatting args: [this.destinationDomainId, toAddress.toBytes32(), amount], value: fee, message: `🎰 Withdrew Hyperlane xERC20 ${symbol} to L1`, @@ -128,13 +120,10 @@ export class HyperlaneXERC20BridgeL2 extends BaseL2BridgeAdapter { `this.l2Token does not match l2Token getL2PendingWithdrawalAmount was called with: ${this.l2Token} != ${l2Token}` ); - let recipientBytes32: string; - const isSpokePool = await isContractDeployedToAddress(fromAddress.toNative(), this.l2Bridge.provider); - if (isSpokePool) { - recipientBytes32 = toBytes32(this.hubPoolAddress); - } else { - recipientBytes32 = fromAddress.toBytes32(); - } + const isAssociatedSpokePool = this.spokePoolAddress.eq(fromAddress); + const recipientBytes32 = isAssociatedSpokePool + ? toBytes32(this.hubPoolAddress.toNative()) + : fromAddress.toBytes32(); const [withdrawalInitiatedEvents, withdrawalFinalizedEvents] = await Promise.all([ paginatedEventQuery( diff --git a/src/utils/ContractUtils.ts b/src/utils/ContractUtils.ts index a17f8417dd..c156f35555 100644 --- a/src/utils/ContractUtils.ts +++ b/src/utils/ContractUtils.ts @@ -1,5 +1,15 @@ import * as typechain from "@across-protocol/contracts"; // TODO: refactor once we've fixed export from contract repo -import { CHAIN_IDs, getNetworkName, Contract, Signer, getDeployedAddress, getDeployedBlockNumber } from "."; +import { + CHAIN_IDs, + getNetworkName, + Contract, + Signer, + getDeployedAddress, + getDeployedBlockNumber, + EvmAddress, + assert, + chainIsEvm, +} from "."; // Return an ethers contract instance for a deployed contract, imported from the Across-protocol contracts repo. export function getDeployedContract(contractName: string, networkId: number, signer?: Signer): Contract { @@ -49,6 +59,15 @@ export function getSpokePool(chainId: number, address?: string): Contract { return new Contract(address ?? getDeployedAddress("SpokePool", chainId), artifact.abi); } +export function getSpokePoolAddressEvm(chainId: number): EvmAddress { + assert(chainIsEvm(chainId)); + return EvmAddress.from(getDeployedAddress("SpokePool", chainId, true)); +} + +export function getHubPoolAddress(chainId: number): EvmAddress { + return EvmAddress.from(getDeployedAddress("HubPool", chainId, true)); +} + export function getParamType(contractName: string, functionName: string, paramName: string): string { const artifact = typechain[`${[contractName]}__factory`]; const fragment = artifact.abi.find((fragment: { name: string }) => fragment.name === functionName); diff --git a/yarn.lock b/yarn.lock index 9fd075fe66..7d759fea06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,7 +11,7 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants@^3.1.66", "@across-protocol/constants@^3.1.68": +"@across-protocol/constants@^3.1.68": version "3.1.68" resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.68.tgz#2a58b091b3f717394ee8025b20ee89022da849d8" integrity sha512-f/o4i323HxwT/4h32xZdxKLwN3xXoAaxcd/xwP0tfAc/+X4/a+1qJ5Mfx+U2IwDkSheiSyqRYF3z5oYPnax3mg== @@ -30,12 +30,12 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" -"@across-protocol/contracts@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-4.1.0.tgz#548cf631d78cd34cd193f41cfed32b5fb881606e" - integrity sha512-ypwmjkZsqswIKX93QwhJf8G5zGyM8aMLH1iBtnXAxjLvuAEe1fAnUDt2E2444DA2bVT5lCcVbmYCZ+BS7ZjHGw== +"@across-protocol/contracts@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-4.1.1.tgz#91c8e0fc867911a17f21b2d79d586f95417cb912" + integrity sha512-APyBXkNrP7VAbN2KzoO3h4VvGUGCgbz1OWACVAQ2/SWm3jMWmzHMOOtLeIqfmPTW0be7pkTbxnfR8hANMcicXw== dependencies: - "@across-protocol/constants" "^3.1.66" + "@across-protocol/constants" "^3.1.69" "@coral-xyz/anchor" "^0.31.1" "@defi-wonderland/smock" "^2.3.4" "@eth-optimism/contracts" "^0.5.40" @@ -61,10 +61,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/contracts@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-4.1.1.tgz#91c8e0fc867911a17f21b2d79d586f95417cb912" - integrity sha512-APyBXkNrP7VAbN2KzoO3h4VvGUGCgbz1OWACVAQ2/SWm3jMWmzHMOOtLeIqfmPTW0be7pkTbxnfR8hANMcicXw== +"@across-protocol/contracts@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-4.1.3.tgz#e7f4693d48eb4528c6258b4a7aee1191789eaf59" + integrity sha512-1HuFXgVA5MH62jGV96KDRUzaokDj0efSHMObkjaclNwompXREYKLgE/0BrMgH1gtw3Al9MJ+lLL64GHscQYP4Q== dependencies: "@across-protocol/constants" "^3.1.69" "@coral-xyz/anchor" "^0.31.1"