From 3de58d69f506b097790bec08c69305cbbc98a3b9 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:11:01 -0400 Subject: [PATCH] feat(Dataworker): ZkSync should count ETH and WETH towards balance needed to execute leaves (#872) * feat(Dataworker): ZkSync should count ETH and WETH towards balance needed to execute leaves Current code assumes only OVM chains need to count both ETH and WETH towards balance that can be used to execute spoke leaves * Update DataworkerUtils.ts * Update src/clients/BalanceAllocator.ts Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> * Update DataworkerUtils.ts * Update BalanceAllocator.ts --------- Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> --- src/clients/BalanceAllocator.ts | 9 ++++++- src/clients/bridges/OptimismAdapter.ts | 2 -- src/common/ContractAddresses.ts | 8 ++++++ src/dataworker/Dataworker.ts | 17 +++++------- src/dataworker/DataworkerUtils.ts | 37 ++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/clients/BalanceAllocator.ts b/src/clients/BalanceAllocator.ts index 16cedea26..50200bdd3 100644 --- a/src/clients/BalanceAllocator.ts +++ b/src/clients/BalanceAllocator.ts @@ -1,3 +1,4 @@ +import { CONTRACT_ADDRESSES } from "../common/ContractAddresses"; import { BigNumber, ERC20, ethers, ZERO_ADDRESS, min } from "../utils"; // This type is used to map used and current balances of different users. @@ -141,9 +142,15 @@ export class BalanceAllocator { this.balances = {}; } + isEthAddress(chainId: number, tokenAddress: string): boolean { + // If there is an ETH address defined in CONTRACT_ADDRESSES, use it, otherwise assume ETH address is the zero + // address. + return (CONTRACT_ADDRESSES[chainId]?.eth?.address ?? ZERO_ADDRESS) === tokenAddress; + } + // This method is primarily here to be overriden for testing purposes. protected async _queryBalance(chainId: number, token: string, holder: string): Promise { - return token === ZERO_ADDRESS + return this.isEthAddress(chainId, token) ? await this.providers[chainId].getBalance(holder) : await ERC20.connect(token, this.providers[chainId]).balanceOf(holder); } diff --git a/src/clients/bridges/OptimismAdapter.ts b/src/clients/bridges/OptimismAdapter.ts index 8187bb0b4..7c17203f6 100644 --- a/src/clients/bridges/OptimismAdapter.ts +++ b/src/clients/bridges/OptimismAdapter.ts @@ -9,8 +9,6 @@ import { constants } from "@across-protocol/sdk-v2"; import { CONTRACT_ADDRESSES } from "../../common"; const { TOKEN_SYMBOLS_MAP } = constants; -export const isOvmChain = (chainId: number): boolean => [10, 288].includes(chainId); - export class OptimismAdapter extends BaseAdapter { public l2Gas: number; private txnClient: TransactionClient; diff --git a/src/common/ContractAddresses.ts b/src/common/ContractAddresses.ts index fefa47a3f..0871002dd 100644 --- a/src/common/ContractAddresses.ts +++ b/src/common/ContractAddresses.ts @@ -415,6 +415,14 @@ export const CONTRACT_ADDRESSES: { ], }, }, + 324: { + eth: { + address: "0x000000000000000000000000000000000000800A", + }, + weth: { + address: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91", + }, + }, 42161: { erc20Gateway: { abi: [ diff --git a/src/dataworker/Dataworker.ts b/src/dataworker/Dataworker.ts index 163d11de0..abb53813a 100644 --- a/src/dataworker/Dataworker.ts +++ b/src/dataworker/Dataworker.ts @@ -37,6 +37,7 @@ import { blockRangesAreInvalidForSpokeClients, getBlockRangeForChain, getImpliedBundleBlockRanges, + l2TokensToCountTowardsSpokePoolLeafExecutionCapital, } from "../dataworker/DataworkerUtils"; import { getEndBlockBuffers, @@ -46,8 +47,7 @@ import { } from "./DataworkerUtils"; import { BalanceAllocator } from "../clients"; import _ from "lodash"; -import { CONTRACT_ADDRESSES, spokePoolClientsToProviders } from "../common"; -import { isOvmChain } from "../clients/bridges"; +import { spokePoolClientsToProviders } from "../common"; import * as sdk from "@across-protocol/sdk-v2"; // Internal error reasons for labeling a pending root bundle as "invalid" that we don't want to submit a dispute @@ -72,8 +72,6 @@ type ProposeRootBundleReturnType = { slowFillTree: MerkleTree; }; -const ovmWethTokens = [CONTRACT_ADDRESSES[10].weth.address, CONTRACT_ADDRESSES[10].eth.address]; - export type PoolRebalanceRoot = { runningBalances: RunningBalances; realizedLpFees: RunningBalances; @@ -1522,9 +1520,10 @@ export class Dataworker { const amountRequired = getRefund(relayData.amount.sub(amountFilled), relayData.realizedLpFeePct); const success = await balanceAllocator.requestBalanceAllocation( relayData.destinationChainId, - isOvmChain(relayData.destinationChainId) && ovmWethTokens.includes(relayData.destinationToken) - ? ovmWethTokens - : [relayData.destinationToken], + l2TokensToCountTowardsSpokePoolLeafExecutionCapital( + relayData.destinationToken, + relayData.destinationChainId + ), client.spokePool.address, amountRequired ); @@ -2096,9 +2095,7 @@ export class Dataworker { const totalSent = refundSum.add(leaf.amountToReturn.gte(0) ? leaf.amountToReturn : BigNumber.from(0)); const success = await balanceAllocator.requestBalanceAllocation( leaf.chainId, - isOvmChain(leaf.chainId) && ovmWethTokens.includes(leaf.l2TokenAddress) - ? ovmWethTokens - : [leaf.l2TokenAddress], + l2TokensToCountTowardsSpokePoolLeafExecutionCapital(leaf.l2TokenAddress, leaf.chainId), client.spokePool.address, totalSent ); diff --git a/src/dataworker/DataworkerUtils.ts b/src/dataworker/DataworkerUtils.ts index 33aa30e88..5ae4e8c5f 100644 --- a/src/dataworker/DataworkerUtils.ts +++ b/src/dataworker/DataworkerUtils.ts @@ -15,6 +15,7 @@ import { buildPoolRebalanceLeafTree, buildRelayerRefundTree, buildSlowRelayTree, + isDefined, MerkleTree, winston, } from "../utils"; @@ -28,6 +29,7 @@ import { subtractExcessFromPreviousSlowFillsFromRunningBalances } from "./PoolRe import { getAmountToReturnForRelayerRefundLeaf } from "./RelayerRefundUtils"; import { sortRefundAddresses, sortRelayerRefundLeaves } from "./RelayerRefundUtils"; import { utils } from "@across-protocol/sdk-v2"; +import { CONTRACT_ADDRESSES } from "../common/ContractAddresses"; export const { getImpliedBundleBlockRanges, getBlockRangeForChain, getBlockForChain } = utils; export function getEndBlockBuffers( @@ -387,3 +389,38 @@ export async function _buildPoolRebalanceRoot( tree: buildPoolRebalanceLeafTree(leaves), }; } + +/** + * @notice Returns WETH and ETH token addresses for chain if defined, or throws an error if they're not + * in the hardcoded dictionary. + * @param chainId chain to check for WETH and ETH addresses + * @returns WETH and ETH addresses. + */ +function getWethAndEth(chainId): string[] { + const wethAndEth = [CONTRACT_ADDRESSES[chainId].weth.address, CONTRACT_ADDRESSES[chainId].eth.address]; + if (wethAndEth.some((tokenAddress) => !isDefined(tokenAddress))) { + throw new Error(`WETH or ETH address not defined for chain ${chainId}`); + } + return wethAndEth; +} +/** + * @notice Some SpokePools will contain balances of ETH and WETH, while others will only contain balances of WETH, + * so if the l2TokenAddress is WETH and the SpokePool is one such chain that holds both ETH and WETH, + * then it should return both ETH and WETH. For other chains, it should only return the l2TokenAddress. + * @param l2TokenAddress L2 token address in spoke leaf that we want to get addresses to check spoke balances for + * @param l2ChainId L2 chain of Spoke + * @returns Tokens that we should check the SpokePool balance for in order to execute a spoke leaf for + * `l2TokenAddress` on `l2ChainId`. + */ +export function l2TokensToCountTowardsSpokePoolLeafExecutionCapital( + l2TokenAddress: string, + l2ChainId: number +): string[] { + const spokesThatHoldEthAndWeth = [10, 324]; + if (!spokesThatHoldEthAndWeth.includes(l2ChainId)) { + return [l2TokenAddress]; + } + // If we get to here, ETH and WETH addresses should be defined, or we'll throw an error. + const ethAndWeth = getWethAndEth(l2ChainId); + return ethAndWeth.includes(l2TokenAddress) ? ethAndWeth : [l2TokenAddress]; +}