Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cherry pick upstream's commits about vm querier mock #180

Merged
merged 5 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/reflect/schema/query_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@
"WasmQuery": {
"oneOf": [
{
"description": "this queries the public API of another contract at a known address (with known ABI) return value is whatever the contract returns (caller should know)",
"description": "this queries the public API of another contract at a known address (with known ABI) Return value is whatever the contract returns (caller should know), wrapped in a ContractResult that is JSON encoded.",
"type": "object",
"required": [
"smart"
Expand Down
172 changes: 157 additions & 15 deletions packages/std/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,13 +386,12 @@ pub fn mock_ibc_packet_timeout(
pub type MockQuerierCustomHandlerResult = SystemResult<ContractResult<Binary>>;

/// MockQuerier holds an immutable table of bank balances
/// TODO: also allow querying contracts
/// and configurable handlers for Wasm queries and custom queries.
pub struct MockQuerier<C: DeserializeOwned = Empty> {
bank: BankQuerier,
#[cfg(feature = "staking")]
staking: StakingQuerier,
// placeholder to add support later
wasm: NoWasmQuerier,
wasm: WasmQuerier,
/// A handler to handle custom queries. This is set to a dummy handler that
/// always errors by default. Update it via `with_custom_handler`.
///
Expand All @@ -406,7 +405,7 @@ impl<C: DeserializeOwned> MockQuerier<C> {
bank: BankQuerier::new(balances),
#[cfg(feature = "staking")]
staking: StakingQuerier::default(),
wasm: NoWasmQuerier {},
wasm: WasmQuerier::default(),
// strange argument notation suggested as a workaround here: https://github.com/rust-lang/rust/issues/41078#issuecomment-294296365
custom_handler: Box::from(|_: &_| -> MockQuerierCustomHandlerResult {
SystemResult::Err(SystemError::UnsupportedRequest {
Expand Down Expand Up @@ -435,6 +434,13 @@ impl<C: DeserializeOwned> MockQuerier<C> {
self.staking = StakingQuerier::new(denom, validators, delegations);
}

pub fn update_wasm<WH: 'static>(&mut self, handler: WH)
where
WH: Fn(&WasmQuery) -> QuerierResult,
{
self.wasm.update_handler(handler)
}

#[must_use]
pub fn with_custom_handler<CH: 'static>(mut self, handler: CH) -> Self
where
Expand Down Expand Up @@ -480,19 +486,42 @@ impl<C: CustomQuery + DeserializeOwned> MockQuerier<C> {
}
}

#[derive(Clone, Default)]
struct NoWasmQuerier {
// FIXME: actually provide a way to call out
struct WasmQuerier {
/// A handler to handle Wasm queries. This is set to a dummy handler that
/// always errors by default. Update it via `with_custom_handler`.
///
/// Use box to avoid the need of generic type.
handler: Box<dyn for<'a> Fn(&'a WasmQuery) -> QuerierResult>,
}

impl NoWasmQuerier {
impl WasmQuerier {
fn new(handler: Box<dyn for<'a> Fn(&'a WasmQuery) -> QuerierResult>) -> Self {
Self { handler }
}

fn update_handler<WH: 'static>(&mut self, handler: WH)
where
WH: Fn(&WasmQuery) -> QuerierResult,
{
self.handler = Box::from(handler)
}

fn query(&self, request: &WasmQuery) -> QuerierResult {
let addr = match request {
WasmQuery::Smart { contract_addr, .. } => contract_addr,
WasmQuery::Raw { contract_addr, .. } => contract_addr,
}
.clone();
SystemResult::Err(SystemError::NoSuchContract { addr })
(*self.handler)(request)
}
}

impl Default for WasmQuerier {
fn default() -> Self {
let handler = Box::from(|request: &WasmQuery| -> QuerierResult {
let addr = match request {
WasmQuery::Smart { contract_addr, .. } => contract_addr,
WasmQuery::Raw { contract_addr, .. } => contract_addr,
}
.clone();
SystemResult::Err(SystemError::NoSuchContract { addr })
});
Self::new(handler)
}
}

Expand Down Expand Up @@ -689,10 +718,11 @@ pub fn mock_wasmd_attr(key: impl Into<String>, value: impl Into<String>) -> Attr
#[cfg(test)]
mod tests {
use super::*;
use crate::{coin, coins, from_binary};
use crate::{coin, coins, from_binary, to_binary, Response};
#[cfg(feature = "staking")]
use crate::{Decimal, Delegation};
use hex_literal::hex;
use serde::Deserialize;

const SECP256K1_MSG_HASH_HEX: &str =
"5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0";
Expand Down Expand Up @@ -1236,6 +1266,118 @@ mod tests {
assert_eq!(dels, Some(del2c));
}

#[test]
fn wasm_querier_works() {
let mut querier = WasmQuerier::default();

let any_addr = "foo".to_string();

// Query WasmQuery::Raw
let system_err = querier
.query(&WasmQuery::Raw {
contract_addr: any_addr.clone(),
key: b"the key".into(),
})
.unwrap_err();
match system_err {
SystemError::NoSuchContract { addr } => assert_eq!(addr, any_addr),
err => panic!("Unexpected error: {:?}", err),
}

// Query WasmQuery::Smart
let system_err = querier
.query(&WasmQuery::Smart {
contract_addr: any_addr.clone(),
msg: b"{}".into(),
})
.unwrap_err();
match system_err {
SystemError::NoSuchContract { addr } => assert_eq!(addr, any_addr),
err => panic!("Unexpected error: {:?}", err),
}

querier.update_handler(|request| {
let constract1 = Addr::unchecked("contract1");
let mut storage1 = HashMap::<Binary, Binary>::default();
storage1.insert(b"the key".into(), b"the value".into());

match request {
WasmQuery::Raw { contract_addr, key } => {
if *contract_addr == constract1 {
if let Some(value) = storage1.get(key) {
SystemResult::Ok(ContractResult::Ok(value.clone()))
} else {
SystemResult::Ok(ContractResult::Ok(Binary::default()))
}
} else {
SystemResult::Err(SystemError::NoSuchContract {
addr: contract_addr.clone(),
})
}
}
WasmQuery::Smart { contract_addr, msg } => {
if *contract_addr == constract1 {
#[derive(Deserialize)]
struct MyMsg {}
let _msg: MyMsg = match from_binary(msg) {
Ok(msg) => msg,
Err(err) => {
return SystemResult::Ok(ContractResult::Err(err.to_string()))
}
};
let response: Response = Response::new().set_data(b"good");
SystemResult::Ok(ContractResult::Ok(to_binary(&response).unwrap()))
} else {
SystemResult::Err(SystemError::NoSuchContract {
addr: contract_addr.clone(),
})
}
}
}
});

// WasmQuery::Raw
let result = querier.query(&WasmQuery::Raw {
contract_addr: "contract1".into(),
key: b"the key".into(),
});
match result {
SystemResult::Ok(ContractResult::Ok(value)) => assert_eq!(value, b"the value" as &[u8]),
res => panic!("Unexpected result: {:?}", res),
}
let result = querier.query(&WasmQuery::Raw {
contract_addr: "contract1".into(),
key: b"other key".into(),
});
match result {
SystemResult::Ok(ContractResult::Ok(value)) => assert_eq!(value, b"" as &[u8]),
res => panic!("Unexpected result: {:?}", res),
}

// WasmQuery::Smart
let result = querier.query(&WasmQuery::Smart {
contract_addr: "contract1".into(),
msg: b"{}".into(),
});
match result {
SystemResult::Ok(ContractResult::Ok(value)) => assert_eq!(
value,
br#"{"messages":[],"attributes":[],"events":[],"data":"Z29vZA=="}"# as &[u8]
),
res => panic!("Unexpected result: {:?}", res),
}
let result = querier.query(&WasmQuery::Smart {
contract_addr: "contract1".into(),
msg: b"a broken request".into(),
});
match result {
SystemResult::Ok(ContractResult::Err(err)) => {
assert_eq!(err, "Error parsing into type cosmwasm_std::mock::tests::wasm_querier_works::{{closure}}::MyMsg: Invalid type")
}
res => panic!("Unexpected result: {:?}", res),
}
}

#[test]
fn riffle_shuffle_works() {
// Example from https://en.wikipedia.org/wiki/In_shuffle
Expand Down
3 changes: 2 additions & 1 deletion packages/std/src/query/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::Binary;
#[serde(rename_all = "snake_case")]
pub enum WasmQuery {
/// this queries the public API of another contract at a known address (with known ABI)
/// return value is whatever the contract returns (caller should know)
/// Return value is whatever the contract returns (caller should know), wrapped in a
/// ContractResult that is JSON encoded.
Smart {
contract_addr: String,
/// msg is the json-encoded QueryMsg struct
Expand Down
8 changes: 7 additions & 1 deletion packages/vm/src/testing/querier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const GAS_COST_QUERY_REQUEST_MULTIPLIER: u64 = 0;
const GAS_COST_QUERY_RESPONSE_MULTIPLIER: u64 = 100;

/// MockQuerier holds an immutable table of bank balances
/// TODO: also allow querying contracts
pub struct MockQuerier<C: CustomQuery + DeserializeOwned = Empty> {
querier: StdMockQuerier<C>,
}
Expand Down Expand Up @@ -46,6 +45,13 @@ impl<C: CustomQuery + DeserializeOwned> MockQuerier<C> {
self.querier.update_staking(denom, validators, delegations);
}

pub fn update_wasm<WH: 'static>(&mut self, handler: WH)
where
WH: Fn(&cosmwasm_std::WasmQuery) -> cosmwasm_std::QuerierResult,
{
self.querier.update_wasm(handler)
}

#[must_use]
pub fn with_custom_handler<CH: 'static>(mut self, handler: CH) -> Self
where
Expand Down