Skip to content

Commit

Permalink
feat: add routing delta for more granular routingIsm config updates (#…
Browse files Browse the repository at this point in the history
…3017)

### Description

- add support for `routingIsmDelta` which filters out the
incompatibility between the onchain deployed config and the desired
config.
- based on the above, you either update the deployed Ism with new
routes, delete old routes, change owners, etc.
- moduleMatchesConfig uses the same

### Drive-by changes

- checking exclusion of domains in the onchain deployed config

### Related issues

- fixes hyperlane-xyz/issues#818

### Backward compatibility

Yes

### Testing

Unit tests

---------

Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>
  • Loading branch information
aroralanuk and yorhodes authored Dec 13, 2023
1 parent b832e57 commit 7919417
Show file tree
Hide file tree
Showing 5 changed files with 465 additions and 165 deletions.
8 changes: 8 additions & 0 deletions .changeset/breezy-bats-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@hyperlane-xyz/sdk': patch
---

Granular control of updating predeployed routingIsms based on routing config mismatch
- Add support for routingIsmDelta which filters out the incompatibility between the onchain deployed config and the desired config.
- Based on the above, you either update the deployed Ism with new routes, delete old routes, change owners, etc.
- `moduleMatchesConfig` uses the same
40 changes: 0 additions & 40 deletions solidity/test/testSendReceiver.test.ts

This file was deleted.

279 changes: 233 additions & 46 deletions typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { ethers } from 'hardhat';

import { error } from '@hyperlane-xyz/utils';
import { Address, error } from '@hyperlane-xyz/utils';

import { TestChains } from '../consts/chains';
import { TestCoreApp } from '../core/TestCoreApp';
Expand Down Expand Up @@ -76,21 +76,37 @@ const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => {
describe('HyperlaneIsmFactory', async () => {
let ismFactory: HyperlaneIsmFactory;
let coreApp: TestCoreApp;

let multiProvider: MultiProvider;
let exampleRoutingConfig: RoutingIsmConfig;
let mailboxAddress: Address, newMailboxAddress: Address;
const chain = 'test1';

before(async () => {
beforeEach(async () => {
const [signer] = await ethers.getSigners();

const multiProvider = MultiProvider.createTestMultiProvider({ signer });

multiProvider = MultiProvider.createTestMultiProvider({ signer });
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
ismFactory = new HyperlaneIsmFactory(
await ismFactoryDeployer.deploy(multiProvider.mapKnownChains(() => ({}))),
multiProvider,
);
const coreDeployer = new TestCoreDeployer(multiProvider, ismFactory);
let coreDeployer = new TestCoreDeployer(multiProvider, ismFactory);
coreApp = await coreDeployer.deployApp();
mailboxAddress = coreApp.getContracts(chain).mailbox.address;

coreDeployer = new TestCoreDeployer(multiProvider, ismFactory);
coreApp = await coreDeployer.deployApp();
newMailboxAddress = coreApp.getContracts(chain).mailbox.address;

exampleRoutingConfig = {
type: IsmType.ROUTING,
owner: await multiProvider.getSignerAddress(chain),
domains: Object.fromEntries(
TestChains.filter((c) => c !== 'test1').map((c) => [
c,
randomMultisigIsmConfig(3, 5),
]),
),
};
});

it('deploys a simple ism', async () => {
Expand Down Expand Up @@ -136,47 +152,218 @@ describe('HyperlaneIsmFactory', async () => {
});
}

it('deploys routingIsm with correct routes', async () => {
const config: RoutingIsmConfig = {
type: IsmType.ROUTING,
owner: randomAddress(),
domains: Object.fromEntries(
TestChains.map((c) => [c, randomIsmConfig()]),
),
};
const ism = await ismFactory.deploy({ destination: chain, config });
const matches = await moduleMatchesConfig(
chain,
ism.address,
config,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
);
expect(matches).to.be.true;
});
for (const type of [IsmType.ROUTING, IsmType.FALLBACK_ROUTING]) {
it(`deploys ${type} routingIsm with correct routes`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
const ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const matches = await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
);
expect(matches).to.be.true;
});

it('deploys defaultFallbackRoutingIsm with correct routes and fallback to mailbox', async () => {
const config: RoutingIsmConfig = {
type: IsmType.FALLBACK_ROUTING,
owner: randomAddress(),
domains: Object.fromEntries(
TestChains.map((c) => [c, randomIsmConfig()]),
),
};
const mailbox = await coreApp.getContracts(chain).mailbox;
const ism = await ismFactory.deploy({
it(`update route in an existing ${type}`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
let matches = true;
let ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
// changing the type of a domain should enroll the domain
(exampleRoutingConfig.domains['test2'] as MultisigIsmConfig).type =
IsmType.MESSAGE_ID_MULTISIG;
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: mailboxAddress,
});
matches =
matches &&
existingIsm === ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
));
expect(matches).to.be.true;
});

it(`deletes route in an existing ${type}`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
let matches = true;
let ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
// deleting the domain should unenroll the domain
delete exampleRoutingConfig.domains['test3'];
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: mailboxAddress,
});
matches =
matches &&
existingIsm == ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
));
expect(matches).to.be.true;
});

it(`updates owner in an existing ${type}`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
let matches = true;
let ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
// change the owner
exampleRoutingConfig.owner = randomAddress();
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: mailboxAddress,
});
matches =
matches &&
existingIsm == ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
));
expect(matches).to.be.true;
});

it(`no changes to an existing ${type} means no redeployment or updates`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
let matches = true;
let ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
// using the same config should not change anything
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: mailboxAddress,
});
matches =
matches &&
existingIsm === ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
));
expect(matches).to.be.true;
});

it(`redeploy same config if the deployer doesn't have ownership of ${type}`, async () => {
exampleRoutingConfig.type = type as
| IsmType.ROUTING
| IsmType.FALLBACK_ROUTING;
let matches = true;
exampleRoutingConfig.owner = randomAddress();
let ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: mailboxAddress,
});
matches =
matches &&
existingIsm !== ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailboxAddress,
));
expect(matches).to.be.true;
});
}

it(`redeploy same config if the mailbox address changes for defaultFallbackRoutingIsm`, async () => {
exampleRoutingConfig.type = IsmType.FALLBACK_ROUTING;
let matches = true;
let ism = await ismFactory.deploy({
destination: chain,
config,
mailbox: mailbox.address,
}); // not through an actual factory just for maintaining consistency in naming
const matches = await moduleMatchesConfig(
chain,
ism.address,
config,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailbox.address,
);
config: exampleRoutingConfig,
mailbox: mailboxAddress,
});
const existingIsm = ism.address;
ism = await ismFactory.deploy({
destination: chain,
config: exampleRoutingConfig,
existingIsmAddress: ism.address,
mailbox: newMailboxAddress,
});
matches =
matches &&
existingIsm !== ism.address &&
(await moduleMatchesConfig(
chain,
ism.address,
exampleRoutingConfig,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
newMailboxAddress,
));
expect(matches).to.be.true;
});
});
Loading

0 comments on commit 7919417

Please sign in to comment.