Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
73 changes: 54 additions & 19 deletions tasks/enableL1TokenAcrossEcosystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,67 @@ const enabledChainIds = [1, 10, 137, 288, 42161]; // Supported mainnet chain IDs

task("enable-l1-token-across-ecosystem", "Enable a provided token across the entire ecosystem of supported chains")
.addParam("chain1token", "Address of the token to enable, as defined on L1")
.addFlag("execute", "Provide this flag if you would like to actually execute the transaction from the EOA")
.addOptionalParam("chain10token", "Address of the token on chainID 10. Used to override the auto detect")
.addOptionalParam("chain137token", "Address of the token on chainID 137. Used to override the auto detect")
.addOptionalParam("chain288token", "Address of the token on chainID 288. Used to override the auto detect")
.addOptionalParam("chain42161token", "Address of the token on chainID 42161. Used to override the auto detect")
.addOptionalParam("ignorechains", "ChainIds to ignore. Separated by comma.")
.setAction(async function (taskArguments, hre_) {
const hre = hre_ as any;
const l1Token = taskArguments.chain1token;
assert(l1Token, "chain1token argument must be provided");
const ignoredChainIds: number[] =
taskArguments.ignorechains
?.replace(/\s/g, "")
?.split(",")
?.map((chainId: string) => Number(chainId)) || [];
if (ignoredChainIds.includes(1)) throw new Error("Cannot ignore chainId 1");
console.log(`\n0. Running task to enable L1 token over entire Across ecosystem 🌉. L1 token: ${l1Token}`);
const { deployments, ethers } = hre;
const signer = (await hre.ethers.getSigners())[0];

// Remove chainIds that are in the ignore list.
let chainIds = enabledChainIds.filter((chainId) => !ignoredChainIds.includes(chainId));

console.log("\n1. Auto detecting L2 companion token address for provided L1 token.");
const autoDetectedTokens = await Promise.all(
enabledChainIds.slice(1).map((chainId) => findL2TokenForL1Token(chainId, l1Token))
chainIds.slice(1).map((chainId) => findL2TokenForL1Token(chainId, l1Token))
);

const tokens: string[] = [];
let tokens: string[] = [];
tokens[0] = l1Token;
enabledChainIds
chainIds
.slice(1)
.forEach(
(chainId, index) => (tokens[index + 1] = taskArguments[`chain${chainId}token`] ?? autoDetectedTokens[index])
);

for (let i = 0; i < chainIds.length; i++) {
const chainId = chainIds[i];
if (
tokens[i] === zeroAddress &&
!(await askYesNoQuestion(
`\nNo address found for chainId: ${chainId}. Would you like to remove routes to and from this chain?`
))
) {
console.log(`Please rerun with override address for chainId: ${chainId}`);
process.exit(0);
}
}

chainIds = chainIds.filter((chainId, index) => tokens[index] !== zeroAddress);
tokens = tokens.filter((token) => token !== zeroAddress);

console.table(
enabledChainIds.map((chainId, index) => {
return { chainId, address: tokens[index], autoDetected: taskArguments[`chain${chainId}token`] == undefined };
chainIds.map((chainId, index) => {
return { chainId, address: tokens[index], autoDetected: taskArguments[`chain${chainId}token`] === undefined };
}),
["chainId", "address", "autoDetected"]
);

enabledChainIds.forEach((chainId, index) => assert(tokens[index] !== zeroAddress, `Bad address on ${chainId}`));

// Check the user is ok with the token addresses provided. If not, abort.
if (!(await askYesNoQuestion("\n2. Do these token addresses match with your expectation?"))) process.exit(0);
if (!(await askYesNoQuestion("\n2. Do 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");
Expand All @@ -50,14 +75,14 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent
console.log("\n4. Constructing calldata to enable these tokens. Using HubPool at address:", hubPool.address);

// Construct calldata to enable these tokens.
let callData = [];
const callData = [];
console.log("\n5. Adding 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:");
let i = 0; // counter for logging.
enabledChainIds.forEach((fromId, fromIndex) => {
enabledChainIds.forEach((toId, toIndex) => {
chainIds.forEach((fromId, fromIndex) => {
chainIds.forEach((toId, toIndex) => {
if (fromId === toId) return;

console.log(`\t 6.${++i}\t Adding calldata for token ${tokens[fromIndex]} for route ${fromId} -> ${toId}`);
Expand All @@ -66,21 +91,31 @@ task("enable-l1-token-across-ecosystem", "Enable a provided token across the ent
});

console.log("\n7. Adding calldata to set the pool rebalance route for the respective destination tokens:");
enabledChainIds.forEach((toId, toIndex) => {
chainIds.forEach((toId, toIndex) => {
console.log(`\t 7.${toIndex}\t Adding calldata for rebalance route for L2Token ${tokens[toIndex]} on ${toId}`);
callData.push(hubPool.interface.encodeFunctionData("setPoolRebalanceRoute", [toId, l1Token, tokens[toIndex]]));
});

console.log("\n8. Adding call data to whitelist L1 token on Arbitrum. This is only needed on this chain");
if (chainIds.includes(42161)) {
console.log("\n8. Adding call data to whitelist L1 token on Arbitrum. This is only needed on this chain");

const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer);
// Find the address of the the Arbitrum representation of this token. Construct whitelistToken call to send to the
// Arbitrum spoke pool via the relaySpokeAdminFunction call.
const arbitrumToken = tokens[enabledChainIds.indexOf(42161)];
const whitelistTokenCallData = spokePool.interface.encodeFunctionData("whitelistToken", [arbitrumToken, l1Token]);
callData.push(hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [42161, whitelistTokenCallData]));
const spokePool = new ethers.Contract(hubPoolDeployment.address, minimalSpokePoolInterface, signer);
// Find the address of the the Arbitrum representation of this token. Construct whitelistToken call to send to the
// Arbitrum spoke pool via the relaySpokeAdminFunction call.
const arbitrumToken = tokens[chainIds.indexOf(42161)];
const whitelistTokenCallData = spokePool.interface.encodeFunctionData("whitelistToken", [arbitrumToken, l1Token]);
callData.push(
hubPool.interface.encodeFunctionData("relaySpokePoolAdminFunction", [42161, whitelistTokenCallData])
);
}

console.log(`\n9. ***DONE.***\nCalldata to enable desired token has been constructed!`);
console.log(`CallData contains ${callData.length} transactions, which can be sent in one multicall🚀`);
console.log(JSON.stringify(callData).replace(/"/g, ""));

if (taskArguments.execute && callData.length > 0) {
console.log(`\n10. --execute provided. Trying to execute this on mainnet.`);
const { hash } = await hubPool.multicall(callData);
console.log(`\nTransaction hash: ${hash}`);
}
});
55 changes: 41 additions & 14 deletions tasks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,73 @@ import readline from "readline";
export const zeroAddress = ethers.constants.AddressZero;

export async function findL2TokenForL1Token(l2ChainId: number, l1TokenAddress: string) {
if (l2ChainId == 10) {
if (l2ChainId === 10) {
const foundOnChain = await _findL2TokenForOvmChain(l2ChainId, l1TokenAddress);
if (foundOnChain != zeroAddress) return foundOnChain;
if (foundOnChain !== zeroAddress) return foundOnChain;
else return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
}
if (l2ChainId == 137) return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
if (l2ChainId == 288) return await _findL2TokenForOvmChain(l2ChainId, l1TokenAddress);
if (l2ChainId == 42161) return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
if (l2ChainId === 137) return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
if (l2ChainId === 288) {
const foundOnChain = await _findL2TokenForOvmChain(l2ChainId, l1TokenAddress);
if (foundOnChain !== zeroAddress) return foundOnChain;
else return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
}
if (l2ChainId === 42161) return await _findL2TokenFromTokenList(l2ChainId, l1TokenAddress);
}

async function _findL2TokenFromTokenList(l2ChainId: number, l1TokenAddress: string) {
if (l2ChainId == 10) {
if (l2ChainId === 10) {
const response = await fetch("https://static.optimism.io/optimism.tokenlist.json");
const body = await response.text();
const tokenList = JSON.parse(body).tokens;
const searchSymbol = tokenList.find(
(element: any) => element.chainId == 1 && element.address == l1TokenAddress.toLocaleLowerCase()
(element: any) => element.chainId === 1 && element.address.toLowerCase() === l1TokenAddress.toLocaleLowerCase()
)?.symbol;
if (!searchSymbol) return zeroAddress;
return tokenList.find((element: any) => element.chainId == 10 && element.symbol == searchSymbol).address;
return tokenList.find((element: any) => element.chainId === 10 && element.symbol === searchSymbol).address;
}
if (l2ChainId == 137) {
if (l2ChainId === 137) {
const response = await fetch(
"https://raw.githubusercontent.com/maticnetwork/polygon-token-list/master/src/tokens/allTokens.json"
);
const body = await response.text();
const tokenList = JSON.parse(body);
const l2Address = tokenList.find(
(element: any) => element.extensions.rootAddress == l1TokenAddress.toLowerCase()
(element: any) => element?.extensions?.rootAddress?.toLowerCase() === l1TokenAddress.toLowerCase()
)?.address;
return l2Address ?? zeroAddress;
}
if (l2ChainId == 42161) {
if (l2ChainId === 42161) {
const response = await fetch("https://bridge.arbitrum.io/token-list-42161.json");
const body = await response.text();
const tokenList = JSON.parse(body).tokens;
const l2Address = tokenList.find(
(element: any) => element.extensions.l1Address == l1TokenAddress.toLowerCase()
(element: any) => element?.extensions?.l1Address?.toLowerCase() === l1TokenAddress.toLowerCase()
)?.address;
return l2Address ?? zeroAddress;
} else if (l2ChainId === 288) {
const url =
"https://raw.githubusercontent.com/bobanetwork/boba/develop/packages/boba/register/addresses/addressesMainnet_0x8376ac6C3f73a25Dd994E0b0669ca7ee0C02F089.json";
const response = await fetch(url);
const body = await response.text();
const tokenList = JSON.parse(body) as { [name: string]: string };
const l1TokenName = Object.entries(tokenList).find(([, address]) => {
return address.toLowerCase() === l1TokenAddress.toLowerCase();
})?.[0];
if (!l1TokenName) return zeroAddress;
if (!l1TokenName.includes("L1")) {
console.error(
`L1 not labeled as expected. Address: ${tokenList[l1TokenName]}, name: ${l1TokenName} at url: ${url}`
);
return zeroAddress;
}
const l2TokenName = l1TokenName.replace("L1", "L2");
const l2TokenAddress = tokenList[l2TokenName];
if (!l2TokenAddress) {
console.error(`L2 token address not found. name ${l2TokenName} url: ${url}`);
return zeroAddress;
}
return l2TokenAddress;
}
return zeroAddress;
}
Expand Down Expand Up @@ -110,7 +137,7 @@ async function askQuestion(query: string) {

export async function askYesNoQuestion(query: string): Promise<boolean> {
const ans = (await askQuestion(`${query} (y/n) `)) as string;
if (ans.toLowerCase() == "y") return true;
if (ans.toLowerCase() == "n") return false;
if (ans.toLowerCase() === "y") return true;
if (ans.toLowerCase() === "n") return false;
return askYesNoQuestion(query);
}