From 508ee5ca2128ef7b0f16b9b9dc1b7ca7a4f8fc1f Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:15:39 -0400 Subject: [PATCH 01/27] feat(minor-multisig-prover)!: idempotent resigning of workerset updates (#327) * feat(minor-multisig-prover)!: idempotent resigning of workerset updates * allow update workerset to be called multiple times for the same workerset update * reuse the same created_at nonce when resigning --- contracts/multisig-prover/src/execute.rs | 59 +++++++-- integration-tests/tests/test_utils/mod.rs | 11 +- integration-tests/tests/update_worker_set.rs | 120 ++++++++++++++++++- 3 files changed, 176 insertions(+), 14 deletions(-) diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/execute.rs index f017afb23..cb9745957 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/execute.rs @@ -161,6 +161,10 @@ fn get_next_worker_set( env: &Env, config: &Config, ) -> Result, ContractError> { + // if there's already a pending worker set update, just return it + if let Some(pending_worker_set) = NEXT_WORKER_SET.may_load(deps.storage)? { + return Ok(Some(pending_worker_set)); + } let cur_worker_set = CURRENT_WORKER_SET.may_load(deps.storage)?; let new_worker_set = make_worker_set(deps, env, config)?; @@ -299,12 +303,10 @@ fn signers_difference_count(s1: &BTreeMap, s2: &BTreeMap bool { if let Ok(Some(next_worker_set)) = NEXT_WORKER_SET.may_load(storage) { - return next_worker_set.signers != new_worker_set.signers - || next_worker_set.threshold != new_worker_set.threshold; + return next_worker_set != *new_worker_set; } false @@ -312,12 +314,21 @@ fn different_set_in_progress(storage: &dyn Storage, new_worker_set: &WorkerSet) #[cfg(test)] mod tests { - use cosmwasm_std::testing::mock_dependencies; + use axelar_wasm_std::Threshold; + use connection_router_api::ChainName; + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Addr, Uint256, + }; - use crate::{execute::should_update_worker_set, state::NEXT_WORKER_SET, test::test_data}; + use crate::{ + execute::should_update_worker_set, + state::{Config, NEXT_WORKER_SET}, + test::test_data, + }; use std::collections::BTreeMap; - use super::different_set_in_progress; + use super::{different_set_in_progress, get_next_worker_set}; #[test] fn should_update_worker_set_no_change() { @@ -373,7 +384,7 @@ mod tests { } #[test] - fn test_same_set_pending_confirmation() { + fn test_same_set_different_nonce() { let mut deps = mock_dependencies(); let mut new_worker_set = test_data::new_worker_set(); @@ -383,7 +394,7 @@ mod tests { new_worker_set.created_at += 1; - assert!(!different_set_in_progress( + assert!(different_set_in_progress( deps.as_ref().storage, &new_worker_set )); @@ -405,4 +416,34 @@ mod tests { &new_worker_set )); } + + #[test] + fn get_next_worker_set_should_return_pending() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let new_worker_set = test_data::new_worker_set(); + NEXT_WORKER_SET + .save(deps.as_mut().storage, &new_worker_set) + .unwrap(); + let ret_worker_set = get_next_worker_set(&deps.as_mut(), &env, &mock_config()); + assert_eq!(ret_worker_set.unwrap().unwrap(), new_worker_set); + } + + fn mock_config() -> Config { + Config { + admin: Addr::unchecked("doesn't matter"), + governance: Addr::unchecked("doesn't matter"), + gateway: Addr::unchecked("doesn't matter"), + multisig: Addr::unchecked("doesn't matter"), + service_registry: Addr::unchecked("doesn't matter"), + voting_verifier: Addr::unchecked("doesn't matter"), + destination_chain_id: Uint256::one(), + signing_threshold: Threshold::try_from((2, 3)).unwrap().try_into().unwrap(), + service_name: "validators".to_string(), + chain_name: ChainName::try_from("ethereum".to_owned()).unwrap(), + worker_set_diff_threshold: 0, + encoder: crate::encoding::Encoder::Abi, + key_type: multisig::key::KeyType::Ecdsa, + } + } } diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 0282ff4e9..ef7849edc 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -148,14 +148,17 @@ pub fn construct_proof_and_sign( sign_proof(protocol, workers, response.unwrap()) } +pub fn get_multisig_session_id(response: AppResponse) -> Uint64 { + get_event_attribute(&response.events, "wasm-signing_started", "session_id") + .map(|attr| attr.value.as_str().try_into().unwrap()) + .expect("couldn't get session_id") +} + pub fn sign_proof(protocol: &mut Protocol, workers: &Vec, response: AppResponse) -> Uint64 { let msg_to_sign = get_event_attribute(&response.events, "wasm-signing_started", "msg") .map(|attr| attr.value.clone()) .expect("couldn't find message to sign"); - let session_id: Uint64 = - get_event_attribute(&response.events, "wasm-signing_started", "session_id") - .map(|attr| attr.value.as_str().try_into().unwrap()) - .expect("couldn't get session_id"); + let session_id = get_multisig_session_id(response); for worker in workers { let signature = tofn::ecdsa::sign( diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index c63199b2f..b2f26dbe0 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -4,6 +4,8 @@ use cw_multi_test::Executor; use integration_tests::contract::Contract; use test_utils::Worker; +use crate::test_utils::get_multisig_session_id; + pub mod test_utils; #[test] @@ -115,6 +117,9 @@ fn worker_set_cannot_be_updated_again_while_pending_worker_is_not_yet_confirmed( vec![("worker3".to_string(), 2), ("worker4".to_string(), 3)], ); + let expected_new_worker_set = + test_utils::workers_to_worker_set(&mut protocol, &first_wave_of_new_workers); + test_utils::register_workers(&mut protocol, &first_wave_of_new_workers, min_worker_bond); // Deregister old workers @@ -158,10 +163,123 @@ fn worker_set_cannot_be_updated_again_while_pending_worker_is_not_yet_confirmed( // Deregister old workers test_utils::deregister_workers(&mut protocol, &first_wave_of_new_workers); + // call update_worker_set again. This should just trigger resigning for the initial worker set update, + // ignoring any further changes to the worker set let response = ethereum.multisig_prover.execute( &mut protocol.app, - Addr::unchecked("relayer"), + ethereum.multisig_prover.admin_addr.clone(), &multisig_prover::msg::ExecuteMsg::UpdateWorkerSet, ); + assert!(response.is_ok()); + + test_utils::confirm_worker_set( + &mut protocol.app, + ethereum.multisig_prover.admin_addr.clone(), + ðereum.multisig_prover, + ); + + let new_worker_set = test_utils::get_worker_set(&mut protocol.app, ðereum.multisig_prover); + + assert_eq!(new_worker_set, expected_new_worker_set); + + // starting and ending a poll for the second worker rotation + // in reality, this shouldn't succeed, because the prover should have prevented another rotation while an existing rotation was in progress. + // But even if there is a poll, the prover should ignore it + test_utils::execute_worker_set_poll( + &mut protocol, + &Addr::unchecked("relayer"), + ðereum.voting_verifier, + &second_wave_of_new_workers, + ); + + let response = ethereum.multisig_prover.execute( + &mut protocol.app, + ethereum.multisig_prover.admin_addr.clone(), + &multisig_prover::msg::ExecuteMsg::ConfirmWorkerSet, + ); assert!(response.is_err()); } + +#[test] +fn worker_set_update_can_be_resigned() { + let chains = vec![ + "Ethereum".to_string().try_into().unwrap(), + "Polygon".to_string().try_into().unwrap(), + ]; + let (mut protocol, ethereum, _, initial_workers, min_worker_bond) = + test_utils::setup_test_case(); + + let simulated_worker_set = test_utils::workers_to_worker_set(&mut protocol, &initial_workers); + + let worker_set = test_utils::get_worker_set(&mut protocol.app, ðereum.multisig_prover); + + assert_eq!(worker_set, simulated_worker_set); + + // creating a new worker set that only consists of two new workers + let first_wave_of_new_workers = test_utils::create_new_workers_vec( + chains.clone(), + vec![("worker3".to_string(), 2), ("worker4".to_string(), 3)], + ); + + test_utils::register_workers(&mut protocol, &first_wave_of_new_workers, min_worker_bond); + + // Deregister old workers + test_utils::deregister_workers(&mut protocol, &initial_workers); + + let response = protocol + .app + .execute_contract( + ethereum.multisig_prover.admin_addr.clone(), + ethereum.multisig_prover.contract_addr.clone(), + &multisig_prover::msg::ExecuteMsg::UpdateWorkerSet, + &[], + ) + .unwrap(); + + let first_session_id = get_multisig_session_id(response.clone()); + + // signing didn't occur, trigger signing again + let response = protocol + .app + .execute_contract( + ethereum.multisig_prover.admin_addr.clone(), + ethereum.multisig_prover.contract_addr.clone(), + &multisig_prover::msg::ExecuteMsg::UpdateWorkerSet, + &[], + ) + .unwrap(); + + let second_session_id = get_multisig_session_id(response.clone()); + assert_ne!(first_session_id, second_session_id); + + test_utils::sign_proof(&mut protocol, &initial_workers, response); + + // signing did occur, trigger signing again (in case proof was lost) + let response = protocol + .app + .execute_contract( + ethereum.multisig_prover.admin_addr.clone(), + ethereum.multisig_prover.contract_addr.clone(), + &multisig_prover::msg::ExecuteMsg::UpdateWorkerSet, + &[], + ) + .unwrap(); + + let third_session_id = get_multisig_session_id(response.clone()); + assert_ne!(first_session_id, second_session_id); + assert_ne!(second_session_id, third_session_id); + + test_utils::sign_proof(&mut protocol, &initial_workers, response); + + let proof = test_utils::get_proof( + &mut protocol.app, + ðereum.multisig_prover, + &second_session_id, + ); + + // proof must be completed + assert!(matches!( + proof.status, + multisig_prover::msg::ProofStatus::Completed { .. } + )); +} From d72e0dd069caaaa52348224a1a61949eb8577140 Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Tue, 2 Apr 2024 11:49:56 -0400 Subject: [PATCH 02/27] docs: add explanatory comment to sensible range of unbonding period (#329) --- contracts/service-registry/src/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index abf101796..bf7373b6e 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -25,6 +25,8 @@ pub struct Service { pub max_num_workers: Option, pub min_worker_bond: Uint128, pub bond_denom: String, + // should be set to a duration longer than the voting period for governance proposals, + // otherwise a verifier could bail before they get penalized pub unbonding_period_days: u16, pub description: String, } From 53624c51ffec58f83d966117a781f5ace8e2bdc1 Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Tue, 2 Apr 2024 12:59:40 -0400 Subject: [PATCH 03/27] chore: include monitoring contract into release pipeline (#330) --- .github/workflows/release.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b07e12117..ca54b0576 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,6 +17,7 @@ on: - rewards - service-registry - voting-verifier + - monitoring dry-run: description: Dry run type: boolean @@ -48,6 +49,7 @@ jobs: ["rewards"]="rewards,/(major-rewards)|(major-contracts)/,/(minor-rewards)|(minor-contracts)/,contracts/rewards packages" ["service-registry"]="service-registry,/(major-service-registry)|(major-contracts)/,/(minor-service-registry)|(minor-contracts)/,contracts/service-registry packages" ["voting-verifier"]="voting-verifier,/(major-voting-verifier)|(major-contracts)/,/(minor-voting-verifier)|(minor-contracts)/,contracts/voting-verifier packages" + ["monitoring"]="monitoring,/(major-monitoring)|(major-contracts)/,/(minor-monitoring)|(minor-contracts)/,contracts/monitoring packages" ) if [[ -n "${binaries_data[$binary]}" ]]; then From c3703d54137351a689e290e6dcc79ced052b39a8 Mon Sep 17 00:00:00 2001 From: Houmaan Chamani Date: Tue, 2 Apr 2024 16:29:48 -0400 Subject: [PATCH 04/27] feat: register chain and prover contract with monitoring (#325) * feat: add CONTRACTS_PER_CHAIN map and execution message * test: add test_instantiation to monitoring * fix: remove redundant error types, address clippy lint issues * refactor: change scope to only include prover, add tests * fix: rollback changes made to rewards contract * fix: cargo formatting for rewards contract * refactor: address PR open conversations * refactor: add more description to Unauthorized and NoProversRegisteredForChain errors --- Cargo.lock | 1 + contracts/monitoring/Cargo.toml | 1 + contracts/monitoring/src/contract.rs | 114 +++++++++++++++++++++++++-- contracts/monitoring/src/error.rs | 16 ++++ contracts/monitoring/src/execute.rs | 28 +++++++ contracts/monitoring/src/lib.rs | 2 + contracts/monitoring/src/msg.rs | 9 ++- contracts/monitoring/src/query.rs | 10 ++- contracts/monitoring/src/state.rs | 8 +- 9 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 contracts/monitoring/src/error.rs create mode 100644 contracts/monitoring/src/execute.rs diff --git a/Cargo.lock b/Cargo.lock index 6bd497e37..6ac673475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4347,6 +4347,7 @@ dependencies = [ "integration-tests", "multisig", "report", + "thiserror", ] [[package]] diff --git a/contracts/monitoring/Cargo.toml b/contracts/monitoring/Cargo.toml index e27833fd7..389b1af16 100644 --- a/contracts/monitoring/Cargo.toml +++ b/contracts/monitoring/Cargo.toml @@ -43,6 +43,7 @@ cw-storage-plus = { workspace = true } error-stack = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] cw-multi-test = "0.15.1" diff --git a/contracts/monitoring/src/contract.rs b/contracts/monitoring/src/contract.rs index fce2ae13f..45244baf0 100644 --- a/contracts/monitoring/src/contract.rs +++ b/contracts/monitoring/src/contract.rs @@ -4,6 +4,8 @@ use crate::state::{Config, CONFIG}; use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use crate::execute; + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -21,14 +23,22 @@ pub fn instantiate( } #[cfg_attr(not(feature = "library"), entry_point)] -#[allow(dead_code)] pub fn execute( - _deps: DepsMut, + deps: DepsMut, _env: Env, - _info: MessageInfo, - _msg: ExecuteMsg, + info: MessageInfo, + msg: ExecuteMsg, ) -> Result { - Ok(Response::new()) + match msg { + ExecuteMsg::RegisterProverContract { + chain_name, + new_prover_addr, + } => { + execute::check_governance(&deps, info)?; + execute::register_prover(deps, chain_name, new_prover_addr) + } + } + .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -40,3 +50,97 @@ pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } } + +#[cfg(test)] +mod tests { + use crate::error::ContractError; + use crate::query; + use connection_router_api::ChainName; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + + use super::*; + + #[test] + #[allow(clippy::arithmetic_side_effects)] + fn test_instantiation() { + let governance = "governance_for_monitoring"; + let mut deps = mock_dependencies(); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + + let res = instantiate( + deps.as_mut(), + env, + info, + InstantiateMsg { + governance_address: governance.to_string(), + }, + ); + assert!(res.is_ok()); + + let config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!(config.governance, governance); + } + + #[test] + fn add_prover_from_goverance_succeeds() { + let governance = "governance_for_monitoring"; + let mut deps = mock_dependencies(); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + + let _ = instantiate( + deps.as_mut(), + env.clone(), + info, + InstantiateMsg { + governance_address: governance.to_string(), + }, + ); + + let eth_prover = Addr::unchecked("eth_prover"); + let eth: ChainName = "Ethereum".to_string().try_into().unwrap(); + let msg = ExecuteMsg::RegisterProverContract { + chain_name: eth.clone(), + new_prover_addr: eth_prover.clone(), + }; + let _res = execute(deps.as_mut(), mock_env(), mock_info(governance, &[]), msg).unwrap(); + let chain_provers = query::provers(deps.as_ref(), eth.clone()).unwrap(); + assert_eq!(chain_provers, vec![eth_prover]); + } + + #[test] + fn add_prover_from_random_address_fails() { + let governance = "governance_for_monitoring"; + let mut deps = mock_dependencies(); + let info = mock_info("instantiator", &[]); + let env = mock_env(); + + let _ = instantiate( + deps.as_mut(), + env.clone(), + info, + InstantiateMsg { + governance_address: governance.to_string(), + }, + ); + + let eth_prover = Addr::unchecked("eth_prover"); + let eth: ChainName = "Ethereum".to_string().try_into().unwrap(); + let msg = ExecuteMsg::RegisterProverContract { + chain_name: eth.clone(), + new_prover_addr: eth_prover.clone(), + }; + let res = execute( + deps.as_mut(), + env.clone(), + mock_info("random_address", &[]), + msg, + ); + assert_eq!( + res.unwrap_err().to_string(), + axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + ); + } +} diff --git a/contracts/monitoring/src/error.rs b/contracts/monitoring/src/error.rs new file mode 100644 index 000000000..5f76464aa --- /dev/null +++ b/contracts/monitoring/src/error.rs @@ -0,0 +1,16 @@ +use axelar_wasm_std_derive::IntoContractError; +use connection_router_api::ChainName; +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, IntoContractError)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("caller not unauthorized to perform this action")] + Unauthorized, + + #[error("no provers registered for chain {0}")] + NoProversRegisteredForChain(ChainName), +} diff --git a/contracts/monitoring/src/execute.rs b/contracts/monitoring/src/execute.rs new file mode 100644 index 000000000..9232f8594 --- /dev/null +++ b/contracts/monitoring/src/execute.rs @@ -0,0 +1,28 @@ +use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; + +use connection_router_api::ChainName; + +use crate::error::ContractError; +use crate::state::{CONFIG, PROVERS_PER_CHAIN}; + +pub fn check_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { + let config = CONFIG.load(deps.storage)?; + if config.governance != info.sender { + return Err(ContractError::Unauthorized); + } + Ok(()) +} + +pub fn register_prover( + deps: DepsMut, + chain_name: ChainName, + new_prover_addr: Addr, +) -> Result { + let existing_provers = PROVERS_PER_CHAIN.may_load(deps.storage, chain_name.clone())?; + let mut provers = existing_provers.unwrap_or_else(Vec::new); + + provers.push(new_prover_addr.clone()); + + PROVERS_PER_CHAIN.save(deps.storage, chain_name.clone(), &(provers))?; + Ok(Response::new()) +} diff --git a/contracts/monitoring/src/lib.rs b/contracts/monitoring/src/lib.rs index 03a7fc2be..b1acd54e1 100644 --- a/contracts/monitoring/src/lib.rs +++ b/contracts/monitoring/src/lib.rs @@ -1,4 +1,6 @@ pub mod contract; +pub mod error; +pub mod execute; pub mod msg; pub mod query; pub mod state; diff --git a/contracts/monitoring/src/msg.rs b/contracts/monitoring/src/msg.rs index 34fbce28b..01da95554 100644 --- a/contracts/monitoring/src/msg.rs +++ b/contracts/monitoring/src/msg.rs @@ -1,5 +1,6 @@ use connection_router_api::ChainName; use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; use multisig::worker_set::WorkerSet; #[cw_serde] @@ -8,7 +9,13 @@ pub struct InstantiateMsg { } #[cw_serde] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + // Can only be called by governance + RegisterProverContract { + chain_name: ChainName, + new_prover_addr: Addr, + }, +} #[cw_serde] #[derive(QueryResponses)] diff --git a/contracts/monitoring/src/query.rs b/contracts/monitoring/src/query.rs index 0f712b602..a498650f1 100644 --- a/contracts/monitoring/src/query.rs +++ b/contracts/monitoring/src/query.rs @@ -1,5 +1,7 @@ +use crate::error::ContractError; +use crate::state::PROVERS_PER_CHAIN; use connection_router_api::ChainName; -use cosmwasm_std::Deps; +use cosmwasm_std::{Addr, Deps}; use multisig::worker_set::WorkerSet; pub fn chains_active_worker_sets( @@ -8,3 +10,9 @@ pub fn chains_active_worker_sets( ) -> Vec<(ChainName, WorkerSet)> { todo!() } + +pub fn provers(deps: Deps, chain_name: ChainName) -> Result, ContractError> { + PROVERS_PER_CHAIN + .may_load(deps.storage, chain_name.clone())? + .ok_or(ContractError::NoProversRegisteredForChain(chain_name)) +} diff --git a/contracts/monitoring/src/state.rs b/contracts/monitoring/src/state.rs index 38a06b742..c8b9bd584 100644 --- a/contracts/monitoring/src/state.rs +++ b/contracts/monitoring/src/state.rs @@ -1,9 +1,15 @@ +use connection_router_api::ChainName; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use cw_storage_plus::Item; +use cw_storage_plus::{Item, Map}; #[cw_serde] pub struct Config { pub governance: Addr, } + pub const CONFIG: Item = Item::new("config"); + +type ProverAddresses = Vec; +// maps chain name to prover addresses +pub const PROVERS_PER_CHAIN: Map = Map::new("provers_per_chain"); From 2a7fb8b64e08989953637e1b30f2fc441f94ae73 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:41:07 -0400 Subject: [PATCH 05/27] feat(minor-voting-verifier)!: allow dynamic updates of voting threshold (#332) --- contracts/voting-verifier/src/contract.rs | 311 +++++++++++++++--- contracts/voting-verifier/src/error.rs | 3 + contracts/voting-verifier/src/execute.rs | 73 +++- contracts/voting-verifier/src/msg.rs | 12 +- contracts/voting-verifier/src/query.rs | 8 +- contracts/voting-verifier/src/state.rs | 1 + .../src/voting_verifier_contract.rs | 21 +- integration-tests/tests/test_utils/mod.rs | 10 +- 8 files changed, 375 insertions(+), 64 deletions(-) diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 0a1a72498..55ea00114 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -16,6 +16,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { let config = Config { + governance: deps.api.addr_validate(&msg.governance_address)?, service_name: msg.service_name, service_registry_contract: deps.api.addr_validate(&msg.service_registry_address)?, source_gateway_address: msg.source_gateway_address, @@ -46,6 +47,12 @@ pub fn execute( message_id, new_operators, } => execute::verify_worker_set(deps, env, message_id, new_operators), + ExecuteMsg::UpdateVotingThreshold { + new_voting_threshold, + } => { + execute::require_governance(&deps, info.sender)?; + execute::update_voting_threshold(deps, new_voting_threshold) + } } .map_err(axelar_wasm_std::ContractError::from) } @@ -63,19 +70,22 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetWorkerSetStatus { new_operators } => { to_json_binary(&query::worker_set_status(deps, &new_operators)?) } + QueryMsg::GetCurrentThreshold => to_json_binary(&query::voting_threshold(deps)?), } } #[cfg(test)] mod test { + use cosmwasm_std::{ from_json, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, Empty, OwnedDeps, Uint128, Uint64, WasmQuery, + Addr, Empty, Fraction, OwnedDeps, Uint128, Uint64, WasmQuery, }; use axelar_wasm_std::{ - nonempty, operators::Operators, voting::Vote, Threshold, VerificationStatus, + nonempty, operators::Operators, voting::Vote, MajorityThreshold, Threshold, + VerificationStatus, }; use connection_router_api::{ChainName, CrossChainId, Message}; use service_registry::state::{ @@ -95,11 +105,20 @@ mod test { const REWARDS_ADDRESS: &str = "rewards_address"; const SERVICE_NAME: &str = "service_name"; const POLL_BLOCK_EXPIRY: u64 = 100; + const GOVERNANCE: &str = "governance"; fn source_chain() -> ChainName { "source-chain".parse().unwrap() } + fn governance() -> Addr { + Addr::unchecked(GOVERNANCE) + } + + fn initial_voting_threshold() -> MajorityThreshold { + Threshold::try_from((2, 3)).unwrap().try_into().unwrap() + } + fn assert_contract_err_strings_equal( actual: impl Into, expected: impl Into, @@ -107,38 +126,30 @@ mod test { assert_eq!(actual.into().to_string(), expected.into().to_string()); } - fn workers() -> Vec { - vec![ - Worker { - address: Addr::unchecked("addr1"), + fn workers(num_workers: usize) -> Vec { + let mut workers = vec![]; + for i in 0..num_workers { + workers.push(Worker { + address: Addr::unchecked(format!("addr{}", i)), bonding_state: BondingState::Bonded { amount: Uint128::from(100u128), }, authorization_state: AuthorizationState::Authorized, service_name: SERVICE_NAME.parse().unwrap(), - }, - Worker { - address: Addr::unchecked("addr2"), - bonding_state: BondingState::Bonded { - amount: Uint128::from(100u128), - }, - authorization_state: AuthorizationState::Authorized, - service_name: SERVICE_NAME.parse().unwrap(), - }, - ] + }) + } + workers } - fn setup() -> OwnedDeps { + fn setup(workers: Vec) -> OwnedDeps { let mut deps = mock_dependencies(); let config = Config { + governance: governance(), service_name: SERVICE_NAME.parse().unwrap(), service_registry_contract: Addr::unchecked(SERVICE_REGISTRY_ADDRESS), source_gateway_address: "source_gateway_address".parse().unwrap(), - voting_threshold: Threshold::try_from((2u64, 3u64)) - .unwrap() - .try_into() - .unwrap(), + voting_threshold: initial_voting_threshold(), block_expiry: POLL_BLOCK_EXPIRY, confirmation_height: 100, source_chain: source_chain(), @@ -146,10 +157,11 @@ mod test { }; CONFIG.save(deps.as_mut().storage, &config).unwrap(); - deps.querier.update_wasm(|wq| match wq { + deps.querier.update_wasm(move |wq| match wq { WasmQuery::Smart { contract_addr, .. } if contract_addr == SERVICE_REGISTRY_ADDRESS => { Ok(to_json_binary( - &workers() + &workers + .clone() .into_iter() .map(|w| WeightedWorker { worker_info: w, @@ -196,7 +208,8 @@ mod test { #[test] fn should_fail_if_messages_are_not_from_same_source() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let msg = ExecuteMsg::VerifyMessages { messages: vec![ @@ -228,7 +241,8 @@ mod test { #[test] fn should_verify_messages_if_not_verified() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let msg = ExecuteMsg::VerifyMessages { messages: messages(2), @@ -260,7 +274,8 @@ mod test { #[test] fn should_not_verify_messages_if_in_progress() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let messages_in_progress = 3; let new_messages = 2; @@ -307,7 +322,8 @@ mod test { #[test] fn should_retry_if_message_not_verified() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let msg = ExecuteMsg::VerifyMessages { messages: messages(1), @@ -356,7 +372,8 @@ mod test { #[test] fn should_retry_if_status_not_final() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let messages = messages(4); @@ -376,7 +393,7 @@ mod test { // 2. Workers cast votes, but only reach consensus on the first three messages - workers().iter().enumerate().for_each(|(i, worker)| { + workers.iter().enumerate().for_each(|(i, worker)| { let msg = ExecuteMsg::Vote { poll_id: 1u64.into(), votes: vec![ @@ -480,7 +497,8 @@ mod test { #[test] fn should_query_message_statuses() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let messages = messages(10); let msg = ExecuteMsg::VerifyMessages { @@ -532,7 +550,7 @@ mod test { .collect::>(), }; - workers().iter().for_each(|worker| { + workers.iter().for_each(|worker| { execute( deps.as_mut(), mock_env(), @@ -585,7 +603,8 @@ mod test { #[test] fn should_start_worker_set_confirmation() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); let msg = ExecuteMsg::VerifyWorkerSet { @@ -611,7 +630,8 @@ mod test { #[test] fn should_confirm_worker_set() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); let msg = ExecuteMsg::VerifyWorkerSet { @@ -625,7 +645,7 @@ mod test { poll_id: 1u64.into(), votes: vec![Vote::SucceededOnChain], }; - for worker in workers() { + for worker in workers { let res = execute( deps.as_mut(), mock_env(), @@ -661,7 +681,8 @@ mod test { #[test] fn should_not_confirm_worker_set() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); let res = execute( @@ -675,7 +696,7 @@ mod test { ); assert!(res.is_ok()); - for worker in workers() { + for worker in workers { let res = execute( deps.as_mut(), mock_env(), @@ -714,9 +735,8 @@ mod test { #[test] fn should_confirm_worker_set_after_failed() { - let mut deps = setup(); - - let workers = workers(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); let res = execute( @@ -816,7 +836,8 @@ mod test { #[test] fn should_not_confirm_twice() { - let mut deps = setup(); + let workers = workers(2); + let mut deps = setup(workers.clone()); let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); let res = execute( @@ -829,8 +850,7 @@ mod test { }, ); assert!(res.is_ok()); - - for worker in workers() { + for worker in workers { let res = execute( deps.as_mut(), mock_env(), @@ -866,4 +886,211 @@ mod test { .unwrap_err(); assert_contract_err_strings_equal(err, ContractError::WorkerSetAlreadyConfirmed); } + + #[test] + fn should_be_able_to_update_threshold_and_then_query_new_threshold() { + let workers = workers(2); + let mut deps = setup(workers.clone()); + + let new_voting_threshold: MajorityThreshold = Threshold::try_from(( + initial_voting_threshold().numerator().u64() + 1, + initial_voting_threshold().denominator().u64() + 1, + )) + .unwrap() + .try_into() + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE, &[]), + ExecuteMsg::UpdateVotingThreshold { + new_voting_threshold, + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCurrentThreshold).unwrap(); + + let threshold: MajorityThreshold = from_json(res).unwrap(); + assert_eq!(threshold, new_voting_threshold); + } + + #[test] + fn threshold_changes_should_not_affect_existing_polls() { + let workers = workers(10); + let initial_threshold = initial_voting_threshold(); + let majority = (workers.len() as u64 * initial_threshold.numerator().u64()) + .div_ceil(initial_threshold.denominator().u64()); + + let mut deps = setup(workers.clone()); + + let messages = messages(1); + + execute( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[]), + ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }, + ) + .unwrap(); + + // simulate a majority of workers voting for succeeded on chain + workers.iter().enumerate().for_each(|(i, worker)| { + if i >= majority as usize { + return; + } + let msg = ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::SucceededOnChain], + }; + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(worker.address.as_str(), &[]), + msg, + ); + assert!(res.is_ok()); + }); + + // increase the threshold. Not enough workers voted to meet the new majority, + // but threshold changes should not affect existing polls + let new_voting_threshold: MajorityThreshold = + Threshold::try_from((majority + 1, workers.len() as u64)) + .unwrap() + .try_into() + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE, &[]), + ExecuteMsg::UpdateVotingThreshold { + new_voting_threshold, + }, + ) + .unwrap(); + + execute( + deps.as_mut(), + mock_env_expired(), + mock_info(SENDER, &[]), + ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }, + ) + .unwrap(); + + let res: Vec<(CrossChainId, VerificationStatus)> = from_json( + query( + deps.as_ref(), + mock_env(), + QueryMsg::GetMessagesStatus { + messages: messages.clone(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + res, + vec![( + messages[0].cc_id.clone(), + VerificationStatus::SucceededOnChain + )] + ); + } + + #[test] + fn threshold_changes_should_affect_new_polls() { + let workers = workers(10); + let initial_threshold = initial_voting_threshold(); + let old_majority = (workers.len() as u64 * initial_threshold.numerator().u64()) + .div_ceil(initial_threshold.denominator().u64()); + + let mut deps = setup(workers.clone()); + + // increase the threshold prior to starting a poll + let new_voting_threshold: MajorityThreshold = + Threshold::try_from((old_majority + 1, workers.len() as u64)) + .unwrap() + .try_into() + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE, &[]), + ExecuteMsg::UpdateVotingThreshold { + new_voting_threshold, + }, + ) + .unwrap(); + + let messages = messages(1); + + // start the poll, should just the new threshold + execute( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[]), + ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }, + ) + .unwrap(); + + // simulate old_majority of workers voting succeeded on chain, + // which is one less than the updated majority. The messages + // should not receive enough votes to be considered verified + workers.iter().enumerate().for_each(|(i, worker)| { + if i >= old_majority as usize { + return; + } + let msg = ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::SucceededOnChain], + }; + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(worker.address.as_str(), &[]), + msg, + ); + assert!(res.is_ok()); + }); + + execute( + deps.as_mut(), + mock_env_expired(), + mock_info(SENDER, &[]), + ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }, + ) + .unwrap(); + + let res: Vec<(CrossChainId, VerificationStatus)> = from_json( + query( + deps.as_ref(), + mock_env(), + QueryMsg::GetMessagesStatus { + messages: messages.clone(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + res, + vec![( + messages[0].cc_id.clone(), + VerificationStatus::FailedToVerify + )] + ); + } } diff --git a/contracts/voting-verifier/src/error.rs b/contracts/voting-verifier/src/error.rs index 42d5c3a00..e8c21fe6d 100644 --- a/contracts/voting-verifier/src/error.rs +++ b/contracts/voting-verifier/src/error.rs @@ -39,6 +39,9 @@ pub enum ContractError { #[error("worker set already confirmed")] WorkerSetAlreadyConfirmed, + + #[error("unauthorized")] + Unauthorized, } impl From for StdError { diff --git a/contracts/voting-verifier/src/execute.rs b/contracts/voting-verifier/src/execute.rs index ee07eb564..28659294d 100644 --- a/contracts/voting-verifier/src/execute.rs +++ b/contracts/voting-verifier/src/execute.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - to_json_binary, Deps, DepsMut, Env, MessageInfo, OverflowError, OverflowOperation, + to_json_binary, Addr, Deps, DepsMut, Env, MessageInfo, OverflowError, OverflowOperation, QueryRequest, Response, Storage, WasmMsg, WasmQuery, }; @@ -8,7 +8,7 @@ use axelar_wasm_std::{ operators::Operators, snapshot, voting::{PollId, Vote, WeightedPoll}, - VerificationStatus, + MajorityThreshold, VerificationStatus, }; use connection_router_api::{ChainName, Message}; @@ -23,6 +23,27 @@ use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS}; use crate::state::{CONFIG, POLLS, POLL_ID}; use crate::{error::ContractError, query::message_status}; +// TODO: this type of function exists in many contracts. Would be better to implement this +// in one place, and then just include it +pub fn require_governance(deps: &DepsMut, sender: Addr) -> Result<(), ContractError> { + let config = CONFIG.load(deps.storage)?; + if config.governance != sender { + return Err(ContractError::Unauthorized); + } + Ok(()) +} + +pub fn update_voting_threshold( + deps: DepsMut, + new_voting_threshold: MajorityThreshold, +) -> Result { + CONFIG.update(deps.storage, |mut config| -> Result<_, ContractError> { + config.voting_threshold = new_voting_threshold; + Ok(config) + })?; + Ok(Response::new()) +} + pub fn verify_worker_set( deps: DepsMut, env: Env, @@ -276,3 +297,51 @@ fn calculate_expiration(block_height: u64, block_expiry: u64) -> Result Config { + Config { + governance, + service_registry_contract: Addr::unchecked("doesn't matter"), + service_name: "validators".to_string().try_into().unwrap(), + source_gateway_address: "0x89e51fA8CA5D66cd220bAed62ED01e8951aa7c40" + .to_string() + .try_into() + .unwrap(), + voting_threshold, + source_chain: "ethereum".to_string().try_into().unwrap(), + block_expiry: 10, + confirmation_height: 2, + rewards_contract: Addr::unchecked("rewards"), + } + } + + #[test] + fn require_governance_should_reject_non_governance() { + let mut deps = mock_dependencies(); + let governance = Addr::unchecked("governance"); + CONFIG + .save( + deps.as_mut().storage, + &mock_config( + governance.clone(), + Threshold::try_from((2, 3)).unwrap().try_into().unwrap(), + ), + ) + .unwrap(); + + let res = require_governance(&deps.as_mut(), Addr::unchecked("random")); + assert!(res.is_err()); + + let res = require_governance(&deps.as_mut(), governance); + assert!(res.is_ok()); + } +} diff --git a/contracts/voting-verifier/src/msg.rs b/contracts/voting-verifier/src/msg.rs index c1e228414..32a2f83bc 100644 --- a/contracts/voting-verifier/src/msg.rs +++ b/contracts/voting-verifier/src/msg.rs @@ -10,7 +10,9 @@ use connection_router_api::{ChainName, CrossChainId, Message}; #[cw_serde] pub struct InstantiateMsg { - // params to query register service + pub governance_address: nonempty::String, + + // params to query service_registry pub service_registry_address: nonempty::String, pub service_name: nonempty::String, @@ -47,6 +49,11 @@ pub enum ExecuteMsg { message_id: nonempty::String, new_operators: Operators, }, + + // Update the threshold used for new polls. Callable only by governance + UpdateVotingThreshold { + new_voting_threshold: MajorityThreshold, + }, } #[cw_serde] @@ -66,6 +73,9 @@ pub enum QueryMsg { #[returns(VerificationStatus)] GetWorkerSetStatus { new_operators: Operators }, + + #[returns(MajorityThreshold)] + GetCurrentThreshold, } #[cw_serde] diff --git a/contracts/voting-verifier/src/query.rs b/contracts/voting-verifier/src/query.rs index ca61ccde2..972a2c3c3 100644 --- a/contracts/voting-verifier/src/query.rs +++ b/contracts/voting-verifier/src/query.rs @@ -1,13 +1,17 @@ use axelar_wasm_std::{ operators::Operators, voting::{PollStatus, Vote}, - VerificationStatus, + MajorityThreshold, VerificationStatus, }; use connection_router_api::{CrossChainId, Message}; use cosmwasm_std::Deps; -use crate::error::ContractError; use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS}; +use crate::{error::ContractError, state::CONFIG}; + +pub fn voting_threshold(deps: Deps) -> Result { + Ok(CONFIG.load(deps.storage)?.voting_threshold) +} pub fn messages_status( deps: Deps, diff --git a/contracts/voting-verifier/src/state.rs b/contracts/voting-verifier/src/state.rs index fe6e333d3..42ef1ede9 100644 --- a/contracts/voting-verifier/src/state.rs +++ b/contracts/voting-verifier/src/state.rs @@ -16,6 +16,7 @@ use crate::error::ContractError; #[cw_serde] pub struct Config { + pub governance: Addr, pub service_registry_contract: Addr, pub service_name: nonempty::String, pub source_gateway_address: nonempty::String, diff --git a/integration-tests/src/voting_verifier_contract.rs b/integration-tests/src/voting_verifier_contract.rs index 9d061a23e..31805430c 100644 --- a/integration-tests/src/voting_verifier_contract.rs +++ b/integration-tests/src/voting_verifier_contract.rs @@ -1,9 +1,10 @@ use crate::contract::Contract; +use crate::protocol::Protocol; use axelar_wasm_std::nonempty; use axelar_wasm_std::MajorityThreshold; use connection_router_api::ChainName; use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_multi_test::{ContractWrapper, Executor}; #[derive(Clone)] pub struct VotingVerifierContract { @@ -12,19 +13,17 @@ pub struct VotingVerifierContract { impl VotingVerifierContract { pub fn instantiate_contract( - app: &mut App, - service_registry_address: nonempty::String, - service_name: nonempty::String, + protocol: &mut Protocol, source_gateway_address: nonempty::String, voting_threshold: MajorityThreshold, source_chain: ChainName, - rewards_address: Addr, ) -> Self { let code = ContractWrapper::new( voting_verifier::contract::execute, voting_verifier::contract::instantiate, voting_verifier::contract::query, ); + let app = &mut protocol.app; let code_id = app.store_code(Box::new(code)); let contract_addr = app @@ -32,14 +31,20 @@ impl VotingVerifierContract { code_id, Addr::unchecked("anyone"), &voting_verifier::msg::InstantiateMsg { - service_registry_address, - service_name, + governance_address: protocol.governance_address.to_string().try_into().unwrap(), + service_registry_address: protocol + .service_registry + .contract_addr + .to_string() + .try_into() + .unwrap(), + service_name: protocol.service_name.clone(), source_gateway_address, voting_threshold, block_expiry: 10, confirmation_height: 5, source_chain, - rewards_address: rewards_address.to_string(), + rewards_address: protocol.rewards.contract_addr.to_string(), }, &[], "voting_verifier", diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index ef7849edc..f9a899858 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -596,18 +596,10 @@ pub struct Chain { pub fn setup_chain(protocol: &mut Protocol, chain_name: ChainName) -> Chain { let voting_verifier = VotingVerifierContract::instantiate_contract( - &mut protocol.app, - protocol - .service_registry - .contract_addr - .to_string() - .try_into() - .unwrap(), - protocol.service_name.clone(), + protocol, "doesn't matter".to_string().try_into().unwrap(), Threshold::try_from((9, 10)).unwrap().try_into().unwrap(), chain_name.clone(), - protocol.rewards.contract_addr.clone(), ); let gateway = GatewayContract::instantiate_contract( From e8b1a6b3b103a7080275563d0ae913c899e41f41 Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Wed, 3 Apr 2024 18:00:21 -0400 Subject: [PATCH 06/27] fix: get_outgoing_messages in the gateway returns error if msgs are missing (#328) --- contracts/gateway/src/contract.rs | 3 + contracts/gateway/src/contract/query.rs | 123 +++++++++++++++++++++++- contracts/gateway/tests/contract.rs | 17 ++-- 3 files changed, 130 insertions(+), 13 deletions(-) diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index 1f5a907c7..423ddf19b 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -1,3 +1,4 @@ +use connection_router_api::CrossChainId; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; @@ -52,6 +53,8 @@ pub enum Error { InvalidAddress, #[error("failed to query message status")] MessageStatus, + #[error("message with ID {0} not found")] + MessageNotFound(CrossChainId), } mod internal { diff --git a/contracts/gateway/src/contract/query.rs b/contracts/gateway/src/contract/query.rs index 3a173f13f..ccace2dae 100644 --- a/contracts/gateway/src/contract/query.rs +++ b/contracts/gateway/src/contract/query.rs @@ -1,9 +1,9 @@ +use crate::contract::Error; +use axelar_wasm_std::error::extend_err; use connection_router_api::{CrossChainId, Message}; use cosmwasm_std::Storage; -use error_stack::{Result, ResultExt}; -use itertools::Itertools; +use error_stack::{report, Result, ResultExt}; -use crate::contract::Error; use crate::state; pub fn get_outgoing_messages( @@ -12,7 +12,120 @@ pub fn get_outgoing_messages( ) -> Result, Error> { cross_chain_ids .into_iter() - .filter_map(|id| state::may_load_outgoing_msg(storage, id).transpose()) - .try_collect() + .map(|id| try_load_msg(storage, id)) + .fold(Ok(vec![]), accumulate_errs) +} + +fn try_load_msg(storage: &dyn Storage, id: CrossChainId) -> Result { + state::may_load_outgoing_msg(storage, id.clone()) .change_context(Error::InvalidStoreAccess) + .transpose() + .unwrap_or(Err(report!(Error::MessageNotFound(id)))) +} + +fn accumulate_errs( + acc: Result, Error>, + msg: Result, +) -> Result, Error> { + match (acc, msg) { + (Ok(mut acc), Ok(msg)) => { + acc.push(msg); + Ok(acc) + } + (Err(acc), Ok(_)) => Err(acc), + (acc, Err(msg_err)) => extend_err(acc, msg_err), + } +} + +#[cfg(test)] +mod test { + use crate::state; + use connection_router_api::{CrossChainId, Message}; + use cosmwasm_std::testing::mock_dependencies; + + #[test] + fn get_outgoing_messages_all_messages_present_returns_all() { + let mut deps = mock_dependencies(); + + let messages = generate_messages(); + + for message in messages.iter() { + state::save_outgoing_msg(deps.as_mut().storage, message.cc_id.clone(), message) + .unwrap(); + } + + let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + + let res = super::get_outgoing_messages(&deps.storage, ids).unwrap(); + assert_eq!(res, messages); + } + + #[test] + fn get_outgoing_messages_nothing_stored_returns_not_found_error() { + let deps = mock_dependencies(); + + let messages = generate_messages(); + let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + + let res = super::get_outgoing_messages(&deps.storage, ids); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().current_frames().len(), messages.len()); + } + + #[test] + fn get_outgoing_messages_only_partially_found_returns_not_found_error() { + let mut deps = mock_dependencies(); + + let messages = generate_messages(); + + state::save_outgoing_msg( + deps.as_mut().storage, + messages[1].cc_id.clone(), + &messages[1], + ) + .unwrap(); + + let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + + let res = super::get_outgoing_messages(&deps.storage, ids); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().current_frames().len(), messages.len() - 1); + } + + fn generate_messages() -> Vec { + vec![ + Message { + cc_id: CrossChainId { + chain: "chain1".parse().unwrap(), + id: "id1".parse().unwrap(), + }, + destination_address: "addr1".parse().unwrap(), + destination_chain: "chain2".parse().unwrap(), + source_address: "addr2".parse().unwrap(), + payload_hash: [0; 32], + }, + Message { + cc_id: CrossChainId { + chain: "chain2".parse().unwrap(), + id: "id2".parse().unwrap(), + }, + destination_address: "addr3".parse().unwrap(), + destination_chain: "chain3".parse().unwrap(), + source_address: "addr4".parse().unwrap(), + payload_hash: [1; 32], + }, + Message { + cc_id: CrossChainId { + chain: "chain3".parse().unwrap(), + id: "id3".parse().unwrap(), + }, + destination_address: "addr5".parse().unwrap(), + destination_chain: "chain4".parse().unwrap(), + source_address: "addr6".parse().unwrap(), + payload_hash: [2; 32], + }, + ] + } } diff --git a/contracts/gateway/tests/contract.rs b/contracts/gateway/tests/contract.rs index a20ba1663..1b346988a 100644 --- a/contracts/gateway/tests/contract.rs +++ b/contracts/gateway/tests/contract.rs @@ -142,14 +142,15 @@ fn successful_route_outgoing() { }; // check no messages are outgoing - iter::repeat(query(deps.as_ref(), mock_env(), query_msg.clone()).unwrap()) - .take(2) - .for_each(|response| { - assert_eq!( - response, - to_json_binary::>(&vec![]).unwrap() - ) - }); + let query_response = query(deps.as_ref(), mock_env(), query_msg.clone()); + if msgs.is_empty() { + assert_eq!( + query_response.unwrap(), + to_json_binary::>(&vec![]).unwrap() + ) + } else { + assert!(query_response.is_err()); + } // check routing of outgoing messages is idempotent let response = iter::repeat( From c8862798b487e2f7845e41db5fdaa80850e83020 Mon Sep 17 00:00:00 2001 From: eguajardo Date: Thu, 4 Apr 2024 17:52:17 -0600 Subject: [PATCH 07/27] chore(multisig): test PublicKey is validated to represent a valid point in the curve (#331) * add unit tests * sort dependencies * fixed typos * review * remove unused import * fixed comments --- Cargo.lock | 1 + contracts/multisig/Cargo.toml | 1 + contracts/multisig/src/key.rs | 58 ++++++++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ac673475..07ee35fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4830,6 +4830,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "curve25519-dalek 4.1.1", "cw-multi-test", "cw-storage-plus 1.1.0", "ed25519-dalek", diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index 0dda4b076..4a8a63dbe 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -61,6 +61,7 @@ signature-verifier-api = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +curve25519-dalek = "4.1.1" cw-multi-test = "0.15.1" [lints] diff --git a/contracts/multisig/src/key.rs b/contracts/multisig/src/key.rs index 9804f13f2..f2793e0b9 100644 --- a/contracts/multisig/src/key.rs +++ b/contracts/multisig/src/key.rs @@ -235,7 +235,6 @@ fn validate_and_normalize_public_key( key_type: KeyType, pub_key: HexBinary, ) -> Result { - // TODO: ensure proper validation, since external crates may only check format. match key_type { KeyType::Ecdsa => Ok(k256::PublicKey::from_sec1_bytes( check_ecdsa_format(pub_key)?.as_slice(), @@ -245,6 +244,8 @@ fn validate_and_normalize_public_key( .as_ref() .into()), // TODO: verify encoding scheme is standard + // Function `from_bytes()` will internally decompress into an EdwardsPoint which can only represent a valid point on the curve + // See https://docs.rs/curve25519-dalek/latest/curve25519_dalek/edwards/index.html#validity-checking KeyType::Ed25519 => Ok(ed25519_dalek::VerifyingKey::from_bytes( pub_key .as_slice() @@ -320,8 +321,14 @@ impl From for HexBinary { #[cfg(test)] mod ecdsa_tests { use cosmwasm_std::HexBinary; + use k256::{AffinePoint, EncodedPoint}; - use crate::{key::Signature, test::common::ecdsa_test_data, types::MsgToSign, ContractError}; + use crate::{ + key::{validate_and_normalize_public_key, Signature}, + test::common::ecdsa_test_data, + types::MsgToSign, + ContractError, + }; use super::{KeyType, PublicKey}; @@ -368,6 +375,26 @@ mod ecdsa_tests { ); } + #[test] + fn validate_ecdsa_public_key_not_on_curve() { + // the compressed format is not validated and should produce an invalid point when decompressed + let invalid_compressed_point = EncodedPoint::from_bytes([ + 3, 132, 180, 161, 194, 115, 211, 43, 90, 122, 205, 26, 76, 14, 117, 209, 243, 206, 192, + 34, 107, 93, 142, 13, 50, 95, 115, 188, 140, 82, 194, 140, 235, + ]) + .unwrap(); + + // Assert that the point is invalid according to the crate we are using for ed25519. Both assert statements must match, + // otherwise `validate_and_normalize_public_key` is not doing the same internally as the crate + assert!(AffinePoint::try_from(&invalid_compressed_point).is_err()); + + let result = validate_and_normalize_public_key( + KeyType::Ecdsa, + HexBinary::from(invalid_compressed_point.as_bytes()), + ); + assert_eq!(result.unwrap_err(), ContractError::InvalidPublicKey); + } + #[test] fn test_try_from_hexbinary_to_signature() { let hex = ecdsa_test_data::signature(); @@ -462,8 +489,14 @@ mod ecdsa_tests { #[cfg(test)] mod ed25519_tests { use cosmwasm_std::HexBinary; + use curve25519_dalek::edwards::CompressedEdwardsY; - use crate::{key::Signature, test::common::ed25519_test_data, types::MsgToSign, ContractError}; + use crate::{ + key::{validate_and_normalize_public_key, Signature}, + test::common::ed25519_test_data, + types::MsgToSign, + ContractError, + }; use super::{KeyType, PublicKey}; @@ -499,7 +532,7 @@ mod ed25519_tests { } #[test] - fn test_try_from_hexbinary_to_eccdsa_public_key_fails() { + fn test_try_from_hexbinary_to_ed25519_public_key_fails() { let hex = HexBinary::from_hex("049b").unwrap(); assert_eq!( PublicKey::try_from((KeyType::Ed25519, hex.clone())).unwrap_err(), @@ -507,6 +540,23 @@ mod ed25519_tests { ); } + #[test] + fn validate_ed25519_public_key_not_in_curve() { + // the compressed format is not validated and should produce an invalid point when decompressed + let invalid_compressed_point = CompressedEdwardsY([ + 42, 20, 146, 53, 38, 178, 23, 135, 135, 22, 77, 119, 129, 45, 141, 139, 212, 122, 180, + 97, 171, 0, 127, 226, 248, 13, 108, 227, 223, 188, 26, 121, + ]); + + assert!(invalid_compressed_point.decompress().is_none()); + + let result = validate_and_normalize_public_key( + KeyType::Ed25519, + HexBinary::from(invalid_compressed_point.as_bytes()), + ); + assert_eq!(result.unwrap_err(), ContractError::InvalidPublicKey); + } + #[test] fn test_try_from_hexbinary_to_signature() { let hex = ed25519_test_data::signature(); From d9c76ceb5ace6f9f92de266a824fd0c9269ce7db Mon Sep 17 00:00:00 2001 From: Houmaan Chamani Date: Fri, 5 Apr 2024 12:00:36 -0400 Subject: [PATCH 08/27] feat(service-registry): add chains_per_worker map to service registry (#334) * feat: add CHAINS_PER_WORKER map to service registry * refactor: optimize CHAINS_PER_WORKER update, address PR conversations * refactor: remove extra service registry message, add unit test to state, refactor saving to maps * refactor: use update instead of may_load and save * fix: remove unused import from service registry state.rs * test: add three more unit tests to service registry, refactor variable names --- contracts/service-registry/src/contract.rs | 9 +- contracts/service-registry/src/state.rs | 140 +++++++++++++++++++-- 2 files changed, 137 insertions(+), 12 deletions(-) diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index 19a3d0747..246b2f57b 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -114,9 +114,9 @@ pub fn execute( } pub mod execute { - use connection_router_api::ChainName; - + use crate::state; use crate::state::{AuthorizationState, WORKERS, WORKERS_PER_CHAIN}; + use connection_router_api::ChainName; use super::*; @@ -255,9 +255,8 @@ pub mod execute { .may_load(deps.storage, (&service_name, &info.sender))? .ok_or(ContractError::WorkerNotFound)?; - for chain in chains { - WORKERS_PER_CHAIN.save(deps.storage, (&service_name, &chain, &info.sender), &())?; - } + let _res = + state::register_chains_support(deps.storage, service_name.clone(), chains, info.sender); Ok(Response::new()) } diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index bf7373b6e..2d19b5b92 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -2,8 +2,9 @@ use connection_router_api::ChainName; use cosmwasm_schema::cw_serde; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; -use cosmwasm_std::{Addr, Timestamp, Uint128}; +use cosmwasm_std::{Addr, Storage, Timestamp, Uint128}; use cw_storage_plus::{Item, Map}; #[cw_serde] @@ -130,16 +131,141 @@ pub enum AuthorizationState { Authorized, } -// maps service_name -> Service -pub const SERVICES: Map<&str, Service> = Map::new("services"); -// maps (service_name, chain_name, worker_address) -> () -pub const WORKERS_PER_CHAIN: Map<(&str, &ChainName, &Addr), ()> = Map::new("workers_per_chain"); -// maps (service_name, worker_address) -> Worker -pub const WORKERS: Map<(&str, &Addr), Worker> = Map::new("workers"); +type ChainNames = HashSet; +type ServiceName = str; +type WorkerAddress = Addr; + +pub const SERVICES: Map<&ServiceName, Service> = Map::new("services"); +pub const WORKERS_PER_CHAIN: Map<(&ServiceName, &ChainName, &WorkerAddress), ()> = + Map::new("workers_per_chain"); +pub const CHAINS_PER_WORKER: Map<(&ServiceName, &WorkerAddress), ChainNames> = + Map::new("chains_per_worker"); +pub const WORKERS: Map<(&ServiceName, &WorkerAddress), Worker> = Map::new("workers"); + +pub fn register_chains_support( + storage: &mut dyn Storage, + service_name: String, + chains: Vec, + worker: WorkerAddress, +) -> Result<(), ContractError> { + CHAINS_PER_WORKER.update(storage, (&service_name, &worker), |current_chains| { + let mut current_chains = current_chains.unwrap_or_default(); + current_chains.extend(chains.iter().cloned()); + Ok::, ContractError>(current_chains) + })?; + + for chain in chains.iter() { + WORKERS_PER_CHAIN.save(storage, (&service_name, chain, &worker), &())?; + } + + Ok(()) +} + +pub fn may_load_chains_per_worker( + storage: &dyn Storage, + service_name: String, + worker_address: WorkerAddress, +) -> Result, ContractError> { + CHAINS_PER_WORKER + .may_load(storage, (&service_name, &worker_address))? + .ok_or(ContractError::WorkerNotFound) +} #[cfg(test)] mod tests { use super::*; + use cosmwasm_std::testing::mock_dependencies; + use std::{str::FromStr, vec}; + + #[test] + fn register_single_worker_chain_single_call_success() { + let mut deps = mock_dependencies(); + let worker = Addr::unchecked("worker"); + let service_name = "validators"; + let chain_name = ChainName::from_str("ethereum").unwrap(); + let chains = vec![chain_name.clone()]; + assert!(register_chains_support( + deps.as_mut().storage, + service_name.into(), + chains, + worker.clone() + ) + .is_ok()); + + let worker_chains = + may_load_chains_per_worker(deps.as_mut().storage, service_name.into(), worker).unwrap(); + assert!(worker_chains.contains(&chain_name)); + } + + #[test] + fn register_multiple_worker_chains_single_call_success() { + let mut deps = mock_dependencies(); + let worker = Addr::unchecked("worker"); + let service_name = "validators"; + let chain_names = vec![ + ChainName::from_str("ethereum").unwrap(), + ChainName::from_str("cosmos").unwrap(), + ]; + + assert!(register_chains_support( + deps.as_mut().storage, + service_name.into(), + chain_names.clone(), + worker.clone() + ) + .is_ok()); + + let worker_chains = + may_load_chains_per_worker(deps.as_mut().storage, service_name.into(), worker).unwrap(); + + for chain_name in chain_names { + assert!(worker_chains.contains(&chain_name)); + } + } + + #[test] + fn register_multiple_worker_chains_multiple_calls_success() { + let mut deps = mock_dependencies(); + let worker = Addr::unchecked("worker"); + let service_name = "validators"; + + let first_chain_name = ChainName::from_str("ethereum").unwrap(); + let first_chains_vector = vec![first_chain_name.clone()]; + assert!(register_chains_support( + deps.as_mut().storage, + service_name.into(), + first_chains_vector, + worker.clone() + ) + .is_ok()); + + let second_chain_name = ChainName::from_str("cosmos").unwrap(); + let second_chains_vector = vec![second_chain_name.clone()]; + assert!(register_chains_support( + deps.as_mut().storage, + service_name.into(), + second_chains_vector, + worker.clone() + ) + .is_ok()); + + let worker_chains = + may_load_chains_per_worker(deps.as_mut().storage, service_name.into(), worker).unwrap(); + + assert!(worker_chains.contains(&first_chain_name)); + assert!(worker_chains.contains(&second_chain_name)); + } + + #[test] + fn get_unregistered_worker_chains_fails() { + let mut deps = mock_dependencies(); + let worker = Addr::unchecked("worker"); + let service_name = "validators"; + + let err = may_load_chains_per_worker(deps.as_mut().storage, service_name.into(), worker) + .unwrap_err(); + assert!(matches!(err, ContractError::WorkerNotFound)); + } #[test] fn test_bonded_add_bond() { From d276394fa7aa17cd251ccbc8bb42bba565fa9c5d Mon Sep 17 00:00:00 2001 From: Houmaan Chamani Date: Fri, 5 Apr 2024 12:07:27 -0400 Subject: [PATCH 09/27] feat: compile and push ampd contracts to CF r2 bucket (#335) * feat: add and finish CF r2 workflow file * chore: remove paths from on_push settings of r2 yaml file * refactor: workflow file cleanup for PR review * refactor: add /contracts to r2 destination folder * refactor: add empty line to the end of workflow file --- .../workflows/build-ampd-and-push-to-r2.yaml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/build-ampd-and-push-to-r2.yaml diff --git a/.github/workflows/build-ampd-and-push-to-r2.yaml b/.github/workflows/build-ampd-and-push-to-r2.yaml new file mode 100644 index 000000000..f1cdb6e0a --- /dev/null +++ b/.github/workflows/build-ampd-and-push-to-r2.yaml @@ -0,0 +1,61 @@ +name: Upload ampd contracts to Cloudflare R2 bucket + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + branch: + description: Github branch to checkout for compilation + required: true + default: main + type: string + + +jobs: + compile-and-upload: + name: Compile contracts and push to R2 + runs-on: ubuntu-22.04 + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Determine branch + id: get-branch-name + run: | + if [ "${{ github.event_name }}" == "push" ]; then + branch="main" + else + branch="${{ inputs.branch }}" + fi + echo "branch=$branch" >> $GITHUB_OUTPUT + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: "0" + path: axelar-amplifier + submodules: recursive + ref: ${{ steps.get-branch-name.outputs.branch }} + + - name: Compile amplifier contracts + id: compile-contracts + run: | + cd axelar-amplifier + docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/rust-optimizer:0.15.1 + commit_hash=$(git rev-parse --short HEAD) + cd .. + mkdir -p ./artifacts/$commit_hash/ + sudo mv axelar-amplifier/artifacts/*.wasm ./artifacts/$commit_hash/ + echo "wasm-directory=./artifacts" >> $GITHUB_OUTPUT + + - uses: ryand56/r2-upload-action@latest + with: + r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} + r2-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CF }} + r2-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CF }} + r2-bucket: ${{ secrets.R2_BUCKET }} + source-dir: ${{ steps.compile-contracts.outputs.wasm-directory }} + destination-dir: ./pre-releases/ampd/contracts/ From eae620bda363af1415d86299a62d39ceb849e45b Mon Sep 17 00:00:00 2001 From: Sammy Date: Wed, 10 Apr 2024 13:51:31 +0800 Subject: [PATCH 10/27] feat(ampd): validate finalizer on startup (#319) --- ampd/src/lib.rs | 112 +++++++++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index ae4085418..bb466f12b 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -2,10 +2,13 @@ use std::pin::Pin; use std::time::Duration; use block_height_monitor::BlockHeightMonitor; +use connection_router_api::ChainName; use cosmos_sdk_proto::cosmos::{ auth::v1beta1::query_client::QueryClient, tx::v1beta1::service_client::ServiceClient, }; use error_stack::{report, FutureExt, Result, ResultExt}; +use evm::finalizer::{pick, Finalization}; +use evm::json_rpc::EthereumClient; use thiserror::Error; use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::oneshot; @@ -129,6 +132,23 @@ async fn prepare_app(cfg: Config, state: State) -> Result, block_height_monitor, ) .configure_handlers(worker, handlers, event_stream_timeout) + .await +} + +async fn check_finalizer<'a, C>( + chain_name: &ChainName, + finalization: &Finalization, + rpc_client: &'a C, +) -> Result<(), Error> +where + C: EthereumClient + Send + Sync, +{ + let _ = pick(finalization, rpc_client, 0) + .latest_finalized_block_height() + .await + .change_context_lazy(|| Error::InvalidFinalizerType(chain_name.to_owned()))?; + + Ok(()) } struct App @@ -187,7 +207,7 @@ where } } - fn configure_handlers( + async fn configure_handlers( mut self, worker: TMAddress, handler_configs: Vec, @@ -199,50 +219,62 @@ where chain, cosmwasm_contract, rpc_timeout, - } => self.create_handler_task( - format!("{}-msg-verifier", chain.name), - handlers::evm_verify_msg::Handler::new( - worker.clone(), - cosmwasm_contract, - chain.name, - chain.finalization, - json_rpc::Client::new_http( - &chain.rpc_url, - reqwest::ClientBuilder::new() - .connect_timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) - .timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) - .build() - .change_context(Error::Connection)?, + } => { + let rpc_client = json_rpc::Client::new_http( + &chain.rpc_url, + reqwest::ClientBuilder::new() + .connect_timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .build() + .change_context(Error::Connection)?, + ); + + check_finalizer(&chain.name, &chain.finalization, &rpc_client).await?; + + self.create_handler_task( + format!("{}-msg-verifier", chain.name), + handlers::evm_verify_msg::Handler::new( + worker.clone(), + cosmwasm_contract, + chain.name, + chain.finalization, + rpc_client, + self.broadcaster.client(), + self.block_height_monitor.latest_block_height(), ), - self.broadcaster.client(), - self.block_height_monitor.latest_block_height(), - ), - stream_timeout, - ), + stream_timeout, + ) + } handlers::config::Config::EvmWorkerSetVerifier { chain, cosmwasm_contract, rpc_timeout, - } => self.create_handler_task( - format!("{}-worker-set-verifier", chain.name), - handlers::evm_verify_worker_set::Handler::new( - worker.clone(), - cosmwasm_contract, - chain.name, - chain.finalization, - json_rpc::Client::new_http( - &chain.rpc_url, - reqwest::ClientBuilder::new() - .connect_timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) - .timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) - .build() - .change_context(Error::Connection)?, + } => { + let rpc_client = json_rpc::Client::new_http( + &chain.rpc_url, + reqwest::ClientBuilder::new() + .connect_timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .build() + .change_context(Error::Connection)?, + ); + + check_finalizer(&chain.name, &chain.finalization, &rpc_client).await?; + + self.create_handler_task( + format!("{}-worker-set-verifier", chain.name), + handlers::evm_verify_worker_set::Handler::new( + worker.clone(), + cosmwasm_contract, + chain.name, + chain.finalization, + rpc_client, + self.broadcaster.client(), + self.block_height_monitor.latest_block_height(), ), - self.broadcaster.client(), - self.block_height_monitor.latest_block_height(), - ), - stream_timeout, - ), + stream_timeout, + ) + } handlers::config::Config::MultisigSigner { cosmwasm_contract } => self .create_handler_task( "multisig-signer", @@ -423,4 +455,6 @@ pub enum Error { InvalidInput, #[error("block height monitor failed")] BlockHeightMonitor, + #[error("invalid finalizer type for chain {0}")] + InvalidFinalizerType(ChainName), } From 801b61fc3200d8b2f8b5f5f91812c74f3c9ac54d Mon Sep 17 00:00:00 2001 From: eguajardo Date: Wed, 10 Apr 2024 09:02:24 -0600 Subject: [PATCH 11/27] refactor(connection-router): remove mock contracts from tests (#340) * moved couple of tests to unit tests * moved more tests to contract unit tests * move test to integration-tests module * move test to integration tests module and remove unused files * added comment --- contracts/connection-router/src/contract.rs | 1259 ++++++++++++++++ contracts/connection-router/tests/mock.rs | 88 -- contracts/connection-router/tests/test.rs | 1290 ----------------- .../connection-router/tests/test_utils/mod.rs | 53 - .../tests/chain_freeze_unfreeze.rs | 93 ++ integration-tests/tests/message_routing.rs | 52 +- integration-tests/tests/test_utils/mod.rs | 63 +- 7 files changed, 1465 insertions(+), 1433 deletions(-) delete mode 100644 contracts/connection-router/tests/mock.rs delete mode 100644 contracts/connection-router/tests/test.rs delete mode 100644 contracts/connection-router/tests/test_utils/mod.rs create mode 100644 integration-tests/tests/chain_freeze_unfreeze.rs diff --git a/contracts/connection-router/src/contract.rs b/contracts/connection-router/src/contract.rs index 0d1b44504..b2b282a6a 100644 --- a/contracts/connection-router/src/contract.rs +++ b/contracts/connection-router/src/contract.rs @@ -112,3 +112,1262 @@ pub fn query( } .map_err(axelar_wasm_std::ContractError::from) } + +#[cfg(test)] +mod test { + use std::{collections::HashMap, str::FromStr}; + + use crate::state::CONFIG; + + use super::*; + + use connection_router_api::{ + error::Error, ChainName, CrossChainId, GatewayDirection, Message, CHAIN_NAME_DELIMITER, + }; + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + Addr, CosmosMsg, Empty, OwnedDeps, WasmMsg, + }; + + const ADMIN_ADDRESS: &str = "admin"; + const GOVERNANCE_ADDRESS: &str = "governance"; + const NEXUS_GATEWAY_ADDRESS: &str = "nexus_gateway"; + const UNAUTHORIZED_ADDRESS: &str = "unauthorized"; + + fn setup() -> OwnedDeps { + let mut deps = mock_dependencies(); + + let config = Config { + admin: Addr::unchecked(ADMIN_ADDRESS), + governance: Addr::unchecked(GOVERNANCE_ADDRESS), + nexus_gateway: Addr::unchecked(NEXUS_GATEWAY_ADDRESS), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + + deps + } + + struct Chain { + chain_name: ChainName, + gateway: Addr, + } + + fn make_chain(name: &str) -> Chain { + Chain { + chain_name: name.parse().unwrap(), + gateway: Addr::unchecked(name), + } + } + + fn register_chain(deps: DepsMut, chain: &Chain) { + execute( + deps, + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: chain.chain_name.clone(), + gateway_address: chain.gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap(); + } + + #[allow(clippy::arithmetic_side_effects)] + fn generate_messages( + src_chain: &Chain, + dest_chain: &Chain, + nonce: &mut usize, + count: usize, + ) -> Vec { + let mut msgs = vec![]; + for x in 0..count { + *nonce += 1; + let id = format!("tx_id:{}", nonce); + msgs.push(Message { + cc_id: CrossChainId { + id: id.parse().unwrap(), + chain: src_chain.chain_name.clone(), + }, + destination_address: "idc".parse().unwrap(), + destination_chain: dest_chain.chain_name.clone(), + source_address: "idc".parse().unwrap(), + payload_hash: [x as u8; 32], + }) + } + msgs + } + + pub fn assert_contract_err_strings_equal( + actual: impl Into, + expected: impl Into, + ) { + assert_eq!(actual.into().to_string(), expected.into().to_string()); + } + + pub fn assert_messages_in_cosmos_msg( + contract_addr: String, + messages: Vec, + cosmos_msg: CosmosMsg, + ) { + assert_eq!( + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, + msg: to_json_binary(&gateway_api::msg::ExecuteMsg::RouteMessages(messages,)) + .unwrap(), + funds: vec![], + }), + cosmos_msg + ); + } + + #[test] + fn successful_routing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let nonce: &mut usize = &mut 0; + let messages = generate_messages(ð, &polygon, nonce, 255); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_messages_in_cosmos_msg( + polygon.gateway.to_string(), + messages.clone(), + res.messages[0].msg.clone(), + ); + + // try to route twice + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ); + + assert!(res.is_ok(), "{:?}", res); + } + + #[test] + fn wrong_source_chain() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let messages = generate_messages(ð, &polygon, &mut 0, 1); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages), + ) + .unwrap_err(); + + assert_contract_err_strings_equal(err, Error::WrongSourceChain); + } + + #[test] + fn multi_chain_route() { + let mut deps = setup(); + let chains = vec![ + make_chain("ethereum"), + make_chain("polygon"), + make_chain("osmosis"), + make_chain("avalanche"), + make_chain("moonbeam"), + ]; + for c in &chains { + register_chain(deps.as_mut(), c); + } + + let nonce = &mut 0; + let mut all_msgs_by_dest = HashMap::new(); + let mut all_msgs_by_src = HashMap::new(); + for d in &chains { + let mut msgs = vec![]; + for s in &chains { + let sending = generate_messages(s, d, nonce, 50); + + all_msgs_by_src + .entry(s.chain_name.to_string()) + .or_insert(vec![]) + .append(&mut sending.clone()); + + msgs.append(&mut sending.clone()); + } + all_msgs_by_dest.insert(d.chain_name.to_string(), msgs); + } + + for s in &chains { + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(s.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages( + all_msgs_by_src + .get(&s.chain_name.to_string()) + .unwrap() + .clone(), + ), + ) + .unwrap(); + + assert_eq!(res.messages.len(), chains.len()); + + for (i, d) in chains.iter().enumerate() { + assert_messages_in_cosmos_msg( + d.chain_name.to_string(), + all_msgs_by_dest + .get(&d.chain_name.to_string()) + .unwrap() + .clone() + .into_iter() + .filter(|m| m.cc_id.chain == s.chain_name) + .collect::>(), + res.messages[i].msg.clone(), + ); + } + } + } + + #[test] + fn authorization() { + let mut deps = setup(); + let chain = make_chain("ethereum"); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: chain.chain_name.clone(), + gateway_address: chain.gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: chain.chain_name.clone(), + gateway_address: chain.gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: chain.chain_name.clone(), + gateway_address: chain.gateway.to_string().try_into().unwrap(), + }, + ); + assert!(res.is_ok()); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::None, + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::None, + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: chain.chain_name.clone(), + direction: GatewayDirection::None, + }, + ); + assert!(res.is_ok()); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: chain.chain_name.clone(), + contract_address: Addr::unchecked("new gateway") + .to_string() + .try_into() + .unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: chain.chain_name.clone(), + contract_address: Addr::unchecked("new gateway") + .to_string() + .try_into() + .unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::Unauthorized); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: chain.chain_name.clone(), + contract_address: Addr::unchecked("new gateway") + .to_string() + .try_into() + .unwrap(), + }, + ); + assert!(res.is_ok()); + } + + #[test] + fn upgrade_gateway_outgoing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + let new_gateway = Addr::unchecked("new_gateway"); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: polygon.chain_name.clone(), + contract_address: new_gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap(); + + let messages = &generate_messages(ð, &polygon, &mut 0, 1); + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_messages_in_cosmos_msg( + new_gateway.to_string(), + messages.clone(), + res.messages[0].msg.clone(), + ); + } + + #[test] + fn upgrade_gateway_incoming() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + let new_gateway = Addr::unchecked("new_gateway"); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: polygon.chain_name.clone(), + contract_address: new_gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap(); + + let messages = &generate_messages(&polygon, ð, &mut 0, 1); + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::GatewayNotRegistered); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(new_gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_messages_in_cosmos_msg( + eth.gateway.to_string(), + messages.clone(), + res.messages[0].msg.clone(), + ); + } + + #[test] + fn register_chain_test() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + let message = &generate_messages(ð, &polygon, &mut 0, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::GatewayNotRegistered); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn chain_already_registered() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + register_chain(deps.as_mut(), ð); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: eth.chain_name, + gateway_address: Addr::unchecked("new gateway") + .to_string() + .try_into() + .unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::ChainAlreadyExists); + + // case insensitive + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: ChainName::from_str("ETHEREUM").unwrap(), + gateway_address: Addr::unchecked("new gateway") + .to_string() + .try_into() + .unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::ChainAlreadyExists); + } + + #[test] + fn invalid_chain_name() { + assert_contract_err_strings_equal( + ChainName::from_str(format!("bad{}", CHAIN_NAME_DELIMITER).as_str()).unwrap_err(), + Error::InvalidChainName, + ); + + assert_contract_err_strings_equal( + ChainName::from_str("").unwrap_err(), + Error::InvalidChainName, + ); + } + + #[test] + fn gateway_already_registered() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterChain { + chain: polygon.chain_name.clone(), + gateway_address: eth.gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap_err(); + assert_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); + + register_chain(deps.as_mut(), &polygon); + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::UpgradeGateway { + chain: eth.chain_name, + contract_address: polygon.gateway.to_string().try_into().unwrap(), + }, + ) + .unwrap_err(); + + assert_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); + } + + #[test] + fn freeze_incoming() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + // can't route from frozen incoming gateway + let message = &generate_messages(&polygon, ð, &mut 0, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + let messages = &generate_messages(ð, &polygon, &mut 0, 1); + // can still route to chain + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_messages_in_cosmos_msg( + polygon.gateway.to_string(), + messages.clone(), + res.messages[0].msg.clone(), + ); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ); + assert!(res.is_ok()); + + let message = &generate_messages(&polygon, ð, &mut 0, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn freeze_outgoing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + // freeze outgoing + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ); + assert!(res.is_ok()); + + // can still send to the chain, messages will queue up + let messages = &generate_messages(ð, &polygon, &mut 0, 1); + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ); + assert!(res.is_ok()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_messages_in_cosmos_msg( + polygon.gateway.to_string(), + messages.clone(), + res.messages[0].msg.clone(), + ); + } + + #[test] + fn freeze_chain() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let nonce = &mut 0; + + // route a message first + let routed_msg = &generate_messages(ð, &polygon, nonce, 1)[0]; + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![routed_msg.clone()]), + ) + .unwrap(); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + let msg = &generate_messages(ð, &polygon, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![msg.clone()]), + ) + .unwrap_err(); + // can't route to frozen chain + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // can't route from frozen chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // unfreeze and test that everything works correctly + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ) + .unwrap(); + + // can route to the chain now + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + + // can route from the chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn unfreeze_incoming() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + let nonce = &mut 0; + + // unfreeze incoming + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + // can route from the chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + // can't route to the chain + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + } + + #[test] + fn unfreeze_outgoing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + let nonce = &mut 0; + + // unfreeze outgoing + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ) + .unwrap(); + + // can't route from frozen chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // can route to the chain now + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn freeze_incoming_then_outgoing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ) + .unwrap(); + + let nonce = &mut 0; + // can't route to frozen chain + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // can't route from frozen chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + } + + #[test] + fn freeze_outgoing_then_incoming() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ) + .unwrap(); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + let nonce = &mut 0; + // can't route to frozen chain + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // can't route from frozen chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + } + + #[test] + fn unfreeze_incoming_then_outgoing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + // unfreeze incoming + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + // unfreeze outgoing + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ) + .unwrap(); + + // can route to the chain now + let nonce = &mut 0; + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + + // can route from the chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn unfreeze_outgoing_then_incoming() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + // unfreeze outgoing + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Outgoing, + }, + ) + .unwrap(); + + // unfreeze incoming + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Incoming, + }, + ) + .unwrap(); + + // can route to the chain now + let nonce = &mut 0; + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + + // can route from the chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ); + assert!(res.is_ok()); + } + + #[test] + fn unfreeze_nothing() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::FreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::Bidirectional, + }, + ); + assert!(res.is_ok()); + + // unfreeze nothing + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::UnfreezeChain { + chain: polygon.chain_name.clone(), + direction: GatewayDirection::None, + }, + ) + .unwrap(); + + let nonce = &mut 0; + let message = &generate_messages(ð, &polygon, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + + // can't route from frozen chain + let message = &generate_messages(&polygon, ð, nonce, 1)[0]; + let err = execute( + deps.as_mut(), + mock_env(), + mock_info(polygon.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(vec![message.clone()]), + ) + .unwrap_err(); + assert_contract_err_strings_equal( + err, + Error::ChainFrozen { + chain: polygon.chain_name.clone(), + }, + ); + } +} diff --git a/contracts/connection-router/tests/mock.rs b/contracts/connection-router/tests/mock.rs deleted file mode 100644 index 18fbcb472..000000000 --- a/contracts/connection-router/tests/mock.rs +++ /dev/null @@ -1,88 +0,0 @@ -use connection_router_api::error::Error; -use connection_router_api::{CrossChainId, Message}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; -use cw_multi_test::{App, ContractWrapper, Executor}; -use cw_storage_plus::Map; - -const MOCK_GATEWAY_MESSAGES: Map = Map::new("gateway_messages"); - -#[cw_serde] -pub enum MockGatewayExecuteMsg { - RouteMessages(Vec), -} - -pub fn mock_gateway_execute( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: MockGatewayExecuteMsg, -) -> Result { - match msg { - MockGatewayExecuteMsg::RouteMessages(messages) => { - for m in messages { - MOCK_GATEWAY_MESSAGES.save(deps.storage, m.cc_id.clone(), &m)?; - } - Ok(Response::new()) - } - } -} - -#[cw_serde] -pub enum MockGatewayQueryMsg { - GetOutgoingMessages { ids: Vec }, -} -pub fn mock_gateway_query(deps: Deps, _env: Env, msg: MockGatewayQueryMsg) -> StdResult { - let mut msgs = vec![]; - - match msg { - MockGatewayQueryMsg::GetOutgoingMessages { ids } => { - for id in ids { - if let Some(m) = MOCK_GATEWAY_MESSAGES.may_load(deps.storage, id)? { - msgs.push(m) - } - } - } - } - to_json_binary(&msgs) -} - -pub fn get_gateway_messages( - app: &mut App, - gateway_address: Addr, - msgs: &[Message], -) -> Vec { - app.wrap() - .query_wasm_smart( - gateway_address, - &MockGatewayQueryMsg::GetOutgoingMessages { - ids: msgs.iter().map(|msg| msg.cc_id.clone()).collect(), - }, - ) - .unwrap() -} - -pub fn make_mock_gateway(app: &mut App) -> Addr { - let code = ContractWrapper::new( - mock_gateway_execute, - |_, _, _, _: connection_router::msg::InstantiateMsg| Ok::(Response::new()), - mock_gateway_query, - ); - let code_id = app.store_code(Box::new(code)); - - app.instantiate_contract( - code_id, - Addr::unchecked("sender"), - &connection_router::msg::InstantiateMsg { - admin_address: Addr::unchecked("admin").to_string(), - governance_address: Addr::unchecked("governance").to_string(), - nexus_gateway: Addr::unchecked("nexus_gateway").to_string(), - }, - &[], - "Contract", - None, - ) - .unwrap() -} diff --git a/contracts/connection-router/tests/test.rs b/contracts/connection-router/tests/test.rs deleted file mode 100644 index abb24c110..000000000 --- a/contracts/connection-router/tests/test.rs +++ /dev/null @@ -1,1290 +0,0 @@ -pub mod mock; -mod test_utils; - -use std::str::FromStr; -use std::{collections::HashMap, vec}; - -use connection_router_api::error::Error; -use connection_router_api::msg::ExecuteMsg; -use connection_router_api::{ - ChainName, CrossChainId, GatewayDirection, Message, CHAIN_NAME_DELIMITER, -}; -use cosmwasm_std::Addr; -use cw_multi_test::App; -use integration_tests::contract::Contract; - -use crate::test_utils::ConnectionRouterContract; - -struct TestConfig { - app: App, - admin_address: Addr, - governance_address: Addr, - connection_router: ConnectionRouterContract, -} - -struct Chain { - chain_name: ChainName, - gateway: Addr, -} - -fn setup() -> TestConfig { - let mut app = App::default(); - - let admin_address = Addr::unchecked("admin"); - let governance_address = Addr::unchecked("governance"); - let nexus_gateway = Addr::unchecked("nexus_gateway"); - - let connection_router = ConnectionRouterContract::instantiate_contract( - &mut app, - admin_address.clone(), - governance_address.clone(), - nexus_gateway.clone(), - ); - - TestConfig { - app, - admin_address, - governance_address, - connection_router, - } -} - -fn make_chain(name: &str, config: &mut TestConfig) -> Chain { - let gateway = mock::make_mock_gateway(&mut config.app); - Chain { - chain_name: name.parse().unwrap(), - gateway, - } -} - -fn register_chain(config: &mut TestConfig, chain: &Chain) { - let _ = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::RegisterChain { - chain: chain.chain_name.clone(), - gateway_address: chain.gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap(); -} - -#[allow(clippy::arithmetic_side_effects)] -fn generate_messages( - src_chain: &Chain, - dest_chain: &Chain, - nonce: &mut usize, - count: usize, -) -> Vec { - let mut msgs = vec![]; - for x in 0..count { - *nonce += 1; - let id = format!("tx_id:{}", nonce); - msgs.push(Message { - cc_id: CrossChainId { - id: id.parse().unwrap(), - chain: src_chain.chain_name.clone(), - }, - destination_address: "idc".parse().unwrap(), - destination_chain: dest_chain.chain_name.clone(), - source_address: "idc".parse().unwrap(), - payload_hash: [x as u8; 32], - }) - } - msgs -} - -// tests that each message is properly delivered -#[test] -fn route() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let nonce: &mut usize = &mut 0; - let messages = generate_messages(ð, &polygon, nonce, 255); - - let _ = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(messages.clone()), - ) - .unwrap(); - - let outgoing_messages = mock::get_gateway_messages(&mut config.app, polygon.gateway, &messages); - - assert_eq!(messages.len(), outgoing_messages.len()); - assert_eq!(messages, outgoing_messages); - - // try to route twice - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(messages.clone()), - ); - - assert!(res.is_ok()); -} - -#[test] -fn wrong_source_chain() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let messages = &generate_messages(ð, &polygon, &mut 0, 1)[0]; - - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![messages.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::WrongSourceChain); -} - -#[test] -fn multi_chain_route() { - let mut config = setup(); - let chains = vec![ - make_chain("ethereum", &mut config), - make_chain("polygon", &mut config), - make_chain("osmosis", &mut config), - make_chain("avalanche", &mut config), - make_chain("moonbeam", &mut config), - ]; - for c in &chains { - register_chain(&mut config, c); - } - - let nonce = &mut 0; - let mut all_msgs_by_dest = HashMap::new(); - let mut all_msgs_by_src = HashMap::new(); - for d in &chains { - let mut msgs = vec![]; - for s in &chains { - let mut sending = generate_messages(s, d, nonce, 50); - - all_msgs_by_src - .entry(s.chain_name.to_string()) - .or_insert(vec![]) - .append(&mut sending); - - msgs.append(&mut sending); - } - all_msgs_by_dest.insert(d.chain_name.to_string(), msgs); - } - - for chain in &chains { - let res = config.connection_router.execute( - &mut config.app, - chain.gateway.clone(), - &ExecuteMsg::RouteMessages( - all_msgs_by_src - .get_mut(&chain.chain_name.to_string()) - .unwrap() - .clone(), - ), - ); - assert!(res.is_ok()); - } - - for chain in &chains { - let expected = all_msgs_by_dest.get(&chain.chain_name.to_string()).unwrap(); - let actual = mock::get_gateway_messages(&mut config.app, chain.gateway.clone(), expected); - - assert_eq!(expected.len(), actual.len()); - assert_eq!(expected, &actual); - } -} - -#[test] -fn authorization() { - let mut config = setup(); - let chain = make_chain("ethereum", &mut config); - - let err = config - .connection_router - .execute( - &mut config.app, - Addr::unchecked("random"), - &ExecuteMsg::RegisterChain { - chain: chain.chain_name.clone(), - gateway_address: chain.gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let err = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::RegisterChain { - chain: chain.chain_name.clone(), - gateway_address: chain.gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let res = config.connection_router.execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::RegisterChain { - chain: chain.chain_name.clone(), - gateway_address: chain.gateway.to_string().try_into().unwrap(), - }, - ); - assert!(res.is_ok()); - - let err = config - .connection_router - .execute( - &mut config.app, - Addr::unchecked("random"), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - let err = config - .connection_router - .execute( - &mut config.app, - Addr::unchecked("random"), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, - }, - ); - assert!(res.is_ok()); - - let err = config - .connection_router - .execute( - &mut config.app, - Addr::unchecked("random"), - &ExecuteMsg::UpgradeGateway { - chain: chain.chain_name.clone(), - contract_address: Addr::unchecked("new gateway") - .to_string() - .try_into() - .unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let err = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: chain.chain_name.clone(), - contract_address: Addr::unchecked("new gateway") - .to_string() - .try_into() - .unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::Unauthorized); - - let res = config.connection_router.execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: chain.chain_name.clone(), - contract_address: Addr::unchecked("new gateway") - .to_string() - .try_into() - .unwrap(), - }, - ); - assert!(res.is_ok()); -} - -#[test] -fn upgrade_gateway_outgoing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - let new_gateway = mock::make_mock_gateway(&mut config.app); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: polygon.chain_name.clone(), - contract_address: new_gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap(); - - let message = &generate_messages(ð, &polygon, &mut 0, 1)[0]; - let _ = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap(); - - let outgoing_messages = - mock::get_gateway_messages(&mut config.app, new_gateway, &[message.clone()]); - assert_eq!(outgoing_messages.len(), 1); - assert_eq!(message.clone(), outgoing_messages[0]); - - let outgoing_messages = - mock::get_gateway_messages(&mut config.app, polygon.gateway, &[message.clone()]); - assert_eq!(outgoing_messages.len(), 0); -} - -#[test] -fn upgrade_gateway_incoming() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - let new_gateway = mock::make_mock_gateway(&mut config.app); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: polygon.chain_name.clone(), - contract_address: new_gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap(); - - let message = &generate_messages(&polygon, ð, &mut 0, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::GatewayNotRegistered); - - let res = config.connection_router.execute( - &mut config.app, - new_gateway, - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - let messages = mock::get_gateway_messages(&mut config.app, eth.gateway, &[message.clone()]); - assert_eq!(messages.len(), 1); - assert_eq!(messages[0], message.clone()); -} - -#[test] -fn register_chain_test() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - let message = &generate_messages(ð, &polygon, &mut 0, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::GatewayNotRegistered); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn chain_already_registered() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - register_chain(&mut config, ð); - - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::RegisterChain { - chain: eth.chain_name, - gateway_address: Addr::unchecked("new gateway") - .to_string() - .try_into() - .unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::ChainAlreadyExists); - - // case insensitive - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::RegisterChain { - chain: ChainName::from_str("ETHEREUM").unwrap(), - gateway_address: Addr::unchecked("new gateway") - .to_string() - .try_into() - .unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::ChainAlreadyExists); -} - -#[test] -fn invalid_chain_name() { - test_utils::are_contract_err_strings_equal( - ChainName::from_str(format!("bad{}", CHAIN_NAME_DELIMITER).as_str()).unwrap_err(), - Error::InvalidChainName, - ); - - test_utils::are_contract_err_strings_equal( - ChainName::from_str("").unwrap_err(), - Error::InvalidChainName, - ); -} - -#[test] -fn gateway_already_registered() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::RegisterChain { - chain: polygon.chain_name.clone(), - gateway_address: eth.gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); - - register_chain(&mut config, &polygon); - let err = config - .connection_router - .execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: eth.chain_name, - contract_address: polygon.gateway.to_string().try_into().unwrap(), - }, - ) - .unwrap_err(); - - test_utils::are_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); -} - -#[test] -fn freeze_incoming() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - // can't route from frozen incoming gateway - let message = &generate_messages(&polygon, ð, &mut 0, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - let message = &generate_messages(ð, &polygon, &mut 0, 1)[0]; - // can still route to chain - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - let messages = - mock::get_gateway_messages(&mut config.app, polygon.gateway.clone(), &[message.clone()]); - assert_eq!(&messages[0], message); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ); - assert!(res.is_ok()); - - let message = &generate_messages(&polygon, ð, &mut 0, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn freeze_outgoing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - // freeze outgoing - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ); - assert!(res.is_ok()); - - // can still send to the chain, messages will queue up - let message = &generate_messages(ð, &polygon, &mut 0, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ); - assert!(res.is_ok()); - - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - let messages = mock::get_gateway_messages(&mut config.app, polygon.gateway, &[message.clone()]); - assert_eq!(messages.len(), 1); - assert_eq!(messages[0], message.clone()); -} - -#[test] -fn freeze_chain() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let nonce = &mut 0; - - // route a message first - let routed_msg = &generate_messages(ð, &polygon, nonce, 1)[0]; - let _ = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![routed_msg.clone()]), - ) - .unwrap(); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - let msg = &generate_messages(ð, &polygon, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![msg.clone()]), - ) - .unwrap_err(); - // can't route to frozen chain - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // can't route from frozen chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // unfreeze and test that everything works correctly - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ) - .unwrap(); - - // routed message should have been preserved - let outgoing_messages = mock::get_gateway_messages( - &mut config.app, - polygon.gateway.clone(), - &[routed_msg.clone()], - ); - assert_eq!(1, outgoing_messages.len()); - assert_eq!(routed_msg.clone(), outgoing_messages[0]); - - // can route to the chain now - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - // can route from the chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn unfreeze_incoming() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - let nonce = &mut 0; - - // unfreeze incoming - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - // can route from the chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - // can't route to the chain - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); -} - -#[test] -fn unfreeze_outgoing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - let nonce = &mut 0; - - // unfreeze outgoing - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ) - .unwrap(); - - // can't route from frozen chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // can route to the chain now - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn freeze_incoming_then_outgoing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ) - .unwrap(); - - let nonce = &mut 0; - // can't route to frozen chain - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // can't route from frozen chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); -} - -#[test] -fn freeze_outgoing_then_incoming() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ) - .unwrap(); - - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - let nonce = &mut 0; - // can't route to frozen chain - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // can't route from frozen chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); -} - -#[test] -fn unfreeze_incoming_then_outgoing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - // unfreeze incoming - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - // unfreeze outgoing - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ) - .unwrap(); - - // can route to the chain now - let nonce = &mut 0; - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - // can route from the chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn unfreeze_outgoing_then_incoming() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - // unfreeze outgoing - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, - }, - ) - .unwrap(); - - // unfreeze incoming - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, - }, - ) - .unwrap(); - - // can route to the chain now - let nonce = &mut 0; - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); - - // can route from the chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let res = config.connection_router.execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_ok()); -} - -#[test] -fn unfreeze_nothing() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, - }, - ); - assert!(res.is_ok()); - - // unfreeze nothing - let _ = config - .connection_router - .execute( - &mut config.app, - config.admin_address.clone(), - &ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::None, - }, - ) - .unwrap(); - - let nonce = &mut 0; - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); - - // can't route from frozen chain - let message = &generate_messages(&polygon, ð, nonce, 1)[0]; - let err = config - .connection_router - .execute( - &mut config.app, - polygon.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ) - .unwrap_err(); - test_utils::are_contract_err_strings_equal( - err, - Error::ChainFrozen { - chain: polygon.chain_name.clone(), - }, - ); -} - -#[test] -fn bad_gateway() { - let mut config = setup(); - let eth = make_chain("ethereum", &mut config); - let polygon = make_chain("polygon", &mut config); - - register_chain(&mut config, ð); - register_chain(&mut config, &polygon); - - let res = config.connection_router.execute( - &mut config.app, - config.governance_address.clone(), - &ExecuteMsg::UpgradeGateway { - chain: polygon.chain_name.clone(), - contract_address: Addr::unchecked("some random address") - .to_string() - .try_into() - .unwrap(), // gateway address does not implement required interface - }, - ); - - assert!(res.is_ok()); - - let nonce: &mut usize = &mut 0; - let message = &generate_messages(ð, &polygon, nonce, 1)[0]; - - let res = config.connection_router.execute( - &mut config.app, - eth.gateway.clone(), - &ExecuteMsg::RouteMessages(vec![message.clone()]), - ); - assert!(res.is_err()); -} diff --git a/contracts/connection-router/tests/test_utils/mod.rs b/contracts/connection-router/tests/test_utils/mod.rs deleted file mode 100644 index 96b93932d..000000000 --- a/contracts/connection-router/tests/test_utils/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -use connection_router::{ - contract::{execute, instantiate, query}, - msg::InstantiateMsg, -}; -use connection_router_api::msg::{ExecuteMsg, QueryMsg}; -use cosmwasm_std::Addr; -use cw_multi_test::{App, ContractWrapper, Executor}; -use integration_tests::contract::Contract; - -#[derive(Clone)] -pub struct ConnectionRouterContract { - pub contract_addr: Addr, -} - -impl ConnectionRouterContract { - pub fn instantiate_contract(app: &mut App, admin: Addr, governance: Addr, nexus: Addr) -> Self { - let code = ContractWrapper::new(execute, instantiate, query); - let code_id = app.store_code(Box::new(code)); - - let contract_addr = app - .instantiate_contract( - code_id, - Addr::unchecked("router"), - &InstantiateMsg { - admin_address: admin.to_string(), - governance_address: governance.to_string(), - nexus_gateway: nexus.to_string(), - }, - &[], - "Contract", - None, - ) - .unwrap(); - - ConnectionRouterContract { contract_addr } - } -} - -impl Contract for ConnectionRouterContract { - type QMsg = QueryMsg; - type ExMsg = ExecuteMsg; - - fn contract_address(&self) -> Addr { - self.contract_addr.clone() - } -} - -pub fn are_contract_err_strings_equal( - actual: impl Into, - expected: impl Into, -) { - assert_eq!(actual.into().to_string(), expected.into().to_string()); -} diff --git a/integration-tests/tests/chain_freeze_unfreeze.rs b/integration-tests/tests/chain_freeze_unfreeze.rs new file mode 100644 index 000000000..94c122935 --- /dev/null +++ b/integration-tests/tests/chain_freeze_unfreeze.rs @@ -0,0 +1,93 @@ +use cosmwasm_std::{Addr, HexBinary}; + +use connection_router_api::{CrossChainId, Message}; +use integration_tests::contract::Contract; + +pub mod test_utils; + +// Tests that a chain can be frozen and unfrozen +#[test] +fn chain_can_be_freezed_unfreezed() { + let (mut protocol, chain1, chain2, workers, _) = test_utils::setup_test_case(); + + let msgs = vec![Message { + cc_id: CrossChainId { + chain: chain1.chain_name.clone(), + id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3" + .to_string() + .try_into() + .unwrap(), + }, + source_address: "0xBf12773B49()0e1Deb57039061AAcFA2A87DEaC9b9" + .to_string() + .try_into() + .unwrap(), + destination_address: "0xce16F69375520ab01377ce7B88f5BA8C48F8D666" + .to_string() + .try_into() + .unwrap(), + destination_chain: chain2.chain_name.clone(), + payload_hash: HexBinary::from_hex( + "3e50a012285f8e7ec59b558179cd546c55c477ebe16202aac7d7747e25be03be", + ) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + }]; + let msg_ids: Vec = msgs.iter().map(|msg| msg.cc_id.clone()).collect(); + + // start the flow by submitting the message to the gateway + let (poll_id, expiry) = test_utils::verify_messages(&mut protocol.app, &chain1.gateway, &msgs); + + // do voting + test_utils::vote_success_for_all_messages( + &mut protocol.app, + &chain1.voting_verifier, + &msgs, + &workers, + poll_id, + ); + + test_utils::advance_at_least_to_height(&mut protocol.app, expiry); + + test_utils::end_poll(&mut protocol.app, &chain1.voting_verifier, poll_id); + + test_utils::route_messages(&mut protocol.app, &chain1.gateway, &msgs); + + test_utils::freeze_chain( + &mut protocol.app, + &protocol.connection_router, + &chain1.chain_name, + connection_router_api::GatewayDirection::Bidirectional, + &protocol.router_admin_address, + ); + + let response = chain1.gateway.execute( + &mut protocol.app, + Addr::unchecked("relayer"), + &gateway_api::msg::ExecuteMsg::RouteMessages(msgs.to_vec()), + ); + test_utils::assert_contract_err_strings_equal( + response.unwrap_err(), + connection_router_api::error::Error::ChainFrozen { + chain: chain1.chain_name.clone(), + }, + ); + + test_utils::unfreeze_chain( + &mut protocol.app, + &protocol.connection_router, + &chain1.chain_name, + connection_router_api::GatewayDirection::Bidirectional, + &protocol.router_admin_address, + ); + + // routed message should have been preserved + let found_msgs = + test_utils::get_messages_from_gateway(&mut protocol.app, &chain2.gateway, &msg_ids); + assert_eq!(found_msgs, msgs); + + // can route again + test_utils::route_messages(&mut protocol.app, &chain1.gateway, &msgs); +} diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index e61425c36..8954f9df3 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -1,6 +1,7 @@ -use cosmwasm_std::{HexBinary, Uint128}; +use cosmwasm_std::{Addr, HexBinary, Uint128}; use connection_router_api::{CrossChainId, Message}; +use integration_tests::contract::Contract; use crate::test_utils::AXL_DENOMINATION; @@ -109,3 +110,52 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu assert_eq!(balance.amount, expected_rewards); } } + +#[test] +fn routing_to_incorrect_gateway_interface() { + let (mut protocol, chain1, chain2, _, _) = test_utils::setup_test_case(); + + let msgs = vec![Message { + cc_id: CrossChainId { + chain: chain1.chain_name.clone(), + id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3" + .to_string() + .try_into() + .unwrap(), + }, + source_address: "0xBf12773B49()0e1Deb57039061AAcFA2A87DEaC9b9" + .to_string() + .try_into() + .unwrap(), + destination_address: "0xce16F69375520ab01377ce7B88f5BA8C48F8D666" + .to_string() + .try_into() + .unwrap(), + destination_chain: chain2.chain_name.clone(), + payload_hash: HexBinary::from_hex( + "3e50a012285f8e7ec59b558179cd546c55c477ebe16202aac7d7747e25be03be", + ) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + }]; + + test_utils::upgrade_gateway( + &mut protocol.app, + &protocol.connection_router, + &protocol.governance_address, + &chain2.chain_name, + Addr::unchecked("some random address") + .to_string() + .try_into() + .unwrap(), // gateway address does not implement required interface, + ); + + let response = protocol.connection_router.execute( + &mut protocol.app, + chain1.gateway.contract_addr.clone(), + &connection_router_api::msg::ExecuteMsg::RouteMessages(msgs.to_vec()), + ); + assert!(response.is_err()) +} diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index f9a899858..abf6a7f60 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -3,7 +3,7 @@ use axelar_wasm_std::{ voting::{PollId, Vote}, Participant, Threshold, }; -use connection_router_api::{ChainName, CrossChainId, Message}; +use connection_router_api::{Address, ChainName, CrossChainId, GatewayDirection, Message}; use cosmwasm_std::{ coins, Addr, Attribute, BlockInfo, Event, HexBinary, StdError, Uint128, Uint256, Uint64, }; @@ -81,6 +81,60 @@ pub fn route_messages(app: &mut App, gateway: &GatewayContract, msgs: &[Message] assert!(response.is_ok()); } +pub fn freeze_chain( + app: &mut App, + router: &ConnectionRouterContract, + chain_name: &ChainName, + direction: GatewayDirection, + admin: &Addr, +) { + let response = router.execute( + app, + admin.clone(), + &connection_router_api::msg::ExecuteMsg::FreezeChain { + chain: chain_name.clone(), + direction, + }, + ); + assert!(response.is_ok(), "{:?}", response); +} + +pub fn unfreeze_chain( + app: &mut App, + router: &ConnectionRouterContract, + chain_name: &ChainName, + direction: GatewayDirection, + admin: &Addr, +) { + let response = router.execute( + app, + admin.clone(), + &connection_router_api::msg::ExecuteMsg::UnfreezeChain { + chain: chain_name.clone(), + direction, + }, + ); + assert!(response.is_ok(), "{:?}", response); +} + +pub fn upgrade_gateway( + app: &mut App, + router: &ConnectionRouterContract, + governance: &Addr, + chain_name: &ChainName, + contract_address: Address, +) { + let response = router.execute( + app, + governance.clone(), + &connection_router_api::msg::ExecuteMsg::UpgradeGateway { + chain: chain_name.clone(), + contract_address, + }, + ); + assert!(response.is_ok(), "{:?}", response); +} + pub fn vote_success_for_all_messages( app: &mut App, voting_verifier: &VotingVerifierContract, @@ -704,3 +758,10 @@ pub fn setup_test_case() -> (Protocol, Chain, Chain, Vec, Uint128) { let chain2 = setup_chain(&mut protocol, chains.get(1).unwrap().clone()); (protocol, chain1, chain2, workers, min_worker_bond) } + +pub fn assert_contract_err_strings_equal( + actual: impl Into, + expected: impl Into, +) { + assert_eq!(actual.into().to_string(), expected.into().to_string()); +} From a52afe89ecd03ea2afbc6ff58362b7fcaa02f56e Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Wed, 10 Apr 2024 12:17:01 -0400 Subject: [PATCH 12/27] feat(minor-connection-router): paginated chains query (#326) add paginated chains query to connection router, monitor contract can use the api to return paginated chains with status --- contracts/connection-router/src/contract.rs | 3 + .../connection-router/src/contract/query.rs | 80 ++++++++++++++++++- packages/connection-router-api/src/msg.rs | 10 +++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/contracts/connection-router/src/contract.rs b/contracts/connection-router/src/contract.rs index b2b282a6a..d9d2c39e1 100644 --- a/contracts/connection-router/src/contract.rs +++ b/contracts/connection-router/src/contract.rs @@ -109,6 +109,9 @@ pub fn query( ) -> Result { match msg { QueryMsg::GetChainInfo(chain) => to_json_binary(&query::get_chain_info(deps, chain)?), + QueryMsg::Chains { start_after, limit } => { + to_json_binary(&query::chains(deps, start_after, limit)?) + } } .map_err(axelar_wasm_std::ContractError::from) } diff --git a/contracts/connection-router/src/contract/query.rs b/contracts/connection-router/src/contract/query.rs index d2274faa0..e7e29bc40 100644 --- a/contracts/connection-router/src/contract/query.rs +++ b/contracts/connection-router/src/contract/query.rs @@ -1,10 +1,15 @@ +use cosmwasm_std::{Deps, Order}; +use cw_storage_plus::Bound; + use connection_router_api::error::Error; use connection_router_api::{ChainEndpoint, ChainName}; -use cosmwasm_std::Deps; use error_stack::{Result, ResultExt}; use crate::state::chain_endpoints; +// Pagination limits +const DEFAULT_LIMIT: u32 = u32::MAX; + pub fn get_chain_info(deps: Deps, chain: ChainName) -> Result { chain_endpoints() .may_load(deps.storage, chain) @@ -12,6 +17,24 @@ pub fn get_chain_info(deps: Deps, chain: ChainName) -> Result, + limit: Option, +) -> Result, Error> { + let limit = limit.unwrap_or(DEFAULT_LIMIT) as usize; + let start = start_after.map(Bound::exclusive); + + chain_endpoints() + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + item.map(|(_, endpoint)| endpoint) + .change_context(Error::StoreFailure) + }) + .collect() +} + #[cfg(test)] mod test { use axelar_wasm_std::flagset::FlagSet; @@ -51,4 +74,59 @@ mod test { assert!(result.is_err()); assert_eq!(result.unwrap_err().current_context(), &Error::ChainNotFound); } + + #[test] + fn paginated_chains() { + let mut deps = mock_dependencies(); + let chains: Vec = vec![ + "a-chain".parse().unwrap(), + "b-chain".parse().unwrap(), + "c-chain".parse().unwrap(), + "d-chain".parse().unwrap(), + ]; + + let endpoints: Vec = chains + .iter() + .map(|chain| ChainEndpoint { + name: chain.clone(), + gateway: Gateway { + address: Addr::unchecked(format!("{} gateway", chain)), + }, + frozen_status: FlagSet::from(GatewayDirection::None), + }) + .collect(); + + // save end points to storage + for chain_info in endpoints.iter() { + assert!(chain_endpoints() + .save(deps.as_mut().storage, chain_info.name.clone(), chain_info) + .is_ok()); + } + + // no pagination + let result = super::chains(deps.as_ref(), 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(); + 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(); + 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(); + 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(); + assert_eq!(result.len(), 0); + } } diff --git a/packages/connection-router-api/src/msg.rs b/packages/connection-router-api/src/msg.rs index 6ea3259b1..0a00a3bf6 100644 --- a/packages/connection-router-api/src/msg.rs +++ b/packages/connection-router-api/src/msg.rs @@ -48,4 +48,14 @@ pub enum ExecuteMsg { pub enum QueryMsg { #[returns(ChainEndpoint)] GetChainInfo(ChainName), + + // Returns a list of chains registered with the router + // The list is paginated by: + // - start_after: the chain name to start after, which the next page of results should start. + // - limit: limit the number of chains returned, default is u32::MAX. + #[returns(Vec)] + Chains { + start_after: Option, + limit: Option, + }, } From af22fee82b0f16d75b3fd90d1b196ee422afbebf Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Wed, 10 Apr 2024 23:14:31 -0400 Subject: [PATCH 13/27] chore: reduce unused dependencies from Cargo.toml files (#341) --- .cargo/config | 2 +- Cargo.lock | 43 ---------------------- Cargo.toml | 3 +- ampd/Cargo.toml | 4 +- contracts/aggregate-verifier/Cargo.toml | 6 +-- contracts/connection-router/Cargo.toml | 7 ---- contracts/gateway/Cargo.toml | 8 +--- contracts/monitoring/Cargo.toml | 1 - contracts/multisig-prover/Cargo.toml | 4 -- contracts/multisig/Cargo.toml | 2 - contracts/rewards/Cargo.toml | 2 - contracts/service-registry/Cargo.toml | 2 - contracts/voting-verifier/Cargo.toml | 7 +--- integration-tests/Cargo.toml | 13 +------ packages/axelar-wasm-std/Cargo.toml | 1 - packages/connection-router-api/Cargo.toml | 1 - packages/signature-verifier-api/Cargo.toml | 1 - 17 files changed, 10 insertions(+), 97 deletions(-) diff --git a/.cargo/config b/.cargo/config index 34fad86cb..e61e7526b 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,5 +1,5 @@ [alias] -wasm = "build --release --lib --target wasm32-unknown-unknown --locked --workspace --exclude ampd" +wasm = "build --release --lib --target wasm32-unknown-unknown --locked --workspace --exclude ampd --exclude integration-tests" unit-test = "test --lib" [build] diff --git a/Cargo.lock b/Cargo.lock index 07ee35fad..7ecceb3f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,17 +82,13 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "error-stack", "integration-tests", - "itertools 0.11.0", "report", - "schemars", "serde", - "serde_json", "thiserror", "voting-verifier", ] @@ -666,7 +662,6 @@ version = "0.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "error-stack", @@ -1461,7 +1456,6 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "error-stack", @@ -1473,12 +1467,6 @@ dependencies = [ "mockall", "rand", "report", - "schemars", - "serde", - "serde_json", - "sha3 0.10.8", - "thiserror", - "valuable", ] [[package]] @@ -1494,7 +1482,6 @@ dependencies = [ "flagset", "hex", "rand", - "regex", "report", "schemars", "serde", @@ -3342,19 +3329,15 @@ version = "0.1.0" dependencies = [ "aggregate-verifier", "axelar-wasm-std", - "axelar-wasm-std-derive", "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "error-stack", "gateway-api", "itertools 0.11.0", - "mockall", "report", - "schemars", "serde", "serde_json", "thiserror", @@ -3906,32 +3889,23 @@ dependencies = [ name = "integration-tests" version = "0.1.0" dependencies = [ - "aggregate-verifier", "axelar-wasm-std", - "axelar-wasm-std-derive", "connection-router", "connection-router-api", - "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", "error-stack", "gateway", "gateway-api", - "itertools 0.11.0", "k256", - "mockall", "multisig", "multisig-prover", "report", "rewards", - "schemars", "serde", "serde_json", "service-registry", "sha3 0.10.8", - "thiserror", "tofn", "voting-verifier", ] @@ -4340,7 +4314,6 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "error-stack", @@ -4829,7 +4802,6 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "curve25519-dalek 4.1.1", "cw-multi-test", "cw-storage-plus 1.1.0", @@ -4840,7 +4812,6 @@ dependencies = [ "k256", "report", "rewards", - "schemars", "serde", "serde_json", "sha3 0.10.8", @@ -4859,11 +4830,9 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "either", "elliptic-curve", "error-stack", "ethabi", @@ -4876,8 +4845,6 @@ dependencies = [ "k256", "multisig", "report", - "schemars", - "serde", "serde_json", "service-registry", "sha3 0.10.8", @@ -6481,9 +6448,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "error-stack", "itertools 0.11.0", - "mockall", "report", - "serde", "thiserror", ] @@ -7139,7 +7104,6 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", "error-stack", @@ -7147,7 +7111,6 @@ dependencies = [ "report", "schemars", "serde", - "serde_json", "thiserror", ] @@ -7277,7 +7240,6 @@ version = "0.1.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "error-stack", "thiserror", ] @@ -8833,19 +8795,14 @@ dependencies = [ "connection-router-api", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.1.0", - "either", "error-stack", "integration-tests", "report", "rewards", - "schemars", - "serde", "serde_json", "service-registry", - "sha3 0.10.8", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 4c2e2b82a..5acd3320b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,8 @@ rust-version = "1.75.0" # be sure there is an optimizer release supporting this [workspace.dependencies] connection-router = { version = "^0.1.0", path = "contracts/connection-router" } -cosmwasm-std = "1.5.3" +cosmwasm-std = "1.3.4" cosmwasm-schema = "1.3.3" -cosmwasm-storage = "1.3.3" cw-storage-plus = "1.1.0" error-stack = { version = "0.4.0", features = ["eyre"] } events = { version = "^0.1.0", path = "packages/events" } diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index e259dd172..57550338a 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -38,7 +38,7 @@ prost = "0.11.9" report = { workspace = true } reqwest = { version = "0.11.24", default-features = false } serde = { version = "1.0.147", features = ["derive"] } -serde_json = "1.0.89" +serde_json = { workspace = true } serde_with = "3.2.0" service-registry = { workspace = true } sha3 = { workspace = true } @@ -49,7 +49,7 @@ sui-types = { git = "https://github.com/mystenlabs/sui", features = ["test-utils # The fix for the issue is at https://github.com/axelarnetwork/tendermint-rs/commit/e97033e20e660a7e707ea86db174ec047bbba50d. tendermint = { git = "https://github.com/axelarnetwork/tendermint-rs.git", branch = "v0.33.x" } tendermint-rpc = { git = "https://github.com/axelarnetwork/tendermint-rs.git", branch = "v0.33.x", features = [ - "http-client", + "http-client", ] } thiserror = { workspace = true } tokio = { version = "1.22.0", features = ["signal"] } diff --git a/contracts/aggregate-verifier/Cargo.toml b/contracts/aggregate-verifier/Cargo.toml index 37509ad12..ce312c9a8 100644 --- a/contracts/aggregate-verifier/Cargo.toml +++ b/contracts/aggregate-verifier/Cargo.toml @@ -38,15 +38,11 @@ axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = "1.0.1" error-stack = { workspace = true } -itertools = { workspace = true } report = { workspace = true } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.89" +serde = { workspace = true } thiserror = { workspace = true } voting-verifier = { workspace = true, features = ["library"] } diff --git a/contracts/connection-router/Cargo.toml b/contracts/connection-router/Cargo.toml index ef9eae1cc..3f9471ddd 100644 --- a/contracts/connection-router/Cargo.toml +++ b/contracts/connection-router/Cargo.toml @@ -38,7 +38,6 @@ axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } @@ -46,12 +45,6 @@ gateway-api = { workspace = true } itertools = { workspace = true } mockall = "0.11.4" report = { workspace = true } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.89" -sha3 = { workspace = true } -thiserror = { workspace = true } -valuable = { version = "0.1.0", features = ["derive"] } [dev-dependencies] cw-multi-test = "0.15.1" diff --git a/contracts/gateway/Cargo.toml b/contracts/gateway/Cargo.toml index 05ef0210e..366bc6cff 100644 --- a/contracts/gateway/Cargo.toml +++ b/contracts/gateway/Cargo.toml @@ -37,20 +37,16 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] aggregate-verifier = { workspace = true, features = ["library"] } axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } gateway-api = { workspace = true } itertools = { workspace = true } -mockall = "0.11.3" report = { workspace = true } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.89" +serde = { workspace = true } +serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/monitoring/Cargo.toml b/contracts/monitoring/Cargo.toml index 389b1af16..a00ab0e67 100644 --- a/contracts/monitoring/Cargo.toml +++ b/contracts/monitoring/Cargo.toml @@ -38,7 +38,6 @@ axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } multisig = { workspace = true, features = ["library"] } diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 7646e6789..5b060a405 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -39,10 +39,8 @@ bcs = "0.1.5" connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = "1.0.1" -either = "1.8.1" error-stack = { workspace = true } ethabi = { version = "18.0.0", default-features = false, features = [] } gateway = { workspace = true } @@ -52,8 +50,6 @@ itertools = "0.11.0" k256 = { version = "0.13.1", features = ["ecdsa"] } multisig = { workspace = true, features = ["library"] } report = { workspace = true } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" service-registry = { workspace = true } sha3 = { workspace = true } diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index 4a8a63dbe..c5da65905 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -44,7 +44,6 @@ connection-router-api = { workspace = true } cosmwasm-crypto = "1.2.7" cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } ed25519-dalek = { version = "2.1.1", default-features = false } enum-display-derive = "0.1.1" @@ -53,7 +52,6 @@ getrandom = { version = "0.2", default-features = false, features = ["custom"] } k256 = { version = "0.13.1", features = ["ecdsa"] } report = { workspace = true } rewards = { workspace = true, features = ["library"] } -schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" sha3 = { workspace = true } diff --git a/contracts/rewards/Cargo.toml b/contracts/rewards/Cargo.toml index 23aac0b85..821d833d1 100644 --- a/contracts/rewards/Cargo.toml +++ b/contracts/rewards/Cargo.toml @@ -41,9 +41,7 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } itertools = "0.11.0" -mockall = "0.11.3" report = { workspace = true } -serde = { version = "1.0.145", default-features = false, features = ["derive"] } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/service-registry/Cargo.toml b/contracts/service-registry/Cargo.toml index d5154e6c8..46c9f7d55 100644 --- a/contracts/service-registry/Cargo.toml +++ b/contracts/service-registry/Cargo.toml @@ -38,13 +38,11 @@ axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } report = { workspace = true } schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.107" thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/voting-verifier/Cargo.toml b/contracts/voting-verifier/Cargo.toml index bd936fe17..b56f0dac3 100644 --- a/contracts/voting-verifier/Cargo.toml +++ b/contracts/voting-verifier/Cargo.toml @@ -38,17 +38,12 @@ axelar-wasm-std-derive = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } -either = "1.8.1" error-stack = { workspace = true } report = { workspace = true } rewards = { workspace = true, features = ["library"] } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.89" +serde_json = { workspace = true } service-registry = { workspace = true, features = ["library"] } -sha3 = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 4b251f229..1733e31d0 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -29,32 +29,23 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -aggregate-verifier = { workspace = true, features = ["library"] } axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } connection-router = { workspace = true } connection-router-api = { workspace = true } -cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-multi-test = "0.15.1" -cw-storage-plus = { workspace = true } error-stack = { workspace = true } gateway = { workspace = true } gateway-api = { workspace = true } -itertools = { workspace = true } k256 = { version = "0.13.1", features = ["ecdsa"] } -mockall = "0.11.3" multisig = { workspace = true } multisig-prover = { workspace = true } report = { workspace = true } rewards = { workspace = true } -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -serde_json = "1.0.89" +serde = { workspace = true } +serde_json = { workspace = true } service-registry = { workspace = true } sha3 = { workspace = true } -thiserror = { workspace = true } tofn = { git = "https://github.com/axelarnetwork/tofn.git", branch = "update-deps" } voting-verifier = { workspace = true } diff --git a/packages/axelar-wasm-std/Cargo.toml b/packages/axelar-wasm-std/Cargo.toml index ccad58344..144859ed4 100644 --- a/packages/axelar-wasm-std/Cargo.toml +++ b/packages/axelar-wasm-std/Cargo.toml @@ -31,7 +31,6 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } diff --git a/packages/connection-router-api/Cargo.toml b/packages/connection-router-api/Cargo.toml index 470270374..b5618ee9c 100644 --- a/packages/connection-router-api/Cargo.toml +++ b/packages/connection-router-api/Cargo.toml @@ -13,7 +13,6 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } -regex = "1.10.0" report = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/packages/signature-verifier-api/Cargo.toml b/packages/signature-verifier-api/Cargo.toml index 032f49ee2..1602a84e2 100644 --- a/packages/signature-verifier-api/Cargo.toml +++ b/packages/signature-verifier-api/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-storage = { workspace = true } error-stack = { workspace = true } thiserror = { workspace = true } From 5b33bfd95b9f97ff6add744fce7570a26c2fd39f Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Wed, 10 Apr 2024 23:18:04 -0400 Subject: [PATCH 14/27] chore: fixing cosmwasm-std to v1.3.4 (#343) --- Cargo.lock | 16 +++-- Cargo.toml | 2 +- contracts/aggregate-verifier/src/client.rs | 8 +-- contracts/aggregate-verifier/src/contract.rs | 12 ++-- contracts/aggregate-verifier/tests/mock.rs | 6 +- contracts/aggregate-verifier/tests/test.rs | 10 +-- contracts/connection-router/src/contract.rs | 9 ++- .../connection-router/src/contract/execute.rs | 4 +- contracts/gateway/src/contract.rs | 4 +- contracts/gateway/tests/contract.rs | 10 +-- contracts/multisig-prover/src/contract.rs | 6 +- contracts/multisig-prover/src/execute.rs | 10 +-- contracts/multisig-prover/src/query.rs | 4 +- contracts/multisig-prover/src/reply.rs | 4 +- .../multisig-prover/src/test/mocks/gateway.rs | 4 +- .../src/test/mocks/multisig.rs | 8 +-- .../src/test/mocks/service_registry.rs | 4 +- .../src/test/mocks/voting_verifier.rs | 4 +- contracts/multisig-prover/src/types.rs | 4 +- contracts/multisig/src/contract.rs | 32 +++++----- contracts/multisig/src/contract/execute.rs | 4 +- contracts/multisig/src/signing.rs | 4 +- .../nexus-gateway/src/contract/execute.rs | 8 +-- contracts/rewards/src/contract.rs | 6 +- contracts/service-registry/src/contract.rs | 9 ++- contracts/service-registry/src/helpers.rs | 4 +- contracts/voting-verifier/src/contract.rs | 62 +++++++++---------- contracts/voting-verifier/src/execute.rs | 10 +-- packages/axelar-wasm-std/src/operators.rs | 5 +- packages/axelar-wasm-std/src/snapshot.rs | 6 +- packages/connection-router-api/src/client.rs | 4 +- .../connection-router-api/src/primitives.rs | 4 +- packages/signature-verifier-api/src/client.rs | 6 +- 33 files changed, 141 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ecceb3f2..183d90124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1062,9 +1062,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.10.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" [[package]] name = "brotli" @@ -1665,12 +1665,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8666e572a3a2519010dde88c04d16e9339ae751b56b2bb35081fe3f7d6be74" +checksum = "0837f920e0ebdb6eed95f2899e39db632f7de8fb68034464a5cee925a2dffdfd" dependencies = [ - "base64 0.21.4", - "bech32", + "base64 0.13.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -1681,15 +1680,14 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.7", - "static_assertions", "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "1.4.0" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab544dfcad7c9e971933d522d99ec75cc8ddfa338854bb992b092e11bcd7e818" +checksum = "f73fc6766006aef09dea5db0c0728446e8c0e22b26eeb33c83ae70a500894ff3" dependencies = [ "cosmwasm-std", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5acd3320b..12b4ded55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ rust-version = "1.75.0" # be sure there is an optimizer release supporting this [workspace.dependencies] connection-router = { version = "^0.1.0", path = "contracts/connection-router" } cosmwasm-std = "1.3.4" -cosmwasm-schema = "1.3.3" +cosmwasm-schema = "1.3.4" cw-storage-plus = "1.1.0" error-stack = { version = "0.4.0", features = ["eyre"] } events = { version = "^0.1.0", path = "packages/events" } diff --git a/contracts/aggregate-verifier/src/client.rs b/contracts/aggregate-verifier/src/client.rs index d06fb662f..10228eb5e 100644 --- a/contracts/aggregate-verifier/src/client.rs +++ b/contracts/aggregate-verifier/src/client.rs @@ -1,7 +1,7 @@ use axelar_wasm_std::utils::TryMapExt; use axelar_wasm_std::{FnExt, VerificationStatus}; use connection_router_api::{CrossChainId, Message}; -use cosmwasm_std::{to_json_binary, Addr, QuerierWrapper, QueryRequest, WasmMsg, WasmQuery}; +use cosmwasm_std::{to_binary, Addr, QuerierWrapper, QueryRequest, WasmMsg, WasmQuery}; use error_stack::{Result, ResultExt}; use serde::de::DeserializeOwned; use std::collections::HashMap; @@ -15,7 +15,7 @@ impl Verifier<'_> { fn execute(&self, msg: &crate::msg::ExecuteMsg) -> WasmMsg { WasmMsg::Execute { contract_addr: self.address.to_string(), - msg: to_json_binary(msg).expect("msg should always be serializable"), + msg: to_binary(msg).expect("msg should always be serializable"), funds: vec![], } } @@ -24,7 +24,7 @@ impl Verifier<'_> { self.querier .query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_json_binary(&msg).expect("msg should always be serializable"), + msg: to_binary(&msg).expect("msg should always be serializable"), })) .change_context(Error::QueryVerifier) } @@ -120,7 +120,7 @@ mod tests { fn verifier_returns_error_on_return_type_mismatch() { let mut querier = MockQuerier::default(); querier.update_wasm(|_| { - Ok(to_json_binary( + Ok(to_binary( &CrossChainId::from_str(format!("eth{}0x1234", CHAIN_NAME_DELIMITER).as_str()) .unwrap(), ) diff --git a/contracts/aggregate-verifier/src/contract.rs b/contracts/aggregate-verifier/src/contract.rs index 6b5c303c9..d238c4c2c 100644 --- a/contracts/aggregate-verifier/src/contract.rs +++ b/contracts/aggregate-verifier/src/contract.rs @@ -3,8 +3,8 @@ use connection_router_api::CrossChainId; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_json, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Reply, - Response, StdResult, WasmQuery, + from_binary, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Reply, Response, + StdResult, WasmQuery, }; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; @@ -45,7 +45,7 @@ pub fn execute( } pub mod execute { - use cosmwasm_std::{to_json_binary, SubMsg, WasmMsg}; + use cosmwasm_std::{to_binary, SubMsg, WasmMsg}; use connection_router_api::Message; @@ -59,7 +59,7 @@ pub mod execute { Ok(Response::new().add_submessage(SubMsg::reply_on_success( WasmMsg::Execute { contract_addr: verifier.to_string(), - msg: to_json_binary(&voting_msg::ExecuteMsg::VerifyMessages { messages: msgs })?, + msg: to_binary(&voting_msg::ExecuteMsg::VerifyMessages { messages: msgs })?, funds: vec![], }, VERIFY_REPLY, @@ -79,7 +79,7 @@ pub fn reply( match parse_reply_execute_data(reply) { Ok(MsgExecuteContractResponse { data: Some(data) }) => { // check format of data - let _: Vec<(CrossChainId, VerificationStatus)> = from_json(&data)?; + let _: Vec<(CrossChainId, VerificationStatus)> = from_binary(&data)?; // only one verifier, so just return the response as is Ok(Response::new().set_data(data)) @@ -102,7 +102,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { let verifier = CONFIG.load(deps.storage)?.verifier; deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: verifier.to_string(), - msg: to_json_binary(&voting_msg::QueryMsg::GetMessagesStatus { messages })?, + msg: to_binary(&voting_msg::QueryMsg::GetMessagesStatus { messages })?, })) } } diff --git a/contracts/aggregate-verifier/tests/mock.rs b/contracts/aggregate-verifier/tests/mock.rs index 3aea3f03f..89376f1b6 100644 --- a/contracts/aggregate-verifier/tests/mock.rs +++ b/contracts/aggregate-verifier/tests/mock.rs @@ -2,7 +2,7 @@ use aggregate_verifier::error::ContractError; use axelar_wasm_std::VerificationStatus; use connection_router_api::{CrossChainId, Message}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, Addr, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response}; use cw_multi_test::{App, ContractWrapper, Executor}; use cw_storage_plus::Map; @@ -33,7 +33,7 @@ pub fn mock_verifier_execute( None => res.push((m.cc_id, VerificationStatus::None)), } } - Ok(Response::new().set_data(to_json_binary(&res)?)) + Ok(Response::new().set_data(to_binary(&res)?)) } MockVotingVerifierExecuteMsg::MessagesVerified { messages } => { for m in messages { @@ -64,7 +64,7 @@ pub fn make_mock_voting_verifier(app: &mut App) -> Addr { |_, _, _, _: MockVotingVerifierInstantiateMsg| { Ok::(Response::new()) }, - |_, _, _: aggregate_verifier::msg::QueryMsg| to_json_binary(&()), + |_, _, _: aggregate_verifier::msg::QueryMsg| to_binary(&()), ); let code_id = app.store_code(Box::new(code)); diff --git a/contracts/aggregate-verifier/tests/test.rs b/contracts/aggregate-verifier/tests/test.rs index ffa6a2131..398739add 100644 --- a/contracts/aggregate-verifier/tests/test.rs +++ b/contracts/aggregate-verifier/tests/test.rs @@ -1,7 +1,7 @@ use aggregate_verifier::msg::ExecuteMsg; use axelar_wasm_std::VerificationStatus; use connection_router_api::{CrossChainId, Message}; -use cosmwasm_std::from_json; +use cosmwasm_std::from_binary; use cosmwasm_std::Addr; use cw_multi_test::App; @@ -48,7 +48,7 @@ fn verify_messages_empty() { &ExecuteMsg::VerifyMessages { messages: vec![] }, ) .unwrap(); - let ret: Vec<(CrossChainId, VerificationStatus)> = from_json(res.data.unwrap()).unwrap(); + let ret: Vec<(CrossChainId, VerificationStatus)> = from_binary(&res.data.unwrap()).unwrap(); assert_eq!(ret, vec![]); } @@ -72,7 +72,7 @@ fn verify_messages_not_verified() { }, ) .unwrap(); - let ret: Vec<(CrossChainId, VerificationStatus)> = from_json(res.data.unwrap()).unwrap(); + let ret: Vec<(CrossChainId, VerificationStatus)> = from_binary(&res.data.unwrap()).unwrap(); assert_eq!( ret, messages @@ -104,7 +104,7 @@ fn verify_messages_verified() { }, ) .unwrap(); - let ret: Vec<(CrossChainId, VerificationStatus)> = from_json(res.data.unwrap()).unwrap(); + let ret: Vec<(CrossChainId, VerificationStatus)> = from_binary(&res.data.unwrap()).unwrap(); assert_eq!( ret, messages @@ -137,7 +137,7 @@ fn verify_messages_mixed_status() { }, ) .unwrap(); - let ret: Vec<(CrossChainId, VerificationStatus)> = from_json(res.data.unwrap()).unwrap(); + let ret: Vec<(CrossChainId, VerificationStatus)> = from_binary(&res.data.unwrap()).unwrap(); assert_eq!( ret, messages diff --git a/contracts/connection-router/src/contract.rs b/contracts/connection-router/src/contract.rs index d9d2c39e1..7818ae16c 100644 --- a/contracts/connection-router/src/contract.rs +++ b/contracts/connection-router/src/contract.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use connection_router_api::msg::{ExecuteMsg, QueryMsg}; @@ -108,9 +108,9 @@ pub fn query( msg: QueryMsg, ) -> Result { match msg { - QueryMsg::GetChainInfo(chain) => to_json_binary(&query::get_chain_info(deps, chain)?), + QueryMsg::GetChainInfo(chain) => to_binary(&query::get_chain_info(deps, chain)?), QueryMsg::Chains { start_after, limit } => { - to_json_binary(&query::chains(deps, start_after, limit)?) + to_binary(&query::chains(deps, start_after, limit)?) } } .map_err(axelar_wasm_std::ContractError::from) @@ -215,8 +215,7 @@ mod test { assert_eq!( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, - msg: to_json_binary(&gateway_api::msg::ExecuteMsg::RouteMessages(messages,)) - .unwrap(), + msg: to_binary(&gateway_api::msg::ExecuteMsg::RouteMessages(messages,)).unwrap(), funds: vec![], }), cosmos_msg diff --git a/contracts/connection-router/src/contract/execute.rs b/contracts/connection-router/src/contract/execute.rs index 861a98e70..c867ef987 100644 --- a/contracts/connection-router/src/contract/execute.rs +++ b/contracts/connection-router/src/contract/execute.rs @@ -1,6 +1,6 @@ use std::vec; -use cosmwasm_std::{to_json_binary, Addr, DepsMut, MessageInfo, Response, StdResult, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, DepsMut, MessageInfo, Response, StdResult, WasmMsg}; use error_stack::report; use itertools::Itertools; @@ -190,7 +190,7 @@ where Ok(WasmMsg::Execute { contract_addr: gateway.to_string(), - msg: to_json_binary(&gateway_api::msg::ExecuteMsg::RouteMessages( + msg: to_binary(&gateway_api::msg::ExecuteMsg::RouteMessages( msgs.cloned().collect(), )) .expect("must serialize message"), diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index 423ddf19b..795aae79a 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -60,7 +60,7 @@ pub enum Error { mod internal { use aggregate_verifier::client::Verifier; use connection_router_api::client::Router; - use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; + use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use error_stack::{Result, ResultExt}; use gateway_api::msg::{ExecuteMsg, QueryMsg}; @@ -125,7 +125,7 @@ mod internal { match msg { QueryMsg::GetOutgoingMessages { message_ids } => { let msgs = contract::query::get_outgoing_messages(deps.storage, message_ids)?; - to_json_binary(&msgs).change_context(Error::SerializeResponse) + to_binary(&msgs).change_context(Error::SerializeResponse) } } } diff --git a/contracts/gateway/tests/contract.rs b/contracts/gateway/tests/contract.rs index 1b346988a..de4355157 100644 --- a/contracts/gateway/tests/contract.rs +++ b/contracts/gateway/tests/contract.rs @@ -9,7 +9,7 @@ use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier} #[cfg(not(feature = "generate_golden_files"))] use cosmwasm_std::Response; use cosmwasm_std::{ - from_json, to_json_binary, Addr, ContractResult, DepsMut, QuerierResult, WasmQuery, + from_binary, to_binary, Addr, ContractResult, DepsMut, QuerierResult, WasmQuery, }; use itertools::Itertools; use serde::Serialize; @@ -146,7 +146,7 @@ fn successful_route_outgoing() { if msgs.is_empty() { assert_eq!( query_response.unwrap(), - to_json_binary::>(&vec![]).unwrap() + to_binary::>(&vec![]).unwrap() ) } else { assert!(query_response.is_err()); @@ -173,7 +173,7 @@ fn successful_route_outgoing() { // check all outgoing messages are stored because the router (sender) is implicitly trusted iter::repeat(query(deps.as_ref(), mock_env().clone(), query_msg).unwrap()) .take(2) - .for_each(|response| assert_eq!(response, to_json_binary(&msgs).unwrap())); + .for_each(|response| assert_eq!(response, to_binary(&msgs).unwrap())); } let golden_file = "tests/test_route_outgoing.json"; @@ -423,8 +423,8 @@ fn update_query_handler( ) { let handler = move |msg: &WasmQuery| match msg { WasmQuery::Smart { msg, .. } => { - let result = handler(from_json(msg).expect("should not fail to deserialize")) - .map(|response| to_json_binary(&response).expect("should not fail to serialize")); + let result = handler(from_binary(msg).expect("should not fail to deserialize")) + .map(|response| to_binary(&response).expect("should not fail to serialize")); QuerierResult::Ok(ContractResult::from(result)) } diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index f142b37ab..868d8ad88 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, }; use crate::{ @@ -94,8 +94,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetProof { multisig_session_id, - } => to_json_binary(&query::get_proof(deps, multisig_session_id)?), - QueryMsg::GetWorkerSet {} => to_json_binary(&query::get_worker_set(deps)?), + } => to_binary(&query::get_proof(deps, multisig_session_id)?), + QueryMsg::GetWorkerSet {} => to_binary(&query::get_worker_set(deps)?), } } diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/execute.rs index cb9745957..d0d8d569e 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/execute.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use cosmwasm_std::{ - to_json_binary, wasm_execute, Addr, DepsMut, Env, MessageInfo, QuerierWrapper, QueryRequest, + to_binary, wasm_execute, Addr, DepsMut, Env, MessageInfo, QuerierWrapper, QueryRequest, Response, Storage, SubMsg, WasmQuery, }; @@ -89,7 +89,7 @@ fn get_messages( let query = gateway_api::msg::QueryMsg::GetOutgoingMessages { message_ids }; let messages: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: gateway.into(), - msg: to_json_binary(&query)?, + msg: to_binary(&query)?, }))?; assert!( @@ -116,7 +116,7 @@ fn get_workers_info(deps: &DepsMut, config: &Config) -> Result = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: config.service_registry.to_string(), - msg: to_json_binary(&active_workers_query)?, + msg: to_binary(&active_workers_query)?, }))?; let participants = workers @@ -136,7 +136,7 @@ fn get_workers_info(deps: &DepsMut, config: &Config) -> Result StdResult Result StdResult { match msg { QueryMsg::GetOutgoingMessages { message_ids: _ } => { let res = test_data::messages(); - to_json_binary(&res) + to_binary(&res) } } } diff --git a/contracts/multisig-prover/src/test/mocks/multisig.rs b/contracts/multisig-prover/src/test/mocks/multisig.rs index a9e337781..3d1db1eee 100644 --- a/contracts/multisig-prover/src/test/mocks/multisig.rs +++ b/contracts/multisig-prover/src/test/mocks/multisig.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, + to_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, StdResult, Uint64, }; use cw_multi_test::{App, Executor}; @@ -37,7 +37,7 @@ pub fn execute( msg: _, sig_verifier: _, chain_name: _, - } => Ok(Response::new().set_data(to_json_binary(&Uint64::one())?)), + } => Ok(Response::new().set_data(to_binary(&Uint64::one())?)), ExecuteMsg::SubmitSignature { session_id: _, signature: _, @@ -80,12 +80,12 @@ pub fn register_pub_keys(app: &mut App, multisig_address: Addr, workers: Vec StdResult { match msg { - QueryMsg::GetMultisig { session_id: _ } => to_json_binary(&query::query_success()), + QueryMsg::GetMultisig { session_id: _ } => to_binary(&query::query_success()), QueryMsg::GetWorkerSet { worker_set_id: _ } => unimplemented!(), QueryMsg::GetPublicKey { worker_address, key_type, - } => to_json_binary(&get_public_key_query_success( + } => to_binary(&get_public_key_query_success( deps, worker_address, key_type, diff --git a/contracts/multisig-prover/src/test/mocks/service_registry.rs b/contracts/multisig-prover/src/test/mocks/service_registry.rs index 3134cbc6d..7a23eb8c1 100644 --- a/contracts/multisig-prover/src/test/mocks/service_registry.rs +++ b/contracts/multisig-prover/src/test/mocks/service_registry.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, }; use cw_multi_test::{App, Executor}; use cw_storage_plus::Map; @@ -95,7 +95,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { }) .collect::>(); - to_json_binary(&workers) + to_binary(&workers) } QueryMsg::GetService { .. } => todo!(), QueryMsg::GetWorker { .. } => todo!(), diff --git a/contracts/multisig-prover/src/test/mocks/voting_verifier.rs b/contracts/multisig-prover/src/test/mocks/voting_verifier.rs index fa9d254ff..85eeced73 100644 --- a/contracts/multisig-prover/src/test/mocks/voting_verifier.rs +++ b/contracts/multisig-prover/src/test/mocks/voting_verifier.rs @@ -1,7 +1,7 @@ use axelar_wasm_std::{hash::Hash, operators::Operators, VerificationStatus}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, + to_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, StdResult, Uint256, }; use cw_multi_test::{App, Executor}; @@ -65,7 +65,7 @@ pub fn confirm_worker_set( pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetWorkerSetStatus { new_operators } => to_json_binary( + QueryMsg::GetWorkerSetStatus { new_operators } => to_binary( &CONFIRMED_WORKER_SETS .may_load(deps.storage, &new_operators.hash())? .map_or(VerificationStatus::None, |_| { diff --git a/contracts/multisig-prover/src/types.rs b/contracts/multisig-prover/src/types.rs index 638004c9c..2e94db66f 100644 --- a/contracts/multisig-prover/src/types.rs +++ b/contracts/multisig-prover/src/types.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use axelar_wasm_std::{Participant, Snapshot}; use connection_router_api::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, HexBinary, StdResult, Uint256}; +use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use multisig::{ key::{PublicKey, Signature}, @@ -65,7 +65,7 @@ impl KeyDeserialize for BatchId { type Output = BatchId; fn from_vec(value: Vec) -> StdResult { - Ok(from_json(value).expect("violated invariant: BatchID is not deserializable")) + Ok(from_binary(&value.into()).expect("violated invariant: BatchID is not deserializable")) } } diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index 82980ace7..55e3cbe3b 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, + to_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, Uint64, }; @@ -92,16 +92,14 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetMultisig { session_id } => { - to_json_binary(&query::get_multisig(deps, session_id)?) - } + QueryMsg::GetMultisig { session_id } => to_binary(&query::get_multisig(deps, session_id)?), QueryMsg::GetWorkerSet { worker_set_id } => { - to_json_binary(&query::get_worker_set(deps, worker_set_id)?) + to_binary(&query::get_worker_set(deps, worker_set_id)?) } QueryMsg::GetPublicKey { worker_address, key_type, - } => to_json_binary(&query::get_public_key( + } => to_binary(&query::get_public_key( deps, deps.api.addr_validate(&worker_address)?, key_type, @@ -114,7 +112,7 @@ mod tests { use std::vec; use cosmwasm_std::{ - from_json, + from_binary, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, Addr, Empty, OwnedDeps, WasmMsg, }; @@ -351,11 +349,11 @@ mod tests { let res = query_worker_set(&worker_set_1.id(), deps.as_ref()); assert!(res.is_ok()); - assert_eq!(worker_set_1, from_json(res.unwrap()).unwrap()); + assert_eq!(worker_set_1, from_binary(&res.unwrap()).unwrap()); let res = query_worker_set(&worker_set_2.id(), deps.as_ref()); assert!(res.is_ok()); - assert_eq!(worker_set_2, from_json(res.unwrap()).unwrap()); + assert_eq!(worker_set_2, from_binary(&res.unwrap()).unwrap()); for (key_type, _) in [ (KeyType::Ecdsa, worker_set_1_id), @@ -405,7 +403,7 @@ mod tests { assert_eq!(session.state, MultisigState::Pending); let res = res.unwrap(); - assert_eq!(res.data, Some(to_json_binary(&session.id).unwrap())); + assert_eq!(res.data, Some(to_binary(&session.id).unwrap())); assert_eq!(res.events.len(), 1); let event = res.events.first().unwrap(); @@ -466,7 +464,7 @@ mod tests { let expected_rewards_msg = WasmMsg::Execute { contract_addr: REWARDS_CONTRACT.to_string(), - msg: to_json_binary(&rewards::msg::ExecuteMsg::RecordParticipation { + msg: to_binary(&rewards::msg::ExecuteMsg::RecordParticipation { chain_name: chain_name.clone(), event_id: session_id.to_string().try_into().unwrap(), worker_address: signer.address.clone().into(), @@ -594,7 +592,7 @@ mod tests { let expected_rewards_msg = WasmMsg::Execute { contract_addr: REWARDS_CONTRACT.to_string(), - msg: to_json_binary(&rewards::msg::ExecuteMsg::RecordParticipation { + msg: to_binary(&rewards::msg::ExecuteMsg::RecordParticipation { chain_name: chain_name.clone(), event_id: session_id.to_string().try_into().unwrap(), worker_address: signer.address.clone().into(), @@ -703,7 +701,7 @@ mod tests { let res = query(deps.as_ref(), mock_env(), msg); assert!(res.is_ok()); - let query_res: Multisig = from_json(&res.unwrap()).unwrap(); + let query_res: Multisig = from_binary(&res.unwrap()).unwrap(); let session = SIGNING_SESSIONS .load(deps.as_ref().storage, session_id.into()) .unwrap(); @@ -801,7 +799,7 @@ mod tests { for (addr, _, _) in &expected_pub_keys { let res = query_registered_public_key(deps.as_ref(), addr.clone(), key_type); assert!(res.is_ok()); - ret_pub_keys.push(from_json(&res.unwrap()).unwrap()); + ret_pub_keys.push(from_binary(&res.unwrap()).unwrap()); } assert_eq!( expected_pub_keys @@ -856,7 +854,7 @@ mod tests { assert!(res.is_ok()); assert_eq!( PublicKey::try_from((KeyType::Ecdsa, new_pub_key.clone())).unwrap(), - from_json::(&res.unwrap()).unwrap() + from_binary::(&res.unwrap()).unwrap() ); // Register an ED25519 key, it should not affect our ECDSA key @@ -877,14 +875,14 @@ mod tests { assert!(res.is_ok()); assert_eq!( PublicKey::try_from((KeyType::Ed25519, ed25519_pub_key)).unwrap(), - from_json::(&res.unwrap()).unwrap() + from_binary::(&res.unwrap()).unwrap() ); let res = query_registered_public_key(deps.as_ref(), pub_keys[0].0.clone(), KeyType::Ecdsa); assert!(res.is_ok()); assert_eq!( PublicKey::try_from((KeyType::Ecdsa, new_pub_key)).unwrap(), - from_json::(&res.unwrap()).unwrap() + from_binary::(&res.unwrap()).unwrap() ); } diff --git a/contracts/multisig/src/contract/execute.rs b/contracts/multisig/src/contract/execute.rs index c36cbe612..ec1d4bb93 100644 --- a/contracts/multisig/src/contract/execute.rs +++ b/contracts/multisig/src/contract/execute.rs @@ -69,7 +69,7 @@ pub fn start_signing_session( }; Ok(Response::new() - .set_data(to_json_binary(&session_id)?) + .set_data(to_binary(&session_id)?) .add_event(event.into())) } @@ -211,7 +211,7 @@ fn signing_response( ) -> Result { let rewards_msg = WasmMsg::Execute { contract_addr: rewards_contract, - msg: to_json_binary(&rewards::msg::ExecuteMsg::RecordParticipation { + msg: to_binary(&rewards::msg::ExecuteMsg::RecordParticipation { chain_name: session.chain_name, event_id: session .id diff --git a/contracts/multisig/src/signing.rs b/contracts/multisig/src/signing.rs index 67c6126e3..888d67c7d 100644 --- a/contracts/multisig/src/signing.rs +++ b/contracts/multisig/src/signing.rs @@ -135,7 +135,7 @@ fn signers_weight(signatures: &HashMap, worker_set: &WorkerSe mod tests { use cosmwasm_std::{ testing::{MockQuerier, MockStorage}, - to_json_binary, Addr, HexBinary, QuerierWrapper, + to_binary, Addr, HexBinary, QuerierWrapper, }; use crate::{ @@ -279,7 +279,7 @@ mod tests { for verification in [true, false] { let mut querier = MockQuerier::default(); - querier.update_wasm(move |_| Ok(to_json_binary(&verification).into()).into()); + querier.update_wasm(move |_| Ok(to_binary(&verification).into()).into()); let sig_verifier = Some(SignatureVerifier { address: Addr::unchecked("verifier".to_string()), querier: QuerierWrapper::new(&querier), diff --git a/contracts/nexus-gateway/src/contract/execute.rs b/contracts/nexus-gateway/src/contract/execute.rs index 2fa86565c..fb426f8e5 100644 --- a/contracts/nexus-gateway/src/contract/execute.rs +++ b/contracts/nexus-gateway/src/contract/execute.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_json_binary, Addr, Response, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, Response, WasmMsg}; use error_stack::report; use crate::error::ContractError; @@ -32,7 +32,7 @@ where Ok(Response::new().add_message(WasmMsg::Execute { contract_addr: self.config.router.to_string(), - msg: to_json_binary(&connection_router_api::msg::ExecuteMsg::RouteMessages(msgs)) + msg: to_binary(&connection_router_api::msg::ExecuteMsg::RouteMessages(msgs)) .expect("must serialize route-messages message"), funds: vec![], })) @@ -67,7 +67,7 @@ where #[cfg(test)] mod test { - use cosmwasm_std::{from_json, CosmosMsg}; + use cosmwasm_std::{from_binary, CosmosMsg}; use hex::decode; use connection_router_api::CrossChainId; @@ -166,7 +166,7 @@ mod test { funds, }) => { if let Ok(connection_router_api::msg::ExecuteMsg::RouteMessages(msgs)) = - from_json(msg) + from_binary(msg) { return *contract_addr == Addr::unchecked("router") && msgs.len() == 2 diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index 7c8883575..45c2ca45e 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -1,9 +1,7 @@ use axelar_wasm_std::nonempty; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_json_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response, -}; +use cosmwasm_std::{to_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response}; use error_stack::ResultExt; use itertools::Itertools; @@ -135,7 +133,7 @@ pub fn query( match msg { QueryMsg::RewardsPool { pool_id } => { let pool = query::rewards_pool(deps.storage, pool_id, env.block.height)?; - to_json_binary(&pool) + to_binary(&pool) .change_context(ContractError::SerializeResponse) .map_err(axelar_wasm_std::ContractError::from) } diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index 246b2f57b..ab465c3bc 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Order, Response, + to_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Order, Response, Uint128, }; @@ -356,15 +356,14 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_json_binary(&query::get_active_workers(deps, service_name, chain_name)?) + } => to_binary(&query::get_active_workers(deps, service_name, chain_name)?) .map_err(|err| err.into()), QueryMsg::GetWorker { service_name, worker, - } => to_json_binary(&query::get_worker(deps, worker, service_name)?) - .map_err(|err| err.into()), + } => to_binary(&query::get_worker(deps, worker, service_name)?).map_err(|err| err.into()), QueryMsg::GetService { service_name } => { - to_json_binary(&query::get_service(deps, service_name)?).map_err(|err| err.into()) + to_binary(&query::get_service(deps, service_name)?).map_err(|err| err.into()) } } } diff --git a/contracts/service-registry/src/helpers.rs b/contracts/service-registry/src/helpers.rs index 7247c3fdf..b5fce9b31 100644 --- a/contracts/service-registry/src/helpers.rs +++ b/contracts/service-registry/src/helpers.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use crate::msg::ExecuteMsg; @@ -16,7 +16,7 @@ impl ServiceRegistry { } pub fn call>(&self, msg: T) -> StdResult { - let msg = to_json_binary(&msg.into())?; + let msg = to_binary(&msg.into())?; Ok(WasmMsg::Execute { contract_addr: self.addr().into(), msg, diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 55ea00114..5a1338b90 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Attribute, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, + to_binary, Attribute, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, }; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -65,12 +65,12 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::GetMessagesStatus { messages } => { - to_json_binary(&query::messages_status(deps, &messages)?) + to_binary(&query::messages_status(deps, &messages)?) } QueryMsg::GetWorkerSetStatus { new_operators } => { - to_json_binary(&query::worker_set_status(deps, &new_operators)?) + to_binary(&query::worker_set_status(deps, &new_operators)?) } - QueryMsg::GetCurrentThreshold => to_json_binary(&query::voting_threshold(deps)?), + QueryMsg::GetCurrentThreshold => to_binary(&query::voting_threshold(deps)?), } } @@ -78,7 +78,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { mod test { use cosmwasm_std::{ - from_json, + from_binary, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, Addr, Empty, Fraction, OwnedDeps, Uint128, Uint64, WasmQuery, }; @@ -159,7 +159,7 @@ mod test { deps.querier.update_wasm(move |wq| match wq { WasmQuery::Smart { contract_addr, .. } if contract_addr == SERVICE_REGISTRY_ADDRESS => { - Ok(to_json_binary( + Ok(to_binary( &workers .clone() .into_iter() @@ -249,7 +249,7 @@ mod test { }; let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); - let reply: VerifyMessagesResponse = from_json(res.data.unwrap()).unwrap(); + let reply: VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); assert_eq!(reply.verification_statuses.len(), 2); assert_eq!( reply.verification_statuses, @@ -432,8 +432,8 @@ mod test { ); assert!(res.is_ok()); - let res: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let res: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { @@ -470,8 +470,8 @@ mod test { ); assert!(res.is_ok()); - let res: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let res: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { @@ -507,7 +507,7 @@ mod test { let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); - let reply: VerifyMessagesResponse = from_json(res.data.unwrap()).unwrap(); + let reply: VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); assert_eq!(reply.verification_statuses.len(), messages.len()); assert_eq!( @@ -518,8 +518,8 @@ mod test { .collect::>() ); - let statuses: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let statuses: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { @@ -572,8 +572,8 @@ mod test { ) .unwrap(); - let statuses: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let statuses: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { @@ -614,8 +614,8 @@ mod test { let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg); assert!(res.is_ok()); - let res: VerificationStatus = from_json( - query( + let res: VerificationStatus = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetWorkerSetStatus { @@ -665,8 +665,8 @@ mod test { ); assert!(res.is_ok()); - let res: VerificationStatus = from_json( - query( + let res: VerificationStatus = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetWorkerSetStatus { @@ -719,8 +719,8 @@ mod test { ); assert!(res.is_ok()); - let res: VerificationStatus = from_json( - query( + let res: VerificationStatus = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetWorkerSetStatus { @@ -773,8 +773,8 @@ mod test { ); assert!(res.is_ok()); - let res: VerificationStatus = from_json( - query( + let res: VerificationStatus = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetWorkerSetStatus { @@ -820,8 +820,8 @@ mod test { ); assert!(res.is_ok()); - let res: VerificationStatus = from_json( - query( + let res: VerificationStatus = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetWorkerSetStatus { @@ -912,7 +912,7 @@ mod test { let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCurrentThreshold).unwrap(); - let threshold: MajorityThreshold = from_json(res).unwrap(); + let threshold: MajorityThreshold = from_binary(&res).unwrap(); assert_eq!(threshold, new_voting_threshold); } @@ -984,8 +984,8 @@ mod test { ) .unwrap(); - let res: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let res: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { @@ -1074,8 +1074,8 @@ mod test { ) .unwrap(); - let res: Vec<(CrossChainId, VerificationStatus)> = from_json( - query( + let res: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( deps.as_ref(), mock_env(), QueryMsg::GetMessagesStatus { diff --git a/contracts/voting-verifier/src/execute.rs b/contracts/voting-verifier/src/execute.rs index 28659294d..e1afc2a67 100644 --- a/contracts/voting-verifier/src/execute.rs +++ b/contracts/voting-verifier/src/execute.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - to_json_binary, Addr, Deps, DepsMut, Env, MessageInfo, OverflowError, OverflowOperation, + to_binary, Addr, Deps, DepsMut, Env, MessageInfo, OverflowError, OverflowOperation, QueryRequest, Response, Storage, WasmMsg, WasmQuery, }; @@ -109,7 +109,7 @@ pub fn verify_messages( .map(|message| message_status(deps.as_ref(), &message).map(|status| (status, message))) .collect::, _>>()?; - let response = Response::new().set_data(to_json_binary(&VerifyMessagesResponse { + let response = Response::new().set_data(to_binary(&VerifyMessagesResponse { verification_statuses: messages .iter() .map(|(status, message)| (message.cc_id.to_owned(), status.to_owned())) @@ -213,7 +213,7 @@ pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result Result Result { @@ -250,7 +250,7 @@ fn take_snapshot(deps: Deps, chain: &ChainName) -> Result = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: config.service_registry_contract.to_string(), - msg: to_json_binary(&active_workers_query)?, + msg: to_binary(&active_workers_query)?, }))?; let participants = workers diff --git a/packages/axelar-wasm-std/src/operators.rs b/packages/axelar-wasm-std/src/operators.rs index 37c0a87e7..ec58163c7 100644 --- a/packages/axelar-wasm-std/src/operators.rs +++ b/packages/axelar-wasm-std/src/operators.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, HexBinary, Uint256}; +use cosmwasm_std::{to_binary, HexBinary, Uint256}; use sha3::{Digest, Keccak256}; @@ -24,8 +24,7 @@ impl Operators { pub fn hash(&self) -> Hash { let mut hasher = Keccak256::new(); hasher.update( - to_json_binary(&self.weights_by_addresses) - .expect("could not serialize serializable object"), + to_binary(&self.weights_by_addresses).expect("could not serialize serializable object"), ); hasher.update(self.threshold.to_be_bytes()); hasher.finalize().into() diff --git a/packages/axelar-wasm-std/src/snapshot.rs b/packages/axelar-wasm-std/src/snapshot.rs index 56e67f4fa..d267a1e9d 100644 --- a/packages/axelar-wasm-std/src/snapshot.rs +++ b/packages/axelar-wasm-std/src/snapshot.rs @@ -65,7 +65,7 @@ impl Snapshot { #[cfg(test)] mod tests { - use cosmwasm_std::{from_json, to_json_binary, Uint64}; + use cosmwasm_std::{from_binary, to_binary, Uint64}; use crate::Threshold; @@ -133,8 +133,8 @@ mod tests { default_participants(), ); - let serialized = to_json_binary(&snapshot).unwrap(); - let deserialized: Snapshot = from_json(serialized).unwrap(); + let serialized = to_binary(&snapshot).unwrap(); + let deserialized: Snapshot = from_binary(&serialized).unwrap(); assert_eq!(snapshot, deserialized); } diff --git a/packages/connection-router-api/src/client.rs b/packages/connection-router-api/src/client.rs index e756a318d..a0a8cad01 100644 --- a/packages/connection-router-api/src/client.rs +++ b/packages/connection-router-api/src/client.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_json_binary, Addr, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, WasmMsg}; use crate::msg::ExecuteMsg; use crate::Message; @@ -11,7 +11,7 @@ impl Router { fn execute(&self, msg: &ExecuteMsg) -> WasmMsg { WasmMsg::Execute { contract_addr: self.address.to_string(), - msg: to_json_binary(&msg).expect("msg should always be serializable"), + msg: to_binary(&msg).expect("msg should always be serializable"), funds: vec![], } } diff --git a/packages/connection-router-api/src/primitives.rs b/packages/connection-router-api/src/primitives.rs index 0b4791ae1..4f7505d35 100644 --- a/packages/connection-router-api/src/primitives.rs +++ b/packages/connection-router-api/src/primitives.rs @@ -277,7 +277,7 @@ impl ChainEndpoint { mod tests { use super::*; - use cosmwasm_std::to_json_vec; + use cosmwasm_std::to_vec; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use sha3::{Digest, Sha3_256}; @@ -292,7 +292,7 @@ mod tests { let msg = dummy_message(); assert_eq!( - hex::encode(Sha3_256::digest(to_json_vec(&msg).unwrap())), + hex::encode(Sha3_256::digest(to_vec(&msg).unwrap())), expected_message_hash ); } diff --git a/packages/signature-verifier-api/src/client.rs b/packages/signature-verifier-api/src/client.rs index 14851c740..3b549ee89 100644 --- a/packages/signature-verifier-api/src/client.rs +++ b/packages/signature-verifier-api/src/client.rs @@ -1,7 +1,5 @@ use cosmwasm_schema::serde::de::DeserializeOwned; -use cosmwasm_std::{ - to_json_binary, Addr, HexBinary, QuerierWrapper, QueryRequest, Uint64, WasmQuery, -}; +use cosmwasm_std::{to_binary, Addr, HexBinary, QuerierWrapper, QueryRequest, Uint64, WasmQuery}; use error_stack::{Result, ResultExt}; use crate::msg::QueryMsg; @@ -16,7 +14,7 @@ impl SignatureVerifier<'_> { self.querier .query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_json_binary(msg).expect("msg should always be serializable"), + msg: to_binary(msg).expect("msg should always be serializable"), })) .change_context(Error::QuerySignatureVerifier) } From 43a5a7f84fec4908e58f09f0c24f837cd4385fcb Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Wed, 10 Apr 2024 23:42:25 -0400 Subject: [PATCH 15/27] refactor: reduce wait durations during unit tests for faster execution (#342) Co-authored-by: Milap Sheth --- ampd/src/broadcaster.rs | 7 ++++++- ampd/src/event_sub.rs | 2 +- ampd/src/queue/queued_broadcaster.rs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ampd/src/broadcaster.rs b/ampd/src/broadcaster.rs index f2eaa4cb4..d83acd592 100644 --- a/ampd/src/broadcaster.rs +++ b/ampd/src/broadcaster.rs @@ -272,6 +272,7 @@ mod tests { use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; use ecdsa::SigningKey; use rand::rngs::OsRng; + use std::time::Duration; use tokio::test; use tonic::Status; @@ -449,7 +450,11 @@ mod tests { acc_number: 0, acc_sequence: 0, pub_key: (key_id.to_string(), pub_key), - config: Config::default(), + config: Config { + broadcast_interval: Duration::from_secs(0), + tx_fetch_interval: Duration::from_secs(0), + ..Config::default() + }, }; let msgs = vec![dummy_msg()]; diff --git a/ampd/src/event_sub.rs b/ampd/src/event_sub.rs index 9a3f161ee..95fa511b4 100644 --- a/ampd/src/event_sub.rs +++ b/ampd/src/event_sub.rs @@ -411,7 +411,7 @@ mod tests { let event_publisher = EventPublisher::new(mock_client, block_count * event_count_per_block); let mut client = event_publisher .start_from(block_height) - .poll_interval(Duration::new(0, 1e8 as u32)); + .poll_interval(Duration::new(0, 1e7 as u32)); let mut stream = client.subscribe(); let child_token = token.child_token(); diff --git a/ampd/src/queue/queued_broadcaster.rs b/ampd/src/queue/queued_broadcaster.rs index 0f7db30d7..1ec493610 100644 --- a/ampd/src/queue/queued_broadcaster.rs +++ b/ampd/src/queue/queued_broadcaster.rs @@ -239,7 +239,7 @@ mod test { async fn should_broadcast_when_broadcast_interval_has_been_reached() { let tx_count = 9; let batch_gas_limit = 100; - let broadcast_interval = Duration::from_secs(1); + let broadcast_interval = Duration::from_millis(100); let gas_limit = 10; let mut broadcaster = MockBroadcaster::new(); @@ -372,7 +372,7 @@ mod test { assert!(client.run().await.is_ok()); }); - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(100)).await; driver.force_broadcast().await.unwrap(); drop(tx); From f6b9c2e6782ef4f3310e0afc2e1c4c9800cfa985 Mon Sep 17 00:00:00 2001 From: Sammy Date: Thu, 11 Apr 2024 15:35:14 +0800 Subject: [PATCH 16/27] refactor(client): add the client package and migrate for the aggregate-verifier contract (#338) * refactor(client): add the client package and migrate for the aggregate-verifier contract * delete weird file * implement From trait for client * fix clippy --- Cargo.lock | 13 ++ Cargo.toml | 1 + contracts/aggregate-verifier/Cargo.toml | 1 + contracts/aggregate-verifier/src/client.rs | 198 ++++++++++++------- contracts/aggregate-verifier/src/contract.rs | 12 +- contracts/aggregate-verifier/src/lib.rs | 5 +- contracts/gateway/Cargo.toml | 2 + contracts/gateway/src/contract.rs | 7 +- contracts/gateway/src/contract/execute.rs | 16 +- packages/client/Cargo.toml | 15 ++ packages/client/src/lib.rs | 57 ++++++ 11 files changed, 236 insertions(+), 91 deletions(-) create mode 100644 packages/client/Cargo.toml create mode 100644 packages/client/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 183d90124..3b8afdc55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,7 @@ version = "0.1.0" dependencies = [ "axelar-wasm-std", "axelar-wasm-std-derive", + "client", "connection-router-api", "cosmwasm-schema", "cosmwasm-std", @@ -1332,6 +1333,16 @@ dependencies = [ "cc", ] +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "error-stack", + "serde", + "thiserror", +] + [[package]] name = "codespan" version = "0.11.1" @@ -3327,6 +3338,8 @@ version = "0.1.0" dependencies = [ "aggregate-verifier", "axelar-wasm-std", + "axelar-wasm-std-derive", + "client", "connection-router-api", "cosmwasm-schema", "cosmwasm-std", diff --git a/Cargo.toml b/Cargo.toml index 12b4ded55..ba0e51ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ gateway = { version = "^0.1.0", path = "contracts/gateway" } gateway-api = { version = "^0.1.0", path = "packages/gateway-api" } connection-router-api = { version = "^0.1.0", path = "packages/connection-router-api" } report = { version = "^0.1.0", path = "packages/report" } +client = { version = "^0.1.0", path = "packages/client" } rewards = { version = "^0.1.0", path = "contracts/rewards" } thiserror = "1.0.47" serde = { version = "1.0.145", default-features = false, features = ["derive"] } diff --git a/contracts/aggregate-verifier/Cargo.toml b/contracts/aggregate-verifier/Cargo.toml index ce312c9a8..e46534689 100644 --- a/contracts/aggregate-verifier/Cargo.toml +++ b/contracts/aggregate-verifier/Cargo.toml @@ -35,6 +35,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] axelar-wasm-std = { workspace = true } axelar-wasm-std-derive = { workspace = true } +client = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/aggregate-verifier/src/client.rs b/contracts/aggregate-verifier/src/client.rs index 10228eb5e..e8f3bae6b 100644 --- a/contracts/aggregate-verifier/src/client.rs +++ b/contracts/aggregate-verifier/src/client.rs @@ -1,61 +1,60 @@ use axelar_wasm_std::utils::TryMapExt; use axelar_wasm_std::{FnExt, VerificationStatus}; use connection_router_api::{CrossChainId, Message}; -use cosmwasm_std::{to_binary, Addr, QuerierWrapper, QueryRequest, WasmMsg, WasmQuery}; +use cosmwasm_std::WasmMsg; use error_stack::{Result, ResultExt}; -use serde::de::DeserializeOwned; use std::collections::HashMap; -pub struct Verifier<'a> { - pub querier: QuerierWrapper<'a>, - pub address: Addr, +use crate::msg::{ExecuteMsg, QueryMsg}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("could not query the verifier contract")] + QueryVerifier, } -impl Verifier<'_> { - fn execute(&self, msg: &crate::msg::ExecuteMsg) -> WasmMsg { - WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_binary(msg).expect("msg should always be serializable"), - funds: vec![], - } +impl<'a> From> for Client<'a> { + fn from(client: client::Client<'a, ExecuteMsg, QueryMsg>) -> Self { + Client { client } } +} - fn query(&self, msg: &crate::msg::QueryMsg) -> Result { - self.querier - .query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.address.to_string(), - msg: to_binary(&msg).expect("msg should always be serializable"), - })) - .change_context(Error::QueryVerifier) - } +pub struct Client<'a> { + client: client::Client<'a, ExecuteMsg, QueryMsg>, +} - pub fn verify(&self, msgs: Vec) -> Option { - ignore_empty(msgs) - .map(|msgs| self.execute(&crate::msg::ExecuteMsg::VerifyMessages { messages: msgs })) +impl<'a> Client<'a> { + pub fn verify_messages(&self, msgs: Vec) -> Option { + ignore_empty(msgs).map(|msgs| { + self.client + .execute(&ExecuteMsg::VerifyMessages { messages: msgs }) + }) } - pub fn messages_with_status( + pub fn messages_status( &self, msgs: Vec, ) -> Result, Error> { ignore_empty(msgs.clone()) - .try_map(|msgs| self.query_message_status(msgs))? + .try_map(|msgs| self.query_messages_status(msgs))? .map(|status_by_id| ids_to_msgs(status_by_id, msgs)) .into_iter() .flatten() .then(Ok) } - fn query_message_status( + fn query_messages_status( &self, msgs: Vec, ) -> Result, Error> { - self.query::>( - &crate::msg::QueryMsg::GetMessagesStatus { messages: msgs }, - )? - .into_iter() - .collect::>() - .then(Ok) + self.client + .query::>(&QueryMsg::GetMessagesStatus { + messages: msgs, + }) + .change_context(Error::QueryVerifier)? + .into_iter() + .collect::>() + .then(Ok) } } @@ -81,65 +80,118 @@ fn ids_to_msgs( }) } -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("could not query the verifier contract")] - QueryVerifier, -} - #[cfg(test)] mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; + use cosmwasm_std::{from_binary, to_binary, Addr, DepsMut, QuerierWrapper, WasmQuery}; + use std::str::FromStr; + use axelar_wasm_std::VerificationStatus; use connection_router_api::{CrossChainId, CHAIN_NAME_DELIMITER}; - use cosmwasm_std::testing::MockQuerier; - use std::str::FromStr; + + use crate::contract::{instantiate, query}; + use crate::msg::InstantiateMsg; use super::*; #[test] - fn verifier_returns_error_when_query_fails() { - let querier = MockQuerier::default(); - let verifier = Verifier { - address: Addr::unchecked("not a contract"), - querier: QuerierWrapper::new(&querier), - }; + fn query_messages_status_returns_empty_statuses() { + let addr = "aggregate-verifier"; - let result = verifier.query::>( - &crate::msg::QueryMsg::GetMessagesStatus { messages: vec![] }, - ); + let mut querier = MockQuerier::default(); + querier.update_wasm(move |msg| match msg { + WasmQuery::Smart { contract_addr, msg } if contract_addr == addr => { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut(), Addr::unchecked("verifier")); + + deps.querier.update_wasm(|_| { + let res: Vec<(CrossChainId, VerificationStatus)> = vec![]; + Ok(to_binary(&res).into()).into() + }); + + let msg = from_binary::(msg).unwrap(); + Ok(query(deps.as_ref(), mock_env(), msg).into()).into() + } + _ => panic!("unexpected query: {:?}", msg), + }); - assert!(matches!( - result.unwrap_err().current_context(), - Error::QueryVerifier - )) + let client: Client = + client::Client::new(QuerierWrapper::new(&querier), Addr::unchecked(addr)).into(); + + assert!(client.query_messages_status(vec![]).unwrap().is_empty()); } - // due to contract updates or misconfigured verifier contract address the verifier might respond, - // but deliver an unexpected data type. This tests that the client returns an error in such cases. #[test] - fn verifier_returns_error_on_return_type_mismatch() { + fn query_messages_status_returns_some_statuses() { + let addr = "aggregate-verifier"; + let msg_1 = Message { + cc_id: CrossChainId::from_str(format!("eth{}0x1234", CHAIN_NAME_DELIMITER).as_str()) + .unwrap(), + source_address: "0x1234".parse().unwrap(), + destination_address: "0x5678".parse().unwrap(), + destination_chain: "eth".parse().unwrap(), + payload_hash: [0; 32], + }; + let msg_2 = Message { + cc_id: CrossChainId::from_str(format!("eth{}0x4321", CHAIN_NAME_DELIMITER).as_str()) + .unwrap(), + source_address: "0x4321".parse().unwrap(), + destination_address: "0x8765".parse().unwrap(), + destination_chain: "eth".parse().unwrap(), + payload_hash: [0; 32], + }; + let mut querier = MockQuerier::default(); - querier.update_wasm(|_| { - Ok(to_binary( - &CrossChainId::from_str(format!("eth{}0x1234", CHAIN_NAME_DELIMITER).as_str()) - .unwrap(), - ) - .into()) - .into() + querier.update_wasm(move |msg| match msg { + WasmQuery::Smart { contract_addr, msg } if contract_addr == addr => { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut(), Addr::unchecked("verifier")); + + deps.querier.update_wasm(|_| { + let res: Vec<(CrossChainId, VerificationStatus)> = vec![ + ( + CrossChainId::from_str( + format!("eth{}0x1234", CHAIN_NAME_DELIMITER).as_str(), + ) + .unwrap(), + VerificationStatus::SucceededOnChain, + ), + ( + CrossChainId::from_str( + format!("eth{}0x4321", CHAIN_NAME_DELIMITER).as_str(), + ) + .unwrap(), + VerificationStatus::FailedOnChain, + ), + ]; + Ok(to_binary(&res).into()).into() + }); + + let msg = from_binary::(msg).unwrap(); + Ok(query(deps.as_ref(), mock_env(), msg).into()).into() + } + _ => panic!("unexpected query: {:?}", msg), }); - let verifier = Verifier { - address: Addr::unchecked("not a contract"), - querier: QuerierWrapper::new(&querier), - }; + let client: Client = + client::Client::new(QuerierWrapper::new(&querier), Addr::unchecked(addr)).into(); - let result = verifier.query::>( - &crate::msg::QueryMsg::GetMessagesStatus { messages: vec![] }, + assert!( + client + .query_messages_status(vec![msg_1, msg_2]) + .unwrap() + .len() + == 2 ); + } + + fn instantiate_contract(deps: DepsMut, verifier: Addr) { + let env = mock_env(); + let info = mock_info("deployer", &[]); + let msg = InstantiateMsg { + verifier_address: verifier.into_string(), + }; - assert!(matches!( - result.unwrap_err().current_context(), - Error::QueryVerifier - )) + instantiate(deps, env, info, msg).unwrap(); } } diff --git a/contracts/aggregate-verifier/src/contract.rs b/contracts/aggregate-verifier/src/contract.rs index d238c4c2c..b9ba09db0 100644 --- a/contracts/aggregate-verifier/src/contract.rs +++ b/contracts/aggregate-verifier/src/contract.rs @@ -99,11 +99,13 @@ pub fn reply( pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetMessagesStatus { messages } => { - let verifier = CONFIG.load(deps.storage)?.verifier; - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: verifier.to_string(), - msg: to_binary(&voting_msg::QueryMsg::GetMessagesStatus { messages })?, - })) + let res: Vec<(CrossChainId, VerificationStatus)> = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: CONFIG.load(deps.storage)?.verifier.to_string(), + msg: to_binary(&voting_msg::QueryMsg::GetMessagesStatus { messages })?, + }))?; + + to_binary(&res) } } } diff --git a/contracts/aggregate-verifier/src/lib.rs b/contracts/aggregate-verifier/src/lib.rs index 1a0902c26..a720f6db9 100644 --- a/contracts/aggregate-verifier/src/lib.rs +++ b/contracts/aggregate-verifier/src/lib.rs @@ -1,4 +1,7 @@ -pub mod client; +mod client; + +pub use client::Client; + pub mod contract; pub mod error; pub mod msg; diff --git a/contracts/gateway/Cargo.toml b/contracts/gateway/Cargo.toml index 366bc6cff..e5586ac5b 100644 --- a/contracts/gateway/Cargo.toml +++ b/contracts/gateway/Cargo.toml @@ -37,6 +37,8 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] aggregate-verifier = { workspace = true, features = ["library"] } axelar-wasm-std = { workspace = true } +axelar-wasm-std-derive = { workspace = true } +client = { workspace = true } connection-router-api = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index 795aae79a..f275c6157 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -58,7 +58,7 @@ pub enum Error { } mod internal { - use aggregate_verifier::client::Verifier; + use client::Client; use connection_router_api::client::Router; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use error_stack::{Result, ResultExt}; @@ -100,10 +100,7 @@ mod internal { msg: ExecuteMsg, ) -> Result { let config = state::load_config(deps.storage).change_context(Error::ConfigMissing)?; - let verifier = Verifier { - address: config.verifier, - querier: deps.querier, - }; + let verifier = Client::new(deps.querier, config.verifier).into(); let router = Router { address: config.router, diff --git a/contracts/gateway/src/contract/execute.rs b/contracts/gateway/src/contract/execute.rs index 198696d7c..0c81508d0 100644 --- a/contracts/gateway/src/contract/execute.rs +++ b/contracts/gateway/src/contract/execute.rs @@ -1,4 +1,3 @@ -use aggregate_verifier::client::Verifier; use axelar_wasm_std::{FnExt, VerificationStatus}; use connection_router_api::client::Router; use connection_router_api::Message; @@ -10,14 +9,17 @@ use crate::contract::Error; use crate::events::GatewayEvent; use crate::state; -pub fn verify_messages(verifier: &Verifier, msgs: Vec) -> Result { +pub fn verify_messages( + verifier: &aggregate_verifier::Client, + msgs: Vec, +) -> Result { apply(verifier, msgs, |msgs_by_status| { verify(verifier, msgs_by_status) }) } pub(crate) fn route_incoming_messages( - verifier: &Verifier, + verifier: &aggregate_verifier::Client, router: &Router, msgs: Vec, ) -> Result { @@ -45,12 +47,12 @@ pub(crate) fn route_outgoing_messages( } fn apply( - verifier: &Verifier, + verifier: &aggregate_verifier::Client, msgs: Vec, action: impl Fn(Vec<(VerificationStatus, Vec)>) -> (Option, Vec), ) -> Result { check_for_duplicates(msgs)? - .then(|msgs| verifier.messages_with_status(msgs)) + .then(|msgs| verifier.messages_status(msgs)) .change_context(Error::MessageStatus)? .then(group_by_status) .then(action) @@ -86,7 +88,7 @@ fn group_by_status( } fn verify( - verifier: &Verifier, + verifier: &aggregate_verifier::Client, msgs_by_status: Vec<(VerificationStatus, Vec)>, ) -> (Option, Vec) { msgs_by_status @@ -98,7 +100,7 @@ fn verify( ) }) .then(flat_unzip) - .then(|(msgs, events)| (verifier.verify(msgs), events)) + .then(|(msgs, events)| (verifier.verify_messages(msgs), events)) } fn route( diff --git a/packages/client/Cargo.toml b/packages/client/Cargo.toml new file mode 100644 index 000000000..8cd9ca5c1 --- /dev/null +++ b/packages/client/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2021" +rust-version = { workspace = true } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/packages/client/src/lib.rs b/packages/client/src/lib.rs new file mode 100644 index 000000000..ed6c03477 --- /dev/null +++ b/packages/client/src/lib.rs @@ -0,0 +1,57 @@ +use std::marker::PhantomData; + +use cosmwasm_std::{to_binary, Addr, QuerierWrapper, QueryRequest, WasmMsg, WasmQuery}; +use error_stack::{Result, ResultExt}; +use serde::{de::DeserializeOwned, Serialize}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("could not query the contract")] + Query, +} + +pub struct Client<'a, M, Q> +where + M: Serialize, + Q: Serialize, +{ + pub querier: QuerierWrapper<'a>, + pub address: Addr, + execute_msg_type: PhantomData, + query_msg_type: PhantomData, +} + +impl<'a, M, Q> Client<'a, M, Q> +where + M: Serialize, + Q: Serialize, +{ + pub fn new(querier: QuerierWrapper<'a>, address: Addr) -> Self { + Client { + querier, + address, + execute_msg_type: PhantomData, + query_msg_type: PhantomData, + } + } + + pub fn execute(&self, msg: &M) -> WasmMsg { + WasmMsg::Execute { + contract_addr: self.address.to_string(), + msg: to_binary(msg).expect("msg should always be serializable"), + funds: vec![], + } + } + + pub fn query(&self, msg: &Q) -> Result + where + R: DeserializeOwned, + { + self.querier + .query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address.to_string(), + msg: to_binary(msg).expect("msg should always be serializable"), + })) + .change_context(Error::Query) + } +} From 3a795885b30d5a6b6b6c9ea52e19dbaa5f7f28ab Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:02:07 -0400 Subject: [PATCH 17/27] feat(minor-multisig-prover)!: allow dynamic update of signing threshold (#336) * feat(minor-multisig-prover)!: allow dynamic update of signing threshold --- contracts/multisig-prover/src/contract.rs | 159 +++++++++++++++++----- contracts/multisig-prover/src/execute.rs | 26 +++- contracts/multisig-prover/src/msg.rs | 10 +- 3 files changed, 158 insertions(+), 37 deletions(-) diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 868d8ad88..3835fad08 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -72,6 +72,12 @@ pub fn execute( execute::update_worker_set(deps, env) } ExecuteMsg::ConfirmWorkerSet {} => execute::confirm_worker_set(deps, info.sender), + ExecuteMsg::UpdateSigningThreshold { + new_signing_threshold, + } => { + execute::require_governance(&deps, info)?; + execute::update_signing_threshold(deps, new_signing_threshold) + } } .map_err(axelar_wasm_std::ContractError::from) } @@ -121,11 +127,11 @@ mod tests { use anyhow::Error; use cosmwasm_std::{ testing::{mock_dependencies, mock_env, mock_info}, - Addr, Fraction, Uint256, Uint64, + Addr, Fraction, Uint128, Uint256, Uint64, }; use cw_multi_test::{AppResponse, Executor}; - use axelar_wasm_std::Threshold; + use axelar_wasm_std::{MajorityThreshold, Threshold}; use connection_router_api::CrossChainId; use multisig::{msg::Signer, worker_set::WorkerSet}; @@ -165,6 +171,19 @@ mod tests { .execute_contract(sender, test_case.prover_address.clone(), &msg, &[]) } + fn execute_update_signing_threshold( + test_case: &mut TestCaseConfig, + sender: Addr, + new_signing_threshold: MajorityThreshold, + ) -> Result { + let msg = ExecuteMsg::UpdateSigningThreshold { + new_signing_threshold, + }; + test_case + .app + .execute_contract(sender, test_case.prover_address.clone(), &msg, &[]) + } + fn execute_construct_proof( test_case: &mut TestCaseConfig, message_ids: Option>, @@ -641,54 +660,126 @@ mod tests { } #[test] - fn test_construct_proof_updates_worker_set() { + fn test_construct_proof_no_worker_set() { let mut test_case = setup_test_case(); - let res = execute_update_worker_set(&mut test_case); - - assert!(res.is_ok()); + let res = execute_construct_proof(&mut test_case, None); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err() + .downcast::() + .unwrap() + .to_string(), + axelar_wasm_std::ContractError::from(ContractError::NoWorkerSet).to_string() + ); + } - let mut new_worker_set = test_data::operators(); - new_worker_set.pop(); - mocks::service_registry::set_active_workers( - &mut test_case.app, - test_case.service_registry_address.clone(), - new_worker_set, + #[test] + fn non_governance_should_not_be_able_to_call_update_signing_threshold() { + let mut test_case = setup_test_case(); + let res = execute_update_signing_threshold( + &mut test_case, + Addr::unchecked("random"), + Threshold::try_from((6, 10)).unwrap().try_into().unwrap(), ); + assert!(res.is_err()); + } - // worker set will update in construct proof. - let res = execute_construct_proof(&mut test_case, None).unwrap(); + #[test] + fn governance_should_be_able_to_call_update_signing_threshold() { + let mut test_case = setup_test_case(); + let governance = test_case.governance.clone(); + let res = execute_update_signing_threshold( + &mut test_case, + governance, + Threshold::try_from((6, 10)).unwrap().try_into().unwrap(), + ); + assert!(res.is_ok()); + } - let event = res - .events + /// Calls update_signing_threshold, increasing the threshold by one. + /// Returns (initial threshold, new threshold) + fn update_signing_threshold_increase_by_one( + test_case: &mut TestCaseConfig, + ) -> (Uint256, Uint256) { + let worker_set = query_get_worker_set(test_case).unwrap(); + let initial_threshold = worker_set.threshold; + let total_weight = worker_set + .signers .iter() - .find(|event| event.ty == "wasm-proof_under_construction"); + .fold(Uint256::zero(), |acc, signer| { + acc.checked_add(signer.1.weight).unwrap() + }); + let new_threshold = initial_threshold.checked_add(Uint256::one()).unwrap(); - assert!(event.is_some()); + let governance = test_case.governance.clone(); + execute_update_signing_threshold( + test_case, + governance.clone(), + Threshold::try_from(( + Uint64::try_from(Uint128::try_from(new_threshold).unwrap()).unwrap(), + Uint64::try_from(Uint128::try_from(total_weight).unwrap()).unwrap(), + )) + .unwrap() + .try_into() + .unwrap(), + ) + .unwrap(); + (initial_threshold, new_threshold) + } - // check that worker set is updated. - let worker_set = query_get_worker_set(&mut test_case); - assert!(worker_set.is_ok()); + #[test] + fn update_signing_threshold_should_not_change_current_threshold() { + let mut test_case = setup_test_case(); + execute_update_worker_set(&mut test_case).unwrap(); - let worker_set = worker_set.unwrap(); + let (initial_threshold, new_threshold) = + update_signing_threshold_increase_by_one(&mut test_case); + assert_ne!(initial_threshold, new_threshold); - let expected_worker_set = - test_operators_to_worker_set(test_data::operators(), test_case.app.block_info().height); + let worker_set = query_get_worker_set(&mut test_case).unwrap(); + assert_eq!(worker_set.threshold, initial_threshold); + } - assert_eq!(worker_set, expected_worker_set); + #[test] + fn update_signing_threshold_should_change_future_threshold() { + let mut test_case = setup_test_case(); + execute_update_worker_set(&mut test_case).unwrap(); + + let (initial_threshold, new_threshold) = + update_signing_threshold_increase_by_one(&mut test_case); + assert_ne!(initial_threshold, new_threshold); + + execute_update_worker_set(&mut test_case).unwrap(); + + let governance = test_case.governance.clone(); + confirm_worker_set(&mut test_case, governance).unwrap(); + + let worker_set = query_get_worker_set(&mut test_case).unwrap(); + assert_eq!(worker_set.threshold, new_threshold); } #[test] - fn test_construct_proof_no_worker_set() { + fn should_confirm_new_threshold_via_voting_verifier() { let mut test_case = setup_test_case(); - let res = execute_construct_proof(&mut test_case, None); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err() - .downcast::() - .unwrap() - .to_string(), - axelar_wasm_std::ContractError::from(ContractError::NoWorkerSet).to_string() + execute_update_worker_set(&mut test_case).unwrap(); + + let (initial_threshold, new_threshold) = + update_signing_threshold_increase_by_one(&mut test_case); + assert_ne!(initial_threshold, new_threshold); + + execute_update_worker_set(&mut test_case).unwrap(); + + mocks::voting_verifier::confirm_worker_set( + &mut test_case.app, + test_case.voting_verifier_address.clone(), + test_data::operators(), + new_threshold, ); + let res = confirm_worker_set(&mut test_case, Addr::unchecked("relayer")); + assert!(res.is_ok()); + + let worker_set = query_get_worker_set(&mut test_case).unwrap(); + assert_eq!(worker_set.threshold, new_threshold); } #[test] diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/execute.rs index d0d8d569e..c4fbf3fa0 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/execute.rs @@ -8,7 +8,7 @@ use cosmwasm_std::{ use itertools::Itertools; use multisig::{key::PublicKey, msg::Signer, worker_set::WorkerSet}; -use axelar_wasm_std::{snapshot, VerificationStatus}; +use axelar_wasm_std::{snapshot, MajorityThreshold, VerificationStatus}; use connection_router_api::{ChainName, CrossChainId, Message}; use service_registry::state::WeightedWorker; @@ -27,6 +27,13 @@ pub fn require_admin(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractEr } } +pub fn require_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { + match CONFIG.load(deps.storage)?.governance { + governance if governance == info.sender => Ok(()), + _ => Err(ContractError::Unauthorized), + } +} + pub fn construct_proof( deps: DepsMut, message_ids: Vec, @@ -288,7 +295,8 @@ pub fn should_update_worker_set( cur_workers: &WorkerSet, max_diff: usize, ) -> bool { - signers_symetric_difference_count(&new_workers.signers, &cur_workers.signers) > max_diff + new_workers.threshold != cur_workers.threshold + || signers_symetric_difference_count(&new_workers.signers, &cur_workers.signers) > max_diff } fn signers_symetric_difference_count( @@ -312,6 +320,20 @@ fn different_set_in_progress(storage: &dyn Storage, new_worker_set: &WorkerSet) false } +pub fn update_signing_threshold( + deps: DepsMut, + new_signing_threshold: MajorityThreshold, +) -> Result { + CONFIG.update( + deps.storage, + |mut config| -> Result { + config.signing_threshold = new_signing_threshold; + Ok(config) + }, + )?; + Ok(Response::new()) +} + #[cfg(test)] mod tests { use axelar_wasm_std::Threshold; diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index 18c83be66..e53d65468 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -27,9 +27,17 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { // Start building a proof that includes specified messages // Queries the gateway for actual message contents - ConstructProof { message_ids: Vec }, + ConstructProof { + message_ids: Vec, + }, UpdateWorkerSet, ConfirmWorkerSet, + // Updates the signing threshold. The threshold currently in use does not change. + // The worker set must be updated and confirmed for the change to take effect. + // Callable only by governance. + UpdateSigningThreshold { + new_signing_threshold: MajorityThreshold, + }, } #[cw_serde] From 4d5fb8f48d42110705346bf9b563eecd546a5e5a Mon Sep 17 00:00:00 2001 From: Houmaan Chamani Date: Thu, 11 Apr 2024 19:47:45 -0400 Subject: [PATCH 18/27] feat(multisig-prover)!: add monitoring field to prover instantiation (#345) --- Cargo.lock | 2 + contracts/multisig-prover/Cargo.toml | 1 + contracts/multisig-prover/src/contract.rs | 6 ++- contracts/multisig-prover/src/execute.rs | 1 + contracts/multisig-prover/src/msg.rs | 1 + contracts/multisig-prover/src/state.rs | 1 + .../multisig-prover/src/test/multicontract.rs | 1 + integration-tests/Cargo.toml | 1 + integration-tests/src/lib.rs | 1 + integration-tests/src/monitoring_contract.rs | 40 +++++++++++++++++++ .../src/multisig_prover_contract.rs | 1 + integration-tests/src/protocol.rs | 6 ++- integration-tests/tests/test_utils/mod.rs | 4 ++ 13 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 integration-tests/src/monitoring_contract.rs diff --git a/Cargo.lock b/Cargo.lock index 3b8afdc55..5a0de32a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3909,6 +3909,7 @@ dependencies = [ "gateway", "gateway-api", "k256", + "monitoring", "multisig", "multisig-prover", "report", @@ -4854,6 +4855,7 @@ dependencies = [ "hex", "itertools 0.11.0", "k256", + "monitoring", "multisig", "report", "serde_json", diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 5b060a405..ef859c7d2 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -48,6 +48,7 @@ gateway-api = { workspace = true } hex = { version = "0.4.3", default-features = false, features = [] } itertools = "0.11.0" k256 = { version = "0.13.1", features = ["ecdsa"] } +monitoring = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } serde_json = "1.0.89" diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 3835fad08..6833420fc 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -35,6 +35,7 @@ fn make_config( let governance = deps.api.addr_validate(&msg.governance_address)?; let gateway = deps.api.addr_validate(&msg.gateway_address)?; let multisig = deps.api.addr_validate(&msg.multisig_address)?; + let monitoring = deps.api.addr_validate(&msg.monitoring_address)?; let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; @@ -43,6 +44,7 @@ fn make_config( governance, gateway, multisig, + monitoring, service_registry, voting_verifier, destination_chain_id: msg.destination_chain_id, @@ -105,7 +107,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -#[cfg_attr(not(feature = "libary"), entry_point)] +#[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( deps: DepsMut, _env: Env, @@ -237,6 +239,7 @@ mod tests { let governance = "governance"; let gateway_address = "gateway_address"; let multisig_address = "multisig_address"; + let monitoring_address = "monitoring_address"; let service_registry_address = "service_registry_address"; let voting_verifier_address = "voting_verifier"; let destination_chain_id = Uint256::one(); @@ -258,6 +261,7 @@ mod tests { governance_address: governance.to_string(), gateway_address: gateway_address.to_string(), multisig_address: multisig_address.to_string(), + monitoring_address: monitoring_address.to_string(), voting_verifier_address: voting_verifier_address.to_string(), service_registry_address: service_registry_address.to_string(), destination_chain_id, diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/execute.rs index c4fbf3fa0..1d80d25ff 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/execute.rs @@ -457,6 +457,7 @@ mod tests { governance: Addr::unchecked("doesn't matter"), gateway: Addr::unchecked("doesn't matter"), multisig: Addr::unchecked("doesn't matter"), + monitoring: Addr::unchecked("doesn't matter"), service_registry: Addr::unchecked("doesn't matter"), voting_verifier: Addr::unchecked("doesn't matter"), destination_chain_id: Uint256::one(), diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index e53d65468..1e42f44ba 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -12,6 +12,7 @@ pub struct InstantiateMsg { pub governance_address: String, pub gateway_address: String, pub multisig_address: String, + pub monitoring_address: String, pub service_registry_address: String, pub voting_verifier_address: String, pub destination_chain_id: Uint256, diff --git a/contracts/multisig-prover/src/state.rs b/contracts/multisig-prover/src/state.rs index 06ae4f3d2..71a19bae2 100644 --- a/contracts/multisig-prover/src/state.rs +++ b/contracts/multisig-prover/src/state.rs @@ -16,6 +16,7 @@ pub struct Config { pub governance: Addr, pub gateway: Addr, pub multisig: Addr, + pub monitoring: Addr, pub service_registry: Addr, pub voting_verifier: Addr, pub destination_chain_id: Uint256, diff --git a/contracts/multisig-prover/src/test/multicontract.rs b/contracts/multisig-prover/src/test/multicontract.rs index b27d804a6..f2df1c3f0 100644 --- a/contracts/multisig-prover/src/test/multicontract.rs +++ b/contracts/multisig-prover/src/test/multicontract.rs @@ -163,6 +163,7 @@ fn instantiate_prover( governance_address: GOVERNANCE.to_string(), gateway_address, multisig_address, + monitoring_address: Addr::unchecked("monitoring").to_string(), service_registry_address, voting_verifier_address, destination_chain_id: test_data::destination_chain_id(), diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 1733e31d0..787424aea 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -38,6 +38,7 @@ error-stack = { workspace = true } gateway = { workspace = true } gateway-api = { workspace = true } k256 = { version = "0.13.1", features = ["ecdsa"] } +monitoring = { workspace = true } multisig = { workspace = true } multisig-prover = { workspace = true } report = { workspace = true } diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index f9192b121..05de894a3 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -1,6 +1,7 @@ pub mod connection_router_contract; pub mod contract; pub mod gateway_contract; +pub mod monitoring_contract; pub mod multisig_contract; pub mod multisig_prover_contract; pub mod protocol; diff --git a/integration-tests/src/monitoring_contract.rs b/integration-tests/src/monitoring_contract.rs new file mode 100644 index 000000000..9ce96f7d1 --- /dev/null +++ b/integration-tests/src/monitoring_contract.rs @@ -0,0 +1,40 @@ +use crate::contract::Contract; +use cosmwasm_std::Addr; +use cw_multi_test::{App, ContractWrapper, Executor}; +use monitoring::contract::{execute, instantiate, query}; + +#[derive(Clone)] +pub struct MonitoringContract { + pub contract_addr: Addr, +} + +impl MonitoringContract { + pub fn instantiate_contract(app: &mut App, governance: Addr) -> Self { + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let contract_addr = app + .instantiate_contract( + code_id, + Addr::unchecked("anyone"), + &monitoring::msg::InstantiateMsg { + governance_address: governance.to_string(), + }, + &[], + "monitoring", + None, + ) + .unwrap(); + + MonitoringContract { contract_addr } + } +} + +impl Contract for MonitoringContract { + type QMsg = monitoring::msg::QueryMsg; + type ExMsg = monitoring::msg::ExecuteMsg; + + fn contract_address(&self) -> Addr { + self.contract_addr.clone() + } +} diff --git a/integration-tests/src/multisig_prover_contract.rs b/integration-tests/src/multisig_prover_contract.rs index c59bcc1a9..07db92953 100644 --- a/integration-tests/src/multisig_prover_contract.rs +++ b/integration-tests/src/multisig_prover_contract.rs @@ -37,6 +37,7 @@ impl MultisigProverContract { governance_address: protocol.governance_address.to_string(), gateway_address: gateway_address.to_string(), multisig_address: protocol.multisig.contract_addr.to_string(), + monitoring_address: protocol.monitoring.contract_addr.to_string(), service_registry_address: protocol.service_registry.contract_addr.to_string(), voting_verifier_address: voting_verifier_address.to_string(), destination_chain_id: Uint256::zero(), diff --git a/integration-tests/src/protocol.rs b/integration-tests/src/protocol.rs index c3a79a493..f504117d1 100644 --- a/integration-tests/src/protocol.rs +++ b/integration-tests/src/protocol.rs @@ -3,8 +3,9 @@ use cosmwasm_std::Addr; use cw_multi_test::App; use crate::{ - connection_router_contract::ConnectionRouterContract, multisig_contract::MultisigContract, - rewards_contract::RewardsContract, service_registry_contract::ServiceRegistryContract, + connection_router_contract::ConnectionRouterContract, monitoring_contract::MonitoringContract, + multisig_contract::MultisigContract, rewards_contract::RewardsContract, + service_registry_contract::ServiceRegistryContract, }; pub struct Protocol { @@ -13,6 +14,7 @@ pub struct Protocol { pub connection_router: ConnectionRouterContract, pub router_admin_address: Addr, pub multisig: MultisigContract, + pub monitoring: MonitoringContract, pub service_registry: ServiceRegistryContract, pub service_name: nonempty::String, pub rewards: RewardsContract, diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index abf6a7f60..4502dafda 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -11,6 +11,7 @@ use cw_multi_test::{App, AppResponse, Executor}; use integration_tests::contract::Contract; use integration_tests::gateway_contract::GatewayContract; +use integration_tests::monitoring_contract::MonitoringContract; use integration_tests::multisig_contract::MultisigContract; use integration_tests::multisig_prover_contract::MultisigProverContract; use integration_tests::rewards_contract::RewardsContract; @@ -375,6 +376,8 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { SIGNATURE_BLOCK_EXPIRY, ); + let monitoring = MonitoringContract::instantiate_contract(&mut app, governance_address.clone()); + let service_registry = ServiceRegistryContract::instantiate_contract(&mut app, governance_address.clone()); @@ -384,6 +387,7 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { connection_router, router_admin_address, multisig, + monitoring, service_registry, service_name, rewards, From 1a0bad297061b6ab9fbec98edfac723a06b85854 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:42:24 -0400 Subject: [PATCH 19/27] feat: add trait for message id (#337) * implement trait for evm message id --- Cargo.lock | 2 + packages/axelar-wasm-std/Cargo.toml | 2 + packages/axelar-wasm-std/src/lib.rs | 1 + packages/axelar-wasm-std/src/msg_id/mod.rs | 47 +++++ .../src/msg_id/tx_hash_event_index.rs | 199 ++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 packages/axelar-wasm-std/src/msg_id/mod.rs create mode 100644 packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs diff --git a/Cargo.lock b/Cargo.lock index 5a0de32a8..1f11925b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,8 +668,10 @@ dependencies = [ "error-stack", "flagset", "hex", + "lazy_static", "num-traits", "rand", + "regex", "report", "schemars", "serde", diff --git a/packages/axelar-wasm-std/Cargo.toml b/packages/axelar-wasm-std/Cargo.toml index 144859ed4..32169afc6 100644 --- a/packages/axelar-wasm-std/Cargo.toml +++ b/packages/axelar-wasm-std/Cargo.toml @@ -34,7 +34,9 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } +lazy_static = "1.4.0" num-traits = { workspace = true } +regex = { version = "1.10.0", default-features = false, features = ["perf", "std"] } report = { workspace = true } schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index d6fb35755..2f0aacb72 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -13,6 +13,7 @@ pub mod flagset; mod fn_ext; pub mod hash; pub mod hex; +pub mod msg_id; pub mod nonempty; pub mod operators; pub mod snapshot; diff --git a/packages/axelar-wasm-std/src/msg_id/mod.rs b/packages/axelar-wasm-std/src/msg_id/mod.rs new file mode 100644 index 000000000..c12108c32 --- /dev/null +++ b/packages/axelar-wasm-std/src/msg_id/mod.rs @@ -0,0 +1,47 @@ +use std::{fmt::Display, str::FromStr}; + +use cosmwasm_schema::cw_serde; +use error_stack::Report; + +use self::tx_hash_event_index::HexTxHashAndEventIndex; + +pub mod tx_hash_event_index; + +#[derive(thiserror::Error)] +#[cw_serde] +pub enum Error { + #[error("invalid message id '{id}', expected format: {expected_format}")] + InvalidMessageID { id: String, expected_format: String }, + #[error("event index in message id '{0}' is larger than u32 max value")] + EventIndexOverflow(String), + #[error("invalid transaction hash in message id '{0}'")] + InvalidTxHash(String), +} + +/// Any message id format must implement this trait. +/// The implementation must satisfy the following invariants: +/// +/// * if m1 != m2 then from_str(m1) != from_str(m2) (two different strings cannot parse to the same message id) +/// +/// * if t1 != t2 then to_string(t1) != to_string(t2) (two different message ids cannot serialize to the same string) +/// +/// There should be only one string that can identify a given message. +/// Take extra care to handle things like leading 0s, casing, etc. +pub trait MessageId: FromStr + Display {} + +/// enum to pass to the router when registering a new chain +#[cw_serde] +pub enum MessageIdFormat { + HexTxHashAndEventIndex, + Base58TxDigestAndEventIndex, +} + +// function the router calls to verify msg ids +pub fn verify_msg_id(message_id: &str, format: &MessageIdFormat) -> Result<(), Report> { + match format { + MessageIdFormat::HexTxHashAndEventIndex => { + HexTxHashAndEventIndex::from_str(message_id).map(|_| ()) + } + MessageIdFormat::Base58TxDigestAndEventIndex => todo!(), + } +} diff --git a/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs new file mode 100644 index 000000000..25215f92d --- /dev/null +++ b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs @@ -0,0 +1,199 @@ +use core::fmt; +use std::{fmt::Display, str::FromStr}; + +use cosmwasm_std::HexBinary; +use error_stack::{Report, ResultExt}; +use lazy_static::lazy_static; +use regex::Regex; + +use super::Error; +use crate::{hash::Hash, nonempty}; + +pub struct HexTxHashAndEventIndex { + pub tx_hash: Hash, + pub event_index: u32, +} + +impl HexTxHashAndEventIndex { + pub fn tx_hash_as_hex(&self) -> nonempty::String { + format!("0x{}", HexBinary::from(self.tx_hash).to_hex()) + .try_into() + .expect("failed to convert tx hash to non-empty string") + } +} + +const PATTERN: &str = "^(0x[0-9a-f]{64})-(0|[1-9][0-9]*)$"; +lazy_static! { + static ref REGEX: Regex = Regex::new(PATTERN).expect("invalid regex"); +} + +impl FromStr for HexTxHashAndEventIndex { + type Err = Report; + + fn from_str(message_id: &str) -> Result + where + Self: Sized, + { + // the PATTERN has exactly two capture groups, so the groups can be extracted safely + let (_, [tx_id, event_index]) = REGEX + .captures(message_id) + .ok_or(Error::InvalidMessageID { + id: message_id.to_string(), + expected_format: PATTERN.to_string(), + })? + .extract(); + Ok(HexTxHashAndEventIndex { + tx_hash: HexBinary::from_hex(&tx_id[2..]) + .change_context(Error::InvalidTxHash(message_id.to_string()))? + .as_slice() + .try_into() + .map_err(|_| Error::InvalidTxHash(message_id.to_string()))?, + event_index: event_index + .parse() + .map_err(|_| Error::EventIndexOverflow(message_id.to_string()))?, + }) + } +} + +impl Display for HexTxHashAndEventIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "0x{}-{}", + HexBinary::from(self.tx_hash).to_hex(), + self.event_index + ) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + fn random_hash() -> String { + let mut bytes = vec![]; + for _ in 0..32 { + let byte: u8 = rand::random(); + bytes.push(byte) + } + format!("0x{}", HexBinary::from(bytes).to_hex()) + } + + fn random_event_index() -> u32 { + rand::random() + } + + #[test] + fn should_parse_msg_id() { + let res = HexTxHashAndEventIndex::from_str( + "0x7cedbb3799cd99636045c84c5c55aef8a138f107ac8ba53a08cad1070ba4385b-0", + ); + assert!(res.is_ok()); + + for _ in 0..1000 { + let tx_hash = random_hash(); + let event_index = random_event_index(); + let msg_id = format!("{}-{}", tx_hash, event_index); + + let res = HexTxHashAndEventIndex::from_str(&msg_id); + let parsed = res.unwrap(); + assert_eq!(parsed.event_index, event_index); + assert_eq!(parsed.tx_hash_as_hex(), tx_hash.try_into().unwrap(),); + assert_eq!(parsed.to_string(), msg_id); + } + } + + #[test] + fn should_not_parse_msg_id_with_wrong_length_tx_hash() { + let tx_hash = random_hash(); + let res = HexTxHashAndEventIndex::from_str(&format!("{}ff-{}", tx_hash, 1)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_uppercase_tx_hash() { + let tx_hash = &random_hash()[2..]; + let res = HexTxHashAndEventIndex::from_str(&format!("0x{}-{}", tx_hash.to_uppercase(), 1)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_non_hex_tx_hash() { + let msg_id = "82GKYvWv5EKm7jnYksHoh3u5M2RxHN2boPreM8Df4ej9-1"; + let res = HexTxHashAndEventIndex::from_str(msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_without_0x() { + let msg_id = "7cedbb3799cd99636045c84c5c55aef8a138f107ac8ba53a08cad1070ba4385b-1"; + let res = HexTxHashAndEventIndex::from_str(msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_missing_event_index() { + let msg_id = random_hash(); + let res = HexTxHashAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_wrong_separator() { + let tx_hash = random_hash(); + let event_index = random_event_index(); + let res = HexTxHashAndEventIndex::from_str(&format!("{}:{}", tx_hash, event_index)); + assert!(res.is_err()); + let res = HexTxHashAndEventIndex::from_str(&format!("{}_{}", tx_hash, event_index)); + assert!(res.is_err()); + let res = HexTxHashAndEventIndex::from_str(&format!("{}+{}", tx_hash, event_index)); + assert!(res.is_err()); + + for _ in 0..10 { + let random_sep: char = rand::random(); + if random_sep == '-' { + continue; + } + let res = HexTxHashAndEventIndex::from_str(&format!( + "{}{}{}", + tx_hash, random_sep, event_index + )); + assert!(res.is_err()); + } + } + + #[test] + fn should_not_parse_msg_id_with_event_index_with_leading_zeroes() { + let tx_hash = random_hash(); + let res = HexTxHashAndEventIndex::from_str(&format!("{}-01", tx_hash)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_non_integer_event_index() { + let tx_hash = random_hash(); + let res = HexTxHashAndEventIndex::from_str(&format!("{}-1.0", tx_hash)); + assert!(res.is_err()); + + let res = HexTxHashAndEventIndex::from_str(&format!("{}-0x00", tx_hash)); + assert!(res.is_err()); + + let res = HexTxHashAndEventIndex::from_str(&format!("{}-foobar", tx_hash)); + assert!(res.is_err()); + + let res = HexTxHashAndEventIndex::from_str(&format!("{}-true", tx_hash)); + assert!(res.is_err()); + + let res = HexTxHashAndEventIndex::from_str(&format!("{}-", tx_hash)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_overflowing_event_index() { + let event_index: u64 = u64::MAX; + let tx_hash = random_hash(); + let res = HexTxHashAndEventIndex::from_str(&format!("{}-{}", tx_hash, event_index)); + assert!(res.is_err()); + } +} From 5deb21d0d1de94e619f453d056f6633a5f1b5ede Mon Sep 17 00:00:00 2001 From: eguajardo Date: Fri, 12 Apr 2024 11:15:14 -0600 Subject: [PATCH 20/27] refactor(voting-verifier): cleanup unit tests (#346) * cleanup tests * renamed function --- contracts/voting-verifier/src/contract.rs | 238 ++++++++++++++++------ 1 file changed, 173 insertions(+), 65 deletions(-) diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 5a1338b90..4be1d3cfd 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -206,6 +206,16 @@ mod test { env } + fn msg_ids_and_statuses( + messages: Vec, + status: VerificationStatus, + ) -> Vec<(CrossChainId, VerificationStatus)> { + messages + .iter() + .map(|message| (message.cc_id.clone(), status)) + .collect::>() + } + #[test] fn should_fail_if_messages_are_not_from_same_source() { let workers = workers(2); @@ -276,15 +286,16 @@ mod test { fn should_not_verify_messages_if_in_progress() { let workers = workers(2); let mut deps = setup(workers.clone()); + let messages_count = 5; let messages_in_progress = 3; - let new_messages = 2; + let messages = messages(messages_count as u64); execute( deps.as_mut(), mock_env(), mock_info(SENDER, &[]), ExecuteMsg::VerifyMessages { - messages: messages(messages_in_progress), + messages: messages[0..messages_in_progress].to_vec(), // verify a subset of the messages }, ) .unwrap(); @@ -294,12 +305,12 @@ mod test { mock_env(), mock_info(SENDER, &[]), ExecuteMsg::VerifyMessages { - messages: messages(messages_in_progress + new_messages), // creates the same messages + some new ones + messages: messages.clone(), // verify all messages including the ones from previous execution }, ) .unwrap(); - let messages: Vec = serde_json::from_str( + let actual: Vec = serde_json::from_str( &res.events .into_iter() .find(|event| event.ty == "messages_poll_started") @@ -317,16 +328,24 @@ mod test { ) .unwrap(); - assert_eq!(messages.len() as u64, new_messages); + // messages starting after the ones already in progress + let expected = messages[messages_in_progress..] + .iter() + .cloned() + .map(|e| e.try_into().unwrap()) + .collect::>(); + + assert_eq!(actual, expected); } #[test] fn should_retry_if_message_not_verified() { let workers = workers(2); let mut deps = setup(workers.clone()); + let messages = messages(5); let msg = ExecuteMsg::VerifyMessages { - messages: messages(1), + messages: messages.clone(), }; execute( deps.as_mut(), @@ -346,10 +365,27 @@ mod test { ) .unwrap(); + // confirm it was not verified + let status: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( + deps.as_ref(), + mock_env(), + QueryMsg::GetMessagesStatus { + messages: messages.clone(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + status, + msg_ids_and_statuses(messages.clone(), VerificationStatus::FailedToVerify) + ); + // retries same message let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); - let messages: Vec = serde_json::from_str( + let actual: Vec = serde_json::from_str( &res.events .into_iter() .find(|event| event.ty == "messages_poll_started") @@ -367,7 +403,12 @@ mod test { ) .unwrap(); - assert_eq!(messages.len() as u64, 1); + let expected = messages + .into_iter() + .map(|e| e.try_into().unwrap()) + .collect::>(); + + assert_eq!(actual, expected); } #[test] @@ -496,27 +537,46 @@ mod test { } #[test] - fn should_query_message_statuses() { + fn should_query_status_none_when_not_verified() { let workers = workers(2); - let mut deps = setup(workers.clone()); + let deps = setup(workers.clone()); let messages = messages(10); - let msg = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; - - let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); - let reply: VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); - - assert_eq!(reply.verification_statuses.len(), messages.len()); + let statuses: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( + deps.as_ref(), + mock_env(), + QueryMsg::GetMessagesStatus { + messages: messages.clone(), + }, + ) + .unwrap(), + ) + .unwrap(); assert_eq!( - reply.verification_statuses, - messages - .iter() - .map(|message| (message.cc_id.clone(), VerificationStatus::None)) - .collect::>() + statuses, + msg_ids_and_statuses(messages, VerificationStatus::None) ); + } + + #[test] + fn should_query_status_in_progress_when_no_consensus_and_poll_not_ended() { + let workers = workers(2); + let mut deps = setup(workers.clone()); + + let messages = messages(10); + + // starts verification process + execute( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[]), + ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }, + ) + .unwrap(); let statuses: Vec<(CrossChainId, VerificationStatus)> = from_binary( &query( @@ -531,44 +591,36 @@ mod test { .unwrap(); assert_eq!( statuses, - messages - .iter() - .map(|message| (message.cc_id.clone(), VerificationStatus::InProgress)) - .collect::>() + msg_ids_and_statuses(messages.clone(), VerificationStatus::InProgress) ); + } - let msg = ExecuteMsg::Vote { - poll_id: Uint64::one().into(), - votes: (0..messages.len()) - .map(|i| { - if i % 2 == 0 { - Vote::SucceededOnChain - } else { - Vote::NotFound - } - }) - .collect::>(), - }; + #[test] + fn should_query_status_failed_to_verify_when_no_consensus_and_poll_ended() { + let workers = workers(2); + let mut deps = setup(workers.clone()); - workers.iter().for_each(|worker| { - execute( - deps.as_mut(), - mock_env(), - mock_info(worker.address.as_str(), &[]), - msg.clone(), - ) - .unwrap(); - }); + let messages = messages(10); - let msg = ExecuteMsg::EndPoll { - poll_id: Uint64::one().into(), - }; + // starts verification process + execute( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[]), + ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }, + ) + .unwrap(); + // end poll execute( deps.as_mut(), mock_env_expired(), mock_info(SENDER, &[]), - msg, + ExecuteMsg::EndPoll { + poll_id: Uint64::one().into(), + }, ) .unwrap(); @@ -583,24 +635,80 @@ mod test { .unwrap(), ) .unwrap(); - assert_eq!( statuses, - messages - .iter() - .enumerate() - .map(|(i, message)| ( - message.cc_id.clone(), - if i % 2 == 0 { - VerificationStatus::SucceededOnChain - } else { - VerificationStatus::NotFound - } - )) - .collect::>() + msg_ids_and_statuses(messages.clone(), VerificationStatus::FailedToVerify) ); } + #[test] + fn should_query_status_according_to_vote() { + for (consensus_vote, expected_status) in [ + (Vote::SucceededOnChain, VerificationStatus::SucceededOnChain), + (Vote::FailedOnChain, VerificationStatus::FailedOnChain), + (Vote::NotFound, VerificationStatus::NotFound), + ] { + let workers = workers(2); + let mut deps = setup(workers.clone()); + + let messages = messages(10); + + // starts verification process + execute( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[]), + ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }, + ) + .unwrap(); + + // all workers vote + let vote_msg = ExecuteMsg::Vote { + poll_id: Uint64::one().into(), + votes: vec![consensus_vote; messages.len()], + }; + workers.iter().for_each(|worker| { + execute( + deps.as_mut(), + mock_env(), + mock_info(worker.address.as_str(), &[]), + vote_msg.clone(), + ) + .unwrap(); + }); + + // end poll + execute( + deps.as_mut(), + mock_env_expired(), + mock_info(SENDER, &[]), + ExecuteMsg::EndPoll { + poll_id: Uint64::one().into(), + }, + ) + .unwrap(); + + // check status corresponds to votes + let statuses: Vec<(CrossChainId, VerificationStatus)> = from_binary( + &query( + deps.as_ref(), + mock_env(), + QueryMsg::GetMessagesStatus { + messages: messages.clone(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + statuses, + msg_ids_and_statuses(messages.clone(), expected_status) + ); + } + } + #[test] fn should_start_worker_set_confirmation() { let workers = workers(2); From 1be790aa833c478753953396bf7b811a011f2951 Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Fri, 12 Apr 2024 16:14:04 -0400 Subject: [PATCH 21/27] fix(patch-service-registry): pass params in correct order (#348) --- contracts/service-registry/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index ab465c3bc..b6b8e7ebb 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -361,7 +361,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&query::get_worker(deps, worker, service_name)?).map_err(|err| err.into()), + } => to_binary(&query::get_worker(deps, service_name, worker)?).map_err(|err| err.into()), QueryMsg::GetService { service_name } => { to_binary(&query::get_service(deps, service_name)?).map_err(|err| err.into()) } From 6624d3971f6cd6e28a7cf6df74157ed9ba696445 Mon Sep 17 00:00:00 2001 From: eguajardo Date: Tue, 16 Apr 2024 08:51:03 -0600 Subject: [PATCH 22/27] fix(minor-voting-verifier)!: remove error thrown when worker set already confirmed --- contracts/voting-verifier/src/contract.rs | 8 ++++---- contracts/voting-verifier/src/error.rs | 3 --- contracts/voting-verifier/src/execute.rs | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 4be1d3cfd..c86a84344 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -981,8 +981,8 @@ mod test { ); assert!(res.is_ok()); - // try again, should fail - let err = execute( + // try again, should return empty response + let res = execute( deps.as_mut(), mock_env_expired(), mock_info(SENDER, &[]), @@ -991,8 +991,8 @@ mod test { new_operators: operators.clone(), }, ) - .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::WorkerSetAlreadyConfirmed); + .unwrap(); + assert_eq!(res, Response::new()); } #[test] diff --git a/contracts/voting-verifier/src/error.rs b/contracts/voting-verifier/src/error.rs index e8c21fe6d..c94b2f019 100644 --- a/contracts/voting-verifier/src/error.rs +++ b/contracts/voting-verifier/src/error.rs @@ -37,9 +37,6 @@ pub enum ContractError { #[error(transparent)] VoteError(#[from] voting::Error), - #[error("worker set already confirmed")] - WorkerSetAlreadyConfirmed, - #[error("unauthorized")] Unauthorized, } diff --git a/contracts/voting-verifier/src/execute.rs b/contracts/voting-verifier/src/execute.rs index e1afc2a67..9d9055393 100644 --- a/contracts/voting-verifier/src/execute.rs +++ b/contracts/voting-verifier/src/execute.rs @@ -52,7 +52,7 @@ pub fn verify_worker_set( ) -> Result { let status = worker_set_status(deps.as_ref(), &new_operators)?; if status.is_confirmed() { - return Err(ContractError::WorkerSetAlreadyConfirmed); + return Ok(Response::new()); } let config = CONFIG.load(deps.storage)?; From 7e65688e7304a44436627fc3193483d24a10a14d Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Tue, 16 Apr 2024 11:56:25 -0400 Subject: [PATCH 23/27] feat(service-registry): jail worker (#349) Governance can jail workers. A jailed worker cannot unbond or claim stake. --- contracts/service-registry/src/contract.rs | 46 ++- contracts/service-registry/src/error.rs | 2 + contracts/service-registry/src/msg.rs | 5 + contracts/service-registry/src/state.rs | 413 ++++++++++++++------- contracts/service-registry/tests/tests.rs | 160 +++++++- 5 files changed, 475 insertions(+), 151 deletions(-) diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index b6b8e7ebb..209f64b35 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -94,6 +94,22 @@ pub fn execute( AuthorizationState::NotAuthorized, ) } + ExecuteMsg::JailWorkers { + workers, + service_name, + } => { + execute::require_governance(&deps, info)?; + let workers = workers + .into_iter() + .map(|worker| deps.api.addr_validate(&worker)) + .collect::, _>>()?; + execute::update_worker_authorization_status( + deps, + workers, + service_name, + AuthorizationState::Jailed, + ) + } ExecuteMsg::RegisterChainSupport { service_name, chains, @@ -224,10 +240,7 @@ pub mod execute { (&service_name.clone(), &info.sender.clone()), |sw| -> Result { match sw { - Some(worker) => Ok(Worker { - bonding_state: worker.bonding_state.add_bond(bond)?, - ..worker - }), + Some(worker) => Ok(worker.add_bond(bond)?), None => Ok(Worker { address: info.sender, bonding_state: BondingState::Bonded { amount: bond }, @@ -298,16 +311,9 @@ pub mod execute { let can_unbond = true; // TODO: actually query the service to determine this value - let bonding_state = worker.bonding_state.unbond(can_unbond, env.block.time)?; + let worker = worker.unbond(can_unbond, env.block.time)?; - WORKERS.save( - deps.storage, - (&service_name, &info.sender), - &Worker { - bonding_state, - ..worker - }, - )?; + WORKERS.save(deps.storage, (&service_name, &info.sender), &worker)?; Ok(Response::new()) } @@ -326,18 +332,10 @@ pub mod execute { .may_load(deps.storage, (&service_name, &info.sender))? .ok_or(ContractError::WorkerNotFound)?; - let (bonding_state, released_bond) = worker - .bonding_state - .claim_stake(env.block.time, service.unbonding_period_days as u64)?; + let (worker, released_bond) = + worker.claim_stake(env.block.time, service.unbonding_period_days as u64)?; - WORKERS.save( - deps.storage, - (&service_name, &info.sender), - &Worker { - bonding_state, - ..worker - }, - )?; + WORKERS.save(deps.storage, (&service_name, &info.sender), &worker)?; Ok(Response::new().add_message(BankMsg::Send { to_address: info.sender.into(), diff --git a/contracts/service-registry/src/error.rs b/contracts/service-registry/src/error.rs index daed547dd..1194735db 100644 --- a/contracts/service-registry/src/error.rs +++ b/contracts/service-registry/src/error.rs @@ -32,4 +32,6 @@ pub enum ContractError { InvalidBondingState(BondingState), #[error("not enough workers")] NotEnoughWorkers, + #[error("worker is jailed")] + WorkerJailed, } diff --git a/contracts/service-registry/src/msg.rs b/contracts/service-registry/src/msg.rs index c95a1f2e0..3b17cb439 100644 --- a/contracts/service-registry/src/msg.rs +++ b/contracts/service-registry/src/msg.rs @@ -30,6 +30,11 @@ pub enum ExecuteMsg { workers: Vec, service_name: String, }, + // Jail workers. Can only be called by governance account. Jailed workers are not allowed to unbond or claim stake. + JailWorkers { + workers: Vec, + service_name: String, + }, // Register support for the specified chains. Called by the worker. RegisterChainSupport { diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index 2d19b5b92..15609aca8 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -40,42 +40,9 @@ pub struct Worker { pub service_name: String, } -#[cw_serde] -pub struct WeightedWorker { - pub worker_info: Worker, - pub weight: nonempty::Uint256, -} - -/// For now, all workers have equal weight, regardless of amount bonded -pub const WORKER_WEIGHT: nonempty::Uint256 = nonempty::Uint256::one(); - -impl From for Participant { - fn from(worker: WeightedWorker) -> Participant { - Self { - weight: worker.weight, - address: worker.worker_info.address, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub enum BondingState { - Bonded { - amount: Uint128, - }, - RequestedUnbonding { - amount: Uint128, - }, - Unbonding { - amount: Uint128, - unbonded_at: Timestamp, - }, - Unbonded, -} - -impl BondingState { +impl Worker { pub fn add_bond(self, to_add: Uint128) -> Result { - let amount = match self { + let amount = match self.bonding_state { BondingState::Bonded { amount } | BondingState::RequestedUnbonding { amount } | BondingState::Unbonding { @@ -86,49 +53,105 @@ impl BondingState { .map_err(ContractError::Overflow)?, BondingState::Unbonded => to_add, }; + if amount.is_zero() { - Err(ContractError::InvalidBondingState(self)) + Err(ContractError::InvalidBondingState(self.bonding_state)) } else { - Ok(BondingState::Bonded { amount }) + Ok(Self { + bonding_state: BondingState::Bonded { amount }, + ..self + }) } } pub fn unbond(self, can_unbond: bool, time: Timestamp) -> Result { - match self { + if self.authorization_state == AuthorizationState::Jailed { + return Err(ContractError::WorkerJailed); + } + + let bonding_state = match self.bonding_state { BondingState::Bonded { amount } | BondingState::RequestedUnbonding { amount } => { if can_unbond { - Ok(BondingState::Unbonding { + BondingState::Unbonding { unbonded_at: time, amount, - }) + } } else { - Ok(BondingState::RequestedUnbonding { amount }) + BondingState::RequestedUnbonding { amount } } } - _ => Err(ContractError::InvalidBondingState(self)), - } + _ => return Err(ContractError::InvalidBondingState(self.bonding_state)), + }; + + Ok(Self { + bonding_state, + ..self + }) } + pub fn claim_stake( self, time: Timestamp, unbonding_period_days: u64, ) -> Result<(Self, Uint128), ContractError> { - match self { + if self.authorization_state == AuthorizationState::Jailed { + return Err(ContractError::WorkerJailed); + } + + match self.bonding_state { BondingState::Unbonding { amount, unbonded_at, - } if unbonded_at.plus_days(unbonding_period_days) <= time => { - Ok((BondingState::Unbonded, amount)) - } - _ => Err(ContractError::InvalidBondingState(self)), + } if unbonded_at.plus_days(unbonding_period_days) <= time => Ok(( + Self { + bonding_state: BondingState::Unbonded, + ..self + }, + amount, + )), + _ => Err(ContractError::InvalidBondingState(self.bonding_state)), + } + } +} + +#[cw_serde] +pub struct WeightedWorker { + pub worker_info: Worker, + pub weight: nonempty::Uint256, +} + +/// For now, all workers have equal weight, regardless of amount bonded +pub const WORKER_WEIGHT: nonempty::Uint256 = nonempty::Uint256::one(); + +impl From for Participant { + fn from(worker: WeightedWorker) -> Participant { + Self { + weight: worker.weight, + address: worker.worker_info.address, } } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum BondingState { + Bonded { + amount: Uint128, + }, + RequestedUnbonding { + amount: Uint128, + }, + Unbonding { + amount: Uint128, + unbonded_at: Timestamp, + }, + Unbonded, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub enum AuthorizationState { NotAuthorized, Authorized, + Jailed, } type ChainNames = HashSet; @@ -269,13 +292,19 @@ mod tests { #[test] fn test_bonded_add_bond() { - let state = BondingState::Bonded { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; - let res = state.add_bond(Uint128::from(200u32)); + + let res = worker.add_bond(Uint128::from(200u32)); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Bonded { amount: Uint128::from(300u32) } @@ -284,13 +313,19 @@ mod tests { #[test] fn test_requested_unbonding_add_bond() { - let state = BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::RequestedUnbonding { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; - let res = state.add_bond(Uint128::from(200u32)); + + let res = worker.add_bond(Uint128::from(200u32)); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Bonded { amount: Uint128::from(300u32) } @@ -299,14 +334,20 @@ mod tests { #[test] fn test_unbonding_add_bond() { - let state = BondingState::Unbonding { - amount: Uint128::from(100u32), - unbonded_at: Timestamp::from_nanos(0), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Unbonding { + amount: Uint128::from(100u32), + unbonded_at: Timestamp::from_nanos(0), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; - let res = state.add_bond(Uint128::from(200u32)); + + let res = worker.add_bond(Uint128::from(200u32)); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Bonded { amount: Uint128::from(300u32) } @@ -315,11 +356,17 @@ mod tests { #[test] fn test_unbonded_add_bond() { - let state = BondingState::Unbonded; - let res = state.add_bond(Uint128::from(200u32)); + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Unbonded, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.add_bond(Uint128::from(200u32)); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Bonded { amount: Uint128::from(200u32) } @@ -328,22 +375,39 @@ mod tests { #[test] fn test_zero_bond() { - let state = BondingState::Unbonded; - let res = state.clone().add_bond(Uint128::from(0u32)); + let bonding_state = BondingState::Unbonded; + + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.add_bond(Uint128::from(0u32)); assert!(res.is_err()); - assert_eq!(res.unwrap_err(), ContractError::InvalidBondingState(state)); + assert_eq!( + res.unwrap_err(), + ContractError::InvalidBondingState(bonding_state) + ); } #[test] fn test_bonded_unbond() { - let state = BondingState::Bonded { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; + let unbonded_at = Timestamp::from_nanos(0); - let res = state.unbond(true, unbonded_at); + let res = worker.unbond(true, unbonded_at); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Unbonding { amount: Uint128::from(100u32), unbonded_at @@ -353,14 +417,20 @@ mod tests { #[test] fn test_bonded_unbond_cant_unbond() { - let state = BondingState::Bonded { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; + let unbonded_at = Timestamp::from_nanos(0); - let res = state.unbond(false, unbonded_at); + let res = worker.unbond(false, unbonded_at); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::RequestedUnbonding { amount: Uint128::from(100u32) } @@ -369,14 +439,20 @@ mod tests { #[test] fn test_requested_unbonding_unbond() { - let state = BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::RequestedUnbonding { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; + let unbonded_at = Timestamp::from_nanos(0); - let res = state.unbond(true, unbonded_at); + let res = worker.unbond(true, unbonded_at); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::Unbonding { amount: Uint128::from(100u32), unbonded_at @@ -386,14 +462,20 @@ mod tests { #[test] fn test_requested_unbonding_cant_unbond() { - let state = BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::RequestedUnbonding { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), }; + let unbonded_at = Timestamp::from_nanos(0); - let res = state.unbond(false, unbonded_at); + let res = worker.unbond(false, unbonded_at); assert!(res.is_ok()); assert_eq!( - res.unwrap(), + res.unwrap().bonding_state, BondingState::RequestedUnbonding { amount: Uint128::from(100u32) } @@ -402,117 +484,198 @@ mod tests { #[test] fn test_unbonding_unbond() { - let unbonded_at = Timestamp::from_nanos(0); - let state = BondingState::Unbonding { + let bonding_state = BondingState::Unbonding { amount: Uint128::from(100u32), - unbonded_at, + unbonded_at: Timestamp::from_nanos(0), }; - let res = state.clone().unbond(true, Timestamp::from_nanos(2)); + + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().unbond(true, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state.clone().unbond(false, Timestamp::from_nanos(2)); + + let res = worker.unbond(false, Timestamp::from_nanos(2)); assert!(res.is_err()); - assert_eq!(res.unwrap_err(), ContractError::InvalidBondingState(state)); + assert_eq!( + res.unwrap_err(), + ContractError::InvalidBondingState(bonding_state.clone()) + ); } #[test] fn test_unbonded_unbond() { - let state = BondingState::Unbonded; - let res = state.clone().unbond(true, Timestamp::from_nanos(2)); + let bonding_state = BondingState::Unbonded; + + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().unbond(true, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state.clone().unbond(false, Timestamp::from_nanos(2)); + + let res = worker.unbond(false, Timestamp::from_nanos(2)); assert!(res.is_err()); - assert_eq!(res.unwrap_err(), ContractError::InvalidBondingState(state)); + assert_eq!( + res.unwrap_err(), + ContractError::InvalidBondingState(bonding_state.clone()) + ); } #[test] fn test_bonded_claim_stake() { - let state = BondingState::Bonded { + let bonding_state = BondingState::Bonded { amount: Uint128::from(100u32), }; - let res = state.clone().claim_stake(Timestamp::from_seconds(60), 1); + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().claim_stake(Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state - .clone() - .claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + + let res = worker.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); } #[test] fn test_requested_unbonding_claim_stake() { - let state = BondingState::RequestedUnbonding { + let bonding_state = BondingState::RequestedUnbonding { amount: Uint128::from(100u32), }; - let res = state.clone().claim_stake(Timestamp::from_seconds(60), 1); + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().claim_stake(Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state - .clone() - .claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + + let res = worker.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); } #[test] fn test_unbonding_claim_stake() { - let unbonded_at = Timestamp::from_nanos(0); - let state = BondingState::Unbonding { + let bonding_state = BondingState::Unbonding { amount: Uint128::from(100u32), - unbonded_at, + unbonded_at: Timestamp::from_nanos(0), }; - let res = state.clone().claim_stake(Timestamp::from_seconds(60), 1); + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().claim_stake(Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state - .clone() - .claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + + let res = worker.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_ok()); + + let (worker, amount) = res.unwrap(); assert_eq!( - res.unwrap(), + (worker.bonding_state, amount), (BondingState::Unbonded, Uint128::from(100u32)) ); } #[test] fn test_unbonded_claim_stake() { - let state = BondingState::Unbonded; - let res = state.clone().claim_stake(Timestamp::from_seconds(60), 1); + let bonding_state = BondingState::Unbonded; + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = worker.clone().claim_stake(Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = state - .clone() - .claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + + let res = worker.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), - ContractError::InvalidBondingState(state.clone()) + ContractError::InvalidBondingState(bonding_state.clone()) ); } + + #[test] + fn jailed_worker_cannot_unbond() { + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u32), + }, + authorization_state: AuthorizationState::Jailed, + service_name: "validators".to_string(), + }; + + let res = worker.unbond(true, Timestamp::from_nanos(0)); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), ContractError::WorkerJailed); + } + + #[test] + fn jailed_worker_cannot_claim_stake() { + let worker = Worker { + address: Addr::unchecked("worker"), + bonding_state: BondingState::Unbonding { + amount: Uint128::from(100u32), + unbonded_at: Timestamp::from_nanos(0), + }, + authorization_state: AuthorizationState::Jailed, + service_name: "validators".to_string(), + }; + + let res = worker.claim_stake(Timestamp::from_nanos(1), 0); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), ContractError::WorkerJailed); + } } diff --git a/contracts/service-registry/tests/tests.rs b/contracts/service-registry/tests/tests.rs index a3e29d157..ccb8f8297 100644 --- a/contracts/service-registry/tests/tests.rs +++ b/contracts/service-registry/tests/tests.rs @@ -3,8 +3,8 @@ mod test_utils; use std::{str::FromStr, vec}; use connection_router_api::ChainName; -use cosmwasm_std::{coins, Addr, BlockInfo, StdResult, Uint128}; -use cw_multi_test::App; +use cosmwasm_std::{coins, Addr, BankMsg, BlockInfo, StdResult, Uint128}; +use cw_multi_test::{App, Executor}; use integration_tests::contract::Contract; use service_registry::msg::QueryMsg; use service_registry::state::{WeightedWorker, WORKER_WEIGHT}; @@ -1717,3 +1717,159 @@ fn get_active_workers_should_not_return_less_than_min() { ); assert!(res.is_err()); } + +#[test] +fn jail_worker() { + let faucet = Addr::unchecked("faucet"); + // init app with faucet balance + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &faucet, coins(100000, AXL_DENOMINATION)) + .unwrap() + }); + + // init service registry contract + let governance = Addr::unchecked("gov"); + let service_registry = + test_utils::ServiceRegistryContract::instantiate_contract(&mut app, governance.clone()); + + // register a service + let service_name = "validators"; + let min_worker_bond = Uint128::new(100); + let unbonding_period_days = 10; + let res = service_registry.execute( + &mut app, + governance.clone(), + &ExecuteMsg::RegisterService { + service_name: service_name.into(), + service_contract: Addr::unchecked("service contract"), + min_num_workers: 0, + max_num_workers: Some(100), + min_worker_bond, + bond_denom: AXL_DENOMINATION.into(), + unbonding_period_days, + description: "Some service".into(), + }, + ); + assert!(res.is_ok()); + + // given a bonded worker + let worker1 = Addr::unchecked("worker-1"); + // fund worker + let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { + to_address: worker1.clone().into(), + amount: coins(min_worker_bond.u128(), AXL_DENOMINATION), + } + .into(); + app.execute(faucet.clone(), msg.clone()).unwrap(); + let res = service_registry.execute_with_funds( + &mut app, + worker1.clone(), + &ExecuteMsg::BondWorker { + service_name: service_name.into(), + }, + &coins(min_worker_bond.u128(), AXL_DENOMINATION), + ); + assert!(res.is_ok()); + + // when worker is jailed + let res = service_registry.execute( + &mut app, + governance.clone(), + &ExecuteMsg::JailWorkers { + workers: vec![worker1.clone().into()], + service_name: service_name.into(), + }, + ); + assert!(res.is_ok()); + + // worker cannot unbond + let err = service_registry + .execute( + &mut app, + worker1.clone(), + &ExecuteMsg::UnbondWorker { + service_name: service_name.into(), + }, + ) + .unwrap_err(); + test_utils::are_contract_err_strings_equal(err, ContractError::WorkerJailed); + + // given a worker passed unbonding period + let worker2 = Addr::unchecked("worker-2"); + // fund worker + let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { + to_address: worker2.clone().into(), + amount: coins(min_worker_bond.u128(), AXL_DENOMINATION), + } + .into(); + app.execute(faucet.clone(), msg.clone()).unwrap(); + // bond worker + let res = service_registry.execute_with_funds( + &mut app, + worker2.clone(), + &ExecuteMsg::BondWorker { + service_name: service_name.into(), + }, + &coins(min_worker_bond.u128(), AXL_DENOMINATION), + ); + assert!(res.is_ok()); + // unbond worker + let res = service_registry.execute( + &mut app, + worker2.clone(), + &ExecuteMsg::UnbondWorker { + service_name: service_name.into(), + }, + ); + assert!(res.is_ok()); + let worker: Worker = service_registry + .query( + &app, + &QueryMsg::GetWorker { + service_name: service_name.into(), + worker: worker2.to_string(), + }, + ) + .unwrap(); + + let block = app.block_info(); + assert_eq!( + worker.bonding_state, + BondingState::Unbonding { + amount: min_worker_bond, + unbonded_at: block.time, + } + ); + + // when worker is jailed + let res = service_registry.execute( + &mut app, + governance, + &ExecuteMsg::JailWorkers { + workers: vec![worker2.clone().into()], + service_name: service_name.into(), + }, + ); + assert!(res.is_ok()); + + // and unbonding period has passed + app.set_block(BlockInfo { + height: block.height + 1, + time: block.time.plus_days((unbonding_period_days + 1).into()), + ..block + }); + + // worker cannot claim stake + let err = service_registry + .execute( + &mut app, + worker2.clone(), + &ExecuteMsg::ClaimStake { + service_name: service_name.into(), + }, + ) + .unwrap_err(); + test_utils::are_contract_err_strings_equal(err, ContractError::WorkerJailed); +} From 33291132026607e2a398462ce845bb12f1cda28f Mon Sep 17 00:00:00 2001 From: eloylp Date: Wed, 17 Apr 2024 15:07:21 -0500 Subject: [PATCH 24/27] feat(minor-ampd): add initial healthcheck endpoint (#344) --- Cargo.lock | 215 ++++++++++++++++++++++------ ampd/Cargo.toml | 1 + ampd/README.md | 2 + ampd/src/config.rs | 3 + ampd/src/health_check.rs | 101 +++++++++++++ ampd/src/lib.rs | 17 +++ ampd/src/tests/config_template.toml | 1 + 7 files changed, 298 insertions(+), 42 deletions(-) create mode 100644 ampd/src/health_check.rs diff --git a/Cargo.lock b/Cargo.lock index 1f11925b3..3a8ec7d4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,7 @@ version = "0.1.0" dependencies = [ "async-trait", "axelar-wasm-std", + "axum 0.7.5", "base64 0.21.4", "bcs", "clap", @@ -237,7 +238,7 @@ dependencies = [ "ed25519 1.5.3", "futures", "hex", - "http", + "http 0.2.9", "matchit 0.5.0", "pin-project-lite", "pkcs8 0.9.0", @@ -701,15 +702,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.3.4", "base64 0.21.4", "bitflags 1.3.2", "bytes", "futures-util", "headers", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "itoa", "matchit 0.7.2", "memchr", @@ -722,7 +723,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 0.1.2", "tokio", "tokio-tungstenite", "tower", @@ -730,6 +731,40 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.0", + "hyper-util", + "itoa", + "matchit 0.7.2", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-core" version = "0.3.4" @@ -739,14 +774,35 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "mime", "rustversion", "tower-layer", "tower-service", ] +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -2801,7 +2857,7 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.9", "instant", "jsonwebtoken", "once_cell", @@ -3447,7 +3503,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.9", "indexmap 2.0.0", "slab", "tokio", @@ -3511,7 +3567,7 @@ dependencies = [ "base64 0.21.4", "bytes", "headers-core", - "http", + "http 0.2.9", "httpdate", "mime", "sha1", @@ -3523,7 +3579,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.9", ] [[package]] @@ -3594,6 +3650,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -3601,7 +3668,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -3650,8 +3740,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -3663,6 +3753,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "hyper-proxy" version = "0.9.1" @@ -3672,8 +3781,8 @@ dependencies = [ "bytes", "futures", "headers", - "http", - "hyper", + "http 0.2.9", + "hyper 0.14.27", "hyper-rustls 0.22.1", "rustls-native-certs", "tokio", @@ -3690,7 +3799,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper", + "hyper 0.14.27", "log", "rustls 0.19.1", "rustls-native-certs", @@ -3707,8 +3816,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.9", + "hyper 0.14.27", "rustls 0.21.7", "tokio", "tokio-rustls 0.24.1", @@ -3720,12 +3829,28 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.27", "pin-project-lite", "tokio", "tokio-io-timeout", ] +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.0", + "pin-project-lite", + "socket2 0.5.4", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -4873,7 +4998,7 @@ version = "0.7.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ "async-trait", - "axum", + "axum 0.6.20", "dashmap", "futures", "once_cell", @@ -4898,7 +5023,7 @@ dependencies = [ "bytes", "eyre", "futures", - "http", + "http 0.2.9", "multiaddr", "serde", "snap", @@ -6415,9 +6540,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-rustls 0.24.1", "ipnet", "js-sys", @@ -6431,7 +6556,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-rustls 0.24.1", @@ -7304,9 +7429,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snap" @@ -7747,6 +7872,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synstructure" version = "0.12.6" @@ -7958,8 +8089,8 @@ dependencies = [ "flex-error", "futures", "getrandom", - "http", - "hyper", + "http 0.2.9", + "hyper 0.14.27", "hyper-proxy", "hyper-rustls 0.22.1", "peg", @@ -8300,15 +8431,15 @@ checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", - "axum", + "axum 0.6.20", "base64 0.13.1", "bytes", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-timeout", "percent-encoding", "pin-project", @@ -8332,13 +8463,13 @@ checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", - "axum", + "axum 0.6.20", "base64 0.21.4", "bytes", "h2", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-timeout", "percent-encoding", "pin-project", @@ -8413,8 +8544,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "http-range-header", "httpdate", "iri-string", @@ -8561,7 +8692,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.9", "httparse", "log", "rand", diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 57550338a..a838e3481 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -8,6 +8,7 @@ rust-version = { workspace = true } [dependencies] async-trait = "0.1.59" axelar-wasm-std = { workspace = true } +axum = "0.7.5" base64 = "0.21.2" bcs = "0.1.5" clap = { version = "4.2.7", features = ["derive", "cargo"] } diff --git a/ampd/README.md b/ampd/README.md index 87861cb68..14435e178 100644 --- a/ampd/README.md +++ b/ampd/README.md @@ -11,6 +11,7 @@ Below is the config file format, with explanations for each entry: tm_jsonrpc=[JSON-RPC URL of Axelar node] tm_grpc=[gRPC URL of Axelar node] event_buffer_cap=[max blockchain events to queue. Will error if set too low] +health_check_bind_addr=[the /status endpoint bind address i.e "0.0.0.0:3000"] [service_registry] cosmwasm_contract=[address of service registry] @@ -52,6 +53,7 @@ type=[handler type. Could be EvmMsgWorkerSetVerifier | SuiWorkerSetVerifier] Below is an example config for connecting to a local axelard node and local tofnd process, and verifying transactions from Avalanche testnet and Sui testnet. ``` +health_check_bind_addr="0.0.0.0:3000" tm_jsonrpc="http://localhost:26657" tm_grpc="tcp://localhost:9090" event_buffer_cap=10000 diff --git a/ampd/src/config.rs b/ampd/src/config.rs index b9b625562..08f7a65d6 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -1,3 +1,4 @@ +use std::net::{Ipv4Addr, SocketAddrV4}; use std::time::Duration; use serde::{Deserialize, Serialize}; @@ -11,6 +12,7 @@ use crate::url::Url; #[derive(Deserialize, Serialize, Debug, PartialEq)] #[serde(default)] pub struct Config { + pub health_check_bind_addr: SocketAddrV4, pub tm_jsonrpc: Url, pub tm_grpc: Url, pub event_buffer_cap: usize, @@ -34,6 +36,7 @@ impl Default for Config { event_buffer_cap: 100000, event_stream_timeout: Duration::from_secs(15), service_registry: ServiceRegistryConfig::default(), + health_check_bind_addr: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 3000), } } } diff --git a/ampd/src/health_check.rs b/ampd/src/health_check.rs new file mode 100644 index 000000000..ffd366fc6 --- /dev/null +++ b/ampd/src/health_check.rs @@ -0,0 +1,101 @@ +use error_stack::{Result, ResultExt}; +use std::net::SocketAddrV4; +use thiserror::Error; +use tracing::info; + +use axum::{http::StatusCode, routing::get, Json, Router}; +use serde::{Deserialize, Serialize}; +use tokio_util::sync::CancellationToken; + +#[derive(Error, Debug)] +pub enum Error { + #[error("failed to start the health check server")] + Start, + #[error("health check server failed unexpectedly")] + WhileRunning, +} + +pub struct Server { + bind_address: SocketAddrV4, +} + +impl Server { + pub fn new(bind_address: SocketAddrV4) -> Self { + Self { bind_address } + } + + pub async fn run(self, cancel: CancellationToken) -> Result<(), Error> { + let listener = tokio::net::TcpListener::bind(self.bind_address) + .await + .change_context(Error::Start)?; + + info!( + address = self.bind_address.to_string(), + "starting health check server" + ); + + let app = Router::new().route("/status", get(status)); + axum::serve(listener, app) + .with_graceful_shutdown(async move { cancel.cancelled().await }) + .await + .change_context(Error::WhileRunning) + } +} + +// basic handler that responds with a static string +async fn status() -> (StatusCode, Json) { + (StatusCode::OK, Json(Status { ok: true })) +} + +#[derive(Serialize, Deserialize)] +struct Status { + ok: bool, +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::net::{SocketAddr, TcpListener}; + use std::time::Duration; + use tokio::test as async_test; + + #[async_test] + async fn server_lifecycle() { + let bind_address = test_bind_addr(); + + let server = Server::new(bind_address); + + let cancel = CancellationToken::new(); + + tokio::spawn(server.run(cancel.clone())); + + let url = format!("http://{}/status", bind_address); + + tokio::time::sleep(Duration::from_millis(100)).await; + + let response = reqwest::get(&url).await.unwrap(); + assert_eq!(reqwest::StatusCode::OK, response.status()); + + let status = response.json::().await.unwrap(); + assert!(status.ok); + + cancel.cancel(); + + tokio::time::sleep(Duration::from_millis(100)).await; + + match reqwest::get(&url).await { + Ok(_) => panic!("health check server should be closed by now"), + Err(error) => assert!(error.is_connect()), + }; + } + + fn test_bind_addr() -> SocketAddrV4 { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + + match listener.local_addr().unwrap() { + SocketAddr::V4(addr) => addr, + SocketAddr::V6(_) => panic!("unexpected address"), + } + } +} diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index bb466f12b..df1e86142 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -38,6 +38,7 @@ mod event_processor; mod event_sub; mod evm; mod handlers; +mod health_check; mod json_rpc; mod queue; pub mod state; @@ -71,6 +72,7 @@ async fn prepare_app(cfg: Config, state: State) -> Result, event_buffer_cap, event_stream_timeout, service_registry: _service_registry, + health_check_bind_addr, } = cfg; let tm_client = tendermint_rpc::HttpClient::new(tm_jsonrpc.to_string().as_str()) @@ -122,6 +124,8 @@ async fn prepare_app(cfg: Config, state: State) -> Result, .build() .change_context(Error::Broadcaster)?; + let health_check_server = health_check::Server::new(health_check_bind_addr); + App::new( tm_client, broadcaster, @@ -130,6 +134,7 @@ async fn prepare_app(cfg: Config, state: State) -> Result, broadcast, event_buffer_cap, block_height_monitor, + health_check_server, ) .configure_handlers(worker, handlers, event_stream_timeout) .await @@ -163,6 +168,7 @@ where state_updater: StateUpdater, ecdsa_client: SharableEcdsaClient, block_height_monitor: BlockHeightMonitor, + health_check_server: health_check::Server, token: CancellationToken, } @@ -170,6 +176,7 @@ impl App where T: Broadcaster + Send + Sync + 'static, { + #[allow(clippy::too_many_arguments)] fn new( tm_client: tendermint_rpc::HttpClient, broadcaster: T, @@ -178,6 +185,7 @@ where broadcast_cfg: broadcaster::Config, event_buffer_cap: usize, block_height_monitor: BlockHeightMonitor, + health_check_server: health_check::Server, ) -> Self { let token = CancellationToken::new(); @@ -203,6 +211,7 @@ where state_updater, ecdsa_client, block_height_monitor, + health_check_server, token, } } @@ -375,6 +384,7 @@ where broadcaster, state_updater, block_height_monitor, + health_check_server, token, .. } = self; @@ -407,6 +417,11 @@ where .run(token) .change_context(Error::EventPublisher) })) + .add_task(CancellableTask::create(|token| { + health_check_server + .run(token) + .change_context(Error::HealthCheck) + })) .add_task(CancellableTask::create(|token| { event_processor .run(token) @@ -457,4 +472,6 @@ pub enum Error { BlockHeightMonitor, #[error("invalid finalizer type for chain {0}")] InvalidFinalizerType(ChainName), + #[error("health check is not working")] + HealthCheck, } diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index 33ecfb8f0..952ec1f66 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -1,3 +1,4 @@ +health_check_bind_addr = '0.0.0.0:3000' tm_jsonrpc = 'http://localhost:26657/' tm_grpc = 'tcp://localhost:9090' event_buffer_cap = 100000 From d360ea457c7ffa77c34ca517f957185679058861 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:32:34 -0400 Subject: [PATCH 25/27] fix(minor-nexus-gateway)!: make msg id consistent with evm chains (#350) * fix(minor-nexus-gateway)!: make msg id consistent with evm chains --- .../nexus-gateway/src/contract/execute.rs | 8 +-- contracts/nexus-gateway/src/error.rs | 6 ++ contracts/nexus-gateway/src/nexus.rs | 66 +++++++++++++++---- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/contracts/nexus-gateway/src/contract/execute.rs b/contracts/nexus-gateway/src/contract/execute.rs index fb426f8e5..04d306ca6 100644 --- a/contracts/nexus-gateway/src/contract/execute.rs +++ b/contracts/nexus-gateway/src/contract/execute.rs @@ -24,8 +24,8 @@ where let msgs: Vec<_> = msgs .into_iter() - .map(connection_router_api::Message::from) - .collect(); + .map(connection_router_api::Message::try_from) + .collect::>>()?; if msgs.is_empty() { return Ok(Response::default()); } @@ -134,7 +134,7 @@ mod test { .unwrap() .try_into() .unwrap(), - source_tx_id: vec![0x2f, 0xe4].try_into().unwrap(), + source_tx_id: vec![0x2f; 32].try_into().unwrap(), source_tx_index: 100, }, nexus::Message { @@ -148,7 +148,7 @@ mod test { .unwrap() .try_into() .unwrap(), - source_tx_id: vec![0x23, 0xf4].try_into().unwrap(), + source_tx_id: vec![0x23; 32].try_into().unwrap(), source_tx_index: 1000, }, ]; diff --git a/contracts/nexus-gateway/src/error.rs b/contracts/nexus-gateway/src/error.rs index f5966bc6f..385d2d5b4 100644 --- a/contracts/nexus-gateway/src/error.rs +++ b/contracts/nexus-gateway/src/error.rs @@ -14,6 +14,12 @@ pub enum ContractError { #[error("invalid message id {0}")] InvalidMessageId(String), + #[error("invalid source tx id {0}")] + InvalidSourceTxId(String), + + #[error("invalid event index {0}")] + InvalidEventIndex(u64), + #[error("invalid payload hash {0}")] InvalidMessagePayloadHash(HexBinary), diff --git a/contracts/nexus-gateway/src/nexus.rs b/contracts/nexus-gateway/src/nexus.rs index 86b7846da..b1176aa80 100644 --- a/contracts/nexus-gateway/src/nexus.rs +++ b/contracts/nexus-gateway/src/nexus.rs @@ -1,7 +1,7 @@ -use axelar_wasm_std::nonempty; +use axelar_wasm_std::{msg_id::tx_hash_event_index::HexTxHashAndEventIndex, nonempty}; use connection_router_api::{Address, ChainName, CrossChainId}; use cosmwasm_std::{CosmosMsg, CustomMsg}; -use error_stack::{Result, ResultExt}; +use error_stack::{Report, Result, ResultExt}; use hex::{FromHex, ToHex}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -68,24 +68,29 @@ impl From for Message { } } -impl From for connection_router_api::Message { - fn from(msg: Message) -> Self { - Self { +impl TryFrom for connection_router_api::Message { + type Error = Report; + + fn try_from(msg: Message) -> Result { + let msg_id = HexTxHashAndEventIndex { + tx_hash: <[u8; 32]>::try_from(msg.source_tx_id.as_ref().as_slice()).map_err(|_| { + ContractError::InvalidSourceTxId(msg.source_tx_id.as_ref().encode_hex::()) + })?, + event_index: u32::try_from(msg.source_tx_index) + .map_err(|_| ContractError::InvalidEventIndex(msg.source_tx_index))?, + }; + + Ok(Self { cc_id: CrossChainId { chain: msg.source_chain, - id: format!( - "{}:{}", - msg.source_tx_id.as_ref().encode_hex::(), - msg.source_tx_index - ) - .try_into() - .expect("cannot be empty"), + id: nonempty::String::try_from(msg_id.to_string()) + .change_context(ContractError::InvalidMessageId(msg_id.to_string()))?, }, source_address: msg.source_address, destination_chain: msg.destination_chain, destination_address: msg.destination_address, payload_hash: msg.payload_hash, - } + }) } } @@ -94,3 +99,38 @@ impl From for CosmosMsg { CosmosMsg::Custom(msg) } } + +#[cfg(test)] +mod test { + use std::vec; + + use cosmwasm_std::HexBinary; + + use super::Message; + + #[test] + fn should_convert_nexus_message_to_router_message() { + let msg = Message { + source_chain: "ethereum".parse().unwrap(), + source_address: "something".parse().unwrap(), + destination_chain: "polygon".parse().unwrap(), + destination_address: "something else".parse().unwrap(), + payload_hash: [1; 32], + source_tx_id: vec![2; 32].try_into().unwrap(), + source_tx_index: 1, + }; + + let router_msg = connection_router_api::Message::try_from(msg.clone()); + assert!(router_msg.is_ok()); + let router_msg = router_msg.unwrap(); + assert_eq!(router_msg.cc_id.chain, msg.source_chain); + assert_eq!( + router_msg.cc_id.id.to_string(), + format!( + "0x{}-{}", + HexBinary::from(msg.source_tx_id.as_ref().clone()).to_hex(), + msg.source_tx_index + ) + ); + } +} From 54be04517e1afba8c8632e00ec4cbcfa7db06637 Mon Sep 17 00:00:00 2001 From: eguajardo Date: Thu, 18 Apr 2024 08:37:45 -0600 Subject: [PATCH 26/27] fix(minor-voting-verifier)!: remove unused response data (#353) * remove response data from voting-verifier verify messages * remove duplicated test * remove unused response data --- contracts/voting-verifier/src/contract.rs | 34 ----------------------- contracts/voting-verifier/src/execute.rs | 29 ++++++------------- contracts/voting-verifier/src/msg.rs | 12 +------- 3 files changed, 10 insertions(+), 65 deletions(-) diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index c86a84344..200d31ee1 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -95,7 +95,6 @@ mod test { use crate::{ error::ContractError, events::{TxEventConfirmation, TX_HASH_EVENT_INDEX_SEPARATOR}, - msg::VerifyMessagesResponse, }; use super::*; @@ -249,39 +248,6 @@ mod test { assert_contract_err_strings_equal(err, ContractError::SourceChainMismatch(source_chain())); } - #[test] - fn should_verify_messages_if_not_verified() { - let workers = workers(2); - let mut deps = setup(workers.clone()); - - let msg = ExecuteMsg::VerifyMessages { - messages: messages(2), - }; - - let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); - let reply: VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); - assert_eq!(reply.verification_statuses.len(), 2); - assert_eq!( - reply.verification_statuses, - vec![ - ( - CrossChainId { - id: message_id("id", 0), - chain: source_chain() - }, - VerificationStatus::None - ), - ( - CrossChainId { - id: message_id("id", 1), - chain: source_chain() - }, - VerificationStatus::None - ), - ] - ); - } - #[test] fn should_not_verify_messages_if_in_progress() { let workers = workers(2); diff --git a/contracts/voting-verifier/src/execute.rs b/contracts/voting-verifier/src/execute.rs index 9d9055393..87c2913fd 100644 --- a/contracts/voting-verifier/src/execute.rs +++ b/contracts/voting-verifier/src/execute.rs @@ -17,7 +17,6 @@ use service_registry::{msg::QueryMsg, state::WeightedWorker}; use crate::events::{ PollEnded, PollMetadata, PollStarted, TxEventConfirmation, Voted, WorkerSetConfirmation, }; -use crate::msg::{EndPollResponse, VerifyMessagesResponse}; use crate::query::worker_set_status; use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS}; use crate::state::{CONFIG, POLLS, POLL_ID}; @@ -109,13 +108,6 @@ pub fn verify_messages( .map(|message| message_status(deps.as_ref(), &message).map(|status| (status, message))) .collect::, _>>()?; - let response = Response::new().set_data(to_binary(&VerifyMessagesResponse { - verification_statuses: messages - .iter() - .map(|(status, message)| (message.cc_id.to_owned(), status.to_owned())) - .collect(), - })?); - let msgs_to_verify: Vec = messages .into_iter() .filter_map(|(status, message)| match status { @@ -129,7 +121,7 @@ pub fn verify_messages( .collect(); if msgs_to_verify.is_empty() { - return Ok(response); + return Ok(Response::new()); } let snapshot = take_snapshot(deps.as_ref(), &msgs_to_verify[0].cc_id.chain)?; @@ -151,7 +143,7 @@ pub fn verify_messages( .map(TryInto::try_into) .collect::, _>>()?; - Ok(response.add_event( + Ok(Response::new().add_event( PollStarted::Messages { messages, metadata: PollMetadata { @@ -225,16 +217,13 @@ pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result Result { diff --git a/contracts/voting-verifier/src/msg.rs b/contracts/voting-verifier/src/msg.rs index 32a2f83bc..7cc078a9e 100644 --- a/contracts/voting-verifier/src/msg.rs +++ b/contracts/voting-verifier/src/msg.rs @@ -3,7 +3,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use axelar_wasm_std::{ nonempty, operators::Operators, - voting::{PollId, PollState, Vote}, + voting::{PollId, Vote}, MajorityThreshold, VerificationStatus, }; use connection_router_api::{ChainName, CrossChainId, Message}; @@ -77,13 +77,3 @@ pub enum QueryMsg { #[returns(MajorityThreshold)] GetCurrentThreshold, } - -#[cw_serde] -pub struct VerifyMessagesResponse { - pub verification_statuses: Vec<(CrossChainId, VerificationStatus)>, -} - -#[cw_serde] -pub struct EndPollResponse { - pub poll_result: PollState, -} From 07bc88f7a23e6cad1cb4b8e746345df75e1430be Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:48:36 -0400 Subject: [PATCH 27/27] feat(multisig-prover): allow governance to update workerset and document instantiation msg parameters (#355) * feat(multisig-prover): allow governance to update workerset and document instantiation msg parameters --- contracts/multisig-prover/src/contract.rs | 29 +++++++++++++++++++++-- contracts/multisig-prover/src/msg.rs | 27 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 6833420fc..9ca278a94 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -70,7 +70,8 @@ pub fn execute( match msg { ExecuteMsg::ConstructProof { message_ids } => execute::construct_proof(deps, message_ids), ExecuteMsg::UpdateWorkerSet {} => { - execute::require_admin(&deps, info)?; + execute::require_admin(&deps, info.clone()) + .or_else(|_| execute::require_governance(&deps, info))?; execute::update_worker_set(deps, env) } ExecuteMsg::ConfirmWorkerSet {} => execute::confirm_worker_set(deps, info.sender), @@ -338,7 +339,7 @@ mod tests { } #[test] - fn test_update_worker_set_from_non_admin_should_fail() { + fn test_update_worker_set_from_non_admin_or_governance_should_fail() { let mut test_case = setup_test_case(); let res = test_case.app.execute_contract( Addr::unchecked("some random address"), @@ -356,6 +357,30 @@ mod tests { ); } + #[test] + fn test_update_worker_set_from_governance_should_succeed() { + let mut test_case = setup_test_case(); + let res = test_case.app.execute_contract( + test_case.governance.clone(), + test_case.prover_address.clone(), + &ExecuteMsg::UpdateWorkerSet {}, + &[], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_update_worker_set_from_admin_should_succeed() { + let mut test_case = setup_test_case(); + let res = test_case.app.execute_contract( + test_case.governance.clone(), + test_case.prover_address.clone(), + &ExecuteMsg::UpdateWorkerSet {}, + &[], + ); + assert!(res.is_ok()); + } + #[test] fn test_update_worker_set_remove_one() { let mut test_case = setup_test_case(); diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index 1e42f44ba..b0be86af0 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -8,19 +8,46 @@ use crate::encoding::{Data, Encoder}; #[cw_serde] pub struct InstantiateMsg { + /// Address that can execute all messages that either have unrestricted or admin permission level, such as UpdateWorkerSet. + /// Should be set to a trusted address that can react to unexpected interruptions to the contract's operation. pub admin_address: String, + /// Address that can call all messages of unrestricted, admin and governance permission level, such as UpdateSigningThreshold. + /// This address can execute messages that bypasses verification checks to rescue the contract if it got into an otherwise unrecoverable state due to external forces. + /// On mainnet, it should match the address of the Cosmos governance module. pub governance_address: String, + /// Address of the gateway on axelar associated with the destination chain. For example, if this prover is creating proofs to + /// be relayed to Ethereum, this is the address of the gateway on Axelar for Ethereum. pub gateway_address: String, + /// Address of the multisig contract on axelar. pub multisig_address: String, + /// Address of the monitoring contract on axelar. pub monitoring_address: String, + /// Address of the service registry contract on axelar. pub service_registry_address: String, + /// Address of the voting verifier contract on axelar associated with the destination chain. For example, if this prover is creating + /// proofs to be relayed to Ethereum, this is the address of the voting verifier for Ethereum. pub voting_verifier_address: String, + /// Chain id of the chain for which this prover contract creates proofs. For example, if the destination chain is Ethereum, the chain id is 1. pub destination_chain_id: Uint256, + /// Threshold of weighted signatures required for signing to be considered complete pub signing_threshold: MajorityThreshold, + /// Name of service in the service registry for which verifiers are registered. pub service_name: String, + /// Name of chain for which this prover contract creates proofs. pub chain_name: String, + /// Maximum tolerable difference between currently active workerset and registered workerset. + /// The workerset registered in the service registry must be different by more than this number + /// of workers before calling UpdateWorkerSet. For example, if this is set to 1, UpdateWorkerSet + /// will fail unless the registered workerset and active workerset differ by more than 1. pub worker_set_diff_threshold: u32, + /// Type of encoding to use for signed batches. Blockchains can encode their execution payloads in various ways (ABI, BCS, etc). + /// This defines the specific encoding type to use for this prover, which should correspond to the encoding type used by the gateway + /// deployed on the destination chain. pub encoder: Encoder, + /// Public key type verifiers use for signing batches. Different blockchains support different cryptographic signature algorithms (ECDSA, Ed25519, etc). + /// This defines the specific signature algorithm to use for this prover, which should correspond to the signature algorithm used by the gateway + /// deployed on the destination chain. The multisig contract supports multiple public keys per verifier (each a different type of key), and this + /// parameter controls which registered public key to use for signing for each verifier registered to the destination chain. pub key_type: KeyType, }