From a74a60a032852cc2bddfcc59f340d05045be4833 Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Thu, 3 Oct 2024 02:41:03 +0800 Subject: [PATCH] feat(minor-router): check duplicate chain name when register chain (#638) Co-authored-by: Christian Gorenflo --- Cargo.lock | 10 +- Cargo.toml | 1 + contracts/router/Cargo.toml | 3 + contracts/router/src/contract.rs | 14 ++- contracts/router/src/contract/execute.rs | 55 ++++++++-- .../router/src/contract/migrations/v0_3_3.rs | 5 + contracts/router/src/contract/query.rs | 34 ++++-- integration-tests/Cargo.toml | 3 + integration-tests/src/contract.rs | 14 ++- integration-tests/src/coordinator_contract.rs | 11 +- integration-tests/src/gateway_contract.rs | 12 +-- integration-tests/src/multisig_contract.rs | 12 +-- .../src/multisig_prover_contract.rs | 22 ++-- integration-tests/src/protocol.rs | 98 ++++++++++++++++- integration-tests/src/rewards_contract.rs | 16 +-- integration-tests/src/router_contract.rs | 17 +-- .../src/service_registry_contract.rs | 7 +- .../src/voting_verifier_contract.rs | 7 +- integration-tests/tests/test_utils/mod.rs | 63 ++++++----- packages/axelar-core-std/Cargo.toml | 5 + packages/axelar-core-std/src/lib.rs | 2 +- packages/axelar-core-std/src/nexus/mod.rs | 102 ++++++++++++++---- packages/axelar-core-std/src/nexus/query.rs | 10 +- packages/axelar-core-std/src/query.rs | 2 +- packages/router-api/src/error.rs | 3 + 25 files changed, 399 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 694a9d401..5ca3b1894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,9 +398,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ark-bls12-381" @@ -815,6 +815,7 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" name = "axelar-core-std" version = "1.0.0" dependencies = [ + "assert_ok", "axelar-wasm-std", "client", "cosmwasm-std", @@ -4043,6 +4044,8 @@ dependencies = [ name = "integration-tests" version = "1.0.0" dependencies = [ + "anyhow", + "axelar-core-std", "axelar-wasm-std", "coordinator", "cosmwasm-std", @@ -4058,6 +4061,7 @@ dependencies = [ "rewards", "router", "router-api", + "schemars", "serde", "serde_json", "service-registry", @@ -6842,7 +6846,9 @@ dependencies = [ name = "router" version = "1.0.0" dependencies = [ + "axelar-core-std", "axelar-wasm-std", + "client", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.15.1", diff --git a/Cargo.toml b/Cargo.toml index 70a0af2dd..8a1795427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ edition = "2021" [workspace.dependencies] alloy-primitives = { version = "0.7.6", default-features = false, features = ["std"] } alloy-sol-types = { version = "0.7.6", default-features = false, features = ["std"] } +anyhow = "1.0.89" assert_ok = "1.0" axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } diff --git a/contracts/router/Cargo.toml b/contracts/router/Cargo.toml index 16ddea4d8..a1f3959d4 100644 --- a/contracts/router/Cargo.toml +++ b/contracts/router/Cargo.toml @@ -32,7 +32,9 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] +axelar-core-std = { workspace = true } axelar-wasm-std = { workspace = true, features = ["derive"] } +client = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } @@ -49,6 +51,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +axelar-core-std = { workspace = true, features = ["test"] } cw-multi-test = "0.15.1" hex = { version = "0.4.3", default-features = false } integration-tests = { workspace = true } diff --git a/contracts/router/src/contract.rs b/contracts/router/src/contract.rs index baf40f991..9b868fab3 100644 --- a/contracts/router/src/contract.rs +++ b/contracts/router/src/contract.rs @@ -84,7 +84,13 @@ pub fn execute( msg_id_format, } => { let gateway_address = address::validate_cosmwasm_address(deps.api, &gateway_address)?; - execute::register_chain(deps.storage, chain, gateway_address, msg_id_format) + Ok(execute::register_chain( + deps.storage, + deps.querier, + chain, + gateway_address, + msg_id_format, + )?) } ExecuteMsg::UpgradeGateway { chain, @@ -129,7 +135,7 @@ pub fn query( match msg { QueryMsg::ChainInfo(chain) => to_json_binary(&query::chain_info(deps.storage, chain)?), QueryMsg::Chains { start_after, limit } => { - to_json_binary(&query::chains(deps, start_after, limit)?) + to_json_binary(&query::chains(deps.storage, start_after, limit)?) } QueryMsg::IsEnabled => to_json_binary(&killswitch::is_contract_active(deps.storage)), } @@ -141,6 +147,7 @@ mod test { use std::collections::HashMap; use std::str::FromStr; + use axelar_core_std::nexus::test_utils::reply_with_is_chain_registered; use axelar_wasm_std::err_contains; use axelar_wasm_std::error::ContractError; use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; @@ -164,6 +171,9 @@ mod test { fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); + deps.querier = deps + .querier + .with_custom_handler(reply_with_is_chain_registered(false)); instantiate( deps.as_mut(), diff --git a/contracts/router/src/contract/execute.rs b/contracts/router/src/contract/execute.rs index c31016b57..005b44d90 100644 --- a/contracts/router/src/contract/execute.rs +++ b/contracts/router/src/contract/execute.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; use std::vec; +use axelar_core_std::nexus; use axelar_wasm_std::flagset::FlagSet; use axelar_wasm_std::killswitch; use axelar_wasm_std::msg_id::{self, MessageIdFormat}; -use cosmwasm_std::{to_json_binary, Addr, Event, Response, StdResult, Storage, WasmMsg}; -use error_stack::{ensure, report, ResultExt}; +use cosmwasm_std::{ + to_json_binary, Addr, Event, QuerierWrapper, Response, StdResult, Storage, WasmMsg, +}; +use error_stack::{bail, ensure, report, Report, ResultExt}; use itertools::Itertools; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName, Gateway, GatewayDirection, Message}; @@ -18,13 +21,27 @@ use crate::{events, state}; pub fn register_chain( storage: &mut dyn Storage, + querier: QuerierWrapper, name: ChainName, gateway: Addr, msg_id_format: MessageIdFormat, -) -> Result { - if find_chain_for_gateway(storage, &gateway)?.is_some() { - return Err(Error::GatewayAlreadyRegistered); +) -> Result> { + if find_chain_for_gateway(storage, &gateway) + .change_context(Error::StoreFailure)? + .is_some() + { + bail!(Error::GatewayAlreadyRegistered) + } + + let client: nexus::Client = client::CosmosClient::new(querier).into(); + if client + .is_chain_registered(&name) + .change_context(Error::Nexus)? + { + Err(Report::new(Error::ChainAlreadyExists)) + .attach_printable(format!("chain {} already exists in core", name))? } + chain_endpoints().update(storage, name.clone(), |chain| match chain { Some(_) => Err(Error::ChainAlreadyExists), None => Ok(ChainEndpoint { @@ -244,15 +261,17 @@ pub fn route_messages( mod test { use std::collections::HashMap; + use axelar_core_std::nexus::test_utils::reply_with_is_chain_registered; + use axelar_wasm_std::assert_err_contains; use axelar_wasm_std::flagset::FlagSet; - use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use axelar_wasm_std::msg_id::{HexTxHashAndEventIndex, MessageIdFormat}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{Addr, Storage}; + use cosmwasm_std::{Addr, QuerierWrapper, Storage}; use rand::{random, RngCore}; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName, CrossChainId, Gateway, GatewayDirection, Message}; - use super::{freeze_chains, unfreeze_chains}; + use super::{freeze_chains, register_chain, unfreeze_chains}; use crate::contract::execute::route_messages; use crate::contract::instantiate; use crate::events::{ChainFrozen, ChainUnfrozen}; @@ -929,6 +948,26 @@ mod test { )); } + #[test] + fn register_chain_with_duplicate_chain_name_in_core() { + let mut deps = mock_dependencies(); + deps.querier = deps + .querier + .with_custom_handler(reply_with_is_chain_registered(true)); + + assert_err_contains!( + register_chain( + &mut deps.storage, + QuerierWrapper::new(&deps.querier), + "ethereum".parse().unwrap(), + Addr::unchecked("gateway"), + MessageIdFormat::HexTxHashAndEventIndex + ), + Error, + Error::ChainAlreadyExists + ); + } + fn assert_chain_endpoint_frozen_status( storage: &dyn Storage, chain: ChainName, diff --git a/contracts/router/src/contract/migrations/v0_3_3.rs b/contracts/router/src/contract/migrations/v0_3_3.rs index 86c849b26..01196aafd 100644 --- a/contracts/router/src/contract/migrations/v0_3_3.rs +++ b/contracts/router/src/contract/migrations/v0_3_3.rs @@ -52,6 +52,7 @@ const CONFIG: Item = Item::new("config"); mod test { use std::collections::HashMap; + use axelar_core_std::nexus::test_utils::reply_with_is_chain_registered; use axelar_wasm_std::error::ContractError; use axelar_wasm_std::killswitch; use axelar_wasm_std::msg_id::MessageIdFormat; @@ -108,6 +109,10 @@ mod test { #[test] fn migration() { let mut deps = mock_dependencies(); + deps.querier = deps + .querier + .with_custom_handler(reply_with_is_chain_registered(false)); + let instantiate_msg = instantiate_0_3_3_contract(deps.as_mut()).unwrap(); let msg = ExecuteMsg::RegisterChain { diff --git a/contracts/router/src/contract/query.rs b/contracts/router/src/contract/query.rs index d10f23aae..90a3b3992 100644 --- a/contracts/router/src/contract/query.rs +++ b/contracts/router/src/contract/query.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Deps, Order, Storage}; +use cosmwasm_std::{Order, Storage}; use cw_storage_plus::Bound; use error_stack::{Result, ResultExt}; use router_api::error::Error; @@ -17,7 +17,7 @@ pub fn chain_info(storage: &dyn Storage, chain: ChainName) -> Result, limit: Option, ) -> Result, Error> { @@ -25,7 +25,7 @@ pub fn chains( let start = start_after.map(Bound::exclusive); chain_endpoints() - .range(deps.storage, start, None, Order::Ascending) + .range(storage, start, None, Order::Ascending) .take(limit) .map(|item| { item.map(|(_, endpoint)| endpoint) @@ -105,29 +105,41 @@ mod test { } // no pagination - let result = super::chains(deps.as_ref(), None, None).unwrap(); + let result = super::chains(deps.as_ref().storage, None, None).unwrap(); assert_eq!(result.len(), 4); assert_eq!(result, endpoints); // with limit - let result = super::chains(deps.as_ref(), None, Some(2)).unwrap(); + let result = super::chains(deps.as_ref().storage, None, Some(2)).unwrap(); assert_eq!(result.len(), 2); assert_eq!(result, vec![endpoints[0].clone(), endpoints[1].clone()]); // with page - let result = - super::chains(deps.as_ref(), Some("c-chain".parse().unwrap()), Some(2)).unwrap(); + let result = super::chains( + deps.as_ref().storage, + Some("c-chain".parse().unwrap()), + Some(2), + ) + .unwrap(); assert_eq!(result.len(), 1); assert_eq!(result, vec![endpoints[3].clone()]); // start after the last chain - let result = - super::chains(deps.as_ref(), Some("d-chain".parse().unwrap()), Some(2)).unwrap(); + let result = super::chains( + deps.as_ref().storage, + Some("d-chain".parse().unwrap()), + Some(2), + ) + .unwrap(); assert_eq!(result.len(), 0); // with a key out of the scope - let result = - super::chains(deps.as_ref(), Some("e-chain".parse().unwrap()), Some(2)).unwrap(); + let result = super::chains( + deps.as_ref().storage, + Some("e-chain".parse().unwrap()), + Some(2), + ) + .unwrap(); assert_eq!(result.len(), 0); } } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index b97b1c623..6978927df 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -28,6 +28,8 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] +anyhow = { workspace = true } +axelar-core-std = { workspace = true } axelar-wasm-std = { workspace = true } coordinator = { workspace = true } cosmwasm-std = { workspace = true } @@ -43,6 +45,7 @@ report = { workspace = true } rewards = { workspace = true } router = { workspace = true } router-api = { workspace = true } +schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } service-registry = { workspace = true } diff --git a/integration-tests/src/contract.rs b/integration-tests/src/contract.rs index 487ca9ca2..29e175c3a 100644 --- a/integration-tests/src/contract.rs +++ b/integration-tests/src/contract.rs @@ -1,15 +1,21 @@ use cosmwasm_std::{Addr, Coin, StdError, StdResult}; -use cw_multi_test::{App, AppResponse, Executor}; +use cw_multi_test::{AppResponse, Executor}; use error_stack::{report, Result}; use serde::de::DeserializeOwned; use serde::Serialize; +use crate::protocol::AxelarApp; + pub trait Contract { type QMsg; type ExMsg; fn contract_address(&self) -> Addr; - fn query(&self, app: &App, query_message: &Self::QMsg) -> StdResult + fn query( + &self, + app: &AxelarApp, + query_message: &Self::QMsg, + ) -> StdResult where Self::QMsg: Serialize, { @@ -19,7 +25,7 @@ pub trait Contract { fn execute( &self, - app: &mut App, + app: &mut AxelarApp, caller: Addr, execute_message: &Self::ExMsg, ) -> Result @@ -32,7 +38,7 @@ pub trait Contract { fn execute_with_funds( &self, - app: &mut App, + app: &mut AxelarApp, caller: Addr, execute_message: &Self::ExMsg, funds: &[Coin], diff --git a/integration-tests/src/coordinator_contract.rs b/integration-tests/src/coordinator_contract.rs index 94570a17b..48818a823 100644 --- a/integration-tests/src/coordinator_contract.rs +++ b/integration-tests/src/coordinator_contract.rs @@ -1,8 +1,9 @@ use coordinator::contract::{execute, instantiate, query}; use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct CoordinatorContract { @@ -10,8 +11,12 @@ pub struct CoordinatorContract { } impl CoordinatorContract { - pub fn instantiate_contract(app: &mut App, governance: Addr, service_registry: Addr) -> Self { - let code = ContractWrapper::new(execute, instantiate, query); + pub fn instantiate_contract( + app: &mut AxelarApp, + governance: Addr, + service_registry: Addr, + ) -> Self { + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/gateway_contract.rs b/integration-tests/src/gateway_contract.rs index 73ec11b32..87ea7f9be 100644 --- a/integration-tests/src/gateway_contract.rs +++ b/integration-tests/src/gateway_contract.rs @@ -1,7 +1,9 @@ use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; +use gateway::contract::{execute, instantiate, query}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct GatewayContract { @@ -10,15 +12,11 @@ pub struct GatewayContract { impl GatewayContract { pub fn instantiate_contract( - app: &mut App, + app: &mut AxelarApp, router_address: Addr, verifier_address: Addr, ) -> Self { - let code = ContractWrapper::new( - gateway::contract::execute, - gateway::contract::instantiate, - gateway::contract::query, - ); + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/multisig_contract.rs b/integration-tests/src/multisig_contract.rs index ef7bbefc4..e4044de71 100644 --- a/integration-tests/src/multisig_contract.rs +++ b/integration-tests/src/multisig_contract.rs @@ -1,8 +1,10 @@ use axelar_wasm_std::nonempty; use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; +use multisig::contract::{execute, instantiate, query}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct MultisigContract { @@ -11,17 +13,13 @@ pub struct MultisigContract { impl MultisigContract { pub fn instantiate_contract( - app: &mut App, + app: &mut AxelarApp, governance: Addr, admin: Addr, rewards_address: Addr, block_expiry: nonempty::Uint64, ) -> Self { - let code = ContractWrapper::new( - multisig::contract::execute, - multisig::contract::instantiate, - multisig::contract::query, - ); + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/multisig_prover_contract.rs b/integration-tests/src/multisig_prover_contract.rs index 8db0da918..37896c2d5 100644 --- a/integration-tests/src/multisig_prover_contract.rs +++ b/integration-tests/src/multisig_prover_contract.rs @@ -1,11 +1,13 @@ +use axelar_core_std::query::AxelarQueryMsg; use axelar_wasm_std::Threshold; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, DepsMut, Env}; use cw_multi_test::{ContractWrapper, Executor}; use multisig::key::KeyType; +use multisig_prover::contract::{execute, instantiate, query}; use multisig_prover::Encoder; use crate::contract::Contract; -use crate::protocol::Protocol; +use crate::protocol::{emptying_deps_mut, Protocol}; #[derive(Clone)] pub struct MultisigProverContract { @@ -21,12 +23,8 @@ impl MultisigProverContract { voting_verifier_address: Addr, chain_name: String, ) -> Self { - let code = ContractWrapper::new( - multisig_prover::contract::execute, - multisig_prover::contract::instantiate, - multisig_prover::contract::query, - ) - .with_reply(multisig_prover::contract::reply); + let code = + ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(custom_reply); let app = &mut protocol.app; let code_id = app.store_code(Box::new(code)); @@ -66,6 +64,14 @@ impl MultisigProverContract { } } +fn custom_reply( + mut deps: DepsMut, + env: Env, + msg: cosmwasm_std::Reply, +) -> Result { + multisig_prover::contract::reply(emptying_deps_mut(&mut deps), env, msg) +} + impl Contract for MultisigProverContract { type QMsg = multisig_prover::msg::QueryMsg; type ExMsg = multisig_prover::msg::ExecuteMsg; diff --git a/integration-tests/src/protocol.rs b/integration-tests/src/protocol.rs index 962ad2dff..036ab91e7 100644 --- a/integration-tests/src/protocol.rs +++ b/integration-tests/src/protocol.rs @@ -1,6 +1,16 @@ +use std::fmt::Debug; +use std::ops::Deref; + +use axelar_core_std::nexus; +use axelar_core_std::query::AxelarQueryMsg; use axelar_wasm_std::nonempty; -use cosmwasm_std::Addr; -use cw_multi_test::App; +use cosmwasm_std::testing::{MockApi, MockStorage}; +use cosmwasm_std::{ + Addr, Api, Binary, BlockInfo, CustomQuery, Deps, DepsMut, Empty, Querier, QuerierWrapper, + Storage, +}; +use cw_multi_test::{App, AppResponse, BankKeeper, CosmosRouter, Module, WasmKeeper}; +use serde::de::DeserializeOwned; use crate::coordinator_contract::CoordinatorContract; use crate::multisig_contract::MultisigContract; @@ -19,5 +29,87 @@ pub struct Protocol { pub service_name: nonempty::String, pub rewards: RewardsContract, pub rewards_params: rewards::msg::Params, - pub app: App, + pub app: AxelarApp, +} + +pub type AxelarApp = + App>; + +#[allow(clippy::type_complexity)] +pub struct AxelarModule { + pub tx_hash_and_nonce: Box anyhow::Result>, + pub is_chain_registered: Box anyhow::Result>, +} + +impl Module for AxelarModule { + type ExecT = Empty; + type QueryT = AxelarQueryMsg; + type SudoT = Empty; + + fn execute( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _sender: Addr, + _msg: Self::ExecT, + ) -> anyhow::Result + where + ExecC: Debug + Clone + PartialEq + schemars::JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> anyhow::Result + where + ExecC: Debug + Clone + PartialEq + schemars::JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } + + fn query( + &self, + _: &dyn Api, + _: &dyn Storage, + _: &dyn Querier, + block: &BlockInfo, + request: Self::QueryT, + ) -> anyhow::Result { + match request { + AxelarQueryMsg::Nexus(query) => match query { + nexus::query::QueryMsg::TxHashAndNonce {} => (self.tx_hash_and_nonce)(block), + nexus::query::QueryMsg::IsChainRegistered { chain } => { + (self.is_chain_registered)(chain) + } + _ => unreachable!("unexpected nexus message {:?}", query), + }, + _ => unreachable!("unexpected query request {:?}", request), + } + } +} + +pub fn emptying_deps<'a>(deps: &'a Deps) -> Deps<'a, Empty> { + Deps { + storage: deps.storage, + api: deps.api, + querier: QuerierWrapper::new(deps.querier.deref()), + } +} + +pub fn emptying_deps_mut<'a>(deps: &'a mut DepsMut) -> DepsMut<'a, Empty> { + DepsMut { + storage: deps.storage, + api: deps.api, + querier: QuerierWrapper::new(deps.querier.deref()), + } } diff --git a/integration-tests/src/rewards_contract.rs b/integration-tests/src/rewards_contract.rs index fb8572b1b..f77cb33c5 100644 --- a/integration-tests/src/rewards_contract.rs +++ b/integration-tests/src/rewards_contract.rs @@ -1,7 +1,9 @@ use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; +use rewards::contract::{execute, instantiate, query}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct RewardsContract { @@ -9,12 +11,12 @@ pub struct RewardsContract { } impl RewardsContract { - pub fn instantiate_contract(app: &mut App, governance: Addr, rewards_denom: String) -> Self { - let code = ContractWrapper::new( - rewards::contract::execute, - rewards::contract::instantiate, - rewards::contract::query, - ); + pub fn instantiate_contract( + app: &mut AxelarApp, + governance: Addr, + rewards_denom: String, + ) -> Self { + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/router_contract.rs b/integration-tests/src/router_contract.rs index 45ca7e33f..30ec0e671 100644 --- a/integration-tests/src/router_contract.rs +++ b/integration-tests/src/router_contract.rs @@ -1,7 +1,9 @@ use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; +use router::contract::{execute, instantiate, query}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct RouterContract { @@ -9,12 +11,13 @@ pub struct RouterContract { } impl RouterContract { - pub fn instantiate_contract(app: &mut App, admin: Addr, governance: Addr, nexus: Addr) -> Self { - let code = ContractWrapper::new( - router::contract::execute, - router::contract::instantiate, - router::contract::query, - ); + pub fn instantiate_contract( + app: &mut AxelarApp, + admin: Addr, + governance: Addr, + nexus: Addr, + ) -> Self { + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/service_registry_contract.rs b/integration-tests/src/service_registry_contract.rs index 6cffa8854..1a5dec751 100644 --- a/integration-tests/src/service_registry_contract.rs +++ b/integration-tests/src/service_registry_contract.rs @@ -1,8 +1,9 @@ use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; use service_registry::contract::{execute, instantiate, query}; use crate::contract::Contract; +use crate::protocol::AxelarApp; #[derive(Clone)] pub struct ServiceRegistryContract { @@ -10,8 +11,8 @@ pub struct ServiceRegistryContract { } impl ServiceRegistryContract { - pub fn instantiate_contract(app: &mut App, governance: Addr) -> Self { - let code = ContractWrapper::new(execute, instantiate, query); + pub fn instantiate_contract(app: &mut AxelarApp, governance: Addr) -> Self { + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let code_id = app.store_code(Box::new(code)); let contract_addr = app diff --git a/integration-tests/src/voting_verifier_contract.rs b/integration-tests/src/voting_verifier_contract.rs index 8b108da3b..aab65abd1 100644 --- a/integration-tests/src/voting_verifier_contract.rs +++ b/integration-tests/src/voting_verifier_contract.rs @@ -2,6 +2,7 @@ use axelar_wasm_std::MajorityThreshold; use cosmwasm_std::Addr; use cw_multi_test::{ContractWrapper, Executor}; use router_api::ChainName; +use voting_verifier::contract::{execute, instantiate, query}; use crate::contract::Contract; use crate::protocol::Protocol; @@ -17,11 +18,7 @@ impl VotingVerifierContract { voting_threshold: MajorityThreshold, source_chain: ChainName, ) -> Self { - let code = ContractWrapper::new( - voting_verifier::contract::execute, - voting_verifier::contract::instantiate, - voting_verifier::contract::query, - ); + let code = ContractWrapper::new_with_empty(execute, instantiate, query); let app = &mut protocol.app; let code_id = app.store_code(Box::new(code)); diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 37f458265..f7a378226 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1,19 +1,20 @@ use std::collections::{HashMap, HashSet}; +use axelar_core_std::nexus::query::IsChainRegisteredResponse; use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::voting::{PollId, Vote}; use axelar_wasm_std::{nonempty, Participant, Threshold}; use coordinator::msg::{ExecuteMsg as CoordinatorExecuteMsg, VerifierInfo}; use cosmwasm_std::{ - coins, Addr, Attribute, BlockInfo, Event, HexBinary, StdError, Uint128, Uint64, + coins, to_json_binary, Addr, Attribute, BlockInfo, Event, HexBinary, StdError, Uint128, Uint64, }; -use cw_multi_test::{App, AppResponse, Executor}; +use cw_multi_test::{AppBuilder, AppResponse, Executor}; use integration_tests::contract::Contract; use integration_tests::coordinator_contract::CoordinatorContract; use integration_tests::gateway_contract::GatewayContract; use integration_tests::multisig_contract::MultisigContract; use integration_tests::multisig_prover_contract::MultisigProverContract; -use integration_tests::protocol::Protocol; +use integration_tests::protocol::{AxelarApp, AxelarModule, Protocol}; use integration_tests::rewards_contract::RewardsContract; use integration_tests::router_contract::RouterContract; use integration_tests::service_registry_contract::ServiceRegistryContract; @@ -48,7 +49,7 @@ fn find_event_attribute<'a>( type PollExpiryBlock = u64; pub fn verify_messages( - app: &mut App, + app: &mut AxelarApp, gateway: &GatewayContract, msgs: &[Message], ) -> (PollId, PollExpiryBlock) { @@ -70,7 +71,7 @@ pub fn verify_messages( (poll_id, expiry) } -pub fn route_messages(app: &mut App, gateway: &GatewayContract, msgs: &[Message]) { +pub fn route_messages(app: &mut AxelarApp, gateway: &GatewayContract, msgs: &[Message]) { let response = gateway.execute( app, Addr::unchecked("relayer"), @@ -80,7 +81,7 @@ pub fn route_messages(app: &mut App, gateway: &GatewayContract, msgs: &[Message] } pub fn freeze_chain( - app: &mut App, + app: &mut AxelarApp, router: &RouterContract, chain_name: ChainName, direction: GatewayDirection, @@ -97,7 +98,7 @@ pub fn freeze_chain( } pub fn unfreeze_chain( - app: &mut App, + app: &mut AxelarApp, router: &RouterContract, chain_name: &ChainName, direction: GatewayDirection, @@ -114,7 +115,7 @@ pub fn unfreeze_chain( } pub fn upgrade_gateway( - app: &mut App, + app: &mut AxelarApp, router: &RouterContract, governance: &Addr, chain_name: &ChainName, @@ -140,7 +141,7 @@ fn random_32_bytes() -> [u8; 32] { } pub fn vote_success_for_all_messages( - app: &mut App, + app: &mut AxelarApp, voting_verifier: &VotingVerifierContract, messages: &[Message], verifiers: &[Verifier], @@ -160,7 +161,7 @@ pub fn vote_success_for_all_messages( } pub fn vote_true_for_verifier_set( - app: &mut App, + app: &mut AxelarApp, voting_verifier: &VotingVerifierContract, verifiers: &Vec, poll_id: PollId, @@ -179,7 +180,7 @@ pub fn vote_true_for_verifier_set( } /// Ends the poll. Be sure the current block height has advanced at least to the poll expiration, else this will fail -pub fn end_poll(app: &mut App, voting_verifier: &VotingVerifierContract, poll_id: PollId) { +pub fn end_poll(app: &mut AxelarApp, voting_verifier: &VotingVerifierContract, poll_id: PollId) { let response = voting_verifier.execute( app, Addr::unchecked("relayer"), @@ -272,7 +273,7 @@ pub fn register_service( } pub fn messages_from_gateway( - app: &mut App, + app: &mut AxelarApp, gateway: &GatewayContract, message_ids: &[CrossChainId], ) -> Vec { @@ -286,7 +287,7 @@ pub fn messages_from_gateway( } pub fn proof( - app: &mut App, + app: &mut AxelarApp, multisig_prover: &MultisigProverContract, multisig_session_id: &Uint64, ) -> multisig_prover::msg::ProofResponse { @@ -303,7 +304,7 @@ pub fn proof( } pub fn verifier_set_from_prover( - app: &mut App, + app: &mut AxelarApp, multisig_prover_contract: &MultisigProverContract, ) -> VerifierSet { let query_response: Result, StdError> = @@ -351,7 +352,7 @@ pub fn assert_verifier_details_are_equal( } #[allow(clippy::arithmetic_side_effects)] -pub fn advance_height(app: &mut App, increment: u64) { +pub fn advance_height(app: &mut AxelarApp, increment: u64) { let cur_block = app.block_info(); app.set_block(BlockInfo { height: cur_block.height + increment, @@ -359,7 +360,7 @@ pub fn advance_height(app: &mut App, increment: u64) { }); } -pub fn advance_at_least_to_height(app: &mut App, desired_height: u64) { +pub fn advance_at_least_to_height(app: &mut AxelarApp, desired_height: u64) { let cur_block = app.block_info(); if app.block_info().height < desired_height { app.set_block(BlockInfo { @@ -386,12 +387,22 @@ pub fn distribute_rewards(protocol: &mut Protocol, chain_name: &ChainName, contr pub fn setup_protocol(service_name: nonempty::String) -> Protocol { let genesis = Addr::unchecked("genesis"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &genesis, coins(u128::MAX, AXL_DENOMINATION)) - .unwrap() - }); + let mut app = AppBuilder::new_custom() + .with_custom(AxelarModule { + tx_hash_and_nonce: Box::new(|_| unimplemented!()), + is_chain_registered: Box::new(|_| { + Ok(to_json_binary(&IsChainRegisteredResponse { + is_registered: false, + })?) + }), + }) + .build(|router, _, storage| { + router + .bank + .init_balance(storage, &genesis, coins(u128::MAX, AXL_DENOMINATION)) + .unwrap() + }); + let admin_address = Addr::unchecked("admin"); let governance_address = Addr::unchecked("governance"); let nexus_gateway = Addr::unchecked("nexus_gateway"); @@ -518,7 +529,7 @@ pub fn claim_stakes( } pub fn confirm_verifier_set( - app: &mut App, + app: &mut AxelarApp, relayer_addr: Addr, multisig_prover: &MultisigProverContract, ) { @@ -549,7 +560,7 @@ fn verifier_set_poll_id_and_expiry(response: AppResponse) -> (PollId, PollExpiry } pub fn create_verifier_set_poll( - app: &mut App, + app: &mut AxelarApp, relayer_addr: Addr, voting_verifier: &VotingVerifierContract, verifier_set: VerifierSet, @@ -806,14 +817,14 @@ pub fn setup_chain(protocol: &mut Protocol, chain_name: ChainName) -> Chain { } } -pub fn query_balance(app: &App, address: &Addr) -> Uint128 { +pub fn query_balance(app: &AxelarApp, address: &Addr) -> Uint128 { app.wrap() .query_balance(address, AXL_DENOMINATION) .unwrap() .amount } -pub fn query_balances(app: &App, verifiers: &Vec) -> Vec { +pub fn query_balances(app: &AxelarApp, verifiers: &Vec) -> Vec { let mut balances = Vec::new(); for verifier in verifiers { balances.push(query_balance(app, &verifier.addr)) diff --git a/packages/axelar-core-std/Cargo.toml b/packages/axelar-core-std/Cargo.toml index db316374e..32b822c9c 100644 --- a/packages/axelar-core-std/Cargo.toml +++ b/packages/axelar-core-std/Cargo.toml @@ -4,6 +4,10 @@ version = "1.0.0" edition = { workspace = true } rust-version = { workspace = true } +[features] +# use this feature to enable test utils +test = [] + [dependencies] axelar-wasm-std = { workspace = true } client = { workspace = true } @@ -16,6 +20,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +assert_ok = { workspace = true } goldie = { workspace = true } hex = { workspace = true } rand = { workspace = true } diff --git a/packages/axelar-core-std/src/lib.rs b/packages/axelar-core-std/src/lib.rs index 480769711..b4079a4ae 100644 --- a/packages/axelar-core-std/src/lib.rs +++ b/packages/axelar-core-std/src/lib.rs @@ -1,2 +1,2 @@ pub mod nexus; -mod query; +pub mod query; diff --git a/packages/axelar-core-std/src/nexus/mod.rs b/packages/axelar-core-std/src/nexus/mod.rs index cb63b97ce..faa47a7cb 100644 --- a/packages/axelar-core-std/src/nexus/mod.rs +++ b/packages/axelar-core-std/src/nexus/mod.rs @@ -1,5 +1,9 @@ use cosmwasm_std::CosmosMsg; use error_stack::ResultExt; +use query::{IsChainRegisteredResponse, QueryMsg}; +use router_api::ChainName; + +use crate::query::AxelarQueryMsg; pub mod execute; pub mod query; @@ -11,6 +15,9 @@ pub enum Error { #[error("failed to query the tx hash and nonce")] QueryTxHashAndNonce, + #[error("failed to query is chain registered")] + QueryIsChainRegistered, + #[error("invalid message id {0}")] InvalidMessageId(String), } @@ -28,24 +35,81 @@ impl<'a> From> for Client<'a> { impl<'a> Client<'a> { pub fn tx_hash_and_nonce(&self) -> Result { self.inner - .query(query::QueryMsg::TxHashAndNonce {}) + .query(QueryMsg::TxHashAndNonce {}) .change_context(Error::QueryTxHashAndNonce) } pub fn route_message(&self, msg: execute::Message) -> CosmosMsg { self.inner.execute(msg) } + + pub fn is_chain_registered(&self, chain: &ChainName) -> Result { + self.inner + .query::( + QueryMsg::IsChainRegistered { + chain: chain.to_string(), + }, + ) + .map(|res| res.is_registered) + .change_context(Error::QueryIsChainRegistered) + } +} + +#[cfg(any(test, feature = "test"))] +pub mod test_utils { + use cosmwasm_std::testing::MockQuerierCustomHandlerResult; + use cosmwasm_std::{ContractResult, SystemResult}; + use serde::de::DeserializeOwned; + use serde_json::json; + + pub fn reply_with_tx_hash_and_nonce( + tx_hash: [u8; 32], + nonce: u64, + ) -> impl Fn(&C) -> MockQuerierCustomHandlerResult + where + C: DeserializeOwned, + { + move |_| { + SystemResult::Ok(ContractResult::Ok( + json!({ + "tx_hash": tx_hash, + "nonce": nonce, + }) + .to_string() + .as_bytes() + .into(), + )) + } + } + + pub fn reply_with_is_chain_registered( + is_registered: bool, + ) -> impl Fn(&C) -> MockQuerierCustomHandlerResult + where + C: DeserializeOwned, + { + move |_| { + SystemResult::Ok(ContractResult::Ok( + json!({ + "is_registered": is_registered + }) + .to_string() + .as_bytes() + .into(), + )) + } + } } #[cfg(test)] mod test { - use cosmwasm_std::testing::{MockQuerier, MockQuerierCustomHandlerResult}; - use cosmwasm_std::{ContractResult, QuerierWrapper, SystemResult}; + use assert_ok::assert_ok; + use cosmwasm_std::testing::MockQuerier; + use cosmwasm_std::QuerierWrapper; use rand::RngCore; - use serde::de::DeserializeOwned; - use serde_json::json; use crate::nexus; + use crate::nexus::test_utils::{reply_with_is_chain_registered, reply_with_tx_hash_and_nonce}; use crate::query::AxelarQueryMsg; #[test] @@ -64,23 +128,15 @@ mod test { .is_ok_and(|response| { response.tx_hash == tx_hash && response.nonce == nonce })); } - fn reply_with_tx_hash_and_nonce( - tx_hash: [u8; 32], - nonce: u64, - ) -> impl Fn(&C) -> MockQuerierCustomHandlerResult - where - C: DeserializeOwned, - { - move |_| { - SystemResult::Ok(ContractResult::Ok( - json!({ - "tx_hash": tx_hash, - "nonce": nonce, - }) - .to_string() - .as_bytes() - .into(), - )) - } + #[test] + fn query_is_chain_registered() { + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(reply_with_is_chain_registered(true)); + + let client: nexus::Client = client::CosmosClient::new(QuerierWrapper::new(&querier)).into(); + + assert!(assert_ok!( + client.is_chain_registered(&"test-chain".parse().unwrap()) + )); } } diff --git a/packages/axelar-core-std/src/nexus/query.rs b/packages/axelar-core-std/src/nexus/query.rs index 1269dcfc4..06b160bc4 100644 --- a/packages/axelar-core-std/src/nexus/query.rs +++ b/packages/axelar-core-std/src/nexus/query.rs @@ -6,10 +6,13 @@ use serde::{Deserialize, Serialize}; #[non_exhaustive] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] -pub(crate) enum QueryMsg { +pub enum QueryMsg { // TxHashAndNonce returns the tx hash and nonce of the current transaction // Note that the empty struct is used to be able to work for Golang TxHashAndNonce {}, + + // IsChainRegistered returns if the chain is already registered in core + IsChainRegistered { chain: String }, } #[derive(Clone, Debug, PartialEq, Deserialize)] @@ -18,6 +21,11 @@ pub struct TxHashAndNonceResponse { pub nonce: u64, } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct IsChainRegisteredResponse { + pub is_registered: bool, +} + impl From for QueryRequest { fn from(msg: QueryMsg) -> QueryRequest { crate::query::AxelarQueryMsg::Nexus(msg).into() diff --git a/packages/axelar-core-std/src/query.rs b/packages/axelar-core-std/src/query.rs index 42b7422bf..0c412fd19 100644 --- a/packages/axelar-core-std/src/query.rs +++ b/packages/axelar-core-std/src/query.rs @@ -6,7 +6,7 @@ use crate::nexus; #[non_exhaustive] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] -pub(crate) enum AxelarQueryMsg { +pub enum AxelarQueryMsg { Nexus(nexus::query::QueryMsg), } diff --git a/packages/router-api/src/error.rs b/packages/router-api/src/error.rs index 4347cfacf..b508ed939 100644 --- a/packages/router-api/src/error.rs +++ b/packages/router-api/src/error.rs @@ -46,4 +46,7 @@ pub enum Error { #[error("store failed saving/loading data")] StoreFailure, + + #[error("failed to query the nexus module")] + Nexus, }