UNPKG

@hyperlane-xyz/cli

Version:

A command-line utility for common Hyperlane operations

286 lines 15.7 kB
import { expect } from 'chai'; import { Wallet } from 'ethers'; import { zeroAddress } from 'viem'; import { HookType, IsmType, MUTABLE_HOOK_TYPE, MUTABLE_ISM_TYPE, TokenType, randomAddress, randomHookConfig, randomIsmConfig, } from '@hyperlane-xyz/sdk'; import { assert, deepCopy, randomInt } from '@hyperlane-xyz/utils'; import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; import { ANVIL_KEY, CHAIN_NAME_2, CHAIN_NAME_3, CORE_CONFIG_PATH, DEFAULT_E2E_TEST_TIMEOUT, KeyBoardKeys, WARP_DEPLOY_OUTPUT_PATH, deployOrUseExistingCore, deployToken, deployXERC20LockboxToken, deployXERC20VSToken, getCombinedWarpRoutePath, handlePrompts, } from '../commands/helpers.js'; import { hyperlaneWarpApply, hyperlaneWarpCheck, hyperlaneWarpCheckRaw, hyperlaneWarpDeploy, } from '../commands/warp.js'; describe('hyperlane warp check e2e tests', async function () { this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); let chain2Addresses = {}; let chain3Addresses = {}; let token; let tokenSymbol; let ownerAddress; let warpConfig; before(async function () { [chain2Addresses, chain3Addresses] = await Promise.all([ deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), ]); token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); tokenSymbol = await token.symbol(); }); async function deployAndExportWarpRoute() { writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); return warpConfig; } // Reset config before each test to avoid test changes intertwining beforeEach(async function () { ownerAddress = new Wallet(ANVIL_KEY).address; warpConfig = { [CHAIN_NAME_2]: { type: TokenType.collateral, token: token.address, mailbox: chain2Addresses.mailbox, owner: ownerAddress, }, [CHAIN_NAME_3]: { type: TokenType.synthetic, mailbox: chain3Addresses.mailbox, owner: ownerAddress, }, }; }); describe('hyperlane warp check --config ...', () => { it(`should exit early if no symbol, chain or warp file have been provided`, async function () { await deployAndExportWarpRoute(); const output = await hyperlaneWarpCheckRaw({ warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, }) .stdio('pipe') .nothrow(); expect(output.exitCode).to.equal(1); expect(output.text()).to.include('Please specify either a symbol, chain and address or warp file'); }); }); describe('hyperlane warp check --symbol ... --config ...', () => { it(`should not find any differences between the on chain config and the local one`, async function () { await deployAndExportWarpRoute(); const 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}`, }, ]; const output = hyperlaneWarpCheckRaw({ symbol: tokenSymbol, warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, }) .stdio('pipe') .nothrow(); const finalOutput = await handlePrompts(output, steps); expect(finalOutput.exitCode).to.equal(0); expect(finalOutput.text()).to.include('No violations found'); }); }); describe('hyperlane warp check --symbol ... --config ... --key ...', () => { it(`should not find any differences between the on chain config and the local one`, async function () { await deployAndExportWarpRoute(); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol); expect(output.exitCode).to.equal(0); expect(output.text()).to.includes('No violations found'); }); describe('when using a custom ISM', () => { before(async function () { warpConfig[CHAIN_NAME_3].interchainSecurityModule = { type: IsmType.TRUSTED_RELAYER, relayer: ownerAddress, }; }); it(`should not find any differences between the on chain config and the local one`, async function () { await deployAndExportWarpRoute(); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol); expect(output.exitCode).to.equal(0); expect(output.text()).to.includes('No violations found'); }); }); describe('when using a custom hook', () => { before(async function () { warpConfig[CHAIN_NAME_3].hook = { type: HookType.PROTOCOL_FEE, protocolFee: '1', maxProtocolFee: '1', owner: ownerAddress, beneficiary: ownerAddress, }; }); it(`should not find any differences between the on chain config and the local one`, async function () { await deployAndExportWarpRoute(); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol); expect(output.exitCode).to.equal(0); expect(output.text()).to.includes('No violations found'); }); }); it(`should find differences between the local config and the on chain config in the ism`, async function () { const warpDeployConfig = await deployAndExportWarpRoute(); warpDeployConfig[CHAIN_NAME_3].interchainSecurityModule = { type: IsmType.TRUSTED_RELAYER, relayer: ownerAddress, }; const expectedDiffText = `EXPECTED:`; const expectedActualText = `ACTUAL: "${zeroAddress.toLowerCase()}"\n`; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol) .stdio('pipe') .nothrow(); expect(output.exitCode).to.equal(1); expect(output.text().includes(expectedDiffText)).to.be.true; expect(output.text().includes(expectedActualText)).to.be.true; }); it(`should find differences between the local config and the on chain config`, async function () { const warpDeployConfig = await deployAndExportWarpRoute(); const wrongOwner = randomAddress(); warpDeployConfig[CHAIN_NAME_3].owner = wrongOwner; const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; const expectedActualText = `ACTUAL: "${ownerAddress.toLowerCase()}"\n`; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text().includes(expectedDiffText)).to.be.true; expect(output.text().includes(expectedActualText)).to.be.true; }); it(`should find differences in the remoteRouters config between the local and on chain config`, async function () { const warpDeployConfig = await deployAndExportWarpRoute(); const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath(tokenSymbol, [ CHAIN_NAME_2, CHAIN_NAME_3, ]); // Unenroll CHAIN 2 from CHAIN 3 warpDeployConfig[CHAIN_NAME_3].remoteRouters = {}; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); await hyperlaneWarpApply(WARP_DEPLOY_OUTPUT_PATH, WARP_CORE_CONFIG_PATH_2_3); // Reset the config to the original state to trigger the inconsistency warpDeployConfig[CHAIN_NAME_3].remoteRouters = undefined; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); const warpCore = readYamlOrJson(WARP_CORE_CONFIG_PATH_2_3); const expectedActualText = `ACTUAL: ""\n`; const expectedDiffText = ` EXPECTED: address: "${warpCore.tokens[0].addressOrDenom.toLowerCase()}"`; const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text()).to.includes(expectedDiffText); expect(output.text()).to.includes(expectedActualText); }); describe('check extra lockboxes', () => { async function deployXERC20WarpRoute() { const xERC20TokenSymbol = 'XERC20TOKEN'; const xERC20Token = await deployXERC20VSToken(ANVIL_KEY, CHAIN_NAME_2, undefined, xERC20TokenSymbol); const token = await deployToken(ANVIL_KEY, CHAIN_NAME_2, undefined, 'XERC20Collateral'); const xERC20Lockbox = await deployXERC20LockboxToken(ANVIL_KEY, CHAIN_NAME_2, token); const tx = await xERC20Token.addBridge({ bridge: xERC20Lockbox.address, bufferCap: '1000', rateLimitPerSecond: '1000', }); await tx.wait(); const warpConfig = { [CHAIN_NAME_2]: { type: TokenType.XERC20, token: xERC20Token.address, mailbox: chain2Addresses.mailbox, owner: ownerAddress, xERC20: { warpRouteLimits: { bufferCap: '0', rateLimitPerSecond: '0', }, extraBridges: [ { limits: { bufferCap: '1000', rateLimitPerSecond: '1000', }, lockbox: xERC20Lockbox.address, }, ], }, }, }; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); return [xERC20TokenSymbol, warpConfig]; } it(`should not find differences between the local limits and the on chain ones`, async function () { const [xERC20TokenSymbol] = await deployXERC20WarpRoute(); const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, xERC20TokenSymbol).nothrow(); expect(output.exitCode).to.equal(0); }); it(`should find differences between the local limits and the on chain ones`, async function () { const [xERC20TokenSymbol, warpDeployConfig] = await deployXERC20WarpRoute(); assert(warpDeployConfig[CHAIN_NAME_2].type === TokenType.XERC20, 'Deploy config should be for an XERC20 token'); const currentExtraBridgesLimits = warpDeployConfig[CHAIN_NAME_2].xERC20.extraBridges[0]; const wrongBufferCap = randomInt(100).toString(); warpDeployConfig[CHAIN_NAME_2].xERC20.extraBridges = [ { ...currentExtraBridgesLimits, limits: { bufferCap: wrongBufferCap, rateLimitPerSecond: currentExtraBridgesLimits.limits.rateLimitPerSecond, }, }, ]; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); const expectedDiffText = `EXPECTED: "${wrongBufferCap}"\n`; const expectedActualText = `ACTUAL: "${currentExtraBridgesLimits.limits.rateLimitPerSecond}"\n`; const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, xERC20TokenSymbol).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text()).includes(expectedDiffText); expect(output.text()).includes(expectedActualText); }); }); }); for (const hookType of MUTABLE_HOOK_TYPE) { it(`should find owner differences between the local config and the on chain config for ${hookType}`, async function () { warpConfig[CHAIN_NAME_3].hook = randomHookConfig(0, 2, hookType); await deployAndExportWarpRoute(); const mutatedWarpConfig = deepCopy(warpConfig); const hookConfig = mutatedWarpConfig[CHAIN_NAME_3].hook; const actualOwner = hookConfig.owner; const wrongOwner = randomAddress(); assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); hookConfig.owner = wrongOwner; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text().includes(expectedDiffText)).to.be.true; expect(output.text().includes(expectedActualText)).to.be.true; }); } for (const ismType of MUTABLE_ISM_TYPE) { it(`should find owner differences between the local config and the on chain config for ${ismType}`, async function () { // Create a Pausable because randomIsmConfig() cannot generate it (reason: NULL type Isms) warpConfig[CHAIN_NAME_3].interchainSecurityModule = ismType === IsmType.PAUSABLE ? { type: IsmType.PAUSABLE, owner: randomAddress(), paused: true, } : randomIsmConfig(0, 2, ismType); await deployAndExportWarpRoute(); const mutatedWarpConfig = deepCopy(warpConfig); const ismConfig = mutatedWarpConfig[CHAIN_NAME_3].interchainSecurityModule; const actualOwner = ismConfig.owner; const wrongOwner = randomAddress(); assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); ismConfig.owner = wrongOwner; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; const output = await hyperlaneWarpCheck(WARP_DEPLOY_OUTPUT_PATH, tokenSymbol).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text().includes(expectedDiffText)).to.be.true; expect(output.text().includes(expectedActualText)).to.be.true; }); } }); //# sourceMappingURL=warp-check.e2e-test.js.map