Skip to content

Commit

Permalink
refactor: isolate mutability and emit missing events (#260)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgorenflo authored Feb 15, 2024
1 parent ece3349 commit 8f99242
Show file tree
Hide file tree
Showing 20 changed files with 12,419 additions and 699 deletions.
1 change: 1 addition & 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 contracts/aggregate-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ 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"] }
Expand Down
141 changes: 141 additions & 0 deletions contracts/aggregate-verifier/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use axelar_wasm_std::utils::TryMapExt;
use axelar_wasm_std::{FnExt, VerificationStatus};
use connection_router::state::CrossChainId;
use connection_router::Message;
use cosmwasm_std::{to_binary, Addr, QuerierWrapper, QueryRequest, WasmMsg, WasmQuery};
use error_stack::{Result, ResultExt};
use serde::de::DeserializeOwned;
use std::collections::HashMap;

pub struct Verifier<'a> {
pub querier: QuerierWrapper<'a>,
pub address: Addr,
}

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![],
}
}

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 fn verify(&self, msgs: Vec<Message>) -> Option<WasmMsg> {
ignore_empty(msgs)
.map(|msgs| self.execute(&crate::msg::ExecuteMsg::VerifyMessages { messages: msgs }))
}

pub fn messages_with_status(
&self,
msgs: Vec<Message>,
) -> Result<impl Iterator<Item = (Message, VerificationStatus)>, Error> {
ignore_empty(msgs.clone())
.try_map(|msgs| self.query_message_status(msgs))?
.map(|status_by_id| ids_to_msgs(status_by_id, msgs))
.into_iter()
.flatten()
.then(Ok)
}

fn query_message_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)
}
}

// TODO: unify across contract clients
fn ignore_empty(msgs: Vec<Message>) -> Option<Vec<Message>> {
if msgs.is_empty() {
None
} else {
Some(msgs)
}
}

fn ids_to_msgs(
status_by_id: HashMap<CrossChainId, VerificationStatus>,
msgs: Vec<Message>,
) -> impl Iterator<Item = (Message, VerificationStatus)> {
msgs.into_iter().map(move |msg| {
let status = status_by_id
.get(&msg.cc_id)
.copied()
.unwrap_or(VerificationStatus::None);
(msg, status)
})
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("could not query the verifier contract")]
QueryVerifier,
}

#[cfg(test)]
mod tests {
use axelar_wasm_std::VerificationStatus;
use connection_router::state::CrossChainId;
use cosmwasm_std::testing::MockQuerier;
use std::str::FromStr;

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),
};

let result = verifier.query::<Vec<(CrossChainId, VerificationStatus)>>(
&crate::msg::QueryMsg::GetMessagesStatus { messages: vec![] },
);

assert!(matches!(
result.unwrap_err().current_context(),
Error::QueryVerifier
))
}

// 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() {
let mut querier = MockQuerier::default();
querier.update_wasm(|_| {
Ok(to_binary(&CrossChainId::from_str("eth:0x1234").unwrap()).into()).into()
});

let verifier = Verifier {
address: Addr::unchecked("not a contract"),
querier: QuerierWrapper::new(&querier),
};

let result = verifier.query::<Vec<(CrossChainId, VerificationStatus)>>(
&crate::msg::QueryMsg::GetMessagesStatus { messages: vec![] },
);

assert!(matches!(
result.unwrap_err().current_context(),
Error::QueryVerifier
))
}
}
1 change: 1 addition & 0 deletions contracts/aggregate-verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod client;
pub mod contract;
pub mod error;
pub mod msg;
Expand Down
30 changes: 30 additions & 0 deletions contracts/connection-router/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::msg::ExecuteMsg;
use crate::Message;
use cosmwasm_std::{to_binary, Addr, WasmMsg};

pub struct Router {
pub address: Addr,
}

impl Router {
fn execute(&self, msg: &ExecuteMsg) -> WasmMsg {
WasmMsg::Execute {
contract_addr: self.address.to_string(),
msg: to_binary(&msg).expect("msg should always be serializable"),
funds: vec![],
}
}

pub fn route(&self, msgs: Vec<Message>) -> Option<WasmMsg> {
ignore_empty(msgs).map(|msgs| self.execute(&ExecuteMsg::RouteMessages(msgs)))
}
}

// TODO: unify across contract clients
fn ignore_empty(msgs: Vec<Message>) -> Option<Vec<Message>> {
if msgs.is_empty() {
None
} else {
Some(msgs)
}
}
1 change: 1 addition & 0 deletions contracts/connection-router/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod client;
pub mod contract;
pub mod error;
pub mod events;
Expand Down
2 changes: 2 additions & 0 deletions contracts/gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ path = "src/bin/schema.rs"
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []
# generate golden files for the tests
generate_golden_files = []

[package.metadata.scripts]
optimize = """docker run --rm -v "$(pwd)":/code \
Expand Down
120 changes: 96 additions & 24 deletions contracts/gateway/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,126 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use std::fmt::Debug;

use crate::contract::execute::Contract;
use crate::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
state::{Config, CONFIG},
};
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

mod execute;
mod query;

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, axelar_wasm_std::ContractError> {
let router = deps.api.addr_validate(&msg.router_address)?;
let verifier = deps.api.addr_validate(&msg.verifier_address)?;

CONFIG.save(deps.storage, &Config { verifier, router })?;

Ok(Response::new())
Ok(internal::instantiate(deps, env, info, msg)?)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, axelar_wasm_std::ContractError> {
let mut contract = Contract::new(deps);
match msg {
ExecuteMsg::VerifyMessages(msgs) => contract.verify_messages(msgs),
ExecuteMsg::RouteMessages(msgs) => contract.route_messages(info.sender, msgs),
}
.map_err(axelar_wasm_std::ContractError::from)
Ok(internal::execute(deps, env, info, msg)?)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(
deps: Deps,
_env: Env,
env: Env,
msg: QueryMsg,
) -> Result<Binary, axelar_wasm_std::ContractError> {
match msg {
QueryMsg::GetMessages { message_ids } => query::get_messages(deps, message_ids),
Ok(internal::query(deps, env, msg)?)
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("gateway contract config is missing")]
ConfigMissing,
#[error("invalid store access")]
InvalidStoreAccess,
#[error("failed to serialize the response")]
SerializeResponse,
#[error("batch contains duplicate message ids")]
DuplicateMessageIds,
#[error("invalid address")]
InvalidAddress,
#[error("failed to query message status")]
MessageStatus,
}

mod internal {
use crate::contract::Error;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::Config;
use crate::{contract, state};
use aggregate_verifier::client::Verifier;
use connection_router::client::Router;
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use error_stack::{Result, ResultExt};

pub(crate) fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, Error> {
let router = deps
.api
.addr_validate(&msg.router_address)
.change_context(Error::InvalidAddress)
.attach_printable(msg.router_address)?;

let verifier = deps
.api
.addr_validate(&msg.verifier_address)
.change_context(Error::InvalidAddress)
.attach_printable(msg.verifier_address)?;

state::save_config(deps.storage, &Config { verifier, router })
.change_context(Error::InvalidStoreAccess)?;

Ok(Response::new())
}

pub(crate) fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
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 router = Router {
address: config.router,
};

match msg {
ExecuteMsg::VerifyMessages(msgs) => contract::execute::verify_messages(&verifier, msgs),
ExecuteMsg::RouteMessages(msgs) => {
if info.sender == router.address {
contract::execute::route_outgoing_messages(deps.storage, msgs)
} else {
contract::execute::route_incoming_messages(&verifier, &router, msgs)
}
}
}
}

pub(crate) fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary, Error> {
match msg {
QueryMsg::GetMessages { message_ids } => {
let msgs = contract::query::get_outgoing_messages(deps.storage, message_ids)?;
to_binary(&msgs).change_context(Error::SerializeResponse)
}
}
}
.map_err(axelar_wasm_std::ContractError::from)
}
Loading

0 comments on commit 8f99242

Please sign in to comment.