Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 8 additions & 1 deletion src/adapter/bridges/BaseBridgeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
isDefined,
EvmAddress,
Address,
getHubPoolAddress,
getSpokePoolAddressEvm,
} from "../../utils";
import { SortableEvent } from "../../interfaces";

Expand All @@ -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;
Expand All @@ -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,
Expand Down
41 changes: 24 additions & 17 deletions src/adapter/bridges/BinanceCEXBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<string>;

constructor(
l2chainId: number,
hubChainId: number,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -85,10 +94,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {
_toAddress: EvmAddress,
eventConfig: EventSearchConfig
): Promise<BridgeEvents> {
// 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);
Expand Down Expand Up @@ -137,10 +143,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {
toAddress: EvmAddress,
eventConfig: EventSearchConfig
): Promise<BridgeEvents> {
// 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.
Expand Down Expand Up @@ -179,15 +182,19 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {
};
}

private async isL1OrL2Contract(address: EvmAddress): Promise<boolean> {
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;
}
}
1 change: 1 addition & 0 deletions src/adapter/bridges/HyperlaneXERC20Bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
12 changes: 4 additions & 8 deletions src/adapter/bridges/OFTBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {};
}

Expand Down
54 changes: 34 additions & 20 deletions src/adapter/bridges/OpStackWethBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;

constructor(
l2chainId: number,
hubChainId: number,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand All @@ -123,16 +127,16 @@ export class OpStackWethBridge extends BaseBridgeAdapter {
toAddress: EvmAddress,
eventConfig: EventSearchConfig
): Promise<BridgeEvents> {
// 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
Expand Down Expand Up @@ -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([]);
}

Expand All @@ -174,11 +178,21 @@ export class OpStackWethBridge extends BaseBridgeAdapter {
}
}

async isHubChainContract(address: EvmAddress): Promise<boolean> {
return utils.isContractDeployedToAddress(address.toNative(), this.getL1Bridge().provider);
isAcrossSpokePool(address: EvmAddress) {
return this.allEvmSpokePools.has(address.toNative());
}
async isL2ChainContract(address: EvmAddress): Promise<boolean> {
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(
Expand Down
16 changes: 7 additions & 9 deletions src/adapter/bridges/ScrollERC20Bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Provider,
toWei,
fixedPointAdjustment,
isContractDeployedToAddress,
bnZero,
compareAddressesSimple,
TOKEN_SYMBOLS_MAP,
Expand All @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -86,8 +82,9 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter {
toAddress: EvmAddress,
eventConfig: EventSearchConfig
): Promise<BridgeEvents> {
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(
Expand All @@ -111,8 +108,9 @@ export class ScrollERC20Bridge extends BaseBridgeAdapter {
toAddress: EvmAddress,
eventConfig: EventSearchConfig
): Promise<BridgeEvents> {
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(
Expand Down
9 changes: 2 additions & 7 deletions src/adapter/bridges/SnxOptimismBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
EventSearchConfig,
Signer,
Provider,
isContractDeployedToAddress,
EvmAddress,
winston,
} from "../../utils";
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -95,8 +94,4 @@ export class SnxOptimismBridge extends BaseBridgeAdapter {
}
return new Contract(hubPoolContractData.address, hubPoolContractData.abi, this.l1Signer);
}

private isL2ChainContract(address: EvmAddress): Promise<boolean> {
return isContractDeployedToAddress(address.toNative(), this.getL2Bridge().provider);
}
}
Loading