UNPKG

@hyperlane-xyz/cli

Version:

A command-line utility for common Hyperlane operations

291 lines 13.2 kB
import { Wallet, ethers } from 'ethers'; import { $ } from 'zx'; import { ERC20Test__factory, ERC4626Test__factory, XERC20LockboxTest__factory, XERC20VSTest__factory, } from '@hyperlane-xyz/core'; import { createWarpRouteConfigId, } from '@hyperlane-xyz/registry'; import { TokenType, WarpCoreConfigSchema, } from '@hyperlane-xyz/sdk'; import { sleep } from '@hyperlane-xyz/utils'; import { getContext } from '../../context/context.js'; import { extendWarpRoute as extendWarpRouteWithoutApplyTransactions } from '../../deploy/warp.js'; import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; import { hyperlaneCoreDeploy } from './core.js'; import { hyperlaneWarpApply, hyperlaneWarpSendRelay, readWarpConfig, } from './warp.js'; export const E2E_TEST_CONFIGS_PATH = './test-configs'; export const REGISTRY_PATH = `${E2E_TEST_CONFIGS_PATH}/anvil`; export const TEMP_PATH = '/tmp'; // /temp gets removed at the end of all-test.sh export const ANVIL_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; export const E2E_TEST_BURN_ADDRESS = '0x0000000000000000000000000000000000000001'; export const CHAIN_NAME_2 = 'anvil2'; export const CHAIN_NAME_3 = 'anvil3'; export const EXAMPLES_PATH = './examples'; export const CORE_CONFIG_PATH = `${EXAMPLES_PATH}/core-config.yaml`; export const CORE_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config.yaml`; export const CORE_READ_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config-read.yaml`; export const CHAIN_2_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_2}/metadata.yaml`; export const CHAIN_3_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_3}/metadata.yaml`; export const WARP_CONFIG_PATH_EXAMPLE = `${EXAMPLES_PATH}/warp-route-deployment.yaml`; export const WARP_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/warp-route-deployment-anvil2.yaml`; export const WARP_DEPLOY_OUTPUT_PATH = `${TEMP_PATH}/warp-route-deployment.yaml`; export const WARP_CORE_CONFIG_PATH_2 = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-config.yaml`; export function getCombinedWarpRoutePath(tokenSymbol, chains) { return `${REGISTRY_PATH}/deployments/warp_routes/${createWarpRouteConfigId(tokenSymbol.toUpperCase(), chains)}-config.yaml`; } export const DEFAULT_E2E_TEST_TIMEOUT = 100000; // Long timeout since these tests can take a while export var KeyBoardKeys; (function (KeyBoardKeys) { KeyBoardKeys["ARROW_DOWN"] = "\u001B[B"; KeyBoardKeys["ARROW_UP"] = "\u001B[A"; KeyBoardKeys["ENTER"] = "\n"; KeyBoardKeys["TAB"] = "\t"; })(KeyBoardKeys || (KeyBoardKeys = {})); export async function asyncStreamInputWrite(stream, data) { stream.write(data); // Adding a slight delay to allow the buffer to update the output await sleep(500); } /** * Takes a {@link ProcessPromise} and a list of inputs that will be supplied * in the provided order when the check in the {@link TestPromptAction} matches the output * of the {@link ProcessPromise}. */ export async function handlePrompts(processPromise, actions) { let expectedStep = 0; for await (const out of processPromise.stdout) { const currentLine = out.toString(); const currentAction = actions[expectedStep]; if (currentAction && currentAction.check(currentLine)) { // Select mainnet chains await asyncStreamInputWrite(processPromise.stdin, currentAction.input); expectedStep++; } } return processPromise; } export const SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat(3)}${KeyBoardKeys.TAB}`; export const SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat(2)}${KeyBoardKeys.TAB}`; export const SELECT_MAINNET_CHAIN_TYPE_STEP = { check: (currentOutput) => currentOutput.includes('Select network type'), // Select mainnet chains input: KeyBoardKeys.ENTER, }; export const SELECT_ANVIL_2_AND_ANVIL_3_STEPS = [ { check: (currentOutput) => currentOutput.includes('--Mainnet Chains--'), input: `${SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER}`, }, { check: (currentOutput) => currentOutput.includes('--Mainnet Chains--'), input: `${SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER}${KeyBoardKeys.ENTER}`, }, ]; export const CONFIRM_DETECTED_OWNER_STEP = { check: (currentOutput) => currentOutput.includes('Detected owner address as'), input: KeyBoardKeys.ENTER, }; export const SETUP_CHAIN_SIGNERS_MANUALLY_STEPS = [ { check: (currentOutput) => currentOutput.includes('Please enter the private key for chain'), input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, }, { check: (currentOutput) => currentOutput.includes('Please enter the private key for chain'), input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, }, { check: (currentOutput) => currentOutput.includes('Please enter the private key for chain'), input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, }, ]; /** * Retrieves the deployed Warp address from the Warp core config. */ export function getDeployedWarpAddress(chain, warpCorePath) { const warpCoreConfig = readYamlOrJson(warpCorePath); WarpCoreConfigSchema.parse(warpCoreConfig); return warpCoreConfig.tokens.find((t) => t.chainName === chain) .addressOrDenom; } /** * Updates the owner of the Warp route deployment config, and then output to a file */ export async function updateWarpOwnerConfig(chain, owner, warpCorePath, warpDeployPath) { const warpDeployConfig = await readWarpConfig(chain, warpCorePath, warpDeployPath); warpDeployConfig[chain].owner = owner; await writeYamlOrJson(warpDeployPath, warpDeployConfig); return warpDeployPath; } /** * Updates the Warp route deployment configuration with a new owner, and then applies the changes. */ export async function updateOwner(owner, chain, warpConfigPath, warpCoreConfigPath) { await updateWarpOwnerConfig(chain, owner, warpCoreConfigPath, warpConfigPath); return hyperlaneWarpApply(warpConfigPath, warpCoreConfigPath); } /** * Extends the Warp route deployment with a new warp config */ export async function extendWarpConfig(params) { const { chain, chainToExtend, extendedConfig, warpCorePath, warpDeployPath, strategyUrl, } = params; const warpDeployConfig = await readWarpConfig(chain, warpCorePath, warpDeployPath); warpDeployConfig[chainToExtend] = extendedConfig; // Remove remoteRouters and destinationGas as they are written in readWarpConfig delete warpDeployConfig[chain].remoteRouters; delete warpDeployConfig[chain].destinationGas; writeYamlOrJson(warpDeployPath, warpDeployConfig); await hyperlaneWarpApply(warpDeployPath, warpCorePath, strategyUrl); return warpDeployPath; } /** * Sets up an incomplete warp route extension for testing purposes. * * This function creates a new warp route configuration for the second chain. */ export async function setupIncompleteWarpRouteExtension(chain2Addresses) { const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; const chain2DomainId = await getDomainId(CHAIN_NAME_2, ANVIL_KEY); const chain3DomainId = await getDomainId(CHAIN_NAME_3, ANVIL_KEY); const configToExtend = { decimals: 18, mailbox: chain2Addresses.mailbox, name: 'Ether', owner: new Wallet(ANVIL_KEY).address, symbol: 'ETH', type: TokenType.native, }; const context = await getContext({ registryUris: [REGISTRY_PATH], key: ANVIL_KEY, }); const warpCoreConfig = readYamlOrJson(WARP_CORE_CONFIG_PATH_2); const warpDeployConfig = await readWarpConfig(CHAIN_NAME_2, WARP_CORE_CONFIG_PATH_2, warpConfigPath); warpDeployConfig[CHAIN_NAME_3] = configToExtend; const signer2 = new Wallet(ANVIL_KEY, context.multiProvider.getProvider(CHAIN_NAME_2)); const signer3 = new Wallet(ANVIL_KEY, context.multiProvider.getProvider(CHAIN_NAME_3)); context.multiProvider.setSigner(CHAIN_NAME_2, signer2); context.multiProvider.setSigner(CHAIN_NAME_3, signer3); await extendWarpRouteWithoutApplyTransactions({ context: { ...context, signer: signer3, key: ANVIL_KEY, }, warpCoreConfig, warpDeployConfig, receiptsDir: TEMP_PATH, }, {}, warpCoreConfig); const combinedWarpCorePath = getCombinedWarpRoutePath('ETH', [ CHAIN_NAME_2, CHAIN_NAME_3, ]); return { chain2DomainId, chain3DomainId, warpConfigPath, configToExtend, context, combinedWarpCorePath, }; } /** * Deploys new core contracts on the specified chain if it doesn't already exist, and returns the chain addresses. */ export async function deployOrUseExistingCore(chain, coreInputPath, key) { const { registry } = await getContext({ registryUris: [REGISTRY_PATH], key, }); const addresses = (await registry.getChainAddresses(chain)); if (!addresses) { await hyperlaneCoreDeploy(chain, coreInputPath); return deployOrUseExistingCore(chain, coreInputPath, key); } return addresses; } export async function getDomainId(chainName, key) { const { registry } = await getContext({ registryUris: [REGISTRY_PATH], key, }); const chainMetadata = await registry.getChainMetadata(chainName); return String(chainMetadata?.domainId); } export async function deployToken(privateKey, chain, decimals = 18, symbol = 'TOKEN') { const { multiProvider } = await getContext({ registryUris: [REGISTRY_PATH], key: privateKey, }); // Future works: make signer compatible with protocol/chain stack multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); const token = await new ERC20Test__factory(multiProvider.getSigner(chain)).deploy('token', symbol.toLocaleUpperCase(), '100000000000000000000', decimals); await token.deployed(); return token; } export async function deploy4626Vault(privateKey, chain, tokenAddress) { const { multiProvider } = await getContext({ registryUris: [REGISTRY_PATH], key: privateKey, }); // Future works: make signer compatible with protocol/chain stack multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); const vault = await new ERC4626Test__factory(multiProvider.getSigner(chain)).deploy(tokenAddress, 'VAULT', 'VAULT'); await vault.deployed(); return vault; } export async function deployXERC20VSToken(privateKey, chain, decimals = 18, symbol = 'TOKEN') { const { multiProvider } = await getContext({ registryUris: [REGISTRY_PATH], key: privateKey, }); // Future works: make signer compatible with protocol/chain stack multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); const token = await new XERC20VSTest__factory(multiProvider.getSigner(chain)).deploy('token', symbol.toLocaleUpperCase(), '100000000000000000000', decimals); await token.deployed(); return token; } export async function deployXERC20LockboxToken(privateKey, chain, token) { const { multiProvider } = await getContext({ registryUris: [REGISTRY_PATH], key: privateKey, }); // Future works: make signer compatible with protocol/chain stack multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); const [tokenSymbol, tokenName, tokenDecimals, tokenTotalSupply] = await Promise.all([ token.symbol(), token.name(), token.decimals(), token.totalSupply(), ]); const lockboxToken = await new XERC20LockboxTest__factory(multiProvider.getSigner(chain)).deploy(tokenName, tokenSymbol.toLocaleUpperCase(), tokenTotalSupply, tokenDecimals); await lockboxToken.deployed(); return lockboxToken; } /** * Performs a round-trip warp relay between two chains using the specified warp core config. * * @param chain1 - The first chain to send the warp relay from. * @param chain2 - The second chain to send the warp relay to and back from. * @param warpCoreConfigPath - The path to the warp core config file. * @returns A promise that resolves when the round-trip warp relay is complete. */ export async function sendWarpRouteMessageRoundTrip(chain1, chain2, warpCoreConfigPath) { await hyperlaneWarpSendRelay(chain1, chain2, warpCoreConfigPath); return hyperlaneWarpSendRelay(chain2, chain1, warpCoreConfigPath); } export async function hyperlaneSendMessage(origin, destination) { return $ `yarn workspace @hyperlane-xyz/cli run hyperlane send message \ --registry ${REGISTRY_PATH} \ --origin ${origin} \ --destination ${destination} \ --key ${ANVIL_KEY} \ --verbosity debug \ --yes`; } export function hyperlaneRelayer(chains, warp) { return $ `yarn workspace @hyperlane-xyz/cli run hyperlane relayer \ --registry ${REGISTRY_PATH} \ --chains ${chains.join(',')} \ --warp ${warp ?? ''} \ --key ${ANVIL_KEY} \ --verbosity debug \ --yes`; } //# sourceMappingURL=helpers.js.map