Relayer aggregation ism (#2411)

### Description

Adds AggregationISM support to the relayer, by adding the scaffolding,
sub-ism metadata fetching, and custom metadata encoding.

### Drive-by changes

- adds `dry_run_verify(...)` to the `InterchainSecurityModule` trait and
implements it for `EthereumInterchainSecurityModule`
- changes the default ISM in the test environment from
`routingIsm(multisigIsm)` to `routingIsm(aggregationIsm([multisigIsm,
messageIdIsm], threshold = 1))`

### Related issues

- Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/1757

### Backward compatibility

_Are these changes backward compatible?_

Yes

_Are there any infrastructure implications, e.g. changes that would
prohibit deploying older commits using this infra tooling?_

None


### Testing

The AggregationISM metadata encoding is tested using unit tests whose
input was taken from the tests in `AggregationIsm.t.sol`, by printing
the individual ISM metadatas and the final metadata.

The overall AggregationISM is tested with the `run-locally` script.
dan/testnet-aggregation-ism
Daniel Savu 1 year ago committed by GitHub
parent 16a29edb00
commit 7889385501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 714
      rust/Cargo.lock
  3. 262
      rust/agents/relayer/src/msg/metadata/aggregation.rs
  4. 25
      rust/agents/relayer/src/msg/metadata/base.rs
  5. 2
      rust/agents/relayer/src/msg/metadata/mod.rs
  6. 1
      rust/chains/hyperlane-ethereum/Cargo.toml
  7. 63
      rust/chains/hyperlane-ethereum/abis/IAggregationIsm.abi.json
  8. 117
      rust/chains/hyperlane-ethereum/src/aggregation_ism.rs
  9. 22
      rust/chains/hyperlane-ethereum/src/interchain_security_module.rs
  10. 10
      rust/chains/hyperlane-ethereum/src/lib.rs
  11. 31
      rust/hyperlane-base/src/settings/chains.rs
  12. 18
      rust/hyperlane-core/src/traits/aggregation_ism.rs
  13. 11
      rust/hyperlane-core/src/traits/interchain_security_module.rs
  14. 2
      rust/hyperlane-core/src/traits/mod.rs
  15. 3
      solidity/update_abis.sh
  16. 14
      typescript/infra/config/environments/test/aggregationIsm.ts
  17. 7
      typescript/infra/config/environments/test/core.ts
  18. 40
      typescript/infra/config/environments/test/multisigIsm.ts
  19. 16
      typescript/infra/config/environments/test/routingIsm.ts

1
.gitignore vendored

@ -27,4 +27,5 @@ yarn-error.log
.idea
**/*.ignore
.vscode

714
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,262 @@
use async_trait::async_trait;
use derive_more::Deref;
use futures_util::future::join_all;
use derive_new::new;
use eyre::Context;
use tracing::{info, instrument};
use hyperlane_core::{HyperlaneMessage, InterchainSecurityModule, H256, U256};
use super::{BaseMetadataBuilder, MetadataBuilder};
/// Bytes used to store one member of the (start, end) range tuple
/// Copied from `AggregationIsmMetadata.sol`
const METADATA_RANGE_SIZE: usize = 4;
#[derive(Clone, Debug, new, Deref)]
pub struct AggregationIsmMetadataBuilder {
base: BaseMetadataBuilder,
}
#[derive(Clone, Debug, new, PartialEq, Eq)]
struct SubModuleMetadata {
/// The index of the sub-module (ISM) in the aggregation ISM.
index: usize,
/// The metadata for the sub-module.
metadata: Vec<u8>,
}
#[derive(Debug)]
struct IsmAndMetadata {
ism: Box<dyn InterchainSecurityModule>,
meta: SubModuleMetadata,
}
impl IsmAndMetadata {
fn new(ism: Box<dyn InterchainSecurityModule>, index: usize, metadata: Vec<u8>) -> Self {
Self {
ism,
meta: SubModuleMetadata::new(index, metadata),
}
}
}
impl AggregationIsmMetadataBuilder {
fn format_metadata(metadatas: &mut [SubModuleMetadata], ism_count: usize) -> Vec<u8> {
// See test solidity implementation of this fn at:
// https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/445da4fb0d8140a08c4b314e3051b7a934b0f968/solidity/test/isms/AggregationIsm.t.sol#L35
fn encode_byte_index(i: usize) -> [u8; 4] {
(i as u32).to_be_bytes()
}
let range_tuples_size = METADATA_RANGE_SIZE * 2 * ism_count;
// Format of metadata:
// [????:????] Metadata start/end uint32 ranges, packed as uint64
// [????:????] ISM metadata, packed encoding
// Initialize the range tuple part of the buffer, so the actual metadatas can
// simply be appended to it
let mut buffer = vec![0; range_tuples_size];
for SubModuleMetadata { index, metadata } in metadatas.iter_mut() {
let range_start = buffer.len();
buffer.append(metadata);
let range_end = buffer.len();
// The new tuple starts at the end of the previous ones.
// Also see: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/445da4fb0d8140a08c4b314e3051b7a934b0f968/solidity/contracts/libs/isms/AggregationIsmMetadata.sol#L49
let encoded_range_start = METADATA_RANGE_SIZE * 2 * (*index);
// Overwrite the 0-initialized buffer
buffer.splice(
encoded_range_start..(encoded_range_start + METADATA_RANGE_SIZE * 2),
[encode_byte_index(range_start), encode_byte_index(range_end)].concat(),
);
}
buffer
}
fn n_cheapest_metas(
mut metas_and_gas: Vec<(SubModuleMetadata, U256)>,
n: usize,
) -> Vec<SubModuleMetadata> {
// Sort by gas cost in ascending order
metas_and_gas.sort_by(|(_, gas_1), (_, gas_2)| gas_1.cmp(gas_2));
// Take the cheapest n (the aggregation ISM threshold)
let mut cheapest: Vec<_> = metas_and_gas[..n].into();
// Sort by index in ascending order, to match the order expected by the smart contract
cheapest.sort_by(|(meta_1, _), (meta_2, _)| meta_1.index.cmp(&meta_2.index));
cheapest.into_iter().map(|(meta, _)| meta).collect()
}
async fn cheapest_valid_metas(
sub_modules: Vec<IsmAndMetadata>,
message: &HyperlaneMessage,
threshold: usize,
) -> Option<Vec<SubModuleMetadata>> {
let gas_cost_results: Vec<_> = join_all(
sub_modules
.iter()
.map(|module| module.ism.dry_run_verify(message, &(module.meta.metadata))),
)
.await;
// Filter out the ISMs without a gas cost estimate
let metas_and_gas: Vec<_> = sub_modules
.into_iter()
.zip(gas_cost_results.into_iter())
.filter_map(|(module, gas_cost)| gas_cost.ok().flatten().map(|gc| (module.meta, gc)))
.collect();
let metas_and_gas_count = metas_and_gas.len();
if metas_and_gas_count < threshold {
info!("Could not fetch all metadata: Found {metas_and_gas_count} of the {threshold} required ISM metadata pieces");
return None;
}
Some(Self::n_cheapest_metas(metas_and_gas, threshold))
}
}
#[async_trait]
impl MetadataBuilder for AggregationIsmMetadataBuilder {
#[instrument(err, skip(self))]
async fn build(
&self,
ism_address: H256,
message: &HyperlaneMessage,
) -> eyre::Result<Option<Vec<u8>>> {
const CTX: &str = "When fetching AggregationIsm metadata";
let ism = self.build_aggregation_ism(ism_address).await.context(CTX)?;
let (ism_addresses, threshold) = ism.modules_and_threshold(message).await.context(CTX)?;
let threshold = threshold as usize;
let metas = join_all(
ism_addresses
.iter()
.map(|ism_address| self.base.build(*ism_address, message)),
)
.await;
let sub_modules = join_all(
ism_addresses
.iter()
.map(|ism_address| self.base.build_ism(*ism_address)),
)
.await;
let filtered_sub_module_metas = metas
.into_iter()
.enumerate()
.zip(sub_modules.into_iter())
.filter_map(|((index, meta_result), sub_module_result)| {
match (meta_result, sub_module_result) {
(Ok(Some(meta)), Ok(ism)) => Some(IsmAndMetadata::new(ism, index, meta)),
_ => None,
}
})
.collect();
let maybe_aggregation_metadata =
Self::cheapest_valid_metas(filtered_sub_module_metas, message, threshold)
.await
.map(|mut metas| Self::format_metadata(&mut metas, ism_addresses.len()));
Ok(maybe_aggregation_metadata)
}
}
#[cfg(test)]
mod test {
use ethers::utils::hex::FromHex;
use super::*;
#[test]
fn test_format_n_of_n_metadata_works_correctly() {
let mut metadatas = vec![
SubModuleMetadata::new(
0,
Vec::from_hex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
.unwrap(),
),
SubModuleMetadata::new(
1,
Vec::from_hex("510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9")
.unwrap(),
),
SubModuleMetadata::new(
2,
Vec::from_hex("356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336")
.unwrap(),
),
];
let expected = Vec::from_hex("000000180000003800000038000000580000005800000078290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336").unwrap();
assert_eq!(
AggregationIsmMetadataBuilder::format_metadata(&mut metadatas, 3),
expected
);
}
#[test]
fn test_format_n_of_m_metadata_works_correctly() {
// We're passing the metadatas of 4 ISMs (indexes 0, 1, 2, 4) out of 5
let mut metadatas = vec![
SubModuleMetadata::new(
0,
Vec::from_hex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
.unwrap(),
),
SubModuleMetadata::new(
1,
Vec::from_hex("510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9")
.unwrap(),
),
SubModuleMetadata::new(
2,
Vec::from_hex("356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336")
.unwrap(),
),
SubModuleMetadata::new(
4,
Vec::from_hex("f2e59013a0a379837166b59f871b20a8a0d101d1c355ea85d35329360e69c000")
.unwrap(),
),
];
let expected = Vec::from_hex("000000280000004800000048000000680000006800000088000000000000000000000088000000a8290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9356e5a2cc1eba076e650ac7473fccc37952b46bc2e419a200cec0c451dce2336f2e59013a0a379837166b59f871b20a8a0d101d1c355ea85d35329360e69c000").unwrap();
assert_eq!(
AggregationIsmMetadataBuilder::format_metadata(&mut metadatas, 5),
expected
);
}
#[test]
fn test_format_empty_metadata_works_correctly() {
let mut metadatas = vec![SubModuleMetadata::new(0, Vec::from_hex("").unwrap())];
let expected = Vec::from_hex("0000000800000008").unwrap();
assert_eq!(
AggregationIsmMetadataBuilder::format_metadata(&mut metadatas, 1),
expected
);
}
#[test]
fn test_n_cheapest_metas_works() {
let metas_and_gas = vec![
(
SubModuleMetadata::new(3, vec![]),
U256::from_dec_str("3").unwrap(),
),
(
SubModuleMetadata::new(2, vec![]),
U256::from_dec_str("2").unwrap(),
),
(
SubModuleMetadata::new(1, vec![]),
U256::from_dec_str("1").unwrap(),
),
];
assert_eq!(
AggregationIsmMetadataBuilder::n_cheapest_metas(metas_and_gas, 2),
vec![
SubModuleMetadata::new(1, vec![]),
SubModuleMetadata::new(2, vec![])
]
)
}
}

@ -13,8 +13,8 @@ use hyperlane_base::{
};
use hyperlane_core::accumulator::merkle::Proof;
use hyperlane_core::{
Checkpoint, HyperlaneDomain, HyperlaneMessage, ModuleType, MultisigIsm, RoutingIsm,
ValidatorAnnounce, H160, H256,
AggregationIsm, Checkpoint, HyperlaneDomain, HyperlaneMessage, InterchainSecurityModule,
ModuleType, MultisigIsm, RoutingIsm, ValidatorAnnounce, H160, H256,
};
use crate::merkle_tree_builder::MerkleTreeBuilder;
@ -22,7 +22,7 @@ use crate::msg::metadata::multisig::{
LegacyMultisigMetadataBuilder, MerkleRootMultisigMetadataBuilder,
MessageIdMultisigMetadataBuilder,
};
use crate::msg::metadata::RoutingIsmMetadataBuilder;
use crate::msg::metadata::{AggregationIsmMetadataBuilder, RoutingIsmMetadataBuilder};
#[derive(Debug, thiserror::Error)]
pub enum MetadataBuilderError {
@ -72,11 +72,7 @@ impl MetadataBuilder for BaseMetadataBuilder {
message: &HyperlaneMessage,
) -> Result<Option<Vec<u8>>> {
const CTX: &str = "When fetching module type";
let ism = self
.destination_chain_setup
.build_ism(ism_address, &self.metrics)
.await
.context(CTX)?;
let ism = self.build_ism(ism_address).await.context(CTX)?;
let module_type = ism.module_type().await.context(CTX)?;
let base = self.clone_with_incremented_depth()?;
@ -87,6 +83,7 @@ impl MetadataBuilder for BaseMetadataBuilder {
}
ModuleType::MessageIdMultisig => Box::new(MessageIdMultisigMetadataBuilder::new(base)),
ModuleType::Routing => Box::new(RoutingIsmMetadataBuilder::new(base)),
ModuleType::Aggregation => Box::new(AggregationIsmMetadataBuilder::new(base)),
_ => return Err(MetadataBuilderError::UnsupportedModuleType(module_type).into()),
};
metadata_builder
@ -138,6 +135,12 @@ impl BaseMetadataBuilder {
self.origin_prover_sync.read().await.count() - 1
}
pub async fn build_ism(&self, address: H256) -> Result<Box<dyn InterchainSecurityModule>> {
self.destination_chain_setup
.build_ism(address, &self.metrics)
.await
}
pub async fn build_routing_ism(&self, address: H256) -> Result<Box<dyn RoutingIsm>> {
self.destination_chain_setup
.build_routing_ism(address, &self.metrics)
@ -150,6 +153,12 @@ impl BaseMetadataBuilder {
.await
}
pub async fn build_aggregation_ism(&self, address: H256) -> Result<Box<dyn AggregationIsm>> {
self.destination_chain_setup
.build_aggregation_ism(address, &self.metrics)
.await
}
pub async fn build_checkpoint_syncer(
&self,
validators: &[H256],

@ -1,7 +1,9 @@
mod aggregation;
mod base;
mod multisig;
mod routing;
use aggregation::AggregationIsmMetadataBuilder;
pub(crate) use base::BaseMetadataBuilder;
pub(crate) use base::MetadataBuilder;
use routing::RoutingIsmMetadataBuilder;

@ -15,6 +15,7 @@ ethers-contract.workspace = true
ethers-core.workspace = true
ethers-signers.workspace = true
ethers.workspace = true
futures-util.workspace = true
hex = "0.4.3"
num = "0.4"
reqwest.workspace = true

@ -0,0 +1,63 @@
[
{
"inputs": [],
"name": "moduleType",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "modulesAndThreshold",
"outputs": [
{
"internalType": "address[]",
"name": "modules",
"type": "address[]"
},
{
"internalType": "uint8",
"name": "threshold",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_metadata",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]

@ -0,0 +1,117 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ethers::providers::Middleware;
use tracing::instrument;
use hyperlane_core::{
AggregationIsm, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, RawHyperlaneMessage, H256,
};
use crate::contracts::i_aggregation_ism::{
IAggregationIsm as EthereumAggregationIsmInternal, IAGGREGATIONISM_ABI,
};
use crate::trait_builder::BuildableWithProvider;
use crate::EthereumProvider;
pub struct AggregationIsmBuilder {}
#[async_trait]
impl BuildableWithProvider for AggregationIsmBuilder {
type Output = Box<dyn AggregationIsm>;
async fn build_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumAggregationIsm::new(Arc::new(provider), locator))
}
}
/// A reference to an AggregationIsm contract on some Ethereum chain
#[derive(Debug)]
pub struct EthereumAggregationIsm<M>
where
M: Middleware,
{
contract: Arc<EthereumAggregationIsmInternal<M>>,
domain: HyperlaneDomain,
}
impl<M> EthereumAggregationIsm<M>
where
M: Middleware + 'static,
{
/// Create a reference to a mailbox at a specific Ethereum address on some
/// chain
pub fn new(provider: Arc<M>, locator: &ContractLocator) -> Self {
Self {
contract: Arc::new(EthereumAggregationIsmInternal::new(
locator.address,
provider,
)),
domain: locator.domain.clone(),
}
}
}
impl<M> HyperlaneChain for EthereumAggregationIsm<M>
where
M: Middleware + 'static,
{
fn domain(&self) -> &HyperlaneDomain {
&self.domain
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
Box::new(EthereumProvider::new(
self.contract.client(),
self.domain.clone(),
))
}
}
impl<M> HyperlaneContract for EthereumAggregationIsm<M>
where
M: Middleware + 'static,
{
fn address(&self) -> H256 {
self.contract.address().into()
}
}
#[async_trait]
impl<M> AggregationIsm for EthereumAggregationIsm<M>
where
M: Middleware + 'static,
{
#[instrument(err)]
async fn modules_and_threshold(
&self,
message: &HyperlaneMessage,
) -> ChainResult<(Vec<H256>, u8)> {
let (isms, threshold) = self
.contract
.modules_and_threshold(RawHyperlaneMessage::from(message).to_vec().into())
.call()
.await?;
let isms_h256 = isms.iter().map(|address| (*address).into()).collect();
Ok((isms_h256, threshold))
}
}
pub struct EthereumRoutingIsmAbi;
impl HyperlaneAbi for EthereumRoutingIsmAbi {
const SELECTOR_SIZE_BYTES: usize = 4;
fn fn_map() -> HashMap<Vec<u8>, &'static str> {
super::extract_fn_map(&IAGGREGATIONISM_ABI)
}
}

@ -8,9 +8,11 @@ use async_trait::async_trait;
use ethers::providers::Middleware;
use tracing::{instrument, warn};
use futures_util::future::try_join;
use hyperlane_core::{
ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneProvider, InterchainSecurityModule, ModuleType, H256,
HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, RawHyperlaneMessage,
H256, U256,
};
use num_traits::cast::FromPrimitive;
@ -106,6 +108,24 @@ where
Ok(ModuleType::Unused)
}
}
#[instrument]
async fn dry_run_verify(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
) -> ChainResult<Option<U256>> {
let tx = self.contract.verify(
metadata.to_owned().into(),
RawHyperlaneMessage::from(message).to_vec().into(),
);
let (verifies, gas_estimate) = try_join(tx.call(), tx.estimate_gas()).await?;
if verifies {
Ok(Some(gas_estimate))
} else {
Ok(None)
}
}
}
pub struct EthereumInterchainSecurityModuleAbi;

@ -10,9 +10,9 @@ use ethers::prelude::{abi, Lazy, Middleware};
#[cfg(not(doctest))]
pub use self::{
config::*, interchain_gas::*, interchain_security_module::*, mailbox::*, multisig_ism::*,
provider::*, routing_ism::*, rpc_clients::*, signers::*, singleton_signer::*, trait_builder::*,
validator_announce::*,
aggregation_ism::*, config::*, interchain_gas::*, interchain_security_module::*, mailbox::*,
multisig_ism::*, provider::*, routing_ism::*, rpc_clients::*, signers::*, singleton_signer::*,
trait_builder::*, validator_announce::*,
};
#[cfg(not(doctest))]
@ -49,6 +49,10 @@ mod routing_ism;
#[cfg(not(doctest))]
mod validator_announce;
/// AggregationIsm abi
#[cfg(not(doctest))]
mod aggregation_ism;
/// Generated contract bindings.
#[cfg(not(doctest))]
mod contracts;

@ -8,10 +8,10 @@ use ethers_prometheus::middleware::{
ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo,
};
use hyperlane_core::{
config::*, ContractLocator, HyperlaneAbi, HyperlaneDomain, HyperlaneDomainProtocol,
HyperlaneProvider, HyperlaneSigner, Indexer, InterchainGasPaymaster, InterchainGasPayment,
InterchainSecurityModule, Mailbox, MessageIndexer, MultisigIsm, RoutingIsm, ValidatorAnnounce,
H160, H256,
config::*, AggregationIsm, ContractLocator, HyperlaneAbi, HyperlaneDomain,
HyperlaneDomainProtocol, HyperlaneProvider, HyperlaneSigner, Indexer, InterchainGasPaymaster,
InterchainGasPayment, InterchainSecurityModule, Mailbox, MessageIndexer, MultisigIsm,
RoutingIsm, ValidatorAnnounce, H160, H256,
};
use hyperlane_ethereum::{
self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi,
@ -529,6 +529,29 @@ impl ChainConf {
.context(ctx)
}
/// Try to convert the chain setting into an AggregationIsm Ism contract
pub async fn build_aggregation_ism(
&self,
address: H256,
metrics: &CoreMetrics,
) -> Result<Box<dyn AggregationIsm>> {
let ctx = "Building aggregation ISM";
let locator = ContractLocator {
domain: &self.domain,
address,
};
match &self.connection()? {
ChainConnectionConf::Ethereum(conf) => {
self.build_ethereum(conf, &locator, metrics, h_eth::AggregationIsmBuilder {})
.await
}
ChainConnectionConf::Fuel(_) => todo!(),
}
.context(ctx)
}
async fn signer<S: BuildableWithSignerConf>(&self) -> Result<Option<S>> {
if let Some(conf) = &self.signer {
Ok(Some(conf.build::<S>().await?))

@ -0,0 +1,18 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use crate::{ChainResult, HyperlaneContract, HyperlaneMessage, H256};
/// Interface for the AggregationIsm chain contract. Allows abstraction over
/// different chains
#[async_trait]
#[auto_impl(&, Box, Arc)]
pub trait AggregationIsm: HyperlaneContract + Send + Sync + Debug {
/// Returns the `m` ISMs and `n` threshold needed to n-of-m verify the message
async fn modules_and_threshold(
&self,
message: &HyperlaneMessage,
) -> ChainResult<(Vec<H256>, u8)>;
}

@ -3,9 +3,10 @@ use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use num_derive::FromPrimitive;
use primitive_types::U256;
use strum::Display;
use crate::{ChainResult, HyperlaneContract};
use crate::{ChainResult, HyperlaneContract, HyperlaneMessage};
/// Enumeration of all known module types
#[derive(FromPrimitive, Clone, Debug, Default, Display, Copy, PartialEq, Eq)]
@ -33,4 +34,12 @@ pub trait InterchainSecurityModule: HyperlaneContract + Send + Sync + Debug {
/// Returns the module type of the ISM compliant with the corresponding
/// metadata offchain fetching and onchain formatting standard.
async fn module_type(&self) -> ChainResult<ModuleType>;
/// Dry runs the `verify()` ISM call and returns `Some(gas_estimate)` if the call
/// succeeds.
async fn dry_run_verify(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
) -> ChainResult<Option<U256>>;
}

@ -1,3 +1,4 @@
pub use aggregation_ism::*;
pub use cursor::*;
pub use db::*;
pub use deployed::*;
@ -12,6 +13,7 @@ pub use routing_ism::*;
pub use signing::*;
pub use validator_announce::*;
mod aggregation_ism;
mod cursor;
mod db;
mod deployed;

@ -14,4 +14,5 @@ copy interfaces/IInterchainGasPaymaster && \
copy interfaces/IValidatorAnnounce && \
copy interfaces/IInterchainSecurityModule && \
copy interfaces/isms/IMultisigIsm && \
copy interfaces/isms/IRoutingIsm
copy interfaces/isms/IRoutingIsm && \
copy interfaces/isms/IAggregationIsm

@ -0,0 +1,14 @@
import { AggregationIsmConfig, ModuleType } from '@hyperlane-xyz/sdk';
import { merkleRootMultisig, messageIdMultisig } from './multisigIsm';
export const aggregationIsm = (validatorKey: string): AggregationIsmConfig => {
return {
type: ModuleType.AGGREGATION,
modules: [
merkleRootMultisig(validatorKey),
messageIdMultisig(validatorKey),
],
threshold: 1,
};
};

@ -6,7 +6,8 @@ import {
objMap,
} from '@hyperlane-xyz/sdk';
import { multisigIsm } from './multisigIsm';
import { aggregationIsm } from './aggregationIsm';
import { chainToValidator } from './multisigIsm';
import { owners } from './owners';
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
@ -14,7 +15,9 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
type: ModuleType.ROUTING,
owner,
domains: Object.fromEntries(
Object.entries(multisigIsm).filter(([chain]) => chain !== local),
Object.entries(chainToValidator)
.filter(([chain, _]) => chain !== local)
.map(([chain, validatorKey]) => [chain, aggregationIsm(validatorKey)]),
),
};

@ -1,21 +1,41 @@
import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk';
// the addresses here must line up with the e2e test's validator addresses
export const multisigIsm: ChainMap<MultisigIsmConfig> = {
// Validators are anvil accounts 4-6
test1: {
export const chainToValidator: Record<string, string> = {
test1: '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
test2: '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
test3: '0x976EA74026E726554dB657fA54763abd0C3a0aa9',
};
export const legacyMultisig = (validatorKey: string): MultisigIsmConfig => {
return {
type: ModuleType.LEGACY_MULTISIG,
validators: ['0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65'],
validators: [chainToValidator[validatorKey]],
threshold: 1,
},
test2: {
};
};
export const merkleRootMultisig = (validatorKey: string): MultisigIsmConfig => {
return {
type: ModuleType.MERKLE_ROOT_MULTISIG,
validators: ['0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc'],
validators: [validatorKey],
threshold: 1,
},
test3: {
};
};
export const messageIdMultisig = (validatorKey: string): MultisigIsmConfig => {
return {
type: ModuleType.MESSAGE_ID_MULTISIG,
validators: ['0x976EA74026E726554dB657fA54763abd0C3a0aa9'],
validators: [validatorKey],
threshold: 1,
},
};
};
// the addresses here must line up with the e2e test's validator addresses
export const multisigIsm: ChainMap<MultisigIsmConfig> = {
// Validators are anvil accounts 4-6
test1: legacyMultisig('test1'),
test2: merkleRootMultisig('test2'),
test3: messageIdMultisig('test3'),
};

@ -0,0 +1,16 @@
import { ModuleType, RoutingIsmConfig } from '@hyperlane-xyz/sdk';
import { multisigIsm } from './multisigIsm';
export const routingIsm = (
local_chain: string,
owner: string,
): RoutingIsmConfig => {
return {
type: ModuleType.ROUTING,
owner,
domains: Object.fromEntries(
Object.entries(multisigIsm).filter(([chain]) => chain !== local_chain),
),
};
};
Loading…
Cancel
Save