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}