From 4dd6b940911cff7d372edf2a488caa44088f641f Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:41:21 +0000 Subject: [PATCH 1/5] feat: hardhat task to test chain adapter token bridging This has some limitations around USDC native/bridged coincidence, but should otherwise be useful for testing chain adapters for new deployments (where CCTP is very unlikely to be enabled anyway). --- hardhat.config.ts | 8 ++++- tasks/testChainAdapter.ts | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tasks/testChainAdapter.ts diff --git a/hardhat.config.ts b/hardhat.config.ts index 09e3d3c18..dff7e13a4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -16,7 +16,13 @@ import "hardhat-deploy"; import "@openzeppelin/hardhat-upgrades"; // Custom tasks to add to HRE. -const tasks = ["enableL1TokenAcrossEcosystem", "finalizeScrollClaims", "rescueStuckScrollTxn", "verifySpokePool"]; +const tasks = [ + "enableL1TokenAcrossEcosystem", + "finalizeScrollClaims", + "rescueStuckScrollTxn", + "verifySpokePool", + "testChainAdapter", +]; // eslint-disable-next-line node/no-missing-require tasks.forEach((task) => require(`./tasks/${task}`)); diff --git a/tasks/testChainAdapter.ts b/tasks/testChainAdapter.ts new file mode 100644 index 000000000..9b5d099e0 --- /dev/null +++ b/tasks/testChainAdapter.ts @@ -0,0 +1,68 @@ +import assert from "assert"; +import { getMnemonic } from "@uma/common"; +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils/constants"; + +// Chain adapter names are not 1:1 consistent with chain names, so some overrides are needed. +const chains = { + [CHAIN_IDs.ARBITRUM]: "Arbitrum_Adapter", + [CHAIN_IDs.WORLD_CHAIN]: "WorldChain_Adapter", + [CHAIN_IDs.ZK_SYNC]: "ZkSync_Adapter", +}; + +task("testChainAdapter", "Verify a chain adapter") + .addParam("chain", "chain ID of the adapter being tested") + .addParam("token", "Token to bridge to the destination chain") + .setAction(async function (args, hre: HardhatRuntimeEnvironment) { + const { deployments, ethers, getChainId, network } = hre; + const provider = new ethers.providers.StaticJsonRpcProvider(network.config.url); + const signer = new ethers.Wallet.fromMnemonic(getMnemonic()).connect(provider); + + const hubChainId = await getChainId(); + const { address: hubPoolAddress, abi: hubPoolAbi } = await deployments.get("HubPool"); + const hubPool = new ethers.Contract(hubPoolAddress, hubPoolAbi, provider); + const spokeChainId = parseInt(args.chain); + + const [spokeName] = Object.entries(CHAIN_IDs).find(([, chainId]) => chainId === spokeChainId) ?? []; + assert(spokeName, `Could not find any chain entry for chainId ${spokeChainId}.`); + const adapterName = + chains[spokeChainId] ?? `${spokeName[0].toUpperCase()}${spokeName.slice(1).toLowerCase()}_Adapter`; + + const { address: adapterAddress, abi: adapterAbi } = await deployments.get(adapterName); + const adapter = new ethers.Contract(adapterAddress, adapterAbi, provider); + // const spokeAddress = getDeployedAddress("SpokePool", spokeChainId, true); + const tokenSymbol = args.token.toUpperCase(); + const tokenAddress = TOKEN_SYMBOLS_MAP[tokenSymbol].addresses[hubChainId]; + + // For USDC this will resolve to native USDC on CCTP-enabled chains. + const l2Token = await hubPool.poolRebalanceRoute(spokeChainId, tokenAddress); + console.log(`Resolved ${tokenSymbol} l2 token address on chain ${spokeChainId}: ${l2Token}.`); + + const erc20 = (await ethers.getContractFactory("ExpandedERC20")).attach(tokenAddress); + const amount = 1_000_000; + let txn = await erc20.connect(signer).transfer(adapterAddress, amount); + console.log(`Transferring ${amount} ${tokenSymbol} -> ${adapterAddress}: ${txn.hash}`); + await txn.wait(); + + const balance = await erc20.balanceOf(adapterAddress); + const recipient = await signer.getAddress(); + + let populatedTxn = await adapter.populateTransaction.relayTokens(tokenAddress, l2Token, balance, recipient); + const gasLimit = await provider.estimateGas(populatedTxn); + + // Any adapter requiring msg.value > 0 (i.e. Scroll) will fail here. + txn = await adapter.connect(signer).relayTokens( + tokenAddress, + l2Token, + balance, + recipient, + { gasLimit: gasLimit.mul(2) } // 2x the gas limit; this helps on OP stack bridges. + ); + + console.log( + `Relaying ${balance} ${tokenSymbol} from ${adapterAddress}` + + ` to chain ${spokeChainId} recipient ${recipient}: ${txn.hash}.` + ); + await txn.wait(); + }); From 8cae0b6fa568e3ece12d4ab6f510019af86aadb9 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:12:18 +0000 Subject: [PATCH 2/5] Support amounts --- package.json | 2 +- tasks/testChainAdapter.ts | 19 +++++++++++++------ yarn.lock | 8 ++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 50b8552c1..ee3a8bc6f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "prepublish": "yarn build && hardhat export --export-all ./cache/massExport.json && ts-node ./scripts/processHardhatExport.ts && prettier --write ./deployments/deployments.json && yarn generate-contract-types" }, "dependencies": { - "@across-protocol/constants": "^3.1.16", + "@across-protocol/constants": "^3.1.17", "@coral-xyz/anchor": "^0.30.1", "@defi-wonderland/smock": "^2.3.4", "@eth-optimism/contracts": "^0.5.40", diff --git a/tasks/testChainAdapter.ts b/tasks/testChainAdapter.ts index 9b5d099e0..44e929f49 100644 --- a/tasks/testChainAdapter.ts +++ b/tasks/testChainAdapter.ts @@ -7,6 +7,7 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils/constants"; // Chain adapter names are not 1:1 consistent with chain names, so some overrides are needed. const chains = { [CHAIN_IDs.ARBITRUM]: "Arbitrum_Adapter", + [CHAIN_IDs.ALEPH_ZERO]: "Arbitrum_CustomGasToken_Adapter", [CHAIN_IDs.WORLD_CHAIN]: "WorldChain_Adapter", [CHAIN_IDs.ZK_SYNC]: "ZkSync_Adapter", }; @@ -14,6 +15,7 @@ const chains = { task("testChainAdapter", "Verify a chain adapter") .addParam("chain", "chain ID of the adapter being tested") .addParam("token", "Token to bridge to the destination chain") + .addParam("amount", "Amount to bridge to the destination chain") .setAction(async function (args, hre: HardhatRuntimeEnvironment) { const { deployments, ethers, getChainId, network } = hre; const provider = new ethers.providers.StaticJsonRpcProvider(network.config.url); @@ -40,19 +42,24 @@ task("testChainAdapter", "Verify a chain adapter") console.log(`Resolved ${tokenSymbol} l2 token address on chain ${spokeChainId}: ${l2Token}.`); const erc20 = (await ethers.getContractFactory("ExpandedERC20")).attach(tokenAddress); - const amount = 1_000_000; - let txn = await erc20.connect(signer).transfer(adapterAddress, amount); - console.log(`Transferring ${amount} ${tokenSymbol} -> ${adapterAddress}: ${txn.hash}`); - await txn.wait(); + let balance = await erc20.balanceOf(adapterAddress); + const decimals = await erc20.decimals(); + const amount = ethers.utils.parseUnits(args.amount, decimals); + + if (balance.lt(amount)) { + const txn = await erc20.connect(signer).transfer(adapterAddress, amount); + console.log(`Transferring ${amount} ${tokenSymbol} -> ${adapterAddress}: ${txn.hash}`); + await txn.wait(); + } - const balance = await erc20.balanceOf(adapterAddress); + balance = await erc20.balanceOf(adapterAddress); const recipient = await signer.getAddress(); let populatedTxn = await adapter.populateTransaction.relayTokens(tokenAddress, l2Token, balance, recipient); const gasLimit = await provider.estimateGas(populatedTxn); // Any adapter requiring msg.value > 0 (i.e. Scroll) will fail here. - txn = await adapter.connect(signer).relayTokens( + const txn = await adapter.connect(signer).relayTokens( tokenAddress, l2Token, balance, diff --git a/yarn.lock b/yarn.lock index 652119ac8..7bb251a1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@across-protocol/constants@^3.1.16": - version "3.1.16" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.16.tgz#c126085d29d4d051fd02a04c833d804d37c3c219" - integrity sha512-+U+AecGWnfY4b4sSfKBvsDj/+yXKEqpTXcZgI8GVVmUTkUhs1efA0kN4q3q10yy5TXI5TtagaG7R9yZg1zgKKg== +"@across-protocol/constants@^3.1.17": + version "3.1.17" + resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.17.tgz#1e43c87fbab06df3a1aab8993d7c5f746838e25d" + integrity sha512-mW++45vP04ahogsHFM5nE7ZKV8vmdxSO8gbBeP24VFxijrIYWqhISG67EU9pnAbgAIB58pT+ZmqdXI25iDmctA== "@across-protocol/contracts@^0.1.4": version "0.1.4" From 0ab674443cd919416c589d438c4753a5c9642406 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:13:02 +0200 Subject: [PATCH 3/5] Update tasks/testChainAdapter.ts --- tasks/testChainAdapter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/testChainAdapter.ts b/tasks/testChainAdapter.ts index 44e929f49..9d426ada4 100644 --- a/tasks/testChainAdapter.ts +++ b/tasks/testChainAdapter.ts @@ -33,7 +33,6 @@ task("testChainAdapter", "Verify a chain adapter") const { address: adapterAddress, abi: adapterAbi } = await deployments.get(adapterName); const adapter = new ethers.Contract(adapterAddress, adapterAbi, provider); - // const spokeAddress = getDeployedAddress("SpokePool", spokeChainId, true); const tokenSymbol = args.token.toUpperCase(); const tokenAddress = TOKEN_SYMBOLS_MAP[tokenSymbol].addresses[hubChainId]; From 4f903f56aa757e748287f34569a52a7548af28d3 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:15:44 +0000 Subject: [PATCH 4/5] Add warning --- tasks/testChainAdapter.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tasks/testChainAdapter.ts b/tasks/testChainAdapter.ts index 9d426ada4..f5d9f1ba1 100644 --- a/tasks/testChainAdapter.ts +++ b/tasks/testChainAdapter.ts @@ -3,6 +3,7 @@ import { getMnemonic } from "@uma/common"; import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../utils/constants"; +import { askYesNoQuestion } from "./utils"; // Chain adapter names are not 1:1 consistent with chain names, so some overrides are needed. const chains = { @@ -43,10 +44,17 @@ task("testChainAdapter", "Verify a chain adapter") const erc20 = (await ethers.getContractFactory("ExpandedERC20")).attach(tokenAddress); let balance = await erc20.balanceOf(adapterAddress); const decimals = await erc20.decimals(); - const amount = ethers.utils.parseUnits(args.amount, decimals); + const { amount } = args; + const scaledAmount = ethers.utils.parseUnits(amount, decimals); if (balance.lt(amount)) { - const txn = await erc20.connect(signer).transfer(adapterAddress, amount); + const proceed = await askYesNoQuestion( + `\nWARNING: ${amount} ${tokenSymbol} may be lost.\n` + + `\nProceed to send ${amount} ${tokenSymbol} to chain adapter ${adapterAddress} ?` + ); + if (!proceed) process.exit(0); + + const txn = await erc20.connect(signer).transfer(adapterAddress, scaledAmount); console.log(`Transferring ${amount} ${tokenSymbol} -> ${adapterAddress}: ${txn.hash}`); await txn.wait(); } From fa55d2a8f831dca9ee6b5ae4e71ee8ec8d55f696 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:24:17 +0000 Subject: [PATCH 5/5] Update message --- tasks/testChainAdapter.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tasks/testChainAdapter.ts b/tasks/testChainAdapter.ts index f5d9f1ba1..2a889ffa5 100644 --- a/tasks/testChainAdapter.ts +++ b/tasks/testChainAdapter.ts @@ -39,7 +39,14 @@ task("testChainAdapter", "Verify a chain adapter") // For USDC this will resolve to native USDC on CCTP-enabled chains. const l2Token = await hubPool.poolRebalanceRoute(spokeChainId, tokenAddress); - console.log(`Resolved ${tokenSymbol} l2 token address on chain ${spokeChainId}: ${l2Token}.`); + if (l2Token === ethers.constants.AddressZero) { + const proceed = await askYesNoQuestion( + `\t\nWARNING: ${tokenSymbol} maps to address ${l2Token} on chain ${spokeChainId}\n\t\nProceed ?` + ); + if (!proceed) process.exit(0); + } else { + console.log(`Resolved ${tokenSymbol} l2 token address on chain ${spokeChainId}: ${l2Token}.`); + } const erc20 = (await ethers.getContractFactory("ExpandedERC20")).attach(tokenAddress); let balance = await erc20.balanceOf(adapterAddress); @@ -49,8 +56,8 @@ task("testChainAdapter", "Verify a chain adapter") if (balance.lt(amount)) { const proceed = await askYesNoQuestion( - `\nWARNING: ${amount} ${tokenSymbol} may be lost.\n` + - `\nProceed to send ${amount} ${tokenSymbol} to chain adapter ${adapterAddress} ?` + `\t\nWARNING: ${amount} ${tokenSymbol} may be lost.\n` + + `\t\nProceed to send ${amount} ${tokenSymbol} to chain adapter ${adapterAddress} ?` ); if (!proceed) process.exit(0);