Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hardhat task to test chain adapter token bridging #669

Merged
merged 5 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`));

Expand Down
68 changes: 68 additions & 0 deletions tasks/testChainAdapter.ts
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably addParam("amount", ...) here to allow the user to specify the amount to bridge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.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);
pxrl marked this conversation as resolved.
Show resolved Hide resolved
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;
pxrl marked this conversation as resolved.
Show resolved Hide resolved
let txn = await erc20.connect(signer).transfer(adapterAddress, amount);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably worth adding a warning here, since this transfer can be rugged by any third party who happens to call relayTokens first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added: 4f903f5 fa55d2a

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();
});
Loading