Skip to content

Commit

Permalink
unit testing various ism configs
Browse files Browse the repository at this point in the history
  • Loading branch information
aroralanuk committed Nov 27, 2023
1 parent 12910a1 commit eed76ba
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 15 deletions.
95 changes: 81 additions & 14 deletions typescript/cli/src/config/ism.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,77 @@
import { confirm, input, select } from '@inquirer/prompts';
import { z } from 'zod';

import { ChainMap, ChainName, IsmConfig, IsmType } from '@hyperlane-xyz/sdk';
import { ChainMap, ChainName, IsmType } from '@hyperlane-xyz/sdk';

import { errorRed, log, logBlue, logGreen } from '../../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js';
import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js';

import { readChainConfigsIfExists } from './chain.js';

const IsmConfigSchema = z.custom<IsmConfig>();
const MultisigIsmConfigSchema = z.object({
type: z.union([
z.literal(IsmType.MERKLE_ROOT_MULTISIG),
z.literal(IsmType.MESSAGE_ID_MULTISIG),
]),
threshold: z.number(),
validators: z.array(z.string()),
});

const RoutingIsmConfigSchema: z.ZodSchema<any> = z.lazy(() =>
z.object({
type: z.literal(IsmType.ROUTING),
owner: z.string(),
domains: z.record(IsmConfigSchema),
}),
);

const AggregationIsmConfigSchema: z.ZodSchema<any> = z
.lazy(() =>
z.object({
type: z.literal(IsmType.AGGREGATION),
modules: z.array(IsmConfigSchema),
threshold: z.number(),
}),
)
.refine(
// check ig modules.length >= threshold
(ismConfig) => {
return ismConfig.modules.length >= ismConfig.threshold;
},
{
message: 'Threshold cannot be greater than number of modules',
},
);

const TestIsmConfigSchema = z.object({
type: z.literal(IsmType.TEST_ISM),
});

const IsmConfigSchema = z.union([
MultisigIsmConfigSchema,
RoutingIsmConfigSchema,
AggregationIsmConfigSchema,
TestIsmConfigSchema,
]);
const IsmConfigMapSchema = z.record(IsmConfigSchema).refine(
(ismConfigMap) => {
// check if any key in IsmConfigMap is found in its own RoutingIsmConfigSchema.domains
for (const [key, config] of Object.entries(ismConfigMap)) {
if (config.type === IsmType.ROUTING) {
if (config.domains && key in config.domains) {
return false;
}
}
}
return true;
},
{
message:
'Cannot set RoutingIsm.domain to the same chain you are configuring',
},
);
export type ZodIsmConfig = z.infer<typeof IsmConfigSchema>;
const IsmConfigMapSchema = z.record(IsmConfigSchema);
export type ZodIsmConfigMap = z.infer<typeof IsmConfigMapSchema>;

export function parseIsmConfig(filePath: string) {
Expand Down Expand Up @@ -52,7 +112,7 @@ export async function createIsmConfigMap({
const result: ZodIsmConfigMap = {};
for (const chain of chains) {
log(`Setting values for chain ${chain}`);
result[chain] = await createIsmConfig(chain, chains);
result[chain] = await createIsmConfig(chain, chainConfigPath);

// TODO consider re-enabling. Disabling based on feedback from @nambrot for now.
// repeat = await confirm({
Expand All @@ -73,7 +133,7 @@ export async function createIsmConfigMap({

export async function createIsmConfig(
chain: ChainName,
chains: ChainName[],
chainConfigPath: string,
): Promise<ZodIsmConfig> {
let lastConfig: ZodIsmConfig;
const moduleType = await select({
Expand Down Expand Up @@ -117,9 +177,9 @@ export async function createIsmConfig(
) {
lastConfig = await createMultisigConfig(moduleType);
} else if (moduleType === IsmType.ROUTING) {
lastConfig = await createRoutingConfig(chain, chains);
lastConfig = await createRoutingConfig(chain, chainConfigPath);
} else if (moduleType === IsmType.AGGREGATION) {
lastConfig = await createAggregationConfig(chain, chains);
lastConfig = await createAggregationConfig(chain, chainConfigPath);
} else if (moduleType === IsmType.TEST_ISM) {
lastConfig = { type: IsmType.TEST_ISM };
} else {
Expand Down Expand Up @@ -149,7 +209,7 @@ export async function createMultisigConfig(

export async function createAggregationConfig(
chain: ChainName,
chains: ChainName[],
chainConfigPath: string,
): Promise<ZodIsmConfig> {
const isms = parseInt(
await input({
Expand All @@ -167,7 +227,7 @@ export async function createAggregationConfig(

const modules: Array<ZodIsmConfig> = [];
for (let i = 0; i < isms; i++) {
modules.push(await createIsmConfig(chain, chains));
modules.push(await createIsmConfig(chain, chainConfigPath));
}
return {
type: IsmType.AGGREGATION,
Expand All @@ -177,20 +237,27 @@ export async function createAggregationConfig(
}

export async function createRoutingConfig(
destination: ChainName,
chains: ChainName[],
chain: ChainName,
chainConfigPath: string,
): Promise<ZodIsmConfig> {
const owner = await input({
message: 'Enter owner address',
});
const ownerAddress = owner;
const customChains = readChainConfigsIfExists(chainConfigPath);
delete customChains[chain];
const chains = await runMultiChainSelectionStep(
customChains,
`Select origin chains to be verified on ${chain}`,
[chain],
);

const domainsMap: ChainMap<ZodIsmConfig> = {};
for (const chain of chains.filter((c) => c !== destination)) {
for (const chain of chains) {
await confirm({
message: `You are about to configure ISM on origin chain ${chain}. Continue?`,
message: `You are about to configure ISM from source chain ${chain}. Continue?`,
});
const config = await createIsmConfig(chain, chains);
const config = await createIsmConfig(chain, chainConfigPath);
domainsMap[chain] = config;
}
return {
Expand Down
85 changes: 85 additions & 0 deletions typescript/cli/src/tests/ism.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { expect } from 'chai';

import { ChainMap, IsmConfig, IsmType } from '@hyperlane-xyz/sdk';

import { readIsmConfig } from '../config/ism.js';

describe('readIsmConfig', () => {
it('parses and validates example correctly', () => {
const ism = readIsmConfig('examples/ism-advanced.yaml');

const exampleIsmConfig: ChainMap<IsmConfig> = {
anvil1: {
type: IsmType.ROUTING,
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720',
domains: {
anvil2: {
type: IsmType.AGGREGATION,
modules: [
{
type: IsmType.MESSAGE_ID_MULTISIG,
threshold: 1,
validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'],
},
{
type: IsmType.MERKLE_ROOT_MULTISIG,
threshold: 1,
validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'],
},
],
threshold: 1,
},
},
},
anvil2: {
type: IsmType.ROUTING,
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720',
domains: {
anvil1: {
type: IsmType.AGGREGATION,
modules: [
{
type: IsmType.MESSAGE_ID_MULTISIG,
threshold: 1,
validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'],
},
{
type: IsmType.MERKLE_ROOT_MULTISIG,
threshold: 1,
validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'],
},
],
threshold: 1,
},
},
},
};
expect(ism).to.deep.equal(exampleIsmConfig);
});

it('parsing failure, missing internal key "threshold"', () => {
expect(function () {
readIsmConfig('src/tests/ism/safe-parse-fail.yaml');
}).to.throw();
});

it('parsing failure, routingIsm.domains includes destination domain', () => {
expect(function () {
readIsmConfig('src/tests/ism/routing-same-chain-fail.yaml');
}).to.throw(
'Cannot set RoutingIsm.domain to the same chain you are configuring',
);
});

it('parsing failure, wrong ism type', () => {
expect(function () {
readIsmConfig('src/tests/ism/wrong-ism-type-fail.yaml');
}).to.throw('Invalid ISM config: anvil2 => Invalid input');
});

it('parsing failure, threshold > modules.length', () => {
expect(function () {
readIsmConfig('src/tests/ism/threshold-gt-modules-length-fail.yaml');
}).to.throw('Threshold cannot be greater than number of modules');
});
});
33 changes: 33 additions & 0 deletions typescript/cli/src/tests/ism/routing-same-chain-fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
anvil1:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil1:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1

anvil2:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil2:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1
32 changes: 32 additions & 0 deletions typescript/cli/src/tests/ism/safe-parse-fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
anvil1:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil2:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1

anvil2:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil1:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
33 changes: 33 additions & 0 deletions typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
anvil1:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil2:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1

anvil2:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil1:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 3
33 changes: 33 additions & 0 deletions typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
anvil1:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil2:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: merkleRootMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1

anvil2:
type: domainRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil1:
type: staticAggregationIsm
modules:
- type: messageIdMultisigIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
- type: domainRoutingIsm
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
threshold: 1
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ anvil1:
anvil2:
threshold: 1
validators:
- '0xa0ee7a142d267c1n36714e4a8f75612f20a79720'
- '0xa0ee7a142d267c1n36714e4a8f7561f20a79720'

0 comments on commit eed76ba

Please sign in to comment.