From c2cf7be8f2c0469c9ec6dddbbf861c7891f1e96e Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:11:24 +0000 Subject: [PATCH] feat(cosmos): ism dry-run (#3077) ### Description Implements `dry_run_verify` for the aggregation ISM on cosmwasm. One remaining issue is that the estimated gas is hardcoded to `1`, because we're actually [just querying](https://github.com/many-things/cw-hyperlane/blob/37fea49429108d0cad46c64c3c1ebc467817ff8c/contracts/isms/aggregate/src/lib.rs#L108) via rpc rather than simulating a tx. The `verify` tx isn't marked as a contract [entrypoint](https://book.cosmwasm.com/basics/entry-points.html) so it can't be called from outside iiuc. (here's the [verify](https://github.com/many-things/cw-hyperlane/blob/37fea49429108d0cad46c64c3c1ebc467817ff8c/contracts/isms/aggregate/src/lib.rs#L124) fn for reference). Worth mentioning that the query interface for the aggregation ISM is named incorrectly - it should return fields called `threshold` and `modules`, but instead copies the response from the multisig ISM and returns `threshold` and `validators`. This can be particularly misleading because validators have 20-bytes long addresses, whereas modules (contracts) have 32-bytes long addresses. ### Related issues - Fixes https://github.com/hyperlane-xyz/issues/issues/807 ### Backward compatibility yes ### Testing E2E. The ISM setup is `routing` -> `aggregation (1/1)` -> `multisig (1/1)` --- .../hyperlane-cosmos/src/aggregation_ism.rs | 32 +++++++++++++------ .../src/interchain_security_module.rs | 23 +++++++++++-- .../src/payloads/aggregate_ism.rs | 25 ++++----------- rust/utils/run-locally/src/cosmos/deploy.rs | 15 +++++++++ rust/utils/run-locally/src/cosmos/link.rs | 2 +- rust/utils/run-locally/src/cosmos/types.rs | 2 ++ 6 files changed, 68 insertions(+), 31 deletions(-) diff --git a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs index a17a4ba3a..d18a6577c 100644 --- a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs @@ -1,15 +1,17 @@ use std::str::FromStr; use crate::{ - address::CosmosAddress, grpc::WasmProvider, - payloads::aggregate_ism::{ModulesAndThresholdRequest, ModulesAndThresholdResponse}, + payloads::{ + ism_routes::QueryIsmGeneralRequest, + multisig_ism::{VerifyInfoRequest, VerifyInfoRequestInner, VerifyInfoResponse}, + }, ConnectionConf, CosmosProvider, Signer, }; use async_trait::async_trait; use hyperlane_core::{ AggregationIsm, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, - HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, H256, + HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, RawHyperlaneMessage, H256, }; use tracing::instrument; @@ -66,15 +68,27 @@ impl AggregationIsm for CosmosAggregationIsm { &self, message: &HyperlaneMessage, ) -> ChainResult<(Vec, u8)> { - let payload = ModulesAndThresholdRequest::new(message); + let payload = VerifyInfoRequest { + verify_info: VerifyInfoRequestInner { + message: hex::encode(RawHyperlaneMessage::from(message)), + }, + }; - let data = self.provider.grpc().wasm_query(payload, None).await?; - let response: ModulesAndThresholdResponse = serde_json::from_slice(&data)?; + let data = self + .provider + .grpc() + .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) + .await?; + let response: VerifyInfoResponse = serde_json::from_slice(&data)?; + // Note that due to a misnomer in the CosmWasm implementation, the `modules` field is called `validators`. let modules: ChainResult> = response - .modules - .into_iter() - .map(|module| CosmosAddress::from_str(&module).map(|ca| ca.digest())) + .validators + .iter() + // The returned values are Bech32-decoded Cosmos addresses. + // Since they are not EOAs but rather contracts, they are 32 bytes long and + // need to be parsed directly as an `H256`. + .map(|module| H256::from_str(module).map_err(Into::into)) .collect(); Ok((modules?, response.threshold)) diff --git a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs index 9e726e562..bfde29f29 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs @@ -1,12 +1,14 @@ use async_trait::async_trait; use hyperlane_core::{ ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, - HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, H256, U256, + HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, RawHyperlaneMessage, + H256, U256, }; use crate::{ grpc::WasmProvider, payloads::{ + aggregate_ism::{VerifyRequest, VerifyRequestInner, VerifyResponse}, general::EmptyStruct, ism_routes::{QueryIsmGeneralRequest, QueryIsmModuleTypeRequest}, }, @@ -91,6 +93,23 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { message: &HyperlaneMessage, metadata: &[u8], ) -> ChainResult> { - Ok(Some(U256::from(1000))) // TODO + let payload = VerifyRequest { + verify: VerifyRequestInner { + metadata: hex::encode(metadata), + message: hex::encode(RawHyperlaneMessage::from(message)), + }, + }; + let data = self + .provider + .grpc() + .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) + .await?; + let response: VerifyResponse = serde_json::from_slice(&data)?; + // We can't simulate the `verify` call in CosmWasm because + // it's not marked as an entrypoint. So we just use the query interface + // and hardcode a gas value - this can be inefficient if one ISM is + // vastly cheaper than another one. + let dummy_gas_value = U256::one(); + Ok(response.verified.then_some(dummy_gas_value)) } } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs index 7bb5d40d0..8276675ff 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs @@ -1,30 +1,17 @@ -use hyperlane_core::{HyperlaneMessage, RawHyperlaneMessage}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] -pub struct ModulesAndThresholdRequest { - modules_and_threshold: ModulesAndThresholdRequestInner, -} - -impl ModulesAndThresholdRequest { - pub fn new(message: &HyperlaneMessage) -> Self { - Self { - modules_and_threshold: ModulesAndThresholdRequestInner { - message: hex::encode(RawHyperlaneMessage::from(message)), - }, - } - } +pub struct VerifyRequest { + pub verify: VerifyRequestInner, } #[derive(Serialize, Deserialize, Debug)] -struct ModulesAndThresholdRequestInner { - /// Hex-encoded Hyperlane message +pub struct VerifyRequestInner { + pub metadata: String, pub message: String, } #[derive(Serialize, Deserialize, Debug)] -pub struct ModulesAndThresholdResponse { - pub threshold: u8, - /// Bech32-encoded module addresses - pub modules: Vec, +pub struct VerifyResponse { + pub verified: bool, } diff --git a/rust/utils/run-locally/src/cosmos/deploy.rs b/rust/utils/run-locally/src/cosmos/deploy.rs index 859f9f05d..25a8d1dae 100644 --- a/rust/utils/run-locally/src/cosmos/deploy.rs +++ b/rust/utils/run-locally/src/cosmos/deploy.rs @@ -113,6 +113,20 @@ pub fn deploy_cw_hyperlane( "hpl_ism_multisig", ); + // deploy ism - aggregation + let ism_aggregate = cli.wasm_init( + &endpoint, + &deployer, + Some(deployer_addr), + codes.hpl_ism_aggregate, + ism::aggregate::InstantiateMsg { + owner: deployer_addr.clone(), + threshold: 1, + isms: vec![ism_multisig.clone()], + }, + "hpl_ism_aggregate", + ); + // deploy merkle hook let hook_merkle = cli.wasm_init( &endpoint, @@ -188,6 +202,7 @@ pub fn deploy_cw_hyperlane( hook_routing, igp, igp_oracle, + ism_aggregate, ism_routing, ism_multisig, mailbox, diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index 1cd1efe28..ff3de059f 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -162,7 +162,7 @@ fn link_network( ism::routing::ExecuteMsg::Set { ism: ism::routing::IsmSet { domain: target_domain, - address: network.deployments.ism_multisig.clone(), + address: network.deployments.ism_aggregate.clone(), }, }, vec![], diff --git a/rust/utils/run-locally/src/cosmos/types.rs b/rust/utils/run-locally/src/cosmos/types.rs index d3a515c4e..7a1575565 100644 --- a/rust/utils/run-locally/src/cosmos/types.rs +++ b/rust/utils/run-locally/src/cosmos/types.rs @@ -42,6 +42,7 @@ pub struct Codes { pub hpl_hook_routing: u64, pub hpl_igp: u64, pub hpl_igp_oracle: u64, + pub hpl_ism_aggregate: u64, pub hpl_ism_multisig: u64, pub hpl_ism_routing: u64, pub hpl_test_mock_ism: u64, @@ -57,6 +58,7 @@ pub struct Deployments { pub hook_routing: String, pub igp: String, pub igp_oracle: String, + pub ism_aggregate: String, pub ism_routing: String, pub ism_multisig: String, pub mailbox: String,