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}