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