|
| 1 | +--- |
| 2 | +sidebar_position: 4 |
| 3 | +--- |
| 4 | + |
| 5 | +# Creating a query |
| 6 | + |
| 7 | +We have already created a simple contract reacting to an empty instantiate message. Unfortunately, |
| 8 | +it is not very useful. Let's make it a bit interactive. |
| 9 | + |
| 10 | +First, we need to add the [`serde`](https://crates.io/crates/serde) crate to our dependencies. It |
| 11 | +would help us with the serialization and deserialization of query messages. |
| 12 | + |
| 13 | +```toml title="Cargo.toml" {11} |
| 14 | +[package] |
| 15 | +name = "contract" |
| 16 | +version = "0.1.0" |
| 17 | +edition = "2021" |
| 18 | + |
| 19 | +[lib] |
| 20 | +crate-type = ["cdylib"] |
| 21 | + |
| 22 | +[dependencies] |
| 23 | +cosmwasm-std = { version = "2.1.4", features = ["staking"] } |
| 24 | +serde = { version = "1.0.214", default-features = false, features = ["derive"] } |
| 25 | +``` |
| 26 | + |
| 27 | +Now go add a new query entry point: |
| 28 | + |
| 29 | +```rust title="src/lib.rs" {7-10,22-29} |
| 30 | +use cosmwasm_std::{ |
| 31 | + entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, |
| 32 | + Response, StdResult, |
| 33 | +}; |
| 34 | +use serde::{Deserialize, Serialize}; |
| 35 | + |
| 36 | +#[derive(Serialize, Deserialize)] |
| 37 | +struct QueryResp { |
| 38 | + message: String, |
| 39 | +} |
| 40 | + |
| 41 | +#[entry_point] |
| 42 | +pub fn instantiate( |
| 43 | + _deps: DepsMut, |
| 44 | + _env: Env, |
| 45 | + _info: MessageInfo, |
| 46 | + _msg: Empty, |
| 47 | +) -> StdResult<Response> { |
| 48 | + Ok(Response::new()) |
| 49 | +} |
| 50 | + |
| 51 | +#[entry_point] |
| 52 | +pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult<Binary> { |
| 53 | + let resp = QueryResp { |
| 54 | + message: "Hello World".to_owned(), |
| 55 | + }; |
| 56 | + |
| 57 | + to_json_binary(&resp) |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +We first need a structure we will return from our query. We always want to return something which is serializable. |
| 62 | +We are just deriving the [`Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and |
| 63 | +[`Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html) traits from `serde` crate. |
| 64 | + |
| 65 | +Then we need to implement our entrypoint. It is very similar to the `instantiate` one. The first |
| 66 | +significant difference is the type of `deps` argument. For `instantiate`, it was a `DepMut`, but |
| 67 | +here we went with a [`Deps`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Deps.html) object. |
| 68 | +That is because the query can never alter the smart contract's internal state. It can only read the state. |
| 69 | +It comes with some consequences, for example, it is impossible to implement caching for future queries |
| 70 | +(as it would require some data cache to write to). |
| 71 | + |
| 72 | +The other difference is the lack of the `info` argument. The reason here is that the entrypoint |
| 73 | +which performs actions (like instantiation or execution) can differ how an action is performed based |
| 74 | +on the message metadata, for example, they can limit who can perform an action (and do so by |
| 75 | +checking the message `sender`). It is not the case for queries. Queries are supposed just purely to |
| 76 | +return some transformed contract state. It can be calculated based on some chain metadata (so the |
| 77 | +state can "automatically" change after some time), but not on message info. |
| 78 | + |
| 79 | +Note that our entrypoint still has the same `Empty` type for its `msg` argument, it means that the |
| 80 | +query message we would send to the contract is still an empty JSON: **`{}`**. |
| 81 | + |
| 82 | +The last thing that changed is the return type. Instead of returning the `Response` type for the |
| 83 | +success case, we return an arbitrary serializable object. This is because queries do not use a |
| 84 | +typical actor model message flow - they cannot trigger any actions nor communicate with other |
| 85 | +contracts in ways different from querying them (which is handled by the `deps` argument). |
| 86 | +The query always returns plain data, which should be presented directly to the querier. |
| 87 | + |
| 88 | +Now take a look at the implementation. Nothing complicated happens there, we create an object we |
| 89 | +want to return and encode it to the [`Binary`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Binary.html) |
| 90 | +type using the [`to_json_binary`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/fn.to_json_binary.html) function. |
| 91 | + |
| 92 | +## Improving the message |
| 93 | + |
| 94 | +We have a query entry point, but there is a problem with the query message. It is always an empty JSON. |
| 95 | +It is terrible, if we would like to add another query in the future, it would be difficult to have any reasonable |
| 96 | +distinction between query variants. |
| 97 | + |
| 98 | +In practice, we address this by using a non-empty query message type. Let's improve our contract: |
| 99 | + |
| 100 | +```rust title="src/lib.rs" {11-14,27,30-38} |
| 101 | +use cosmwasm_std::{ |
| 102 | + entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, |
| 103 | +}; |
| 104 | +use serde::{Deserialize, Serialize}; |
| 105 | + |
| 106 | +#[derive(Serialize, Deserialize)] |
| 107 | +struct QueryResp { |
| 108 | + message: String, |
| 109 | +} |
| 110 | + |
| 111 | +#[derive(Serialize, Deserialize)] |
| 112 | +pub enum QueryMsg { |
| 113 | + Greet {}, |
| 114 | +} |
| 115 | + |
| 116 | +#[entry_point] |
| 117 | +pub fn instantiate( |
| 118 | + _deps: DepsMut, |
| 119 | + _env: Env, |
| 120 | + _info: MessageInfo, |
| 121 | + _msg: Empty, |
| 122 | +) -> StdResult<Response> { |
| 123 | + Ok(Response::new()) |
| 124 | +} |
| 125 | + |
| 126 | +#[entry_point] |
| 127 | +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { |
| 128 | + use QueryMsg::*; |
| 129 | + |
| 130 | + match msg { |
| 131 | + Greet {} => { |
| 132 | + let resp = QueryResp { |
| 133 | + message: "Hello World".to_owned(), |
| 134 | + }; |
| 135 | + |
| 136 | + to_json_binary(&resp) |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +Now we have introduced a proper message type for the query message. It is an |
| 143 | +[enum](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html), and by default, it would |
| 144 | +serialize to a JSON with a single field. The name of the field will be an enum variant |
| 145 | +(in our case, always "greet", at least for now), and the value of this field would be an object |
| 146 | +assigned to this enum variant. |
| 147 | + |
| 148 | +Note that our enum has no type assigned to the only `Greet` variant. Typically in Rust, we create |
| 149 | +such variants without additional `{}` after the variant name. Here the curly braces have a purpose: |
| 150 | +without them, the variant would serialize to just a string type, so instead of `{ "greet": {} }`, |
| 151 | +the JSON representation of this variant would be `"greet"`. This behavior brings inconsistency in |
| 152 | +the message schema. It is, generally, a good habit to always add the `{}` to serde serializable |
| 153 | +empty enum variants - for better JSON representation. |
| 154 | + |
| 155 | +But now, we can still improve the code further. Right now, the `query` function has two responsibilities. |
| 156 | +The first is obvious: handling the query itself. It was the first assumption, and it is still there. |
| 157 | +But there is a new thing happening there, the query message dispatching. It may not be obvious, |
| 158 | +as there is a single variant, but the query function is an excellent way to become a massive |
| 159 | +unreadable `match` statement. To make the code more [SOLID](https://en.wikipedia.org/wiki/SOLID), |
| 160 | +we will refactor it and take out handling the `greet` message to a separate function. |
| 161 | + |
| 162 | +```rust title="src/lib.rs" {31,35-45} |
| 163 | +use cosmwasm_std::{ |
| 164 | + entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, |
| 165 | +}; |
| 166 | +use serde::{Deserialize, Serialize}; |
| 167 | + |
| 168 | +#[derive(Serialize, Deserialize)] |
| 169 | +pub struct GreetResp { |
| 170 | + message: String, |
| 171 | +} |
| 172 | + |
| 173 | +#[derive(Serialize, Deserialize)] |
| 174 | +pub enum QueryMsg { |
| 175 | + Greet {}, |
| 176 | +} |
| 177 | + |
| 178 | +#[entry_point] |
| 179 | +pub fn instantiate( |
| 180 | + _deps: DepsMut, |
| 181 | + _env: Env, |
| 182 | + _info: MessageInfo, |
| 183 | + _msg: Empty, |
| 184 | +) -> StdResult<Response> { |
| 185 | + Ok(Response::new()) |
| 186 | +} |
| 187 | + |
| 188 | +#[entry_point] |
| 189 | +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { |
| 190 | + use QueryMsg::*; |
| 191 | + |
| 192 | + match msg { |
| 193 | + Greet {} => to_json_binary(&query::greet()?), |
| 194 | + } |
| 195 | +} |
| 196 | + |
| 197 | +mod query { |
| 198 | + use super::*; |
| 199 | + |
| 200 | + pub fn greet() -> StdResult<GreetResp> { |
| 201 | + let resp = GreetResp { |
| 202 | + message: "Hello World".to_owned(), |
| 203 | + }; |
| 204 | + |
| 205 | + Ok(resp) |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +Now it looks much better. Note there are a couple of additional improvements. We have renamed the |
| 211 | +query-response type `GreetResp` as there may exist different responses for different queries. |
| 212 | +We want the name to relate only to the specific enumeration variant, not the whole message. |
| 213 | + |
| 214 | +Next enhancement is enclosing my new function in the module `query`. It makes it easier to avoid name |
| 215 | +collisions. We can have the same variant for queries and execution messages in the future, and their |
| 216 | +handlers would be placed in separate namespaces. |
| 217 | + |
| 218 | +A questionable decision may be returning `StdResult` instead of `GreetResp` from `greet` function, |
| 219 | +as it would never return an error. It is a matter of style, but we prefer consistency over the |
| 220 | +message handler, and the majority of them would have failure cases, e.g. when reading the state. |
| 221 | + |
| 222 | +Also, you might pass `deps` and `env` arguments to all your query handlers for consistency. |
| 223 | +We are not too fond of this, as it introduces unnecessary boilerplate which doesn't read well, |
| 224 | +but we also agree with the consistency argument, we leave it to your judgment. |
| 225 | + |
| 226 | +## Structuring the contract |
| 227 | + |
| 228 | +You can see that our contract is becoming a bit bigger now. About 50 lines are not so much, |
| 229 | +but there are many different entities in a single file, and we think we can do better. |
| 230 | +We can already distinguish three different types of entities in the code: **entrypoints**, |
| 231 | +**messages**, and **handlers**. In most contracts, we would divide them across three files. |
| 232 | +Let's start with extracting all the messages to `src/msg.rs`: |
| 233 | + |
| 234 | +```rust title="src/msg.rs" |
| 235 | +use serde::{Deserialize, Serialize}; |
| 236 | + |
| 237 | +#[derive(Serialize, Deserialize)] |
| 238 | +pub struct GreetResp { |
| 239 | + pub message: String, |
| 240 | +} |
| 241 | + |
| 242 | +#[derive(Serialize, Deserialize)] |
| 243 | +pub enum QueryMsg { |
| 244 | + Greet {}, |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +You probably noticed that we have made the `GreetResp` fields public. It is because they have |
| 249 | +to be now accessed from a different module. Now, let's move forward to the `src/contract.rs` file: |
| 250 | + |
| 251 | +```rust title="src/contract.rs" {1} |
| 252 | +use crate::msg::{GreetResp, QueryMsg}; |
| 253 | +use cosmwasm_std::{ |
| 254 | + to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, |
| 255 | +}; |
| 256 | + |
| 257 | +pub fn instantiate( |
| 258 | + _deps: DepsMut, |
| 259 | + _env: Env, |
| 260 | + _info: MessageInfo, |
| 261 | + _msg: Empty, |
| 262 | +) -> StdResult<Response> { |
| 263 | + Ok(Response::new()) |
| 264 | +} |
| 265 | + |
| 266 | +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { |
| 267 | + use QueryMsg::*; |
| 268 | + |
| 269 | + match msg { |
| 270 | + Greet {} => to_json_binary(&query::greet()?), |
| 271 | + } |
| 272 | +} |
| 273 | + |
| 274 | +mod query { |
| 275 | + use super::*; |
| 276 | + |
| 277 | + pub fn greet() -> StdResult<GreetResp> { |
| 278 | + let resp = GreetResp { |
| 279 | + message: "Hello World".to_owned(), |
| 280 | + }; |
| 281 | + |
| 282 | + Ok(resp) |
| 283 | + } |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +We have moved most of the logic here, so my `src/lib.rs` is just a very thin library entry with nothing |
| 288 | +else but module definitions and entry points definition. We have removed the `#[entry_point]` attribute |
| 289 | +from my `query` function in `src/contract.rs`. We will have a function with this attribute. |
| 290 | +Still, we wanted to split functions' responsibility further, not the `contract::query` function is the |
| 291 | +top-level query handler responsible for dispatching the query message. The `query` function on |
| 292 | +crate-level is only an entrypoint. It is a subtle distinction, but it will make sense in the future |
| 293 | +when we would like not to generate the entry points but to keep the dispatching functions. |
| 294 | +We have introduced the split now, to show you the typical contract structure. |
| 295 | + |
| 296 | +Now the last part, the `src/lib.rs` file: |
| 297 | + |
| 298 | +```rust title="src/lib.rs" |
| 299 | +use cosmwasm_std::{ |
| 300 | + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, |
| 301 | +}; |
| 302 | + |
| 303 | +mod contract; |
| 304 | +mod msg; |
| 305 | + |
| 306 | +#[entry_point] |
| 307 | +pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: Empty) -> StdResult<Response> |
| 308 | +{ |
| 309 | + contract::instantiate(deps, env, info, msg) |
| 310 | +} |
| 311 | + |
| 312 | +#[entry_point] |
| 313 | +pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) -> StdResult<Binary> |
| 314 | +{ |
| 315 | + contract::query(deps, env, msg) |
| 316 | +} |
| 317 | +``` |
| 318 | + |
| 319 | +Straightforward top-level module. Definition of submodules and entrypoints, nothing more. |
| 320 | + |
| 321 | +Now, since our contract is ready to do something, let's test it! |
0 commit comments