cosmwasm_std/results/response.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::prelude::*;
5use crate::Binary;
6
7use super::{Attribute, CosmosMsg, Empty, Event, SubMsg};
8
9/// A response of a contract entry point, such as `instantiate`, `execute` or `migrate`.
10///
11/// This type can be constructed directly at the end of the call. Alternatively a
12/// mutable response instance can be created early in the contract's logic and
13/// incrementally be updated.
14///
15/// ## Examples
16///
17/// Direct:
18///
19/// ```
20/// # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo};
21/// # type InstantiateMsg = ();
22/// #
23/// use cosmwasm_std::{attr, Response, StdResult};
24///
25/// pub fn instantiate(
26/// deps: DepsMut,
27/// _env: Env,
28/// _info: MessageInfo,
29/// msg: InstantiateMsg,
30/// ) -> StdResult<Response> {
31/// // ...
32///
33/// Ok(Response::new().add_attribute("action", "instantiate"))
34/// }
35/// ```
36///
37/// Mutating:
38///
39/// ```
40/// # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, MessageInfo, SubMsg};
41/// # type InstantiateMsg = ();
42/// # type MyError = ();
43/// #
44/// use cosmwasm_std::Response;
45///
46/// pub fn instantiate(
47/// deps: DepsMut,
48/// _env: Env,
49/// info: MessageInfo,
50/// msg: InstantiateMsg,
51/// ) -> Result<Response, MyError> {
52/// let mut response = Response::new()
53/// .add_attribute("Let the", "hacking begin")
54/// .add_message(BankMsg::Send {
55/// to_address: String::from("recipient"),
56/// amount: coins(128, "uint"),
57/// })
58/// .add_attribute("foo", "bar")
59/// .set_data(b"the result data");
60/// Ok(response)
61/// }
62/// ```
63#[derive(
64 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
65)]
66#[non_exhaustive]
67pub struct Response<T = Empty> {
68 /// Optional list of messages to pass. These will be executed in order.
69 /// If the ReplyOn variant matches the result (Always, Success on Ok, Error on Err),
70 /// the runtime will invoke this contract's `reply` entry point
71 /// after execution. Otherwise, they act like "fire and forget".
72 /// Use `SubMsg::new` to create messages with the older "fire and forget" semantics.
73 pub messages: Vec<SubMsg<T>>,
74 /// The attributes that will be emitted as part of a "wasm" event.
75 ///
76 /// More info about events (and their attributes) can be found in [*Cosmos SDK* docs].
77 ///
78 /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
79 pub attributes: Vec<Attribute>,
80 /// Extra, custom events separate from the main `wasm` one. These will have
81 /// `wasm-` prepended to the type.
82 ///
83 /// More info about events can be found in [*Cosmos SDK* docs].
84 ///
85 /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
86 pub events: Vec<Event>,
87 /// The binary payload to include in the response.
88 pub data: Option<Binary>,
89}
90
91impl<T> Default for Response<T> {
92 fn default() -> Self {
93 Response {
94 messages: vec![],
95 attributes: vec![],
96 events: vec![],
97 data: None,
98 }
99 }
100}
101
102impl<T> Response<T> {
103 pub fn new() -> Self {
104 Self::default()
105 }
106
107 /// Add an attribute included in the main `wasm` event.
108 ///
109 /// For working with optional values or optional attributes, see [`add_attributes`][Self::add_attributes].
110 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
111 self.attributes.push(Attribute::new(key, value));
112 self
113 }
114
115 /// This creates a "fire and forget" message, by using `SubMsg::new()` to wrap it,
116 /// and adds it to the list of messages to process.
117 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
118 self.messages.push(SubMsg::new(msg));
119 self
120 }
121
122 /// This takes an explicit SubMsg (creates via eg. `reply_on_error`)
123 /// and adds it to the list of messages to process.
124 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
125 self.messages.push(msg);
126 self
127 }
128
129 /// Adds an extra event to the response, separate from the main `wasm` event
130 /// that is always created.
131 ///
132 /// The `wasm-` prefix will be appended by the runtime to the provided type
133 /// of event.
134 pub fn add_event(mut self, event: impl Into<Event>) -> Self {
135 self.events.push(event.into());
136 self
137 }
138
139 /// Bulk add attributes included in the main `wasm` event.
140 ///
141 /// Anything that can be turned into an iterator and yields something
142 /// that can be converted into an `Attribute` is accepted.
143 ///
144 /// ## Examples
145 ///
146 /// Adding a list of attributes using the pair notation for key and value:
147 ///
148 /// ```
149 /// use cosmwasm_std::Response;
150 ///
151 /// let attrs = vec![
152 /// ("action", "reaction"),
153 /// ("answer", "42"),
154 /// ("another", "attribute"),
155 /// ];
156 /// let res: Response = Response::new().add_attributes(attrs.clone());
157 /// assert_eq!(res.attributes, attrs);
158 /// ```
159 ///
160 /// Adding an optional value as an optional attribute by turning it into a list of 0 or 1 elements:
161 ///
162 /// ```
163 /// use cosmwasm_std::{Attribute, Response};
164 ///
165 /// // Some value
166 /// let value: Option<String> = Some("sarah".to_string());
167 /// let attribute: Option<Attribute> = value.map(|v| Attribute::new("winner", v));
168 /// let res: Response = Response::new().add_attributes(attribute);
169 /// assert_eq!(res.attributes, [Attribute {
170 /// key: "winner".to_string(),
171 /// value: "sarah".to_string(),
172 /// }]);
173 ///
174 /// // No value
175 /// let value: Option<String> = None;
176 /// let attribute: Option<Attribute> = value.map(|v| Attribute::new("winner", v));
177 /// let res: Response = Response::new().add_attributes(attribute);
178 /// assert_eq!(res.attributes.len(), 0);
179 /// ```
180 pub fn add_attributes<A: Into<Attribute>>(
181 mut self,
182 attrs: impl IntoIterator<Item = A>,
183 ) -> Self {
184 self.attributes.extend(attrs.into_iter().map(A::into));
185 self
186 }
187
188 /// Bulk add "fire and forget" messages to the list of messages to process.
189 ///
190 /// ## Examples
191 ///
192 /// ```
193 /// use cosmwasm_std::{CosmosMsg, Response};
194 ///
195 /// fn make_response_with_msgs(msgs: Vec<CosmosMsg>) -> Response {
196 /// Response::new().add_messages(msgs)
197 /// }
198 /// ```
199 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
200 self.add_submessages(msgs.into_iter().map(SubMsg::new))
201 }
202
203 /// Bulk add explicit SubMsg structs to the list of messages to process.
204 ///
205 /// ## Examples
206 ///
207 /// ```
208 /// use cosmwasm_std::{SubMsg, Response};
209 ///
210 /// fn make_response_with_submsgs(msgs: Vec<SubMsg>) -> Response {
211 /// Response::new().add_submessages(msgs)
212 /// }
213 /// ```
214 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
215 self.messages.extend(msgs);
216 self
217 }
218
219 /// Bulk add custom events to the response. These are separate from the main
220 /// `wasm` event.
221 ///
222 /// The `wasm-` prefix will be appended by the runtime to the provided types
223 /// of events.
224 pub fn add_events<E>(mut self, events: impl IntoIterator<Item = E>) -> Self
225 where
226 E: Into<Event>,
227 {
228 self.events.extend(events.into_iter().map(|e| e.into()));
229 self
230 }
231
232 /// Set the binary data included in the response.
233 pub fn set_data(mut self, data: impl Into<Binary>) -> Self {
234 self.data = Some(data.into());
235 self
236 }
237
238 /// Convert this [`Response<T>`] to a [`Response<U>`] with a different custom message type.
239 /// This allows easier interactions between code written for a specific chain and
240 /// code written for multiple chains.
241 /// If this contains a [`CosmosMsg::Custom`] submessage, the function returns `None`.
242 pub fn change_custom<U>(self) -> Option<Response<U>> {
243 Some(Response {
244 messages: self
245 .messages
246 .into_iter()
247 .map(|msg| msg.change_custom())
248 .collect::<Option<Vec<_>>>()?,
249 attributes: self.attributes,
250 events: self.events,
251 data: self.data,
252 })
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::super::BankMsg;
259 use super::*;
260 use crate::results::submessages::{ReplyOn, UNUSED_MSG_ID};
261 use crate::{coins, from_json, to_json_vec, ContractResult};
262
263 #[test]
264 fn response_add_attributes_works() {
265 let res = Response::<Empty>::new().add_attributes(core::iter::empty::<Attribute>());
266 assert_eq!(res.attributes.len(), 0);
267
268 let res = Response::<Empty>::new().add_attributes([Attribute::new("test", "ing")]);
269 assert_eq!(res.attributes.len(), 1);
270 assert_eq!(
271 res.attributes[0],
272 Attribute {
273 key: "test".to_string(),
274 value: "ing".to_string(),
275 }
276 );
277
278 let attrs = vec![
279 ("action", "reaction"),
280 ("answer", "42"),
281 ("another", "attribute"),
282 ];
283 let res: Response = Response::new().add_attributes(attrs.clone());
284 assert_eq!(res.attributes, attrs);
285
286 let optional = Option::<Attribute>::None;
287 let res: Response = Response::new().add_attributes(optional);
288 assert_eq!(res.attributes.len(), 0);
289
290 let optional = Option::<Attribute>::Some(Attribute::new("test", "ing"));
291 let res: Response = Response::new().add_attributes(optional);
292 assert_eq!(res.attributes.len(), 1);
293 assert_eq!(
294 res.attributes[0],
295 Attribute {
296 key: "test".to_string(),
297 value: "ing".to_string(),
298 }
299 );
300 }
301
302 #[test]
303 fn can_serialize_and_deserialize_init_response() {
304 let original = Response {
305 messages: vec![
306 SubMsg {
307 id: 12,
308 payload: Binary::new(vec![9, 8, 7, 6, 5]),
309 msg: BankMsg::Send {
310 to_address: String::from("checker"),
311 amount: coins(888, "moon"),
312 }
313 .into(),
314 gas_limit: Some(12345u64),
315 reply_on: ReplyOn::Always,
316 },
317 SubMsg {
318 id: UNUSED_MSG_ID,
319 payload: Binary::default(),
320 msg: BankMsg::Send {
321 to_address: String::from("you"),
322 amount: coins(1015, "earth"),
323 }
324 .into(),
325 gas_limit: None,
326 reply_on: ReplyOn::Never,
327 },
328 ],
329 attributes: vec![Attribute {
330 key: "action".to_string(),
331 value: "release".to_string(),
332 }],
333 events: vec![],
334 data: Some(Binary::from([0xAA, 0xBB])),
335 };
336 let serialized = to_json_vec(&original).expect("encode contract result");
337 let deserialized: Response = from_json(serialized).expect("decode contract result");
338 assert_eq!(deserialized, original);
339 }
340
341 #[test]
342 fn contract_result_is_ok_works() {
343 let success = ContractResult::<()>::Ok(());
344 let failure = ContractResult::<()>::Err("broken".to_string());
345 assert!(success.is_ok());
346 assert!(!failure.is_ok());
347 }
348
349 #[test]
350 fn contract_result_is_err_works() {
351 let success = ContractResult::<()>::Ok(());
352 let failure = ContractResult::<()>::Err("broken".to_string());
353 assert!(failure.is_err());
354 assert!(!success.is_err());
355 }
356
357 // struct implements `Into<Event>`
358 #[derive(Clone)]
359 struct OurEvent {
360 msg: String,
361 }
362
363 // allow define `into` rather than `from` to define `into` clearly
364 #[allow(clippy::from_over_into)]
365 impl Into<Event> for OurEvent {
366 fn into(self) -> Event {
367 Event::new("our_event").add_attribute("msg", self.msg)
368 }
369 }
370
371 #[test]
372 fn add_event_takes_into_event() {
373 let msg = "message".to_string();
374 let our_event = OurEvent { msg };
375 let event: Event = our_event.clone().into();
376 let actual = Response::<Empty>::new().add_event(our_event);
377 let expected = Response::<Empty>::new().add_event(event);
378 assert_eq!(expected, actual);
379 }
380
381 #[test]
382 fn add_events_takes_into_event() {
383 let msg1 = "foo".to_string();
384 let msg2 = "bare".to_string();
385 let our_event1 = OurEvent { msg: msg1 };
386 let our_event2 = OurEvent { msg: msg2 };
387 let events: Vec<Event> = vec![our_event1.clone().into(), our_event2.clone().into()];
388 let actual = Response::<Empty>::new().add_events([our_event1, our_event2]);
389 let expected = Response::<Empty>::new().add_events(events);
390 assert_eq!(expected, actual);
391 }
392
393 #[test]
394 fn change_custom_works() {
395 let response: Response<Empty> = Response {
396 messages: vec![SubMsg::new(BankMsg::Send {
397 to_address: "address".to_string(),
398 amount: coins(123, "earth"),
399 })],
400 attributes: vec![Attribute::new("foo", "bar")],
401 events: vec![Event::new("our_event").add_attribute("msg", "hello")],
402 data: None,
403 };
404 let converted_resp: Response<String> = response.clone().change_custom().unwrap();
405 assert_eq!(
406 converted_resp.messages,
407 vec![SubMsg::new(BankMsg::Send {
408 to_address: "address".to_string(),
409 amount: coins(123, "earth"),
410 })]
411 );
412 assert_eq!(converted_resp.attributes, response.attributes);
413 assert_eq!(converted_resp.events, response.events);
414 assert_eq!(converted_resp.data, response.data);
415
416 // response with custom message
417 let response = Response {
418 messages: vec![SubMsg::new(CosmosMsg::Custom(Empty {}))],
419 attributes: vec![Attribute::new("foo", "bar")],
420 events: vec![Event::new("our_event").add_attribute("msg", "hello")],
421 data: None,
422 };
423
424 assert_eq!(response.change_custom::<String>(), None);
425 }
426}