cosmwasm_std/results/
submessages.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::prelude::*;
5use crate::Binary;
6
7use super::{CosmosMsg, Empty, Event};
8
9/// Use this to define when the contract gets a response callback.
10/// If you only need it for errors or success you can select just those in order
11/// to save gas.
12#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
13#[serde(rename_all = "snake_case")]
14pub enum ReplyOn {
15    /// Always perform a callback after SubMsg is processed
16    Always,
17    /// Only callback if SubMsg returned an error, no callback on success case
18    Error,
19    /// Only callback if SubMsg was successful, no callback on error case
20    Success,
21    /// Never make a callback - this is like the original CosmosMsg semantics
22    Never,
23}
24
25/// A submessage that will guarantee a `reply` call on success or error, depending on
26/// the `reply_on` setting. If you do not need to process the result, use regular messages instead.
27///
28/// Note: On error the submessage execution will revert any partial state changes due to this message,
29/// but not revert any state changes in the calling contract. If this is required, it must be done
30/// manually in the `reply` entry point.
31#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
32pub struct SubMsg<T = Empty> {
33    /// An arbitrary ID chosen by the contract.
34    /// This is typically used to match `Reply`s in the `reply` entry point to the submessage.
35    pub id: u64,
36    /// Some arbirary data that the contract can set in an application specific way.
37    /// This is just passed into the `reply` entry point and is not stored to state.
38    /// Any encoding can be used. If `id` is used to identify a particular action,
39    /// the encoding can also be different for each of those actions since you can match `id`
40    /// first and then start processing the `payload`.
41    ///
42    /// The environment restricts the length of this field in order to avoid abuse. The limit
43    /// is environment specific and can change over time. The initial default is 128 KiB.
44    ///
45    /// Unset/nil/null cannot be differentiated from empty data.
46    ///
47    /// On chains running CosmWasm 1.x this field will be ignored.
48    #[serde(default)]
49    pub payload: Binary,
50    pub msg: CosmosMsg<T>,
51    /// Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
52    ///
53    /// Setting this to `None` means unlimited. Then the submessage execution can consume all gas of the
54    /// current execution context.
55    pub gas_limit: Option<u64>,
56    pub reply_on: ReplyOn,
57}
58
59/// This is used for cases when we use ReplyOn::Never and the id doesn't matter
60pub const UNUSED_MSG_ID: u64 = 0;
61
62impl<T> SubMsg<T> {
63    /// Creates a "fire and forget" message with the pre-0.14 semantics.
64    /// Since this is just an alias for [`SubMsg::reply_never`] it is somewhat recommended
65    /// to use the latter in order to make the behaviour more explicit in the caller code.
66    /// But that's up to you for now.
67    ///
68    /// By default, the submessage's gas limit will be unlimited. Use [`SubMsg::with_gas_limit`] to change it.
69    /// Setting `payload` is not advised as this will never be used.
70    pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
71        Self::reply_never(msg)
72    }
73
74    /// Creates a `SubMsg` that will provide a `reply` with the given `id` if the message returns `Ok`.
75    ///
76    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
77    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
78    pub fn reply_on_success(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
79        Self::reply_on(msg.into(), id, ReplyOn::Success)
80    }
81
82    /// Creates a `SubMsg` that will provide a `reply` with the given `id` if the message returns `Err`.
83    ///
84    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
85    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
86    pub fn reply_on_error(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
87        Self::reply_on(msg.into(), id, ReplyOn::Error)
88    }
89
90    /// Create a `SubMsg` that will always provide a `reply` with the given `id`.
91    ///
92    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
93    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
94    pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
95        Self::reply_on(msg.into(), id, ReplyOn::Always)
96    }
97
98    /// Create a `SubMsg` that will never `reply`. This is equivalent to standard message semantics.
99    ///
100    /// By default, the submessage's gas limit will be unlimited. Use [`SubMsg::with_gas_limit`] to change it.
101    /// Setting `payload` is not advised as this will never be used.
102    pub fn reply_never(msg: impl Into<CosmosMsg<T>>) -> Self {
103        Self::reply_on(msg.into(), UNUSED_MSG_ID, ReplyOn::Never)
104    }
105
106    /// Add a gas limit to the submessage.
107    /// This gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
108    ///
109    /// ## Examples
110    ///
111    /// ```
112    /// # use cosmwasm_std::{coins, BankMsg, ReplyOn, SubMsg};
113    /// # let msg = BankMsg::Send { to_address: String::from("you"), amount: coins(1015, "earth") };
114    /// let sub_msg: SubMsg = SubMsg::reply_always(msg, 1234).with_gas_limit(60_000);
115    /// assert_eq!(sub_msg.id, 1234);
116    /// assert_eq!(sub_msg.gas_limit, Some(60_000));
117    /// assert_eq!(sub_msg.reply_on, ReplyOn::Always);
118    /// ```
119    pub fn with_gas_limit(mut self, limit: u64) -> Self {
120        self.gas_limit = Some(limit);
121        self
122    }
123
124    /// Add a payload to the submessage.
125    ///
126    /// ## Examples
127    ///
128    /// ```
129    /// # use cosmwasm_std::{coins, BankMsg, Binary, ReplyOn, SubMsg};
130    /// # let msg = BankMsg::Send { to_address: String::from("you"), amount: coins(1015, "earth") };
131    /// let sub_msg: SubMsg = SubMsg::reply_always(msg, 1234)
132    ///     .with_payload(vec![1, 2, 3, 4]);
133    /// assert_eq!(sub_msg.id, 1234);
134    /// assert_eq!(sub_msg.payload, Binary::new(vec![1, 2, 3, 4]));
135    /// assert_eq!(sub_msg.reply_on, ReplyOn::Always);
136    /// ```
137    pub fn with_payload(mut self, payload: impl Into<Binary>) -> Self {
138        self.payload = payload.into();
139        self
140    }
141
142    fn reply_on(msg: CosmosMsg<T>, id: u64, reply_on: ReplyOn) -> Self {
143        SubMsg {
144            id,
145            payload: Default::default(),
146            msg,
147            reply_on,
148            gas_limit: None,
149        }
150    }
151
152    /// Convert this [`SubMsg<T>`] to a [`SubMsg<U>`] with a different generic type.
153    /// This allows easier interactions between code written for a specific chain and
154    /// code written for multiple chains.
155    /// If this is a [`CosmosMsg::Custom`] submessage, the function returns `None`.
156    pub fn change_custom<U>(self) -> Option<SubMsg<U>> {
157        Some(SubMsg {
158            id: self.id,
159            payload: self.payload,
160            msg: self.msg.change_custom::<U>()?,
161            gas_limit: self.gas_limit,
162            reply_on: self.reply_on,
163        })
164    }
165}
166
167/// The result object returned to `reply`. We always get the ID from the submessage
168/// back and then must handle success and error cases ourselves.
169#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
170pub struct Reply {
171    /// The ID that the contract set when emitting the `SubMsg`.
172    /// Use this to identify which submessage triggered the `reply`.
173    pub id: u64,
174    /// Some arbirary data that the contract set when emitting the `SubMsg`.
175    /// This is just passed into the `reply` entry point and is not stored to state.
176    ///
177    /// Unset/nil/null cannot be differentiated from empty data.
178    ///
179    /// On chains running CosmWasm 1.x this field is never filled.
180    #[serde(default)]
181    pub payload: Binary,
182    /// The amount of gas used by the submessage,
183    /// measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
184    ///
185    /// This only contains a useful value on chains running CosmWasm 2.0 or higher.
186    /// On older chains, this field is always 0.
187    #[serde(default)]
188    pub gas_used: u64,
189    pub result: SubMsgResult,
190}
191
192/// This is the result type that is returned from a sub message execution.
193///
194/// We use a custom type here instead of Rust's Result because we want to be able to
195/// define the serialization, which is a public interface. Every language that compiles
196/// to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.
197///
198/// Until version 1.0.0-beta5, `ContractResult<SubMsgResponse>` was used instead
199/// of this type. Once serialized, the two types are the same. However, in the Rust type
200/// system we want different types for clarity and documenation reasons.
201///
202/// # Examples
203///
204/// Success:
205///
206/// ```
207/// # use cosmwasm_std::{to_json_string, Binary, Event, SubMsgResponse, SubMsgResult};
208/// #[allow(deprecated)]
209/// let response = SubMsgResponse {
210///     data: Some(Binary::from_base64("MTIzCg==").unwrap()),
211///     events: vec![Event::new("wasm").add_attribute("fo", "ba")],
212///     msg_responses: vec![],
213/// };
214/// let result: SubMsgResult = SubMsgResult::Ok(response);
215/// assert_eq!(
216///     to_json_string(&result).unwrap(),
217///     r#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg==","msg_responses":[]}}"#,
218/// );
219/// ```
220///
221/// Failure:
222///
223/// ```
224/// # use cosmwasm_std::{to_json_string, SubMsgResult, Response};
225/// let error_msg = String::from("Something went wrong");
226/// let result = SubMsgResult::Err(error_msg);
227/// assert_eq!(to_json_string(&result).unwrap(), r#"{"error":"Something went wrong"}"#);
228/// ```
229#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
230#[serde(rename_all = "snake_case")]
231pub enum SubMsgResult {
232    Ok(SubMsgResponse),
233    /// An error type that every custom error created by contract developers can be converted to.
234    /// This could potientially have more structure, but String is the easiest.
235    #[serde(rename = "error")]
236    Err(String),
237}
238
239// Implementations here mimic the Result API and should be implemented via a conversion to Result
240// to ensure API consistency
241impl SubMsgResult {
242    /// Converts a `SubMsgResult<S>` to a `Result<S, String>` as a convenient way
243    /// to access the full Result API.
244    pub fn into_result(self) -> Result<SubMsgResponse, String> {
245        Result::<SubMsgResponse, String>::from(self)
246    }
247
248    pub fn unwrap(self) -> SubMsgResponse {
249        self.into_result().unwrap()
250    }
251
252    pub fn unwrap_err(self) -> String {
253        self.into_result().unwrap_err()
254    }
255
256    pub fn is_ok(&self) -> bool {
257        matches!(self, SubMsgResult::Ok(_))
258    }
259
260    pub fn is_err(&self) -> bool {
261        matches!(self, SubMsgResult::Err(_))
262    }
263}
264
265impl<E: ToString> From<Result<SubMsgResponse, E>> for SubMsgResult {
266    fn from(original: Result<SubMsgResponse, E>) -> SubMsgResult {
267        match original {
268            Ok(value) => SubMsgResult::Ok(value),
269            Err(err) => SubMsgResult::Err(err.to_string()),
270        }
271    }
272}
273
274impl From<SubMsgResult> for Result<SubMsgResponse, String> {
275    fn from(original: SubMsgResult) -> Result<SubMsgResponse, String> {
276        match original {
277            SubMsgResult::Ok(value) => Ok(value),
278            SubMsgResult::Err(err) => Err(err),
279        }
280    }
281}
282
283/// The information we get back from a successful sub message execution,
284/// with full Cosmos SDK events.
285#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
286pub struct SubMsgResponse {
287    pub events: Vec<Event>,
288    #[deprecated = "Deprecated in the Cosmos SDK in favor of msg_responses. If your chain is running on CosmWasm 2.0 or higher, msg_responses will be filled. For older versions, the data field is still needed since msg_responses is empty in those cases."]
289    pub data: Option<Binary>,
290    /// The responses from the messages emitted by the submessage.
291    /// In most cases, this is equivalent to the Cosmos SDK's [MsgResponses], which usually contains a [single message].
292    /// However, wasmd allows chains to translate a single contract message into multiple SDK messages.
293    /// In that case all the MsgResponses from each are concatenated into this flattened `Vec`.
294    ///
295    /// [MsgResponses]: https://github.com/cosmos/cosmos-sdk/blob/316750cc8cd8b3296fa233f4da2e39cbcdc34517/proto/cosmos/base/abci/v1beta1/abci.proto#L106-L109
296    /// [single message]: https://github.com/cosmos/cosmos-sdk/blob/v0.50.4/baseapp/baseapp.go#L1020-L1023
297    #[serde(default)]
298    pub msg_responses: Vec<MsgResponse>,
299}
300
301#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
302pub struct MsgResponse {
303    pub type_url: String,
304    pub value: Binary,
305}
306
307#[cfg(test)]
308#[allow(deprecated)]
309mod tests {
310    use super::*;
311    use crate::{coins, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult};
312
313    #[test]
314    fn sub_msg_new_works() {
315        let msg = BankMsg::Send {
316            to_address: String::from("you"),
317            amount: coins(1015, "earth"),
318        };
319        let sub_msg: SubMsg = SubMsg::new(msg.clone());
320        // id and payload don't matter since there is no reply
321        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
322        assert_eq!(sub_msg.gas_limit, None);
323        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
324    }
325
326    #[test]
327    fn sub_msg_reply_never_works() {
328        let msg = BankMsg::Send {
329            to_address: String::from("you"),
330            amount: coins(1015, "earth"),
331        };
332        let sub_msg: SubMsg = SubMsg::reply_never(msg.clone());
333        // id and payload don't matter since there is no reply
334        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
335        assert_eq!(sub_msg.gas_limit, None);
336        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
337    }
338
339    #[test]
340    fn sub_msg_reply_always_works() {
341        let msg = BankMsg::Send {
342            to_address: String::from("you"),
343            amount: coins(1015, "earth"),
344        };
345        let sub_msg: SubMsg = SubMsg::reply_always(msg.clone(), 54);
346        assert_eq!(sub_msg.id, 54);
347        assert_eq!(sub_msg.payload, Binary::default());
348        assert_eq!(sub_msg.reply_on, ReplyOn::Always);
349        assert_eq!(sub_msg.gas_limit, None);
350        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
351    }
352
353    #[test]
354    fn sub_msg_with_gas_limit_works() {
355        let msg = BankMsg::Send {
356            to_address: String::from("you"),
357            amount: coins(1015, "earth"),
358        };
359        let sub_msg: SubMsg = SubMsg::reply_never(msg);
360        assert_eq!(sub_msg.gas_limit, None);
361        let sub_msg = sub_msg.with_gas_limit(20);
362        assert_eq!(sub_msg.gas_limit, Some(20));
363    }
364
365    #[test]
366    fn sub_msg_with_payload_works() {
367        let msg = BankMsg::Send {
368            to_address: String::from("you"),
369            amount: coins(1015, "earth"),
370        };
371        let sub_msg: SubMsg = SubMsg::reply_never(msg);
372        assert_eq!(sub_msg.payload, Binary::default());
373        let sub_msg = sub_msg.with_payload(vec![0xAA, 3, 5, 1, 2]);
374        assert_eq!(sub_msg.payload, Binary::new(vec![0xAA, 3, 5, 1, 2]));
375    }
376
377    #[test]
378    fn sub_msg_result_serialization_works() {
379        let result = SubMsgResult::Ok(SubMsgResponse {
380            data: None,
381            msg_responses: vec![],
382            events: vec![],
383        });
384        assert_eq!(
385            &to_json_vec(&result).unwrap(),
386            br#"{"ok":{"events":[],"data":null,"msg_responses":[]}}"#
387        );
388
389        let result = SubMsgResult::Ok(SubMsgResponse {
390            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
391            msg_responses: vec![MsgResponse {
392                type_url: "URL".to_string(),
393                value: Binary::from_base64("MTIzCg==").unwrap(),
394            }],
395            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
396        });
397        println!("{}", &crate::to_json_string(&result).unwrap());
398        assert_eq!(
399            &to_json_vec(&result).unwrap(),
400            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg==","msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#
401        );
402
403        let result: SubMsgResult = SubMsgResult::Err("broken".to_string());
404        assert_eq!(&to_json_vec(&result).unwrap(), b"{\"error\":\"broken\"}");
405    }
406
407    #[test]
408    fn sub_msg_result_deserialization_works() {
409        // should work without `msg_responses`
410        let result: SubMsgResult = from_json(br#"{"ok":{"events":[]}}"#).unwrap();
411        assert_eq!(
412            result,
413            SubMsgResult::Ok(SubMsgResponse {
414                events: vec![],
415                data: None,
416                msg_responses: vec![]
417            })
418        );
419
420        // should work with `data` and no `msg_responses`
421        // this is the case for pre-2.0 CosmWasm chains
422        let result: SubMsgResult = from_json(br#"{"ok":{"events":[],"data":"aGk="}}"#).unwrap();
423        assert_eq!(
424            result,
425            SubMsgResult::Ok(SubMsgResponse {
426                events: vec![],
427                data: Some(Binary::from_base64("aGk=").unwrap()),
428                msg_responses: vec![]
429            })
430        );
431
432        let result: SubMsgResult = from_json(
433            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg==",
434            "msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#).unwrap();
435        assert_eq!(
436            result,
437            SubMsgResult::Ok(SubMsgResponse {
438                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
439                msg_responses: vec![MsgResponse {
440                    type_url: "URL".to_string(),
441                    value: Binary::from_base64("MTIzCg==").unwrap(),
442                }],
443                events: vec![Event::new("wasm").add_attribute("fo", "ba")],
444            })
445        );
446
447        let result: SubMsgResult = from_json(br#"{"error":"broken"}"#).unwrap();
448        assert_eq!(result, SubMsgResult::Err("broken".to_string()));
449
450        // fails for additional attributes
451        let parse: StdResult<SubMsgResult> = from_json(br#"{"unrelated":321,"error":"broken"}"#);
452        match parse.unwrap_err() {
453            StdError::ParseErr { .. } => {}
454            err => panic!("Unexpected error: {err:?}"),
455        }
456        let parse: StdResult<SubMsgResult> = from_json(br#"{"error":"broken","unrelated":321}"#);
457        match parse.unwrap_err() {
458            StdError::ParseErr { .. } => {}
459            err => panic!("Unexpected error: {err:?}"),
460        }
461    }
462
463    #[test]
464    fn sub_msg_result_unwrap_works() {
465        let response = SubMsgResponse {
466            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
467            msg_responses: vec![MsgResponse {
468                type_url: "URL".to_string(),
469                value: Binary::from_base64("MTIzCg==").unwrap(),
470            }],
471            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
472        };
473        let success = SubMsgResult::Ok(response.clone());
474        assert_eq!(success.unwrap(), response);
475    }
476
477    #[test]
478    #[should_panic]
479    fn sub_msg_result_unwrap_panicks_for_err() {
480        let failure = SubMsgResult::Err("broken".to_string());
481        let _ = failure.unwrap();
482    }
483
484    #[test]
485    fn sub_msg_result_unwrap_err_works() {
486        let failure = SubMsgResult::Err("broken".to_string());
487        assert_eq!(failure.unwrap_err(), "broken");
488    }
489
490    #[test]
491    #[should_panic]
492    fn sub_msg_result_unwrap_err_panics_for_ok() {
493        let response = SubMsgResponse {
494            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
495            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
496            msg_responses: vec![],
497        };
498        let success = SubMsgResult::Ok(response);
499        let _ = success.unwrap_err();
500    }
501
502    #[test]
503    fn sub_msg_result_is_ok_works() {
504        let success = SubMsgResult::Ok(SubMsgResponse {
505            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
506            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
507            msg_responses: vec![],
508        });
509        let failure = SubMsgResult::Err("broken".to_string());
510        assert!(success.is_ok());
511        assert!(!failure.is_ok());
512    }
513
514    #[test]
515    fn sub_msg_result_is_err_works() {
516        let success = SubMsgResult::Ok(SubMsgResponse {
517            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
518            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
519            msg_responses: vec![],
520        });
521        let failure = SubMsgResult::Err("broken".to_string());
522        assert!(failure.is_err());
523        assert!(!success.is_err());
524    }
525
526    #[test]
527    fn sub_msg_result_can_convert_from_core_result() {
528        let original: Result<SubMsgResponse, StdError> = Ok(SubMsgResponse {
529            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
530            events: vec![],
531            msg_responses: vec![],
532        });
533        let converted: SubMsgResult = original.into();
534        assert_eq!(
535            converted,
536            SubMsgResult::Ok(SubMsgResponse {
537                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
538                events: vec![],
539                msg_responses: vec![],
540            })
541        );
542
543        let original: Result<SubMsgResponse, StdError> = Err(StdError::generic_err("broken"));
544        let converted: SubMsgResult = original.into();
545        assert_eq!(
546            converted,
547            SubMsgResult::Err("Generic error: broken".to_string())
548        );
549    }
550
551    #[test]
552    fn sub_msg_result_can_convert_to_core_result() {
553        let original = SubMsgResult::Ok(SubMsgResponse {
554            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
555            events: vec![],
556            msg_responses: vec![],
557        });
558        let converted: Result<SubMsgResponse, String> = original.into();
559        assert_eq!(
560            converted,
561            Ok(SubMsgResponse {
562                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
563                events: vec![],
564                msg_responses: vec![],
565            })
566        );
567
568        let original = SubMsgResult::Err("went wrong".to_string());
569        let converted: Result<SubMsgResponse, String> = original.into();
570        assert_eq!(converted, Err("went wrong".to_string()));
571    }
572
573    #[test]
574    fn reply_deserialization_works() {
575        // 1.x reply without payload (from https://github.com/CosmWasm/cosmwasm/issues/1909)
576        let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
577        assert_eq!(
578            reply,
579            Reply {
580                id: 75,
581                payload: Binary::default(),
582                gas_used: 4312324,
583                result: SubMsgResult::Ok(SubMsgResponse {
584                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
585                    events: vec![Event {
586                        ty: "hi".to_string(),
587                        attributes: vec![Attribute {
588                            key: "si".to_string(),
589                            value: "claro".to_string(),
590                        }]
591                    }],
592                    msg_responses: vec![],
593                })
594            }
595        );
596
597        // with payload (manually added to the above test)
598        let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"payload":"3NxjC5U=","result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
599        assert_eq!(
600            reply,
601            Reply {
602                id: 75,
603                payload: Binary::from_base64("3NxjC5U=").unwrap(),
604                gas_used: 4312324,
605                result: SubMsgResult::Ok(SubMsgResponse {
606                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
607                    events: vec![Event {
608                        ty: "hi".to_string(),
609                        attributes: vec![Attribute {
610                            key: "si".to_string(),
611                            value: "claro".to_string(),
612                        }]
613                    }],
614                    msg_responses: vec![],
615                })
616            }
617        );
618    }
619
620    #[test]
621    fn reply_serialization_cosmwasm_1() {
622        // json coming from wasmvm 1.5.0
623        let json = r#"{"id":1234,"result":{"ok":{"events":[{"type":"message","attributes":[{"key":"signer","value":"caller-addr"}]}],"data":"Zm9vYmFy"}}}"#;
624
625        let reply: Reply = from_json(json).unwrap();
626        assert_eq!(reply.id, 1234);
627        assert_eq!(reply.payload, Binary::default());
628        assert_eq!(
629            reply.result,
630            SubMsgResult::Ok(SubMsgResponse {
631                data: Some(Binary::from_base64("Zm9vYmFy").unwrap()),
632                events: vec![Event {
633                    ty: "message".to_string(),
634                    attributes: vec![Attribute {
635                        key: "signer".to_string(),
636                        value: "caller-addr".to_string()
637                    }]
638                }],
639                msg_responses: vec![]
640            })
641        );
642        assert_eq!(reply.gas_used, 0);
643    }
644}