Skip to content

Commit

Permalink
Feature/add mock querier (#147)
Browse files Browse the repository at this point in the history
* Started mock remote querier implementation

* formatting

* Better mock querier

* formatting

* Fix terra rpc

* Format

* Removed base on querier

* Need page response in request

* formatting

* Revert commit

* move and rename to `live_mock`

* add information on mock querier to docs.

---------

Co-authored-by: cyberhoward <[email protected]>
Co-authored-by: CyberHoward <[email protected]>
  • Loading branch information
3 people authored Jun 21, 2023
1 parent ec584eb commit 75ccce8
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 0 deletions.
3 changes: 3 additions & 0 deletions cw-orch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ pub mod mock;
mod paths;

pub mod state;

#[cfg(feature = "daemon")]
pub mod live_mock;
262 changes: 262 additions & 0 deletions cw-orch/src/live_mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
//! Live mock is a mock that uses a live chain to query for data.
//! It can be used to do chain-backed unit-testing. It can't be used for state-changing operations.

use crate::daemon::queriers::CosmWasm;
use crate::daemon::queriers::DaemonQuerier;
use crate::prelude::queriers::Bank;
use crate::prelude::queriers::Staking;
use cosmwasm_std::Addr;
use cosmwasm_std::AllBalanceResponse;
use cosmwasm_std::BalanceResponse;
use cosmwasm_std::Delegation;
use cosmwasm_std::{AllDelegationsResponse, BondedDenomResponse};

use cosmwasm_std::BankQuery;
use cosmwasm_std::Binary;
use cosmwasm_std::Empty;
use cosmwasm_std::StakingQuery;
use ibc_chain_registry::chain::ChainData;
use tokio::runtime::Runtime;
use tonic::transport::Channel;

use std::marker::PhantomData;
use std::str::FromStr;

use cosmwasm_std::testing::{MockApi, MockStorage};
use cosmwasm_std::{
from_slice, to_binary, Coin, ContractResult, OwnedDeps, Querier, QuerierResult, QueryRequest,
SystemError, SystemResult, Uint128, WasmQuery,
};

use crate::daemon::GrpcChannel;

fn to_cosmwasm_coin(c: cosmrs::proto::cosmos::base::v1beta1::Coin) -> Coin {
Coin {
amount: Uint128::from_str(&c.amount).unwrap(),
denom: c.denom,
}
}

const QUERIER_ERROR: &str =
"Only Bank balances and Wasm (raw + smart) and Some staking queries are covered for now";

/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies
/// this uses our CustomQuerier.
pub fn mock_dependencies(
chain_info: ChainData,
) -> OwnedDeps<MockStorage, MockApi, WasmMockQuerier> {
let custom_querier: WasmMockQuerier = WasmMockQuerier::new(chain_info);

OwnedDeps {
storage: MockStorage::default(),
api: MockApi::default(),
querier: custom_querier,
custom_query_type: PhantomData,
}
}

/// Querier struct that fetches queries on-chain directly
pub struct WasmMockQuerier {
channel: Channel,
runtime: Runtime,
}

impl Querier for WasmMockQuerier {
fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
// MockQuerier doesn't support Custom, so we ignore it completely here
let request: QueryRequest<Empty> = match from_slice(bin_request) {
Ok(v) => v,
Err(e) => {
return SystemResult::Err(SystemError::InvalidRequest {
error: format!("Parsing query request: {}", e),
request: bin_request.into(),
})
}
};
self.handle_query(&request)
}
}

impl WasmMockQuerier {
/// Function used to handle a query and customize the query behavior
/// This implements some queries by querying an actual node for the responses
pub fn handle_query(&self, request: &QueryRequest<Empty>) -> QuerierResult {
match &request {
QueryRequest::Wasm(x) => {
let querier = CosmWasm::new(self.channel.clone());
match x {
WasmQuery::Smart { contract_addr, msg } => {
// We forward the request to the cosmwasm querier

let query_result: Result<Binary, _> = self
.runtime
.block_on(
querier.contract_state(contract_addr.to_string(), msg.to_vec()),
)
.map(|query_result| query_result.into());
SystemResult::Ok(ContractResult::from(query_result))
}
WasmQuery::Raw { contract_addr, key } => {
// We forward the request to the cosmwasm querier

let query_result = self
.runtime
.block_on(
querier.contract_raw_state(contract_addr.to_string(), key.to_vec()),
)
.map(|query_result| query_result.data.into());
SystemResult::Ok(ContractResult::from(query_result))
}
_ => SystemResult::Err(SystemError::InvalidRequest {
error: QUERIER_ERROR.to_string(),
request: to_binary(&request).unwrap(),
}),
}
}
QueryRequest::Bank(x) => {
let querier = Bank::new(self.channel.clone());
match x {
BankQuery::Balance { address, denom } => {
let query_result = self
.runtime
.block_on(querier.balance(address, Some(denom.clone())))
.map(|result| {
to_binary(&BalanceResponse {
amount: Coin {
amount: Uint128::from_str(&result[0].amount).unwrap(),
denom: result[0].denom.clone(),
},
})
.unwrap()
});
SystemResult::Ok(ContractResult::from(query_result))
}
BankQuery::AllBalances { address } => {
let query_result = self
.runtime
.block_on(querier.balance(address, None))
.map(|result| AllBalanceResponse {
amount: result
.into_iter()
.map(|c| Coin {
amount: Uint128::from_str(&c.amount).unwrap(),
denom: c.denom,
})
.collect(),
})
.map(|query_result| to_binary(&query_result))
.unwrap();
SystemResult::Ok(ContractResult::from(query_result))
}
_ => SystemResult::Err(SystemError::InvalidRequest {
error: QUERIER_ERROR.to_string(),
request: to_binary(&request).unwrap(),
}),
}
}
QueryRequest::Staking(x) => {
let querier = Staking::new(self.channel.clone());
match x {
StakingQuery::BondedDenom {} => {
let query_result = self
.runtime
.block_on(querier.params())
.map(|result| BondedDenomResponse {
denom: result.params.unwrap().bond_denom,
})
.map(|query_result| to_binary(&query_result))
.unwrap();
SystemResult::Ok(ContractResult::from(query_result))
}
// This query is not perfect. I guess that on_chain you should be able to get ALL delegations and not a paginated result
// TODO, do better here
StakingQuery::AllDelegations { delegator } => {
let query_result = self
.runtime
.block_on(querier.delegator_delegations(delegator, None))
.map(|result| AllDelegationsResponse {
delegations: result
.delegation_responses
.into_iter()
.filter_map(|delegation| {
delegation.delegation.map(|d| Delegation {
delegator: Addr::unchecked(d.delegator_address),
validator: d.validator_address,
amount: to_cosmwasm_coin(delegation.balance.unwrap()),
})
})
.collect(),
})
.map(|query_result| to_binary(&query_result))
.unwrap();
SystemResult::Ok(ContractResult::from(query_result))
}
_ => todo!(),
}
}
_ => SystemResult::Err(SystemError::InvalidRequest {
error: QUERIER_ERROR.to_string(),
request: to_binary(&request).unwrap(),
}),
}
}
}

impl WasmMockQuerier {
/// Creates a querier from chain information
pub fn new(chain: ChainData) -> Self {
let rt = Runtime::new().unwrap();

let channel = rt
.block_on(GrpcChannel::connect(&chain.apis.grpc, &chain.chain_id))
.unwrap();

WasmMockQuerier {
channel,
runtime: rt,
}
}
}

#[cfg(test)]
mod tests {

use super::*;
use crate::prelude::networks::JUNO_1;

use super::mock_dependencies;

#[test]
fn bank_balance_querier() -> Result<(), anyhow::Error> {
let address = "juno1rkhrfuq7k2k68k0hctrmv8efyxul6tgn8hny6y";

let deps = mock_dependencies(JUNO_1.into());
let deps_ref = deps.as_ref();
let _response: BalanceResponse =
deps_ref
.querier
.query(&QueryRequest::Bank(BankQuery::Balance {
address: address.to_string(),
denom: "ujuno".to_string(),
}))?;
// We can't really test that response, but it has to unwrap at least !

Ok(())
}

#[test]
fn bank_all_balances_querier() -> Result<(), anyhow::Error> {
let address = "juno1rkhrfuq7k2k68k0hctrmv8efyxul6tgn8hny6y";

let deps = mock_dependencies(JUNO_1.into());
let deps_ref = deps.as_ref();
let _response: AllBalanceResponse =
deps_ref
.querier
.query(&QueryRequest::Bank(BankQuery::AllBalances {
address: address.to_string(),
}))?;
// We can't really test that response, but it has to unwrap at least !
Ok(())
}
}
1 change: 1 addition & 0 deletions docs/src/single_contract/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ The environments that are currently supported are:

1. [cw-multi-test](https://crates.io/crates/cw-multi-test) by using [`Mock`](https://docs.rs/cw-orch/latest/cw_orch/prelude/struct.Mock.html) as the environment.
2. Blockchain daemons like [junod](https://github.com/CosmosContracts/juno), [osmosisd](https://github.com/osmosis-labs/osmosis), etc. These use the [`Daemon`](https://docs.rs/cw-orch/latest/cw_orch/prelude/struct.Daemon.html) environment.
3. Chain-backed mock `deps` for unit-testing. This uses the [`MockQuerier`](https://docs.rs/cw-orch/latest/cw_orch/live_mock/struct.MockQuerier.html) that resolves all queries on a real node over gRPC.

### Generic function

Expand Down

0 comments on commit 75ccce8

Please sign in to comment.