From 33eba475619e11621e21fc7440d51a5971cd1ef3 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 May 2024 00:03:01 -0400 Subject: [PATCH] Test aggregation metadata builder --- .../sdk/src/ism/metadata/aggregation.test.ts | 58 +++++++++++++ .../sdk/src/ism/metadata/aggregation.ts | 82 ++++++++----------- typescript/sdk/src/ism/metadata/builder.ts | 27 ++++-- typescript/sdk/src/ism/metadata/multisig.ts | 27 +++--- 4 files changed, 127 insertions(+), 67 deletions(-) create mode 100644 typescript/sdk/src/ism/metadata/aggregation.test.ts diff --git a/typescript/sdk/src/ism/metadata/aggregation.test.ts b/typescript/sdk/src/ism/metadata/aggregation.test.ts new file mode 100644 index 0000000000..d330bd278d --- /dev/null +++ b/typescript/sdk/src/ism/metadata/aggregation.test.ts @@ -0,0 +1,58 @@ +import { expect } from 'chai'; + +import { + AggregationIsmMetadata, + AggregationIsmMetadataBuilder, +} from './aggregation.js'; + +type Fixture = { + decoded: AggregationIsmMetadata; + encoded: string; +}; + +const fixtures: Fixture[] = [ + { + decoded: { + submoduleMetadata: [ + '290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563', + '510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9', + '356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336', + ], + count: 3, + }, + encoded: + '000000180000003800000038000000580000005800000078290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336', + }, + { + decoded: { + count: 5, + submoduleMetadata: [ + '290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563', + '510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9', + '356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336', + 'f2e59013a0a379837166b59f871b20a8a0d101d1c355ea85d35329360e69c000', + ], + }, + encoded: + '000000280000004800000048000000680000006800000088000000000000000000000088000000a8290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336f2e59013a0a379837166b59f871b20a8a0d101d1c355ea85d35329360e69c000', + }, +]; + +describe('AggregationMetadataBuilder', () => { + fixtures.forEach((fixture, i) => { + it(`should encode fixture ${i}`, () => { + expect(AggregationIsmMetadataBuilder.encode(fixture.decoded)).to.equal( + fixture.encoded, + ); + }); + + it(`should decode fixture ${i}`, () => { + expect( + AggregationIsmMetadataBuilder.decode( + fixture.encoded, + fixture.decoded.count, + ), + ).to.deep.equal(fixture.decoded); + }); + }); +}); diff --git a/typescript/sdk/src/ism/metadata/aggregation.ts b/typescript/sdk/src/ism/metadata/aggregation.ts index eecce730c1..e846f7a028 100644 --- a/typescript/sdk/src/ism/metadata/aggregation.ts +++ b/typescript/sdk/src/ism/metadata/aggregation.ts @@ -1,6 +1,4 @@ -import { defaultAbiCoder } from '@ethersproject/abi'; - -import { WithAddress, assert } from '@hyperlane-xyz/utils'; +import { WithAddress } from '@hyperlane-xyz/utils'; import { DispatchedMessage } from '../../core/types.js'; import { DerivedIsmConfigWithAddress } from '../read.js'; @@ -10,17 +8,13 @@ import { BaseMetadataBuilder, MetadataBuilder } from './builder.js'; export interface AggregationIsmMetadata { submoduleMetadata: string[]; -} - -interface Range { - start: number; - end: number; + count: number; } const RANGE_SIZE = 4; export class AggregationIsmMetadataBuilder - implements MetadataBuilder + implements MetadataBuilder> { constructor(protected readonly base: BaseMetadataBuilder) {} @@ -33,57 +27,51 @@ export class AggregationIsmMetadataBuilder this.base.build(message, module as DerivedIsmConfigWithAddress), ), ); - return this.encode({ submoduleMetadata: metadatas }); + return AggregationIsmMetadataBuilder.encode({ + submoduleMetadata: metadatas, + count: ismConfig.threshold, + }); } - encode(metadata: AggregationIsmMetadata): string { - const lengths = metadata.submoduleMetadata.map((m) => m.length / 2); - const ranges: Range[] = []; + static encode(metadata: AggregationIsmMetadata): string { + const rangeSize = 2 * RANGE_SIZE * metadata.count; - let offset = 0; - for (const length of lengths) { - ranges.push({ start: offset, end: offset + length }); - offset += length; - } + let encoded = Buffer.alloc(rangeSize, 0); + metadata.submoduleMetadata.forEach((meta, index) => { + if (meta.length === 0) { + return; + } - let encoded = ''; - for (const range of ranges) { - const encodedRange = defaultAbiCoder.encode( - ['uint32', 'uint32'], - [range.start, range.end], - ); - assert(encodedRange.length === RANGE_SIZE * 2); - encoded += encodedRange; - } + const start = encoded.length; + encoded = Buffer.concat([encoded, Buffer.from(meta)]); + // TODO: FIX THIS, IDK WHY ITS NOT WORKING + const end = encoded.length; - for (const m of metadata.submoduleMetadata) { - encoded += m; - } + const rangeStart = 2 * RANGE_SIZE * index; + encoded.writeUint32BE(start, rangeStart); + encoded.writeUint32BE(end, rangeStart + RANGE_SIZE); + }); - return encoded; + return encoded.toString('hex'); } - metadataRange(metadata: string, index: number): Range { - const start = index * RANGE_SIZE * 2; - const mid = start + RANGE_SIZE; - const end = mid + RANGE_SIZE; - return { - start: parseInt(metadata.slice(start, mid)), - end: parseInt(metadata.slice(mid, end)), - }; + static metadataRange(metadata: string, index: number): string { + const rangeStart = index * 2 * RANGE_SIZE; + const encoded = Buffer.from(metadata, 'hex'); + const start = encoded.readUint32BE(rangeStart); + const end = encoded.readUint32BE(rangeStart + 1); + return encoded.subarray(start, end).toString('hex'); } - hasMetadata(metadata: string, index: number): boolean { - const { start } = this.metadataRange(metadata, index); - return start > 0; + static hasMetadata(metadata: string, index: number): boolean { + return this.metadataRange(metadata, index).length > 0; } - decode(metadata: string): AggregationIsmMetadata { + static decode(metadata: string, count: number): AggregationIsmMetadata { const submoduleMetadata = []; - for (let i = 0; this.hasMetadata(metadata, i); i++) { - const { start, end } = this.metadataRange(metadata, i); - submoduleMetadata.push(metadata.slice(start, end)); + for (let i = 0; i < count; i++) { + submoduleMetadata.push(this.metadataRange(metadata, i)); } - return { submoduleMetadata }; + return { submoduleMetadata, count }; } } diff --git a/typescript/sdk/src/ism/metadata/builder.ts b/typescript/sdk/src/ism/metadata/builder.ts index 31fd4e08a4..0b2669a5a9 100644 --- a/typescript/sdk/src/ism/metadata/builder.ts +++ b/typescript/sdk/src/ism/metadata/builder.ts @@ -1,9 +1,9 @@ -import { WithAddress, eqAddress } from '@hyperlane-xyz/utils'; +import { eqAddress } from '@hyperlane-xyz/utils'; import { HyperlaneCore } from '../../core/HyperlaneCore.js'; import { DispatchedMessage } from '../../core/types.js'; import { DerivedIsmConfigWithAddress } from '../read.js'; -import { IsmConfig, IsmType } from '../types.js'; +import { IsmType } from '../types.js'; import { AggregationIsmMetadata, @@ -11,16 +11,25 @@ import { } from './aggregation.js'; import { MultisigMetadata, MultisigMetadataBuilder } from './multisig.js'; -export interface MetadataBuilder { - build(message: DispatchedMessage, ismConfig: WithAddress): Promise; - encode?(metadata: M): string; - decode?(metadata: string): M; -} +type NullMetadata = { + type: + | IsmType.PAUSABLE + | IsmType.TEST_ISM + | IsmType.OP_STACK + | IsmType.TRUSTED_RELAYER; +}; + +export type StructuredMetadata = + | AggregationIsmMetadata + | MultisigMetadata + | NullMetadata; -type StructuredMetadata = AggregationIsmMetadata | MultisigMetadata; +export interface MetadataBuilder { + build(message: DispatchedMessage, ismConfig: T): Promise; +} export class BaseMetadataBuilder - implements MetadataBuilder + implements MetadataBuilder { constructor(protected readonly core: HyperlaneCore) {} diff --git a/typescript/sdk/src/ism/metadata/multisig.ts b/typescript/sdk/src/ism/metadata/multisig.ts index 6b189edf7e..2390a67596 100644 --- a/typescript/sdk/src/ism/metadata/multisig.ts +++ b/typescript/sdk/src/ism/metadata/multisig.ts @@ -21,11 +21,14 @@ import { IsmType, MultisigIsmConfig } from '../types.js'; import { MetadataBuilder } from './builder.js'; interface MessageIdMultisigMetadata { + type: IsmType.MESSAGE_ID_MULTISIG; checkpoint: Omit; signatures: SignatureLike[]; } -interface MerkleRootMultisigMetadata extends MessageIdMultisigMetadata { +interface MerkleRootMultisigMetadata + extends Omit { + type: IsmType.MERKLE_ROOT_MULTISIG; proof: MerkleProof; } @@ -34,7 +37,7 @@ export type MultisigMetadata = | MerkleRootMultisigMetadata; export class MultisigMetadataBuilder - implements MetadataBuilder + implements MetadataBuilder> { constructor(protected readonly core: HyperlaneCore) {} @@ -81,19 +84,21 @@ export class MultisigMetadataBuilder ); const metadata: MessageIdMultisigMetadata = { + type: IsmType.MESSAGE_ID_MULTISIG, checkpoint: matching.value.checkpoint, signatures: checkpoints .filter((c): c is S3CheckpointWithId => c !== undefined) .map((c) => c.signature), }; - return this.encode(metadata); + return MultisigMetadataBuilder.encode(metadata); } - encode(metadata: MultisigMetadata): string { - if ('proof' in metadata) { - throw new Error('Merkle proofs are not yet supported'); - } + static encode(metadata: MultisigMetadata): string { + assert( + metadata.type === IsmType.MESSAGE_ID_MULTISIG, + 'Merkle proofs are not yet supported', + ); let encoded = defaultAbiCoder.encode( ['bytes32', 'bytes32', 'uint32'], @@ -114,13 +119,13 @@ export class MultisigMetadataBuilder return encoded; } - signatureAt(metadata: string, index: number): SignatureLike { + static signatureAt(metadata: string, index: number): SignatureLike { const start = 68 + index * 65; const end = start + 65; return metadata.slice(start, end); } - hasSignature(metadata: string, index: number): boolean { + static hasSignature(metadata: string, index: number): boolean { try { this.signatureAt(metadata, index); return true; @@ -129,7 +134,7 @@ export class MultisigMetadataBuilder } } - decode(metadata: string): MessageIdMultisigMetadata { + static decode(metadata: string): MessageIdMultisigMetadata { const checkpoint = { merkle_tree_hook_address: metadata.slice(0, 32), root: metadata.slice(32, 64), @@ -141,6 +146,6 @@ export class MultisigMetadataBuilder signatures.push(this.signatureAt(metadata, i)); } - return { checkpoint, signatures }; + return { type: IsmType.MESSAGE_ID_MULTISIG, checkpoint, signatures }; } }