Skip to content

Commit

Permalink
Merge branch 'master' into npai/validate-running-balance-expected
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaspai authored May 10, 2024
2 parents da6b9f1 + 1a171d3 commit 7888501
Show file tree
Hide file tree
Showing 28 changed files with 491 additions and 326 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"node": ">=16.18.0"
},
"dependencies": {
"@across-protocol/constants-v2": "1.0.22",
"@across-protocol/constants-v2": "1.0.24",
"@across-protocol/contracts-v2": "2.5.6",
"@across-protocol/sdk-v2": "0.23.10",
"@arbitrum/sdk": "^3.1.3",
Expand Down
34 changes: 21 additions & 13 deletions src/clients/InventoryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,15 @@ export class InventoryClient {
}

// Decrement Tokens Balance And Increment Cross Chain Transfer
trackCrossChainTransfer(l1Token: string, rebalance: BigNumber, chainId: number | string): void {
trackCrossChainTransfer(l1Token: string, l2Token: string, rebalance: BigNumber, chainId: number | string): void {
this.tokenClient.decrementLocalBalance(this.hubPoolClient.chainId, l1Token, rebalance);
this.crossChainTransferClient.increaseOutstandingTransfer(this.relayer, l1Token, rebalance, Number(chainId));
this.crossChainTransferClient.increaseOutstandingTransfer(
this.relayer,
l1Token,
l2Token,
rebalance,
Number(chainId)
);
}

async getAllBundleRefunds(): Promise<CombinedRefunds[]> {
Expand Down Expand Up @@ -677,7 +683,7 @@ export class InventoryClient {
// Next, evaluate if we have enough tokens on L1 to actually do these rebalances.

for (const rebalance of rebalancesRequired) {
const { balance, amount, l1Token, chainId } = rebalance;
const { balance, amount, l1Token, l2Token, chainId } = rebalance;

// This is the balance left after any assumed rebalances from earlier loop iterations.
const unallocatedBalance = this.tokenClient.getBalance(this.hubPoolClient.chainId, l1Token);
Expand Down Expand Up @@ -711,7 +717,7 @@ export class InventoryClient {
});
possibleRebalances.push(rebalance);
// Decrement token balance in client for this chain and increment cross chain counter.
this.trackCrossChainTransfer(l1Token, amount, chainId);
this.trackCrossChainTransfer(l1Token, l2Token, amount, chainId);
}
} else {
// Extract unexecutable rebalances for logging.
Expand All @@ -729,8 +735,8 @@ export class InventoryClient {
// sends each transaction one after the other with incrementing nonce. this will be left for a follow on PR as this
// is already complex logic and most of the time we'll not be sending batches of rebalance transactions.
for (const rebalance of possibleRebalances) {
const { chainId, l1Token, amount } = rebalance;
const { hash } = await this.sendTokenCrossChain(chainId, l1Token, amount, this.simMode);
const { chainId, l1Token, l2Token, amount } = rebalance;
const { hash } = await this.sendTokenCrossChain(chainId, l1Token, amount, this.simMode, l2Token);
executedTransactions.push({ ...rebalance, hash });
}

Expand Down Expand Up @@ -783,7 +789,7 @@ export class InventoryClient {
" This chain's pending L1->L2 transfer amount is " +
`${formatter(
this.crossChainTransferClient
.getOutstandingCrossChainTransferAmount(this.relayer, chainId, l1Token)
.getOutstandingCrossChainTransferAmount(this.relayer, chainId, l1Token, l2Token)
.toString()
)}.\n`;
}
Expand Down Expand Up @@ -957,7 +963,8 @@ export class InventoryClient {
const transfers = this.crossChainTransferClient.getOutstandingCrossChainTransferAmount(
this.relayer,
chainId,
l1Token
l1Token,
l2Token
);
const actualBalanceOnChain = this.tokenClient.getBalance(chainId, l2Token);

Expand All @@ -979,20 +986,21 @@ export class InventoryClient {
});
}

async sendTokenCrossChain(
sendTokenCrossChain(
chainId: number | string,
l1Token: string,
amount: BigNumber,
simMode = false
simMode = false,
l2Token?: string
): Promise<TransactionResponse> {
return await this.adapterManager.sendTokenCrossChain(this.relayer, Number(chainId), l1Token, amount, simMode);
return this.adapterManager.sendTokenCrossChain(this.relayer, Number(chainId), l1Token, amount, simMode, l2Token);
}

async _unwrapWeth(chainId: number, _l2Weth: string, amount: BigNumber): Promise<TransactionResponse> {
_unwrapWeth(chainId: number, _l2Weth: string, amount: BigNumber): Promise<TransactionResponse> {
const l2Signer = this.tokenClient.spokePoolClients[chainId].spokePool.signer;
const l2Weth = new Contract(_l2Weth, CONTRACT_ADDRESSES[1].weth.abi, l2Signer);
this.log("Unwrapping WETH", { amount: amount.toString() });
return await runTransaction(this.logger, l2Weth, "withdraw", [amount]);
return runTransaction(this.logger, l2Weth, "withdraw", [amount]);
}

async setL1TokenApprovals(): Promise<void> {
Expand Down
24 changes: 14 additions & 10 deletions src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { BigNumber, isDefined, winston, Signer, getL2TokenAddresses, TransactionResponse, assert } from "../../utils";
import { SpokePoolClient, HubPoolClient } from "../";
import { OptimismAdapter, ArbitrumAdapter, PolygonAdapter, BaseAdapter, ZKSyncAdapter } from "./";
import {
OptimismAdapter,
ArbitrumAdapter,
PolygonAdapter,
BaseAdapter,
ZKSyncAdapter,
BaseChainAdapter,
LineaAdapter,
} from "./";
import { InventoryConfig, OutstandingTransfers } from "../../interfaces";
import { utils } from "@across-protocol/sdk-v2";
import { CHAIN_IDs } from "@across-protocol/constants-v2";
import { BaseChainAdapter } from "./op-stack/base/BaseChainAdapter";
import { spokesThatHoldEthAndWeth } from "../../common/Constants";
import { LineaAdapter } from "./LineaAdapter";

export class AdapterManager {
public adapters: { [chainId: number]: BaseAdapter } = {};

Expand Down Expand Up @@ -71,10 +78,7 @@ export class AdapterManager {
return Object.keys(this.adapters).map((chainId) => Number(chainId));
}

async getOutstandingCrossChainTokenTransferAmount(
chainId: number,
l1Tokens: string[]
): Promise<OutstandingTransfers> {
getOutstandingCrossChainTokenTransferAmount(chainId: number, l1Tokens: string[]): Promise<OutstandingTransfers> {
const adapter = this.adapters[chainId];
// @dev The adapter should filter out tokens that are not supported by the adapter, but we do it here as well.
const adapterSupportedL1Tokens = l1Tokens.filter((token) =>
Expand All @@ -86,10 +90,10 @@ export class AdapterManager {
adapterSupportedL1Tokens,
searchConfigs: adapter.getUpdatedSearchConfigs(),
});
return await this.adapters[chainId].getOutstandingCrossChainTransfers(adapterSupportedL1Tokens);
return this.adapters[chainId].getOutstandingCrossChainTransfers(adapterSupportedL1Tokens);
}

async sendTokenCrossChain(
sendTokenCrossChain(
address: string,
chainId: number | string,
l1Token: string,
Expand All @@ -100,7 +104,7 @@ export class AdapterManager {
chainId = Number(chainId); // Ensure chainId is a number before using.
this.logger.debug({ at: "AdapterManager", message: "Sending token cross-chain", chainId, l1Token, amount });
l2Token ??= this.l2TokenForL1Token(l1Token, Number(chainId));
return await this.adapters[chainId].sendTokenToTargetChain(address, l1Token, l2Token, amount, simMode);
return this.adapters[chainId].sendTokenToTargetChain(address, l1Token, l2Token, amount, simMode);
}

// Check how much ETH is on the target chain and if it is above the threshold the wrap it to WETH. Note that this only
Expand Down
7 changes: 5 additions & 2 deletions src/clients/bridges/ArbitrumAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,15 @@ export class ArbitrumAdapter extends CCTPAdapter {
};
});
const eventsStorage = index % 2 === 0 ? this.l1DepositInitiatedEvents : this.l2DepositFinalizedEvents;
assign(eventsStorage, [monitoredAddress, l1Token], events);
const l2Token = this.resolveL2TokenAddress(l1Token, false); // This codepath will never have native USDC - therefore we should pass `false`.
assign(eventsStorage, [monitoredAddress, l1Token, l2Token], events);
});
if (isDefined(resultingCCTPEvents[monitoredAddress])) {
const usdcL1Token = TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId];
const usdcL2Token = this.resolveL2TokenAddress(usdcL1Token, true); // Must specifically be native USDC
assign(
this.l1DepositInitiatedEvents,
[monitoredAddress, TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId]],
[monitoredAddress, usdcL1Token, usdcL2Token],
resultingCCTPEvents[monitoredAddress]
);
}
Expand Down
76 changes: 43 additions & 33 deletions src/clients/bridges/BaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
BigNumberish,
TOKEN_SYMBOLS_MAP,
getRedisCache,
getTokenAddressWithCCTP,
} from "../../utils";
import { utils } from "@across-protocol/sdk-v2";

Expand All @@ -40,7 +41,7 @@ export interface DepositEvent extends SortableEvent {

interface Events {
[address: string]: {
[l1Token: string]: DepositEvent[];
[l1Token: string]: { [l2Token: string]: DepositEvent[] };
};
}

Expand Down Expand Up @@ -117,6 +118,10 @@ export abstract class BaseAdapter {
).getAddress()}_targetContract:${targetContract}`;
}

resolveL2TokenAddress(l1Token: string, isNativeUsdc = false): string {
return getTokenAddressWithCCTP(l1Token, this.hubChainId, this.chainId, isNativeUsdc);
}

async checkAndSendTokenApprovals(address: string, l1Tokens: string[], associatedL1Bridges: string[]): Promise<void> {
this.log("Checking and sending token approvals", { l1Tokens, associatedL1Bridges });

Expand Down Expand Up @@ -202,12 +207,9 @@ export abstract class BaseAdapter {
continue;
}

if (outstandingTransfers[monitoredAddress] === undefined) {
outstandingTransfers[monitoredAddress] = {};
}
if (this.l2DepositFinalizedEvents[monitoredAddress] === undefined) {
this.l2DepositFinalizedEvents[monitoredAddress] = {};
}
outstandingTransfers[monitoredAddress] ??= {};

this.l2DepositFinalizedEvents[monitoredAddress] ??= {};

for (const l1Token of l1Tokens) {
// Skip if there has been no deposits for this token.
Expand All @@ -216,35 +218,43 @@ export abstract class BaseAdapter {
}

// It's okay to not have any finalization events. In that case, all deposits are outstanding.
if (this.l2DepositFinalizedEvents[monitoredAddress][l1Token] === undefined) {
this.l2DepositFinalizedEvents[monitoredAddress][l1Token] = [];
}
const l2FinalizationSet = this.l2DepositFinalizedEvents[monitoredAddress][l1Token];

// Match deposits and finalizations by amount. We're only doing a limited lookback of events so collisions
// should be unlikely.
const finalizedAmounts = l2FinalizationSet.map((finalization) => finalization.amount.toString());
const pendingDeposits = this.l1DepositInitiatedEvents[monitoredAddress][l1Token].filter((deposit) => {
// Remove the first match. This handles scenarios where are collisions by amount.
const index = finalizedAmounts.indexOf(deposit.amount.toString());
if (index > -1) {
finalizedAmounts.splice(index, 1);
return false;
this.l2DepositFinalizedEvents[monitoredAddress][l1Token] ??= {};

// We want to iterate over the deposit events that have been initiated. We'll then match them with the
// finalization events to determine which deposits are still outstanding.
for (const l2Token of Object.keys(this.l1DepositInitiatedEvents[monitoredAddress][l1Token])) {
this.l2DepositFinalizedEvents[monitoredAddress][l1Token][l2Token] ??= [];
const l2FinalizationSet = this.l2DepositFinalizedEvents[monitoredAddress][l1Token][l2Token];

// Match deposits and finalizations by amount. We're only doing a limited lookback of events so collisions
// should be unlikely.
const finalizedAmounts = l2FinalizationSet.map((finalization) => finalization.amount.toString());
const pendingDeposits = this.l1DepositInitiatedEvents[monitoredAddress][l1Token][l2Token].filter(
(deposit) => {
// Remove the first match. This handles scenarios where are collisions by amount.
const index = finalizedAmounts.indexOf(deposit.amount.toString());
if (index > -1) {
finalizedAmounts.splice(index, 1);
return false;
}
return true;
}
);

// Short circuit early if there are no pending deposits.
if (pendingDeposits.length === 0) {
continue;
}
return true;
});

// Short circuit early if there are no pending deposits.
if (pendingDeposits.length === 0) {
continue;
}
outstandingTransfers[monitoredAddress][l1Token] ??= {};

const totalAmount = pendingDeposits.reduce((acc, curr) => acc.add(curr.amount), bnZero);
const depositTxHashes = pendingDeposits.map((deposit) => deposit.transactionHash);
outstandingTransfers[monitoredAddress][l1Token] = {
totalAmount,
depositTxHashes,
};
const totalAmount = pendingDeposits.reduce((acc, curr) => acc.add(curr.amount), bnZero);
const depositTxHashes = pendingDeposits.map((deposit) => deposit.transactionHash);
outstandingTransfers[monitoredAddress][l1Token][l2Token] = {
totalAmount,
depositTxHashes,
};
}
}
}

Expand Down
9 changes: 0 additions & 9 deletions src/clients/bridges/CCTPAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,6 @@ import { BaseAdapter } from "./BaseAdapter";
* to be used to bridge USDC via CCTP.
*/
export abstract class CCTPAdapter extends BaseAdapter {
/**
* Get the CCTP domain of the hub chain. This is used to determine the source
* domain of a CCTP message.
* @returns The CCTP domain of the hub chain
*/
private get l1SourceDomain(): number {
return chainIdsToCctpDomains[this.hubChainId];
}

/**
* Get the CCTP domain of the target chain. This is used to determine the destination
* domain of a CCTP message.
Expand Down
Loading

0 comments on commit 7888501

Please sign in to comment.