Skip to content

Commit 715988d

Browse files
committed
Added Query chapter.
1 parent 6bed71b commit 715988d

File tree

2 files changed

+322
-1
lines changed

2 files changed

+322
-1
lines changed

docs/tutorial/writing-contracts/entrypoints.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use cosmwasm_std::{entry_point, DepsMut, Empty, Env, MessageInfo, Response, StdR
2727

2828
#[entry_point]
2929
pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: Empty) -> StdResult<Response> {
30-
Ok(Response::new())
30+
Ok(Response::new())
3131
}
3232
```
3333

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
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

Comments
 (0)