From 05abadcc9c881e2824929dd571c260a3c565f521 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 00:27:28 +0200 Subject: [PATCH 1/6] improve(tasks): Update deposit route script A few updates: - Auto-inherit the complete set of known chain IDs if not otherwise specified via the --chains option. - Display token symbols alongside token addresses. - Drop superfluous numbers from log messages (they were manually maintained and non-sequential). - Throw on default network (hardhat). - Sub in symbolic chainId names instead of hardcoded values. Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 160 +++++++++++++++----------- 1 file changed, 91 insertions(+), 69 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index ad36ca1a0..c5cdf3a2a 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -3,8 +3,16 @@ import assert from "assert"; import { askYesNoQuestion, minimalSpokePoolInterface } from "./utils"; import { CHAIN_IDs, MAINNET_CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils/constants"; +const { ARBITRUM } = CHAIN_IDs; + // Supported mainnet chain IDs. -const enabledChainIds = Object.values(MAINNET_CHAIN_IDs); +const enabledChainIds = Object.values(MAINNET_CHAIN_IDs) + .map(Number) + .filter((chainId) => chainId !== CHAIN_IDs.BOBA) + .sort((x, y) => x - y); + +const chainPadding = enabledChainIds.at(-1).toString().length; +const formatChainId = (chainId: number): string => chainId.toString().padStart(chainPadding, " "); const getChainsFromList = (taskArgInput: string): number[] => taskArgInput @@ -15,7 +23,7 @@ const getChainsFromList = (taskArgInput: string): number[] => task("enable-l1-token-across-ecosystem", "Enable a provided token across the entire ecosystem of supported chains") .addFlag("execute", "Provide this flag if you would like to actually execute the transaction from the EOA") .addParam("token", "Symbol of token to enable") - .addParam("chains", "Comma-delimited list of chains to enable the token on. Defaults to all supported chains") + .addOptionalParam("chains", "Comma-delimited list of chains to enable the token on. Defaults to all supported chains") .addOptionalParam( "customoptimismbridge", "Custom token bridge to set for optimism, for example used with SNX and DAI" @@ -23,18 +31,26 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent .addOptionalParam("depositroutechains", "ChainIds to enable deposit routes for exclusively. Separated by comma.") .setAction(async function (taskArguments, hre_) { const hre = hre_ as any; + const { chains, token: symbol } = taskArguments; + + const hubPoolChainId = parseInt(await hre.getChainId()); + if (hubPoolChainId === 31337) { + throw new Error(`Defaulted to network \`hardhat\`; specify \`--network mainnet\` or \`--network sepolia\``); + } + const matchedSymbol = Object.keys(TOKEN_SYMBOLS_MAP).find( - (symbol) => symbol === taskArguments.token + (_symbol) => _symbol === symbol ) as keyof typeof TOKEN_SYMBOLS_MAP; - assert(matchedSymbol !== undefined, `Could not find token with symbol ${taskArguments.token} in TOKEN_SYMBOLS_MAP`); - const hubPoolChainId = await hre.getChainId(); + assert(matchedSymbol !== undefined, `Could not find token with symbol ${symbol} in TOKEN_SYMBOLS_MAP`); + const l1Token = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[hubPoolChainId]; + assert(l1Token !== undefined, `Could not find ${symbol} in TOKEN_SYMBOLS_MAP`); // If deposit routes chains are provided then we'll only add routes involving these chains. This is used to add new // deposit routes to a new chain for an existing L1 token, so we also won't add a new LP token if this is defined. const depositRouteChains = getChainsFromList(taskArguments.depositroutechains); if (depositRouteChains.length > 0) { - console.log(`\n0. Only adding deposit routes involving chains on list ${depositRouteChains.join(", ")}`); + console.log(`\nOnly adding deposit routes involving chains on list ${depositRouteChains.join(", ")}`); } const hasSetConfigStore = await askYesNoQuestion( @@ -42,20 +58,17 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent ); if (!hasSetConfigStore) process.exit(0); - console.log(`\n0. Running task to enable L1 token over entire Across ecosystem 🌉. L1 token: ${l1Token}`); + console.log(`\nRunning task to enable L1 token over entire Across ecosystem 🌉. L1 token: ${l1Token}`); const { deployments, ethers } = hre; - const signer = (await hre.ethers.getSigners())[0]; + const [signer] = await hre.ethers.getSigners(); // Remove chainIds that are in the ignore list. let inputChains: number[] = []; try { - const parsedChains: string[] = taskArguments.chains.split(","); - inputChains = parsedChains.map((x) => Number(x)); + inputChains = (chains?.split(",") ?? enabledChainIds).map(Number); console.log(`\nParsed 'chains' argument:`, inputChains); } catch (error) { - throw new Error( - `Failed to parse 'chains' argument ${taskArguments.chains} as a comma-separated list of numbers.` - ); + throw new Error(`Failed to parse 'chains' argument ${chains} as a comma-separated list of numbers.`); } if (inputChains.length === 0) inputChains = enabledChainIds; else if (inputChains.some((chain) => isNaN(chain) || !Number.isInteger(chain) || chain < 0)) { @@ -63,64 +76,68 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent } const chainIds = enabledChainIds.filter((chainId) => inputChains.includes(chainId)); - console.log("\n1. Loading L2 companion token address for provided L1 token."); - const tokens = await Promise.all( - chainIds.map((chainId) => { - // Handle USDC special case where L1 USDC is mapped to different token symbols on L2s. - if (matchedSymbol === "USDC") { - const symbols = ["USDC", "USDC.e", "USDbC", "USDzC"] as (keyof typeof TOKEN_SYMBOLS_MAP)[]; - const symbol = symbols.find((symbol) => TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]); - if (!symbol) { - throw new Error( - `Could not find TOKEN_SYMBOLS_MAP mapping on chain ${chainId} for any of ${symbols.join(", ")}` - ); + console.log("\nLoading L2 companion token address for provided L1 token."); + const tokens = Object.fromEntries( + chainIds + .map((chainId) => { + let symbol = matchedSymbol; + + // Handle USDC special case where L1 USDC is mapped to different token symbols on L2s. + if (matchedSymbol === "USDC") { + const symbols = ["USDC", "USDC.e", "USDbC", "USDzC"] as (keyof typeof TOKEN_SYMBOLS_MAP)[]; + symbol = symbols.find((symbol) => TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]); + if (!symbol) { + console.log(`No known mapping for ${matchedSymbol} on ${chainId}, skipping...`); + return; + } + } else if (matchedSymbol === "DAI" && chainId === CHAIN_IDs.BLAST) { + symbol = "USDB"; } - return TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]; - } else if (matchedSymbol === "DAI" && chainId === CHAIN_IDs.BLAST) { - return TOKEN_SYMBOLS_MAP.USDB.addresses[chainId]; // DAI maps to USDB on Blast. - } - const l2Address = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[chainId]; - if (l2Address === undefined) { - throw new Error(`Could not find token address on chain ${chainId} in TOKEN_SYMBOLS_MAP`); - } - return l2Address; - }) + const address = TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]; + if (!address) { + throw new Error(`Could not find token address on chain ${chainId} in TOKEN_SYMBOLS_MAP`); + } + + return [chainId, { symbol, address }]; + }) + .filter((x) => x) ); console.table( - chainIds.map((chainId, index) => { - return { - chainId, - address: tokens[index], - }; - }), - ["chainId", "address"] + Object.entries(tokens).map(([_chainId, { symbol, address }]) => ({ chainId: Number(_chainId), symbol, address })), + ["chainId", "symbol", "address"] ); // Check the user is ok with the token addresses provided. If not, abort. - if (!(await askYesNoQuestion("\n2. Do these token addresses match your expectations?"))) process.exit(0); + if (!(await askYesNoQuestion("\nDo these token addresses match your expectations?"))) process.exit(0); // Construct an ethers contract to access the `interface` prop to create encoded function calls. const hubPoolDeployment = await deployments.get("HubPool"); const hubPool = new ethers.Contract(hubPoolDeployment.address, hubPoolDeployment.abi, signer); - - console.log("\n4. Constructing calldata to enable these tokens. Using HubPool at address:", hubPool.address); + console.log(`\nConstructing calldata to enable these tokens. Using HubPool at address: ${hubPool.address}`); // Construct calldata to enable these tokens. const callData = []; // If deposit route chains are defined then we don't want to add a new LP token: if (depositRouteChains.length === 0) { - console.log("\n5. Adding calldata to enable liquidity provision on", l1Token); + console.log(`\nAdding calldata to enable liquidity provision on ${l1Token}`); callData.push(hubPool.interface.encodeFunctionData("enableL1TokenForLiquidityProvision", [l1Token])); } - console.log("\n6. Adding calldata to enable routes between all chains and tokens:"); + console.log("\nAdding calldata to enable routes between all chains and tokens:"); let i = 0; // counter for logging. - chainIds.forEach((fromId, fromIndex) => { - chainIds.forEach((toId, _) => { - if (fromId === toId) return; + const skipped: { [originChainId: number]: number } = {}; + const routeChainIds = Object.keys(tokens).map(Number); + routeChainIds.forEach((fromId) => { + const formattedFromId = formatChainId(fromId); + const { symbol, address: inputToken } = tokens[fromId]; + skipped[fromId] = []; + routeChainIds.forEach((toId, _) => { + if (fromId === toId) { + return; + } // If deposit route chains are defined, only add route if it involves a chain on that list if ( @@ -128,42 +145,47 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent depositRouteChains.includes(toId) || depositRouteChains.includes(fromId) ) { - console.log(`\t 6.${++i}\t Adding calldata for token ${tokens[fromIndex]} for route ${fromId} -> ${toId}`); - callData.push( - hubPool.interface.encodeFunctionData("setDepositRoute", [fromId, toId, tokens[fromIndex], true]) - ); + console.log(`\t${++i}\tAdded route for ${inputToken} from ${formattedFromId} -> ${formatChainId(toId)}.`); + callData.push(hubPool.interface.encodeFunctionData("setDepositRoute", [fromId, toId, inputToken, true])); } else { - console.log( - `\t\t Skipping route ${fromId} -> ${toId} because it doesn't involve a chain on the exclusive list` - ); + skipped[fromId].push(toId); } }); }); + console.log(""); + + Object.entries(skipped).forEach(([srcChainId, dstChainIds]) => { + if (dstChainIds.length > 0) { + const { address: inputToken } = tokens[srcChainId]; + console.log(`\tSkipped route for ${inputToken} on chains ${srcChainId} -> ${dstChainIds.join(", ")}.`); + } + }); // If deposit route chains are defined then we don't want to add a new PoolRebalanceRoute if (depositRouteChains.length === 0) { - console.log("\n7. Adding calldata to set the pool rebalance route for the respective destination tokens:"); + console.log("\nAdding calldata to set the pool rebalance route for the respective destination tokens:"); let j = 0; // counter for logging. chainIds.forEach((toId, toIndex) => { // If deposit route chains are defined, only add route if it involves a chain on that list if (depositRouteChains.length === 0 || depositRouteChains.includes(toId)) { - console.log(`\t 7.${++j}\t Adding calldata for rebalance route for L2Token ${tokens[toIndex]} on ${toId}`); + console.log(`\t${++j}\t Adding calldata for rebalance route for L2Token ${tokens[toIndex]} on ${toId}`); callData.push( hubPool.interface.encodeFunctionData("setPoolRebalanceRoute", [toId, l1Token, tokens[toIndex]]) ); } else { console.log( - `\t\t Skipping pool rebalance rout -> ${toId} because it doesn't involve a chain on the exclusive list` + `\t\tSkipping pool rebalance route -> ${toId} because it doesn't involve a chain on the exclusive list` ); } }); // We only need to whitelist an Arbitrum token on the SpokePool if we're setting up a pool rebalance route between // mainnet and Arbitrum, so if deposit route chains are set then no need to do this. - if (chainIds.includes(42161)) { - const arbitrumToken = tokens[chainIds.indexOf(42161)]; + if (chainIds.includes(ARBITRUM)) { + const arbitrumToken = tokens[chainIds.indexOf(ARBITRUM)]; console.log( - `\n8. Adding call data to whitelist L2 ${arbitrumToken} -> L1 token ${l1Token} on Arbitrum. This is only needed on this chain` + `\nAdding call data to whitelist L2 ${arbitrumToken} -> L1 token ${l1Token} on Arbitrum.` + + " This is only needed on this chain." ); // Address doesn't matter, we only want the interface. @@ -175,35 +197,35 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent l1Token, ]); callData.push( - hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [42161, whitelistTokenCallData]) + hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [ARBITRUM, whitelistTokenCallData]) ); } // Add optimism setTokenBridge call if the token has a custom bridge needed to get to mainnet. - if (chainIds.includes(10) && taskArguments.customoptimismbridge) { - console.log("\n9. Adding call data to set custom Optimism bridge."); + if (chainIds.includes(OPTIMISM) && taskArguments.customoptimismbridge) { + console.log("\nAdding call data to set custom Optimism bridge."); // Address doesn't matter, we only want the interface: const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer); - const optimismToken = tokens[chainIds.indexOf(10)]; + const optimismToken = tokens[chainIds.indexOf(OPTIMISM)]; const setTokenBridgeCallData = spokePool.interface.encodeFunctionData("setTokenBridge", [ optimismToken, taskArguments.customoptimismbridge, ]); callData.push( - hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [10, setTokenBridgeCallData]) + hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [OPTIMISM, setTokenBridgeCallData]) ); } } - console.log(`\n10. ***DONE.***\nCalldata to enable desired token has been constructed!`); + console.log(`\n***DONE.***\nCalldata to enable desired token has been constructed!`); console.log( `CallData contains ${callData.length} transactions, which can be sent in one multicall to hub pool @ ${hubPoolDeployment.address}🚀` ); console.log(JSON.stringify(callData).replace(/"/g, "")); if (taskArguments.execute && callData.length > 0) { - console.log(`\n10. --execute provided. Trying to execute this on mainnet.`); + console.log(`\n--execute provided. Trying to execute this on mainnet.`); const { hash } = await hubPool.multicall(callData); console.log(`\nTransaction hash: ${hash}`); } From 2463ad6b54495f369bd3dbf757425c1916903cd4 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:54:48 +0200 Subject: [PATCH 2/6] fixups Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 90 +++++++++++++++++---------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index c5cdf3a2a..0c581d5da 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -3,7 +3,44 @@ import assert from "assert"; import { askYesNoQuestion, minimalSpokePoolInterface } from "./utils"; import { CHAIN_IDs, MAINNET_CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils/constants"; -const { ARBITRUM } = CHAIN_IDs; +type TokenSymbol = keyof typeof TOKEN_SYMBOLS_MAP; + +/** + * Given a token symbol, determine whether it is a valid key for the TOKEN_SYMBOLS_MAP object. + */ +function isTokenSymbol(symbol: unknown): symbol is TokenSymbol { + return TOKEN_SYMBOLS_MAP[symbol as TokenSymbol] !== undefined; +} + +/** + * Given a token symbol from the HubPool chain and a remote chain ID, resolve the relevant token symbol and address. + */ +function resolveTokenOnChain( + mainnetSymbol: string, + chainId: number +): { symbol: TokenSymbol; address: string } | undefined { + assert(isTokenSymbol(mainnetSymbol), `Unrecognised token symbol (${mainnetSymbol})`); + let symbol = mainnetSymbol; + + // Handle USDC special case where L1 USDC is mapped to different token symbols on L2s. + if (mainnetSymbol === "USDC") { + const symbols = ["USDC", "USDC.e", "USDbC", "USDzC"] as TokenSymbol[]; + const tokenSymbol = symbols.find((symbol) => TOKEN_SYMBOLS_MAP[symbol]?.addresses[chainId]); + if (!isTokenSymbol(tokenSymbol)) { + return; + } + symbol = tokenSymbol; + } else if (symbol === "DAI" && chainId === CHAIN_IDs.BLAST) { + symbol = "USDB"; + } + + const address = TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]; + return address ? { symbol, address } : undefined; +} + +const { ARBITRUM, OPTIMISM } = CHAIN_IDs; +const NO_SYMBOL = "----"; +const NO_ADDRESS = "------------------------------------------"; // Supported mainnet chain IDs. const enabledChainIds = Object.values(MAINNET_CHAIN_IDs) @@ -11,7 +48,7 @@ const enabledChainIds = Object.values(MAINNET_CHAIN_IDs) .filter((chainId) => chainId !== CHAIN_IDs.BOBA) .sort((x, y) => x - y); -const chainPadding = enabledChainIds.at(-1).toString().length; +const chainPadding = enabledChainIds[enabledChainIds.length - 1].toString().length; const formatChainId = (chainId: number): string => chainId.toString().padStart(chainPadding, " "); const getChainsFromList = (taskArgInput: string): number[] => @@ -38,10 +75,8 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent throw new Error(`Defaulted to network \`hardhat\`; specify \`--network mainnet\` or \`--network sepolia\``); } - const matchedSymbol = Object.keys(TOKEN_SYMBOLS_MAP).find( - (_symbol) => _symbol === symbol - ) as keyof typeof TOKEN_SYMBOLS_MAP; - assert(matchedSymbol !== undefined, `Could not find token with symbol ${symbol} in TOKEN_SYMBOLS_MAP`); + const matchedSymbol = Object.keys(TOKEN_SYMBOLS_MAP).find((_symbol) => _symbol === symbol); + assert(isTokenSymbol(matchedSymbol)); const l1Token = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[hubPoolChainId]; assert(l1Token !== undefined, `Could not find ${symbol} in TOKEN_SYMBOLS_MAP`); @@ -78,30 +113,15 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent console.log("\nLoading L2 companion token address for provided L1 token."); const tokens = Object.fromEntries( - chainIds - .map((chainId) => { - let symbol = matchedSymbol; - - // Handle USDC special case where L1 USDC is mapped to different token symbols on L2s. - if (matchedSymbol === "USDC") { - const symbols = ["USDC", "USDC.e", "USDbC", "USDzC"] as (keyof typeof TOKEN_SYMBOLS_MAP)[]; - symbol = symbols.find((symbol) => TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]); - if (!symbol) { - console.log(`No known mapping for ${matchedSymbol} on ${chainId}, skipping...`); - return; - } - } else if (matchedSymbol === "DAI" && chainId === CHAIN_IDs.BLAST) { - symbol = "USDB"; - } - - const address = TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]; - if (!address) { - throw new Error(`Could not find token address on chain ${chainId} in TOKEN_SYMBOLS_MAP`); - } - - return [chainId, { symbol, address }]; - }) - .filter((x) => x) + chainIds.map((chainId) => { + const token = resolveTokenOnChain(matchedSymbol, chainId); + if (token === undefined) { + return [chainId, { symbol: NO_SYMBOL, address: NO_ADDRESS }]; + } + + const { symbol, address } = token; + return [chainId, { symbol: symbol as string, address }]; + }) ); console.table( @@ -124,18 +144,22 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent if (depositRouteChains.length === 0) { console.log(`\nAdding calldata to enable liquidity provision on ${l1Token}`); callData.push(hubPool.interface.encodeFunctionData("enableL1TokenForLiquidityProvision", [l1Token])); + } else { + depositRouteChains.forEach((chainId) => + assert(tokens[chainId].symbol !== NO_SYMBOL, `Token ${symbol} is not defined for chain ${chainId}`) + ); } console.log("\nAdding calldata to enable routes between all chains and tokens:"); let i = 0; // counter for logging. - const skipped: { [originChainId: number]: number } = {}; + const skipped: { [originChainId: number]: number[] } = {}; const routeChainIds = Object.keys(tokens).map(Number); routeChainIds.forEach((fromId) => { const formattedFromId = formatChainId(fromId); const { symbol, address: inputToken } = tokens[fromId]; skipped[fromId] = []; - routeChainIds.forEach((toId, _) => { - if (fromId === toId) { + routeChainIds.forEach((toId) => { + if (fromId === toId || symbol === NO_SYMBOL) { return; } From 86bf1970f931eb32cd3158871a99cde8131879d7 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:08:13 +0200 Subject: [PATCH 3/6] Tweaks Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index 0c581d5da..d92ad9480 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -20,7 +20,7 @@ function resolveTokenOnChain( chainId: number ): { symbol: TokenSymbol; address: string } | undefined { assert(isTokenSymbol(mainnetSymbol), `Unrecognised token symbol (${mainnetSymbol})`); - let symbol = mainnetSymbol; + let symbol = mainnetSymbol as TokenSymbol; // Handle USDC special case where L1 USDC is mapped to different token symbols on L2s. if (mainnetSymbol === "USDC") { @@ -35,7 +35,11 @@ function resolveTokenOnChain( } const address = TOKEN_SYMBOLS_MAP[symbol].addresses[chainId]; - return address ? { symbol, address } : undefined; + if (!address) { + return; + } + + return { symbol, address }; } const { ARBITRUM, OPTIMISM } = CHAIN_IDs; @@ -75,8 +79,9 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent throw new Error(`Defaulted to network \`hardhat\`; specify \`--network mainnet\` or \`--network sepolia\``); } - const matchedSymbol = Object.keys(TOKEN_SYMBOLS_MAP).find((_symbol) => _symbol === symbol); - assert(isTokenSymbol(matchedSymbol)); + const _matchedSymbol = Object.keys(TOKEN_SYMBOLS_MAP).find((_symbol) => _symbol === symbol); + assert(isTokenSymbol(_matchedSymbol)); + const matchedSymbol = _matchedSymbol as TokenSymbol; const l1Token = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[hubPoolChainId]; assert(l1Token !== undefined, `Could not find ${symbol} in TOKEN_SYMBOLS_MAP`); @@ -169,7 +174,8 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent depositRouteChains.includes(toId) || depositRouteChains.includes(fromId) ) { - console.log(`\t${++i}\tAdded route for ${inputToken} from ${formattedFromId} -> ${formatChainId(toId)}.`); + const n = (++i).toString().padStart(2, ' '); + console.log(`\t${n} Added route for ${inputToken} from ${formattedFromId} -> ${formatChainId(toId)}.`); callData.push(hubPool.interface.encodeFunctionData("setDepositRoute", [fromId, toId, inputToken, true])); } else { skipped[fromId].push(toId); From bc9662faf5715fef448e4ad4891de076cb976eba Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:20:52 +0200 Subject: [PATCH 4/6] More Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index d92ad9480..707d1a0ba 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -164,7 +164,7 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent const { symbol, address: inputToken } = tokens[fromId]; skipped[fromId] = []; routeChainIds.forEach((toId) => { - if (fromId === toId || symbol === NO_SYMBOL) { + if (fromId === toId || [fromId, toId].some((chainId) => tokens[chainId].symbol === NO_SYMBOL)) { return; } @@ -174,7 +174,7 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent depositRouteChains.includes(toId) || depositRouteChains.includes(fromId) ) { - const n = (++i).toString().padStart(2, ' '); + const n = (++i).toString().padStart(2, " "); console.log(`\t${n} Added route for ${inputToken} from ${formattedFromId} -> ${formatChainId(toId)}.`); callData.push(hubPool.interface.encodeFunctionData("setDepositRoute", [fromId, toId, inputToken, true])); } else { From 2fabae977b04b5bf325c3fefff54f4e8b6059b65 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:55:33 +0200 Subject: [PATCH 5/6] Support setting PoolRebalanceRoutes Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 108 +++++++++++++------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index 707d1a0ba..9dcfeecee 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -74,8 +74,8 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent const hre = hre_ as any; const { chains, token: symbol } = taskArguments; - const hubPoolChainId = parseInt(await hre.getChainId()); - if (hubPoolChainId === 31337) { + const hubChainId = parseInt(await hre.getChainId()); + if (hubChainId === 31337) { throw new Error(`Defaulted to network \`hardhat\`; specify \`--network mainnet\` or \`--network sepolia\``); } @@ -83,7 +83,7 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent assert(isTokenSymbol(_matchedSymbol)); const matchedSymbol = _matchedSymbol as TokenSymbol; - const l1Token = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[hubPoolChainId]; + const l1Token = TOKEN_SYMBOLS_MAP[matchedSymbol].addresses[hubChainId]; assert(l1Token !== undefined, `Could not find ${symbol} in TOKEN_SYMBOLS_MAP`); // If deposit routes chains are provided then we'll only add routes involving these chains. This is used to add new @@ -192,60 +192,64 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent }); // If deposit route chains are defined then we don't want to add a new PoolRebalanceRoute - if (depositRouteChains.length === 0) { - console.log("\nAdding calldata to set the pool rebalance route for the respective destination tokens:"); - let j = 0; // counter for logging. - chainIds.forEach((toId, toIndex) => { - // If deposit route chains are defined, only add route if it involves a chain on that list - if (depositRouteChains.length === 0 || depositRouteChains.includes(toId)) { - console.log(`\t${++j}\t Adding calldata for rebalance route for L2Token ${tokens[toIndex]} on ${toId}`); - callData.push( - hubPool.interface.encodeFunctionData("setPoolRebalanceRoute", [toId, l1Token, tokens[toIndex]]) - ); - } else { - console.log( - `\t\tSkipping pool rebalance route -> ${toId} because it doesn't involve a chain on the exclusive list` - ); - } - }); + console.log("\nAdding calldata to set the pool rebalance route for the respective destination tokens:"); + i = 0; // counter for logging. + const rebalanceRoutesSkipped: number[] = []; + chainIds.forEach((toId) => { + const destinationToken = tokens[toId].address; + if (destinationToken === NO_ADDRESS) { + return; + } - // We only need to whitelist an Arbitrum token on the SpokePool if we're setting up a pool rebalance route between - // mainnet and Arbitrum, so if deposit route chains are set then no need to do this. - if (chainIds.includes(ARBITRUM)) { - const arbitrumToken = tokens[chainIds.indexOf(ARBITRUM)]; + // If deposit route chains are defined, only add route if it involves a chain on that list + if (depositRouteChains.length === 0 || depositRouteChains.includes(toId)) { + const n = (++i).toString().padStart(2, " "); console.log( - `\nAdding call data to whitelist L2 ${arbitrumToken} -> L1 token ${l1Token} on Arbitrum.` + - " This is only needed on this chain." - ); - - // Address doesn't matter, we only want the interface. - const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer); - // Find the address of the Arbitrum representation of this token. Construct whitelistToken call to send to the - // Arbitrum spoke pool via the relaySpokeAdminFunction call. - const whitelistTokenCallData = spokePool.interface.encodeFunctionData("whitelistToken", [ - arbitrumToken, - l1Token, - ]); - callData.push( - hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [ARBITRUM, whitelistTokenCallData]) + `\t${n} Setting rebalance route for chain ${symbol} ${hubChainId} -> ${destinationToken} on ${toId}.` ); + callData.push(hubPool.interface.encodeFunctionData("setPoolRebalanceRoute", [toId, l1Token, destinationToken])); + } else { + rebalanceRoutesSkipped.push(toId); } + }); - // Add optimism setTokenBridge call if the token has a custom bridge needed to get to mainnet. - if (chainIds.includes(OPTIMISM) && taskArguments.customoptimismbridge) { - console.log("\nAdding call data to set custom Optimism bridge."); - - // Address doesn't matter, we only want the interface: - const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer); - const optimismToken = tokens[chainIds.indexOf(OPTIMISM)]; - const setTokenBridgeCallData = spokePool.interface.encodeFunctionData("setTokenBridge", [ - optimismToken, - taskArguments.customoptimismbridge, - ]); - callData.push( - hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [OPTIMISM, setTokenBridgeCallData]) - ); - } + if (rebalanceRoutesSkipped.length > 0) { + console.log(`\n\tSkipped pool rebalance routes ${hubChainId} -> ${rebalanceRoutesSkipped.join(", ")}.`); + } + + // We only need to whitelist an Arbitrum token on the SpokePool if we're setting up a pool rebalance route between + // mainnet and Arbitrum, so if deposit route chains are set then no need to do this. + if (chainIds.includes(ARBITRUM)) { + const arbitrumToken = tokens[ARBITRUM].address; + console.log( + `\nAdding call data to whitelist L2 ${arbitrumToken} -> L1 token ${l1Token} on Arbitrum.` + + " This is only needed on this chain." + ); + + // Address doesn't matter, we only want the interface. + const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer); + // Find the address of the Arbitrum representation of this token. Construct whitelistToken call to send to the + // Arbitrum spoke pool via the relaySpokeAdminFunction call. + const whitelistTokenCallData = spokePool.interface.encodeFunctionData("whitelistToken", [arbitrumToken, l1Token]); + callData.push( + hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [ARBITRUM, whitelistTokenCallData]) + ); + } + + // Add optimism setTokenBridge call if the token has a custom bridge needed to get to mainnet. + if (chainIds.includes(OPTIMISM) && taskArguments.customoptimismbridge) { + console.log("\nAdding call data to set custom Optimism bridge."); + + // Address doesn't matter, we only want the interface: + const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer); + const optimismToken = tokens[OPTIMISM].address; + const setTokenBridgeCallData = spokePool.interface.encodeFunctionData("setTokenBridge", [ + optimismToken, + taskArguments.customoptimismbridge, + ]); + callData.push( + hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [OPTIMISM, setTokenBridgeCallData]) + ); } console.log(`\n***DONE.***\nCalldata to enable desired token has been constructed!`); From bc7ce847e3defbb30906c5c9ad3955f883f80264 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:42:53 +0200 Subject: [PATCH 6/6] Make Arbitrum & Optimism updates conditional Signed-off-by: Paul <108695806+pxrl@users.noreply.github.com> --- tasks/enableL1TokenAcrossEcosystem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/enableL1TokenAcrossEcosystem.ts b/tasks/enableL1TokenAcrossEcosystem.ts index 9dcfeecee..b937e3919 100644 --- a/tasks/enableL1TokenAcrossEcosystem.ts +++ b/tasks/enableL1TokenAcrossEcosystem.ts @@ -219,7 +219,7 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent // We only need to whitelist an Arbitrum token on the SpokePool if we're setting up a pool rebalance route between // mainnet and Arbitrum, so if deposit route chains are set then no need to do this. - if (chainIds.includes(ARBITRUM)) { + if (depositRouteChains.includes(ARBITRUM)) { const arbitrumToken = tokens[ARBITRUM].address; console.log( `\nAdding call data to whitelist L2 ${arbitrumToken} -> L1 token ${l1Token} on Arbitrum.` + @@ -237,7 +237,7 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent } // Add optimism setTokenBridge call if the token has a custom bridge needed to get to mainnet. - if (chainIds.includes(OPTIMISM) && taskArguments.customoptimismbridge) { + if (depositRouteChains.includes(OPTIMISM) && taskArguments.customoptimismbridge) { console.log("\nAdding call data to set custom Optimism bridge."); // Address doesn't matter, we only want the interface: