@hyperlane-xyz/cli
Version:
A command-line utility for common Hyperlane operations
291 lines • 13.2 kB
JavaScript
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