First contract Query
In the previous section, you added a message to register an address under a name in storage. In this section, you make it possible to easily query for said stored values.
If you skipped the previous section, you can just switch the project to its first-execute-message
branch and take it from there.
The query message and response
To query from storage, you add a specific query message and its corresponding response. Add a QueryResponse
import to src/msg.rs
:
- use cosmwasm_schema::cw_serde;
+ use cosmwasm_schema::{cw_serde, QueryResponses};
Then add the new enum and struct:
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(ResolveRecordResponse)]
ResolveRecord { name: String },
}
#[cw_serde]
pub struct ResolveRecordResponse {
pub address: Option<String>,
}
Note how:
- As with the transaction message, the
QueryMsg
is an enum. - The
ResolveRecord
type mentions the type it returns with the use of thereturns
macro. QueryResponses
is a macro.ResolveRecordResponse
contains anOption<String>
to account for the fact that a missing owner is a valid result when resolving a name.ResolveRecordResponse
otherwise looks very much like aNameRecord
, but it could be different and collect different values from different places, depending on what is needed with this query.
The query function
You define the message handling in src/contract.rs
. Adjust the imports:
use crate::{
error::ContractError,
- msg::{ExecuteMsg, InstantiateMsg},
+ msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ResolveRecordResponse},
state::{NameRecord, NAME_RESOLVER},
};
- use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response};
+ use cosmwasm_std::{
+ entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
+ };
Then, below the execute
function, you add the query functions:
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::ResolveRecord { name } => query_resolve_record(deps, name),
}
}
fn query_resolve_record(deps: Deps, name: String) -> StdResult<Binary> {
let key = name.as_bytes();
let address = NAME_RESOLVER
.may_load(deps.storage, key)?
.map(|record| record.owner.to_string());
let resp = ResolveRecordResponse { address };
to_json_binary(&resp)
}
Note how:
- Just as for the
execute
function, thequery
function is only here to dispatch to other functions depending on the message variant. - Unlike the
execute
function, it takes a non-mutableDeps
. Indeed, a query is not meant to modify storage, and Rust can catch such errors at compilation instead of run time. - The function uses the
.may_load
method to handle potential errors gracefully. See Rust's?
conditionalreturn
. - The
address
variable is anOption<String>
because a missing value in storage is a valid response. - The return type is JSON binary, that you create by calling the standard
serde
functionto_json_binary
.
With this done, you can now query registered addresses by their names.
Unit testing
It's time for your third unit test. In src/contract.rs
, add the following:
...
#[cfg(test)]
mod tests {
use crate::{
- msg::{ExecuteMsg, InstantiateMsg},
+ msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
state::{NameRecord, NAME_RESOLVER},
};
- use cosmwasm_std::{testing, Addr, Response};
+ use cosmwasm_std::{testing, Addr, Binary, Response};
...
#[test]
fn test_execute() {
...
}
+ #[test]
+ fn test_query() {
+ // Arrange
+ let mut mocked_deps_mut = testing::mock_dependencies();
+ let mocked_env = testing::mock_env();
+ let name = "alice".to_owned();
+ let mocked_addr_value = "addr".to_owned();
+ let mocked_addr = Addr::unchecked(mocked_addr_value.clone());
+ let mocked_msg_info = testing::message_info(&mocked_addr, &[]);
+ let _ = super::execute_register(mocked_deps_mut.as_mut(), mocked_msg_info, name.clone())
+ .expect("Failed to register alice");
+ let query_msg = QueryMsg::ResolveRecord { name };
+
+ // Act
+ let query_result = super::query(mocked_deps_mut.as_ref(), mocked_env, query_msg);
+
+ // Assert
+ assert!(query_result.is_ok(), "Failed to query alice name");
+ let expected_response = format!(r#"{{"address":"{mocked_addr_value}"}}"#);
+ let expected = Binary::new(expected_response.as_bytes().to_vec());
+ assert_eq!(query_result.unwrap(), expected);
+ }
}
Note that:
- When arranging for the test, you keep the address string value for reuse.
- The expected string reads as a JSON, and that it is created with escaped characters made possible with
{{
and the raw string markerr#...#
. Binary::new(expected_response.as_bytes().to_vec())
converts to a binary.
After you run cargo test
, it should print its success in the output:
...
running 3 tests
test contract::tests::test_instantiate ... ok
test contract::tests::test_execute ... ok
test contract::tests::test_query ... ok
...
Conclusion
You have created a query message and added its handling so that users and other smart contracts can check the registration status of names.
At this stage, you should have something similar to the first-query-message
branch, with this as the diff.
So far your unit tests have only tested functions in isolation and run within Rust, without touching WebAssembly or CosmWasm. You expand a bit in the next section by having your functions interact with a mocked app chain.