Creating a query

We can now initialize our contract and store some data in it. Let's write query to read it's content. We have already created a simple contract reacting to an empty instantiate message. Unfortunately, it is not very useful. Let's make it more reactive.

Declaring query response

Let's create a new file, src/responses.rs, containing responses to all the queries in our contract.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, schemars::JsonSchema, Debug, Default)]
pub struct AdminListResp {
    pub admins: Vec<String>,
}

We have here similar derives like in the case of InstantiateMsg. The most important ones are Serialize and Deserialize as we always want to return something serializable.

src/responses.rs is not part of our project, so let's change it. Go to src/lib.rs and add this module:

pub mod contract;
pub mod responses;

use cosmwasm_std::{entry_point, DepsMut, Empty, Env, MessageInfo, Response, StdResult};

use crate::contract::{InstantiateMsg, AdminContract};

const CONTRACT: AdminContract = AdminContract::new();

#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    msg.dispatch(&CONTRACT, (deps, env, info))
}

Now that we have a response created, go to your src/contract.rs file and declare a new query.

use crate::responses::AdminListResp;
use cosmwasm_std::{Addr, Deps, DepsMut, Empty, Env, MessageInfo, Order, Response, StdResult};
use cw_storage_plus::Map;
use schemars;
use sylvia::contract;

pub struct AdminContract<'a> {
   pub(crate) admins: Map<'static, &'a Addr, Empty>,
}

#[contract]
impl AdminContract<'_> {
    ...

    #[msg(query)]
    pub fn admin_list(&self, ctx: (Deps, Env)) -> StdResult<AdminListResp> {
        let (deps, _) = ctx;

        let admins: Result<_, _> = self
            .admins
            .keys(deps.storage, None, None, Order::Ascending)
            .map(|addr| addr.map(String::from))
            .collect();

        Ok(AdminListResp { admins: admins? })
    }
}

With this done, we can expand our contract macro and see that QueryMsg is generated.

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(
    sylvia::serde::Serialize,
    sylvia::serde::Deserialize,
    Clone,
    Debug,
    PartialEq,
    sylvia::schemars::JsonSchema,
    cosmwasm_schema::QueryResponses,
)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    #[returns(AdminListResp)]
    AdminList {},
}
impl QueryMsg {
    pub fn dispatch(
        self,
        contract: &AdminContract,
        ctx: (cosmwasm_std::Deps, cosmwasm_std::Env),
    ) -> std::result::Result<sylvia::cw_std::Binary, ContractError> {
        use QueryMsg::*;
        match self {
            AdminList {} => {
                cosmwasm_std::to_binary(&contract.admin_list(ctx.into())?).map_err(Into::into)
            }
        }
    }
}

We will ignore #[returns(_)] and cosmwasm_schema::QueryResponses as they will be described later when we will talk about generating schema.

QueryMsg is an enum that will contain every query declared in your expanded impl. Thanks to that you can focus solely on defining the behavior of the contract on receiving a message, and you can leave it to sylvia to generate the messages and the dispatch.

Note that our enum has no type assigned to the only AdminList variant. Typically in Rust, we create such variants without additional {} after the variant name. Here the curly braces have a purpose. Without, them the variant would serialize to just a string type - so instead of { "admin_list": {} }, the JSON representation of this variant would be "admin_list".

Instead of returning the Response type on the success case, we return an arbitrary serializable object. It's because queries are not using a typical actor model message flow - they cannot trigger any actions nor communicate with other contracts in ways different than querying them (which is handled by the deps argument). The query always returns plain data, which should be presented directly to the querier. Sylvia does that by returning encoded response as Binary by calling to_binary function in dispatch.

In the case of query ctx is tuple of Deps and Env. We use Deps instead of DepsMut as we did in the case of instantiate because the query can never alter internal state of the smart contracts . It can only read the state. It comes with some consequences - for example, it is impossible to implement caching for future queries (as it would require some data cache to write to).

The other difference is the lack of the info argument. The reason here is that the entry point which performs actions (like instantiation or execution) can differ in how an action is performed based on the message metadata - for example, they can limit who can perform an action (and do so by checking the message sender). It is not a case for queries. Queries are purely to return some transformed contract state. It can be calculated based on chain metadata (so the state can "automatically" change after some time) but not on message info.

Now that QueryMsg is created, let's allow users to call it by defining the entry point for query in src/lib.rs.

pub mod contract;
pub mod responses;

use contract::{ContractError, ContractQueryMsg};
use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};

use crate::contract::{AdminContract, InstantiateMsg};

const CONTRACT: AdminContract = AdminContract::new();

#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    msg.dispatch(&CONTRACT, (deps, env, info))
}

#[entry_point]
pub fn query(deps: Deps, env: Env, msg: ContractQueryMsg) -> Result<Binary, ContractError> {
    msg.dispatch(&CONTRACT, (deps, env))
}

There is one more new thing here. We were still talking about QueryMsg, but now we use ContractQueryMsg out of nowhere. Let me explain. Sylvia framework allows us to define interfaces. Users can create interfaces with specific functionalities and then implement them on contract. ContractQueryMsg is wrapper over QueryMsgs from a contract and it's interfaces which dispatch will call proper implementation. We will learn about Interfaces further in the book.

Now, when we have the contract ready to do something, let's go and test it.