UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

414 lines 20.7 kB
import assert from 'assert'; import { expect } from 'chai'; import hre from 'hardhat'; import { eqAddress } from '@hyperlane-xyz/utils'; import { TestChainName, testChains } from '../consts/testChains.js'; import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { randomAddress, randomIsmConfig, randomMultisigIsmConfig, } from '../test/testUtils.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmIsmModule } from './EvmIsmModule.js'; import { HyperlaneIsmFactory } from './HyperlaneIsmFactory.js'; import { IsmType, } from './types.js'; describe('EvmIsmModule', async () => { let multiProvider; let exampleRoutingConfig; let mailboxAddress; let fundingAccount; const chain = TestChainName.test4; let factoryAddresses; let factoryContracts; before(async () => { const [signer, funder] = await hre.ethers.getSigners(); fundingAccount = funder; multiProvider = MultiProvider.createTestMultiProvider({ signer }); const contractsMap = await new HyperlaneProxyFactoryDeployer(multiProvider).deploy(multiProvider.mapKnownChains(() => ({}))); // get addresses of factories for the chain factoryContracts = contractsMap[chain]; factoryAddresses = Object.keys(factoryContracts).reduce((acc, key) => { acc[key] = contractsMap[chain][key].address; return acc; }, {}); // legacy HyperlaneIsmFactory is required to do a core deploy const legacyIsmFactory = new HyperlaneIsmFactory(contractsMap, multiProvider); // mailbox mailboxAddress = (await new TestCoreDeployer(multiProvider, legacyIsmFactory).deployApp()).getContracts(chain).mailbox.address; }); beforeEach(async () => { // Reset the MultiProvider for each test const [signer] = await hre.ethers.getSigners(); multiProvider = MultiProvider.createTestMultiProvider({ signer }); // example routing config exampleRoutingConfig = { type: IsmType.ROUTING, owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(), domains: Object.fromEntries(testChains .filter((c) => c !== TestChainName.test4) .map((c) => [c, randomMultisigIsmConfig(3, 5)])), }; }); // Helper method for create a new multiprovider with an impersonated account async function impersonateAccount(account) { await hre.ethers.provider.send('hardhat_impersonateAccount', [account]); await fundingAccount.sendTransaction({ to: account, value: hre.ethers.utils.parseEther('1.0'), }); return MultiProvider.createTestMultiProvider({ signer: hre.ethers.provider.getSigner(account), }); } // Helper method to expect exactly N updates to be applied async function expectTxsAndUpdate(ism, config, n) { const txs = await ism.update(config); expect(txs.length).to.equal(n); for (const tx of txs) { await multiProvider.sendTransaction(chain, tx); } } // ism module and config for testing let testIsm; let testConfig; // expect that the ISM matches the config after all tests afterEach(async () => { const derivedConfiig = await testIsm.read(); const normalizedDerivedConfig = normalizeConfig(derivedConfiig); const normalizedConfig = normalizeConfig(testConfig); assert.deepStrictEqual(normalizedDerivedConfig, normalizedConfig); }); // create a new ISM and verify that it matches the config async function createIsm(config) { const ism = await EvmIsmModule.create({ chain, config, proxyFactoryFactories: factoryAddresses, mailbox: mailboxAddress, multiProvider, }); testIsm = ism; testConfig = config; return { ism, initialIsmAddress: ism.serialize().deployedIsm }; } describe('create', async () => { it('deploys a simple ism', async () => { const config = randomMultisigIsmConfig(3, 5); await createIsm(config); }); it('deploys a trusted relayer ism', async () => { const relayer = randomAddress(); const config = { type: IsmType.TRUSTED_RELAYER, relayer, }; await createIsm(config); }); for (const type of [IsmType.ROUTING, IsmType.FALLBACK_ROUTING]) { it(`deploys ${type} routingIsm with correct routes`, async () => { exampleRoutingConfig.type = type; await createIsm(exampleRoutingConfig); }); } it(`deploys ${IsmType.AMOUNT_ROUTING}`, async () => { await createIsm({ type: IsmType.AMOUNT_ROUTING, lowerIsm: randomMultisigIsmConfig(3, 5), upperIsm: randomMultisigIsmConfig(3, 5), threshold: 2, }); }); for (let i = 0; i < 16; i++) { it(`deploys a random ism config #${i}`, async () => { const config = randomIsmConfig(); await createIsm(config); }); } }); describe('update', async () => { for (const type of [IsmType.ROUTING, IsmType.FALLBACK_ROUTING]) { beforeEach(() => { exampleRoutingConfig.type = type; }); it(`should skip deployment with warning if no chain metadata configured ${type}`, async () => { // create a new ISM const { ism } = await createIsm(exampleRoutingConfig); // create an updated config with a domain the multiprovider doesn't have const updatedRoutingConfig = { ...exampleRoutingConfig, domains: { ...exampleRoutingConfig.domains, test5: randomMultisigIsmConfig(3, 5), }, }; // expect 0 txs, as adding test5 domain is no-op await expectTxsAndUpdate(ism, updatedRoutingConfig, 0); }); it(`update route in an existing ${type}`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // changing the type of a domain should enroll the domain exampleRoutingConfig.domains[TestChainName.test2].type = IsmType.MESSAGE_ID_MULTISIG; // expect 1 tx to enroll test2 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // check that the ISM address is the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`deletes route in an existing ${type}`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // deleting the domain should unenroll the domain delete exampleRoutingConfig.domains[TestChainName.test3]; // expect 1 tx to unenroll test3 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`deletes route in an existing ${type} even if not in multiprovider`, async () => { // create a new ISM const { ism } = await createIsm(exampleRoutingConfig); // keep track of the domains before deleting const numDomainsBefore = Object.keys((await ism.read()).domains).length; // deleting the domain and removing from multiprovider should unenroll the domain delete exampleRoutingConfig.domains[TestChainName.test3]; multiProvider = multiProvider.intersect( // remove test3 from multiprovider testChains.filter((c) => c !== TestChainName.test3)).result; // expect 1 tx to unenroll test3 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // domains should have decreased by 1 const numDomainsAfter = Object.keys((await ism.read()).domains).length; expect(numDomainsBefore - 1).to.equal(numDomainsAfter); }); it(`updates owner in an existing ${type}`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // change the config owner exampleRoutingConfig.owner = randomAddress(); // expect 1 tx to transfer ownership await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`no changes to an existing ${type} means no redeployment or updates`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // expect 0 updates await expectTxsAndUpdate(ism, exampleRoutingConfig, 0); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`reordering validators in an existing ${type} should not trigger a redeployment`, async () => { // create a new ISM const routerConfig = { type: IsmType.ROUTING, owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(), domains: { test1: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', ], threshold: 2, }, test2: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', ], threshold: 2, }, }, }; const { ism, initialIsmAddress } = await createIsm(routerConfig); const updatedRouterConfig = { type: IsmType.ROUTING, owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(), domains: { test1: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', '0x5FbDB2315678afecb367f032d93F642f64180aa3', ], threshold: 2, }, test2: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x5FbDB2315678afecb367f032d93F642f64180aa3', ], threshold: 2, }, }, }; // expect 0 updates await expectTxsAndUpdate(ism, updatedRouterConfig, 0); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`update owner in an existing ${type} not owned by deployer`, async () => { // ISM owner is not the deployer exampleRoutingConfig.owner = randomAddress(); const originalOwner = exampleRoutingConfig.owner; // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // update the config owner and impersonate the original owner exampleRoutingConfig.owner = randomAddress(); multiProvider = await impersonateAccount(originalOwner); // expect 1 tx to transfer ownership await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be unchanged expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`update validators in an existing ${type}`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // update the validators for a domain exampleRoutingConfig.domains[TestChainName.test2] = { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [randomAddress(), randomAddress()], threshold: 2, }; // expect 1 tx to update validator set for test2 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`update threshold in an existing ${type}`, async () => { // create a new ISM const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); // update the threshold for a domain exampleRoutingConfig.domains[TestChainName.test2].threshold = 2; // expect 1 tx to update threshold for test2 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); it(`update threshold in an existing ${type} with Module creating using constructor`, async () => { // create an initial ISM const { initialIsmAddress } = await createIsm(exampleRoutingConfig); // update the threshold for a domain exampleRoutingConfig.domains[TestChainName.test2].threshold = 2; // create a new IsmModule using it's constructor. Set it's deployedIsm address to the initialIsmAddr const ism = new EvmIsmModule(multiProvider, { chain, config: exampleRoutingConfig, addresses: { ...factoryAddresses, mailbox: mailboxAddress, deployedIsm: initialIsmAddress, }, }); // expect 1 tx to update threshold for test2 domain await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); } it(`reordering modules in an existing staticAggregationIsm should not trigger a redeployment`, async () => { // create a new ISM const config = { type: IsmType.AGGREGATION, modules: [ { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', ], threshold: 2, }, { type: IsmType.ROUTING, owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(), domains: { test1: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', ], threshold: 2, }, test2: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', ], threshold: 2, }, }, }, ], threshold: 2, }; const { ism, initialIsmAddress } = await createIsm(config); const updatedConfig = { type: IsmType.AGGREGATION, modules: [ { type: IsmType.ROUTING, owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(), domains: { test2: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', ], threshold: 2, }, test1: { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', ], threshold: 2, }, }, }, { type: IsmType.MERKLE_ROOT_MULTISIG, validators: [ '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2', '0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db', ], threshold: 2, }, ], threshold: 2, }; // expect 0 updates await expectTxsAndUpdate(ism, updatedConfig, 0); // expect the ISM address to be the same expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be .true; }); }); }); //# sourceMappingURL=EvmIsmModule.hardhat-test.js.map