Skip to content

Commit

Permalink
refactor(client): add the client package and migrate for the aggregat…
Browse files Browse the repository at this point in the history
…e-verifier contract (axelarnetwork#338)

* refactor(client): add the client package and migrate for the aggregate-verifier contract

* delete weird file

* implement From trait for client

* fix clippy
  • Loading branch information
fish-sammy authored Apr 11, 2024
1 parent 43a5a7f commit f6b9c2e
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 91 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions contracts/aggregate-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
198 changes: 125 additions & 73 deletions contracts/aggregate-verifier/src/client.rs
Original file line number Diff line number Diff line change
@@ -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<client::Client<'a, ExecuteMsg, QueryMsg>> for Client<'a> {
fn from(client: client::Client<'a, ExecuteMsg, QueryMsg>) -> Self {
Client { client }
}
}

fn query<U: DeserializeOwned + 'static>(&self, msg: &crate::msg::QueryMsg) -> Result<U, Error> {
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<Message>) -> Option<WasmMsg> {
ignore_empty(msgs)
.map(|msgs| self.execute(&crate::msg::ExecuteMsg::VerifyMessages { messages: msgs }))
impl<'a> Client<'a> {
pub fn verify_messages(&self, msgs: Vec<Message>) -> Option<WasmMsg> {
ignore_empty(msgs).map(|msgs| {
self.client
.execute(&ExecuteMsg::VerifyMessages { messages: msgs })
})
}

pub fn messages_with_status(
pub fn messages_status(
&self,
msgs: Vec<Message>,
) -> Result<impl Iterator<Item = (Message, VerificationStatus)>, 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<Message>,
) -> Result<HashMap<CrossChainId, VerificationStatus>, Error> {
self.query::<Vec<(CrossChainId, VerificationStatus)>>(
&crate::msg::QueryMsg::GetMessagesStatus { messages: msgs },
)?
.into_iter()
.collect::<HashMap<_, _>>()
.then(Ok)
self.client
.query::<Vec<(CrossChainId, VerificationStatus)>>(&QueryMsg::GetMessagesStatus {
messages: msgs,
})
.change_context(Error::QueryVerifier)?
.into_iter()
.collect::<HashMap<_, _>>()
.then(Ok)
}
}

Expand All @@ -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::<Vec<(CrossChainId, VerificationStatus)>>(
&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::<QueryMsg>(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::<QueryMsg>(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::<Vec<(CrossChainId, VerificationStatus)>>(
&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();
}
}
12 changes: 7 additions & 5 deletions contracts/aggregate-verifier/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,13 @@ pub fn reply(
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
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)
}
}
}
5 changes: 4 additions & 1 deletion contracts/aggregate-verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod client;
mod client;

pub use client::Client;

pub mod contract;
pub mod error;
pub mod msg;
Expand Down
2 changes: 2 additions & 0 deletions contracts/gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
7 changes: 2 additions & 5 deletions contracts/gateway/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -100,10 +100,7 @@ mod internal {
msg: ExecuteMsg,
) -> Result<Response, Error> {
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,
Expand Down
Loading

0 comments on commit f6b9c2e

Please sign in to comment.