From f13b07c0abf1e0e10859006991d4bab4e4357427 Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Wed, 4 Sep 2024 17:23:01 -0400 Subject: [PATCH] feat(minor-multisig): implement client for queries --- Cargo.lock | 1 + contracts/multisig/Cargo.toml | 1 + contracts/multisig/src/client.rs | 279 ++++++++++++++++++ contracts/multisig/src/lib.rs | 1 + ...orized_returns_caller_authorization.golden | 1 + ...zed_returns_error_when_query_errors.golden | 1 + ...ion_returns_error_when_query_errors.golden | 1 + ...ry_multisig_session_returns_session.golden | 45 +++ ...key_returns_error_when_query_errors.golden | 1 + ...query_public_key_returns_public_key.golden | 3 + ...set_returns_error_when_query_errors.golden | 1 + ...y_verifier_set_returns_verifier_set.golden | 27 ++ 12 files changed, 362 insertions(+) create mode 100644 contracts/multisig/src/client.rs create mode 100644 contracts/multisig/src/testdata/query_is_caller_authorized_returns_caller_authorization.golden create mode 100644 contracts/multisig/src/testdata/query_is_caller_authorized_returns_error_when_query_errors.golden create mode 100644 contracts/multisig/src/testdata/query_multisig_session_returns_error_when_query_errors.golden create mode 100644 contracts/multisig/src/testdata/query_multisig_session_returns_session.golden create mode 100644 contracts/multisig/src/testdata/query_public_key_returns_error_when_query_errors.golden create mode 100644 contracts/multisig/src/testdata/query_public_key_returns_public_key.golden create mode 100644 contracts/multisig/src/testdata/query_verifier_set_returns_error_when_query_errors.golden create mode 100644 contracts/multisig/src/testdata/query_verifier_set_returns_verifier_set.golden diff --git a/Cargo.lock b/Cargo.lock index e8262480a..0e43f329b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5016,6 +5016,7 @@ name = "multisig" version = "1.0.0" dependencies = [ "axelar-wasm-std", + "client", "cosmwasm-crypto", "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index b286194ba..e31ba5c54 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -40,6 +40,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] axelar-wasm-std = { workspace = true, features = ["derive"] } +client = { workspace = true } cosmwasm-crypto = "1.2.7" cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/multisig/src/client.rs b/contracts/multisig/src/client.rs new file mode 100644 index 000000000..712035e2e --- /dev/null +++ b/contracts/multisig/src/client.rs @@ -0,0 +1,279 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint64; +use error_stack::{Result, ResultExt}; +use router_api::ChainName; + +use crate::key::{KeyType, PublicKey}; +use crate::msg::{ExecuteMsg, QueryMsg}; +use crate::multisig::Multisig; +use crate::verifier_set::VerifierSet; + +#[derive(thiserror::Error)] +#[cw_serde] +pub enum Error { + #[error("failed to query multisig contract for multisig session. session_id: {0}")] + QueryMultisigSession(Uint64), + + #[error("failed to query multisig contract for verifier set: verifier_set_id: {0}")] + QueryVerifierSet(String), + + #[error("failed to query multisig contract for verifier public key. verifier_address: {verifier_address}, key_type: {key_type}")] + QueryPublicKey { + verifier_address: String, + key_type: KeyType, + }, + + #[error("failed to query multisig contract for caller authorization. contract_address: {contract_address}, chain_name: {chain_name}")] + QueryIsCallerAuthorized { + contract_address: String, + chain_name: ChainName, + }, +} + +impl<'a> From> for Client<'a> { + fn from(client: client::Client<'a, ExecuteMsg, QueryMsg>) -> Self { + Client { client } + } +} + +impl From for Error { + fn from(value: QueryMsg) -> Self { + match value { + QueryMsg::Multisig { session_id } => Error::QueryMultisigSession(session_id), + QueryMsg::VerifierSet { verifier_set_id } => Error::QueryVerifierSet(verifier_set_id), + QueryMsg::PublicKey { + verifier_address, + key_type, + } => Error::QueryPublicKey { + verifier_address, + key_type, + }, + QueryMsg::IsCallerAuthorized { + contract_address, + chain_name, + } => Error::QueryIsCallerAuthorized { + contract_address, + chain_name, + }, + } + } +} + +pub struct Client<'a> { + client: client::Client<'a, ExecuteMsg, QueryMsg>, +} + +impl<'a> Client<'a> { + pub fn multisig(&self, session_id: Uint64) -> Result { + let msg = QueryMsg::Multisig { session_id }; + self.client.query(&msg).change_context_lazy(|| msg.into()) + } + + pub fn verifier_set(&self, verifier_set_id: String) -> Result { + let msg = QueryMsg::VerifierSet { verifier_set_id }; + self.client.query(&msg).change_context_lazy(|| msg.into()) + } + + pub fn public_key( + &self, + verifier_address: String, + key_type: KeyType, + ) -> Result { + let msg = QueryMsg::PublicKey { + verifier_address, + key_type, + }; + self.client.query(&msg).change_context_lazy(|| msg.into()) + } + + pub fn is_caller_authorized( + &self, + contract_address: String, + chain_name: ChainName, + ) -> Result { + let msg = QueryMsg::IsCallerAuthorized { + contract_address, + chain_name, + }; + self.client.query(&msg).change_context_lazy(|| msg.into()) + } +} + +#[cfg(test)] +mod test { + + use cosmwasm_std::testing::MockQuerier; + use cosmwasm_std::{ + from_json, to_json_binary, Addr, QuerierWrapper, SystemError, Uint64, WasmQuery, + }; + + use crate::client::Client; + use crate::key::{KeyType, PublicKey, Signature}; + use crate::msg::QueryMsg; + use crate::multisig::Multisig; + use crate::test::common::{build_verifier_set, ecdsa_test_data}; + use crate::types::MultisigState; + + #[test] + fn query_multisig_session_returns_error_when_query_errors() { + let (querier, addr) = setup_queries_to_fail(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let session_id: Uint64 = 1u64.into(); + let res = client.multisig(session_id); + assert!(res.is_err()); + goldie::assert!(res.unwrap_err().to_string()); + } + + #[test] + fn query_multisig_session_returns_session() { + let (querier, addr) = setup_queries_to_succeed(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let session_id: Uint64 = 1u64.into(); + let res = client.multisig(session_id); + assert!(res.is_ok()); + goldie::assert_json!(res.unwrap()); + } + + #[test] + fn query_verifier_set_returns_error_when_query_errors() { + let (querier, addr) = setup_queries_to_fail(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let verifier_set_id = "my_set".to_string(); + let res = client.verifier_set(verifier_set_id.clone()); + assert!(res.is_err()); + goldie::assert!(res.unwrap_err().to_string()); + } + + #[test] + fn query_verifier_set_returns_verifier_set() { + let (querier, addr) = setup_queries_to_succeed(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let verifier_set_id = "my_set".to_string(); + let res = client.verifier_set(verifier_set_id.clone()); + assert!(res.is_ok()); + goldie::assert_json!(res.unwrap()); + } + + #[test] + fn query_public_key_returns_error_when_query_errors() { + let (querier, addr) = setup_queries_to_fail(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let verifier_address = Addr::unchecked("verifier").to_string(); + let key_type = crate::key::KeyType::Ecdsa; + let res = client.public_key(verifier_address.clone(), key_type); + assert!(res.is_err()); + goldie::assert!(res.unwrap_err().to_string()); + } + + #[test] + fn query_public_key_returns_public_key() { + let (querier, addr) = setup_queries_to_succeed(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let verifier_address = Addr::unchecked("verifier").to_string(); + let key_type = crate::key::KeyType::Ecdsa; + let res = client.public_key(verifier_address.clone(), key_type); + println!("{:?}", res); + assert!(res.is_ok()); + goldie::assert_json!(res.unwrap()); + } + + #[test] + fn query_is_caller_authorized_returns_error_when_query_errors() { + let (querier, addr) = setup_queries_to_fail(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let contract_address = Addr::unchecked("prover").to_string(); + let chain_name = "ethereum".parse().unwrap(); + let res = client.is_caller_authorized(contract_address, chain_name); + assert!(res.is_err()); + goldie::assert!(res.unwrap_err().to_string()); + } + + #[test] + fn query_is_caller_authorized_returns_caller_authorization() { + let (querier, addr) = setup_queries_to_succeed(); + let client: Client = client::Client::new(QuerierWrapper::new(&querier), &addr).into(); + + let contract_address = Addr::unchecked("prover").to_string(); + let chain_name = "ethereum".parse().unwrap(); + let res = client.is_caller_authorized(contract_address, chain_name); + assert!(res.is_ok()); + goldie::assert_json!(res.unwrap()); + } + + fn setup_queries_to_fail() -> (MockQuerier, Addr) { + let addr = "multisig"; + + let mut querier = MockQuerier::default(); + querier.update_wasm(move |msg| match msg { + WasmQuery::Smart { + contract_addr, + msg: _, + } if contract_addr == addr => { + Err(SystemError::Unknown {}).into() // simulate cryptic error seen in production + } + _ => panic!("unexpected query: {:?}", msg), + }); + + (querier, Addr::unchecked(addr)) + } + + fn setup_queries_to_succeed() -> (MockQuerier, Addr) { + let addr = "multisig"; + + let mut querier = MockQuerier::default(); + querier.update_wasm(move |msg| match msg { + WasmQuery::Smart { contract_addr, msg } if contract_addr == addr => { + let msg = from_json::(msg).unwrap(); + match msg { + QueryMsg::Multisig { session_id: _ } => Ok(to_json_binary(&Multisig { + state: MultisigState::Completed { completed_at: 1 }, + verifier_set: build_verifier_set( + crate::key::KeyType::Ecdsa, + &ecdsa_test_data::signers(), + ), + + signatures: ecdsa_test_data::signers() + .into_iter() + .map(|signer| { + ( + signer.address.to_string(), + Signature::try_from((KeyType::Ecdsa, signer.signature)) + .unwrap(), + ) + }) + .collect(), + }) + .into()) + .into(), + QueryMsg::VerifierSet { verifier_set_id: _ } => Ok(to_json_binary( + &build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), + ) + .into()) + .into(), + QueryMsg::PublicKey { + verifier_address: _, + key_type: _, + } => Ok(to_json_binary( + &PublicKey::try_from((KeyType::Ecdsa, ecdsa_test_data::pub_key())).unwrap(), + ) + .into()) + .into(), + QueryMsg::IsCallerAuthorized { + contract_address: _, + chain_name: _, + } => Ok(to_json_binary(&true).into()).into(), + } + } + _ => panic!("unexpected query: {:?}", msg), + }); + + (querier, Addr::unchecked(addr)) + } +} diff --git a/contracts/multisig/src/lib.rs b/contracts/multisig/src/lib.rs index e533db1d9..406ce2af1 100644 --- a/contracts/multisig/src/lib.rs +++ b/contracts/multisig/src/lib.rs @@ -1,3 +1,4 @@ +pub mod client; pub mod contract; pub mod error; pub mod events; diff --git a/contracts/multisig/src/testdata/query_is_caller_authorized_returns_caller_authorization.golden b/contracts/multisig/src/testdata/query_is_caller_authorized_returns_caller_authorization.golden new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/contracts/multisig/src/testdata/query_is_caller_authorized_returns_caller_authorization.golden @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_is_caller_authorized_returns_error_when_query_errors.golden b/contracts/multisig/src/testdata/query_is_caller_authorized_returns_error_when_query_errors.golden new file mode 100644 index 000000000..89ed00b20 --- /dev/null +++ b/contracts/multisig/src/testdata/query_is_caller_authorized_returns_error_when_query_errors.golden @@ -0,0 +1 @@ +failed to query multisig contract for caller authorization. contract_address: prover, chain_name: ethereum \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_multisig_session_returns_error_when_query_errors.golden b/contracts/multisig/src/testdata/query_multisig_session_returns_error_when_query_errors.golden new file mode 100644 index 000000000..7c566195b --- /dev/null +++ b/contracts/multisig/src/testdata/query_multisig_session_returns_error_when_query_errors.golden @@ -0,0 +1 @@ +failed to query multisig contract for multisig session. session_id: 1 \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_multisig_session_returns_session.golden b/contracts/multisig/src/testdata/query_multisig_session_returns_session.golden new file mode 100644 index 000000000..dfb68953c --- /dev/null +++ b/contracts/multisig/src/testdata/query_multisig_session_returns_session.golden @@ -0,0 +1,45 @@ +{ + "state": { + "completed": { + "completed_at": 1 + } + }, + "verifier_set": { + "signers": { + "signer1": { + "address": "signer1", + "weight": "1", + "pub_key": { + "ecdsa": "025e0231bfad810e5276e2cf9eb2f3f380ce0bdf6d84c3b6173499d3ddcc008856" + } + }, + "signer2": { + "address": "signer2", + "weight": "1", + "pub_key": { + "ecdsa": "036ff6f4b2bc5e08aba924bd8fd986608f3685ca651a015b3d9d6a656de14769fe" + } + }, + "signer3": { + "address": "signer3", + "weight": "1", + "pub_key": { + "ecdsa": "03686cbbef9f9e9a5c852883cb2637b55fc76bee6ee6a3ff636e7bea2e41beece4" + } + } + }, + "threshold": "2", + "created_at": 0 + }, + "signatures": { + "signer2": { + "ecdsa": "a7ec5d1c15e84ba4b5da23fee49d77c5c81b3b1859411d1ef8193bf5a39783c76813e4cf4e1e1bfa0ea19c9f5b61d25ce978da137f3adb1730cba3d842702e72" + }, + "signer3": { + "ecdsa": "d1bc22fd89d97dfe4091c73d2002823ca9ab29b742ae531d2560bf2abafb313f7d2c3263d09d9aa72f01ed1d49046e39f6513ea61241fd59cc53d02fc4222351" + }, + "signer1": { + "ecdsa": "d7822dd89b9df02d64b91f69cff5811dfd4de16b792d9c6054b417c733bbcc542c1e504c8a1dffac94b5828a93e33a6b45d1bf59b2f9f28ffa56b8398d68a1c5" + } + } +} \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_public_key_returns_error_when_query_errors.golden b/contracts/multisig/src/testdata/query_public_key_returns_error_when_query_errors.golden new file mode 100644 index 000000000..cef40250f --- /dev/null +++ b/contracts/multisig/src/testdata/query_public_key_returns_error_when_query_errors.golden @@ -0,0 +1 @@ +failed to query multisig contract for verifier public key. verifier_address: verifier, key_type: Ecdsa \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_public_key_returns_public_key.golden b/contracts/multisig/src/testdata/query_public_key_returns_public_key.golden new file mode 100644 index 000000000..ee8142a34 --- /dev/null +++ b/contracts/multisig/src/testdata/query_public_key_returns_public_key.golden @@ -0,0 +1,3 @@ +{ + "ecdsa": "025e0231bfad810e5276e2cf9eb2f3f380ce0bdf6d84c3b6173499d3ddcc008856" +} \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_verifier_set_returns_error_when_query_errors.golden b/contracts/multisig/src/testdata/query_verifier_set_returns_error_when_query_errors.golden new file mode 100644 index 000000000..be3fb0413 --- /dev/null +++ b/contracts/multisig/src/testdata/query_verifier_set_returns_error_when_query_errors.golden @@ -0,0 +1 @@ +failed to query multisig contract for verifier set: verifier_set_id: my_set \ No newline at end of file diff --git a/contracts/multisig/src/testdata/query_verifier_set_returns_verifier_set.golden b/contracts/multisig/src/testdata/query_verifier_set_returns_verifier_set.golden new file mode 100644 index 000000000..ac93d5e0b --- /dev/null +++ b/contracts/multisig/src/testdata/query_verifier_set_returns_verifier_set.golden @@ -0,0 +1,27 @@ +{ + "signers": { + "signer1": { + "address": "signer1", + "weight": "1", + "pub_key": { + "ecdsa": "025e0231bfad810e5276e2cf9eb2f3f380ce0bdf6d84c3b6173499d3ddcc008856" + } + }, + "signer2": { + "address": "signer2", + "weight": "1", + "pub_key": { + "ecdsa": "036ff6f4b2bc5e08aba924bd8fd986608f3685ca651a015b3d9d6a656de14769fe" + } + }, + "signer3": { + "address": "signer3", + "weight": "1", + "pub_key": { + "ecdsa": "03686cbbef9f9e9a5c852883cb2637b55fc76bee6ee6a3ff636e7bea2e41beece4" + } + } + }, + "threshold": "2", + "created_at": 0 +} \ No newline at end of file