Skip to content

Commit

Permalink
Add mailbox unit tests for post dispatch (many-things#91)
Browse files Browse the repository at this point in the history
* Add pausable ism with tests

* Fix paused query error case

* Run CI against all PRs

* Add pausable ISM to README

* Build wasm

* Fix scripts

* Allow threshold == set size and add tests

* Upload artifacts

* Force

* Move into makefile

* Install rename

* Rename properly

* Update test.yaml

* Fix renaming

* Fix makefile indentation

* Force cargo install

* simple fee hook (#6)

* Implement simple fee hook

* Address pr comments

* Fix unit tests

* Make set fee only owner

* Implement remaining unit tests

* Fix merkle integration test use

---------

Co-authored-by: nambrot <nambrot@googlemail.com>
Co-authored-by: ByeongSu Hong <hong@byeongsu.dev>

* Add mailbox unit tests for post dispatch (#7)

* Add mailbox unit tests for post dispatch

* Add test for different denoms

---------

Co-authored-by: nambrot <nambrot@googlemail.com>
Co-authored-by: ByeongSu Hong <hong@byeongsu.dev>
  • Loading branch information
3 people authored Jan 25, 2024
1 parent e5bfbba commit 6595945
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 13 deletions.
8 changes: 7 additions & 1 deletion contracts/core/mailbox/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cosmwasm_std::StdError;
use cosmwasm_std::{Coin, StdError};
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
Expand All @@ -9,6 +9,12 @@ pub enum ContractError {
#[error("{0}")]
Payment(#[from] cw_utils::PaymentError),

#[error("insufficient hook payment: wanted {wanted:?}, received {received:?}")]
HookPayment {
wanted: Vec<Coin>,
received: Vec<Coin>,
},

#[error("{0}")]
CoinsError(#[from] cosmwasm_std::CoinsError),

Expand Down
130 changes: 118 additions & 12 deletions contracts/core/mailbox/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,13 @@ pub fn dispatch(
let required_hook_fees: Vec<Coin> =
quote_dispatch(&deps.querier, &required_hook, metadata.clone(), msg.clone())?.fees;

let mut funds = Coins::try_from(info.funds)?;
let mut funds = Coins::try_from(info.funds.clone())?;
for coin in required_hook_fees.iter() {
if let Err(_) = funds.sub(coin.clone()) {
return Err(ContractError::Payment(MissingDenom(coin.denom.clone())));
return Err(ContractError::HookPayment {
wanted: required_hook_fees,
received: info.funds,
});
}
}

Expand Down Expand Up @@ -225,15 +228,18 @@ pub fn process(

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use cosmwasm_std::{
coin, from_json,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage},
to_json_binary, Addr, ContractResult, OwnedDeps, QuerierResult, SystemResult, WasmQuery,
to_json_binary, Addr, ContractResult, CosmosMsg, OwnedDeps, QuerierResult,
SystemResult, WasmMsg, WasmQuery,
};

use hpl_interface::{
core::mailbox::InstantiateMsg,
hook::{ExpectedHookQueryMsg, HookQueryMsg, QuoteDispatchResponse},
hook::{ExpectedHookQueryMsg, HookQueryMsg, PostDispatchMsg, QuoteDispatchResponse},
ism::IsmQueryMsg,
types::bech32_encode,
};
Expand All @@ -252,8 +258,11 @@ mod tests {

type TestDeps = OwnedDeps<MockStorage, MockApi, MockQuerier>;

fn mock_query_handler(req: &WasmQuery) -> QuerierResult {
let (req, _addr) = match req {
fn mock_query_handler(
req: &WasmQuery,
addr_fees: &Option<HashMap<String, Vec<Coin>>>,
) -> QuerierResult {
let (req, addr) = match req {
WasmQuery::Smart { msg, contract_addr } => (from_json(msg).unwrap(), contract_addr),
_ => unreachable!("wrong query type"),
};
Expand All @@ -263,17 +272,18 @@ mod tests {
_ => unreachable!("wrong query type"),
};

let mut fees = Coins::default();
let mut fees = match addr_fees {
Some(fees) => fees.get(addr).unwrap_or(&vec![]).clone(),
None => vec![],
};

if !req.metadata.is_empty() {
let parsed_fee = u32::from_be_bytes(req.metadata.as_slice().try_into().unwrap());

fees = Coins::from(coin(parsed_fee as u128, "utest"));
fees = vec![coin(parsed_fee as u128, "utest")];
}

let res = QuoteDispatchResponse {
fees: fees.into_vec(),
};
let res = QuoteDispatchResponse { fees };
let res = to_json_binary(&res).unwrap();

SystemResult::Ok(ContractResult::Ok(res))
Expand Down Expand Up @@ -396,7 +406,7 @@ mod tests {

let mut deps = mock_dependencies();

deps.querier.update_wasm(mock_query_handler);
deps.querier.update_wasm(|q| mock_query_handler(q, &None));

instantiate(
deps.as_mut(),
Expand Down Expand Up @@ -442,6 +452,102 @@ mod tests {
);
}

#[rstest]
#[case(vec![coin(100, "usd")], vec![coin(100, "usd")])]
#[should_panic]
#[case(vec![coin(100, "usd")], vec![coin(50, "usd")])]
#[should_panic]
#[case(vec![coin(100, "usdt")], vec![coin(100, "usd")])]
#[case(vec![coin(50, "usd")], vec![coin(100, "usd")])]
fn test_post_dispatch(#[case] required_hook_fees: Vec<Coin>, #[case] funds: Vec<Coin>) {
let mut deps = mock_dependencies();

let mut hook_fees = HashMap::new();
hook_fees.insert("required_hook".into(), required_hook_fees.clone());

// not enforced by mailbox
// hook_fees.insert("default_hook".into(), default_hook_fees);

let opt = Some(hook_fees);

deps.querier
.update_wasm(move |q| mock_query_handler(q, &opt));

let hrp = "osmo";

instantiate(
deps.as_mut(),
mock_env(),
mock_info(OWNER, &[]),
InstantiateMsg {
hrp: "osmo".to_string(),
owner: OWNER.to_string(),
domain: LOCAL_DOMAIN,
},
)
.unwrap();

set_default_hook(deps.as_mut(), mock_info(OWNER, &[]), "default_hook".into()).unwrap();
set_required_hook(deps.as_mut(), mock_info(OWNER, &[]), "required_hook".into()).unwrap();

let dispatch_msg = DispatchMsg::new(DEST_DOMAIN, gen_bz(32), gen_bz(123));

let sender = bech32_encode(hrp, gen_bz(32).as_slice()).unwrap();

let msg = dispatch_msg
.clone()
.to_msg(
MAILBOX_VERSION,
NONCE.load(deps.as_ref().storage).unwrap(),
LOCAL_DOMAIN,
&sender,
)
.unwrap();

let post_dispatch_msg = to_json_binary(
&PostDispatchMsg {
metadata: HexBinary::default(),
message: msg.into(),
}
.wrap(), // not sure why I need this
)
.unwrap();

let res = dispatch(
deps.as_mut(),
mock_info(sender.as_str(), &funds),
dispatch_msg.clone(),
)
.map_err(|e| e.to_string())
.unwrap();

let msgs: Vec<_> = res.messages.into_iter().map(|v| v.msg).collect();

assert_eq!(
msgs[0],
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: "required_hook".to_string(),
msg: post_dispatch_msg.clone(),
funds: required_hook_fees.clone()
},)
);

// subtract required_hook_fees from funds
let mut remaining_funds = Coins::try_from(funds).unwrap();
for coin in required_hook_fees {
remaining_funds.sub(coin).unwrap();
}

assert_eq!(
msgs[1],
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: "default_hook".to_string(),
msg: post_dispatch_msg,
funds: remaining_funds.into_vec() // forward all remaining funds
})
);
}

fn test_process_query_handler(query: &WasmQuery) -> QuerierResult {
match query {
WasmQuery::Smart { contract_addr, msg } => {
Expand Down

0 comments on commit 6595945

Please sign in to comment.