Skip to content

Commit 75223ab

Browse files
committed
Added events chapter.
1 parent 87aa049 commit 75223ab

File tree

1 file changed

+214
-0
lines changed

1 file changed

+214
-0
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
---
2+
title: Passing events
3+
sidebar_position: 9
4+
---
5+
6+
# Events attributes and data
7+
8+
The only way our contract can communicate with the world, for now, is through queries. Smart
9+
contracts are passive - they cannot invoke any action by themselves. They can do it only as a
10+
reaction to a call. But if you've ever tried playing with
11+
[`wasmd`](https://github.com/CosmWasm/wasmd), you know that execution on the blockchain can return
12+
some metadata.
13+
14+
There are two things the contract can return to the caller: events and data. Events are something
15+
produced by almost every real-life smart contract. In contrast, data is rarely used, designed for
16+
contract-to-contract communication.
17+
18+
## Returning events
19+
20+
As an example, we would add an event `admin_added` emitted by our contract on the execution of `AddMembers`:
21+
22+
```rust title="src/contract.rs" {4,37-43,53}
23+
use crate::error::ContractError;
24+
use crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg};
25+
use crate::state::ADMINS;
26+
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
27+
28+
// ...
29+
30+
pub fn execute(
31+
deps: DepsMut,
32+
_env: Env,
33+
info: MessageInfo,
34+
msg: ExecuteMsg,
35+
) -> Result<Response, ContractError> {
36+
use ExecuteMsg::*;
37+
38+
match msg {
39+
AddMembers { admins } => exec::add_members(deps, info, admins),
40+
Leave {} => exec::leave(deps, info),
41+
}
42+
}
43+
44+
mod exec {
45+
use super::*;
46+
47+
pub fn add_members(
48+
deps: DepsMut,
49+
info: MessageInfo,
50+
admins: Vec<String>,
51+
) -> Result<Response, ContractError> {
52+
let mut curr_admins = ADMINS.load(deps.storage)?;
53+
if !curr_admins.contains(&info.sender) {
54+
return Err(ContractError::Unauthorized {
55+
sender: info.sender,
56+
});
57+
}
58+
59+
let events = admins
60+
.iter()
61+
.map(|admin| Event::new("admin_added").add_attribute("addr", admin));
62+
let resp = Response::new()
63+
.add_events(events)
64+
.add_attribute("action", "add_members")
65+
.add_attribute("added_count", admins.len().to_string());
66+
67+
let admins: StdResult<Vec<_>> = admins
68+
.into_iter()
69+
.map(|addr| deps.api.addr_validate(&addr))
70+
.collect();
71+
72+
curr_admins.append(&mut admins?);
73+
ADMINS.save(deps.storage, &curr_admins)?;
74+
75+
Ok(resp)
76+
}
77+
78+
pub fn leave(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
79+
ADMINS.update(deps.storage, move |admins| -> StdResult<_> {
80+
let admins = admins
81+
.into_iter()
82+
.filter(|admin| *admin != info.sender)
83+
.collect();
84+
Ok(admins)
85+
})?;
86+
87+
Ok(Response::new())
88+
}
89+
}
90+
91+
// ...
92+
```
93+
94+
An event is built from two things: an event type provided in the
95+
[`new`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Event.html#method.new) function and
96+
attributes. Attributes are added to an event with the
97+
[`add_attributes`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Event.html#method.add_attributes)
98+
or the
99+
[`add_attribute`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Event.html#method.add_attribute)
100+
call. Attributes are key-value pairs. Because an event cannot contain any list, to achieve reporting
101+
multiple similar actions taking place, we need to emit multiple small events instead of a collective
102+
one.
103+
104+
Events are emitted by adding them to the response with
105+
[`add_event`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html#method.add_event)
106+
or
107+
[`add_events`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html#method.add_events)
108+
call. Additionally, there is a possibility to add attributes directly to the response. It is just
109+
sugar. By default, every execution emits a standard "wasm" event. Adding attributes to the result
110+
adds them to the default event.
111+
112+
We can check if events are properly emitted by the contract. It is not always done, as it is much of
113+
boilerplate in test, but events are, generally, more like logs - not necessarily considered main
114+
contract logic. Let's now write single test checking if execution emits events:
115+
116+
```rust title="src/contract.rs" {43-76}
117+
#[cfg(test)]
118+
mod tests {
119+
use cw_multi_test::{App, ContractWrapper, Executor, IntoAddr};
120+
121+
use crate::msg::AdminsListResp;
122+
123+
use super::*;
124+
125+
// ...
126+
127+
#[test]
128+
fn add_members() {
129+
let mut app = App::default();
130+
131+
let code = ContractWrapper::new(execute, instantiate, query);
132+
let code_id = app.store_code(Box::new(code));
133+
let owner = "owner".into_addr();
134+
135+
let addr = app
136+
.instantiate_contract(
137+
code_id,
138+
owner.clone(),
139+
&InstantiateMsg {
140+
admins: vec![owner.to_string()],
141+
},
142+
&[],
143+
"Contract",
144+
None,
145+
)
146+
.unwrap();
147+
148+
let resp = app
149+
.execute_contract(
150+
owner.clone(),
151+
addr,
152+
&ExecuteMsg::AddMembers {
153+
admins: vec![owner.to_string()],
154+
},
155+
&[],
156+
)
157+
.unwrap();
158+
159+
let wasm = resp.events.iter().find(|ev| ev.ty == "wasm").unwrap();
160+
assert_eq!(
161+
wasm.attributes
162+
.iter()
163+
.find(|attr| attr.key == "action")
164+
.unwrap()
165+
.value,
166+
"add_members"
167+
);
168+
assert_eq!(
169+
wasm.attributes
170+
.iter()
171+
.find(|attr| attr.key == "added_count")
172+
.unwrap()
173+
.value,
174+
"1"
175+
);
176+
177+
let admin_added: Vec<_> = resp
178+
.events
179+
.iter()
180+
.filter(|ev| ev.ty == "wasm-admin_added")
181+
.collect();
182+
assert_eq!(admin_added.len(), 1);
183+
184+
assert_eq!(
185+
admin_added[0]
186+
.attributes
187+
.iter()
188+
.find(|attr| attr.key == "addr")
189+
.unwrap()
190+
.value,
191+
owner.to_string()
192+
);
193+
}
194+
}
195+
```
196+
197+
As you can see, testing events on a simple test made it clunky. First of all, every event is heavily
198+
string-based - a lack of type control makes writing such tests difficult. Also, event types are
199+
prefixed with "wasm-" - it may not be a huge problem, but it doesn't clarify verification. But the
200+
problem is how layered the structure of events is, which makes verifying them tricky. Also, the
201+
"wasm" event is particularly tricky, as it contains an implied attribute - `_contract_addr`
202+
containing the address that called the contract. My general rule is - do not test emitted events
203+
unless some logic depends on them.
204+
205+
## Data
206+
207+
Besides events, any smart contract execution may produce a `data` object. In contrast to events,
208+
`data` can be structured. It makes it a way better choice to perform any communication the logic
209+
relies on. On the other hand, it turns out it is very rarely helpful outside of contract-to-contract
210+
communication. Data is always a singular object within the response, and it's set with the
211+
[`set_data`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html#method.set_data)
212+
function. Because of its low usefulness in a single contract environment, we will not spend time on
213+
it right now - an example of it will be covered later when contract-to-contract communication will
214+
be discussed. Until then, it is just helpful to know such an entity exists.

0 commit comments

Comments
 (0)