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 arbitrary 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 arbitrary 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 documentation 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("foo", "bar")],
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":"foo","value":"bar"}]}],"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 potentially 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
285pub struct SubMsgResponse {
286    /// The Cosmos SDK events emitted by the submessage.
287    ///
288    /// This is only filled if the submessage was itself a [`crate::WasmMsg`].
289    /// The reason for this is that events are [not part of consensus] and therefore not guaranteed to be deterministic,
290    /// so the VM only returns events of wasm messages, which we know are deterministic.
291    ///
292    /// [not part of consensus]: https://github.com/tendermint/tendermint/blob/eed27addecb339cfaeba8fda774e6ab37cdb3774/spec/abci/abci.md#events
293    pub events: Vec<Event>,
294    #[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."]
295    pub data: Option<Binary>,
296    /// The responses from the messages emitted by the submessage.
297    /// In most cases, this is equivalent to the Cosmos SDK's [MsgResponses], which usually contains a [single message].
298    /// However, wasmd allows chains to translate a single contract message into multiple SDK messages.
299    /// In that case all the MsgResponses from each are concatenated into this flattened `Vec`.
300    ///
301    /// [MsgResponses]: https://github.com/cosmos/cosmos-sdk/blob/316750cc8cd8b3296fa233f4da2e39cbcdc34517/proto/cosmos/base/abci/v1beta1/abci.proto#L106-L109
302    /// [single message]: https://github.com/cosmos/cosmos-sdk/blob/v0.50.4/baseapp/baseapp.go#L1020-L1023
303    #[serde(default)]
304    pub msg_responses: Vec<MsgResponse>,
305}
306
307#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
308pub struct MsgResponse {
309    pub type_url: String,
310    pub value: Binary,
311}
312
313#[cfg(test)]
314#[allow(deprecated)]
315mod tests {
316    use super::*;
317    use crate::{coins, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult};
318
319    #[test]
320    fn sub_msg_new_works() {
321        let msg = BankMsg::Send {
322            to_address: String::from("you"),
323            amount: coins(1015, "earth"),
324        };
325        let sub_msg: SubMsg = SubMsg::new(msg.clone());
326        // id and payload don't matter since there is no reply
327        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
328        assert_eq!(sub_msg.gas_limit, None);
329        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
330    }
331
332    #[test]
333    fn sub_msg_reply_never_works() {
334        let msg = BankMsg::Send {
335            to_address: String::from("you"),
336            amount: coins(1015, "earth"),
337        };
338        let sub_msg: SubMsg = SubMsg::reply_never(msg.clone());
339        // id and payload don't matter since there is no reply
340        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
341        assert_eq!(sub_msg.gas_limit, None);
342        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
343    }
344
345    #[test]
346    fn sub_msg_reply_always_works() {
347        let msg = BankMsg::Send {
348            to_address: String::from("you"),
349            amount: coins(1015, "earth"),
350        };
351        let sub_msg: SubMsg = SubMsg::reply_always(msg.clone(), 54);
352        assert_eq!(sub_msg.id, 54);
353        assert_eq!(sub_msg.payload, Binary::default());
354        assert_eq!(sub_msg.reply_on, ReplyOn::Always);
355        assert_eq!(sub_msg.gas_limit, None);
356        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
357    }
358
359    #[test]
360    fn sub_msg_with_gas_limit_works() {
361        let msg = BankMsg::Send {
362            to_address: String::from("you"),
363            amount: coins(1015, "earth"),
364        };
365        let sub_msg: SubMsg = SubMsg::reply_never(msg);
366        assert_eq!(sub_msg.gas_limit, None);
367        let sub_msg = sub_msg.with_gas_limit(20);
368        assert_eq!(sub_msg.gas_limit, Some(20));
369    }
370
371    #[test]
372    fn sub_msg_with_payload_works() {
373        let msg = BankMsg::Send {
374            to_address: String::from("you"),
375            amount: coins(1015, "earth"),
376        };
377        let sub_msg: SubMsg = SubMsg::reply_never(msg);
378        assert_eq!(sub_msg.payload, Binary::default());
379        let sub_msg = sub_msg.with_payload(vec![0xAA, 3, 5, 1, 2]);
380        assert_eq!(sub_msg.payload, Binary::new(vec![0xAA, 3, 5, 1, 2]));
381    }
382
383    #[test]
384    fn sub_msg_result_serialization_works() {
385        let result = SubMsgResult::Ok(SubMsgResponse {
386            data: None,
387            msg_responses: vec![],
388            events: vec![],
389        });
390        assert_eq!(
391            &to_json_vec(&result).unwrap(),
392            br#"{"ok":{"events":[],"data":null,"msg_responses":[]}}"#
393        );
394
395        let result = SubMsgResult::Ok(SubMsgResponse {
396            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
397            msg_responses: vec![MsgResponse {
398                type_url: "URL".to_string(),
399                value: Binary::from_base64("MTIzCg==").unwrap(),
400            }],
401            events: vec![Event::new("wasm").add_attribute("foo", "bar")],
402        });
403        println!("{}", &crate::to_json_string(&result).unwrap());
404        assert_eq!(
405            &to_json_vec(&result).unwrap(),
406            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==","msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#
407        );
408
409        let result: SubMsgResult = SubMsgResult::Err("broken".to_string());
410        assert_eq!(&to_json_vec(&result).unwrap(), b"{\"error\":\"broken\"}");
411    }
412
413    #[test]
414    fn sub_msg_result_deserialization_works() {
415        // should work without `msg_responses`
416        let result: SubMsgResult = from_json(br#"{"ok":{"events":[]}}"#).unwrap();
417        assert_eq!(
418            result,
419            SubMsgResult::Ok(SubMsgResponse {
420                events: vec![],
421                data: None,
422                msg_responses: vec![]
423            })
424        );
425
426        // should work with `data` and no `msg_responses`
427        // this is the case for pre-2.0 CosmWasm chains
428        let result: SubMsgResult = from_json(br#"{"ok":{"events":[],"data":"aGk="}}"#).unwrap();
429        assert_eq!(
430            result,
431            SubMsgResult::Ok(SubMsgResponse {
432                events: vec![],
433                data: Some(Binary::from_base64("aGk=").unwrap()),
434                msg_responses: vec![]
435            })
436        );
437
438        let result: SubMsgResult = from_json(
439            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==",
440            "msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#).unwrap();
441        assert_eq!(
442            result,
443            SubMsgResult::Ok(SubMsgResponse {
444                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
445                msg_responses: vec![MsgResponse {
446                    type_url: "URL".to_string(),
447                    value: Binary::from_base64("MTIzCg==").unwrap(),
448                }],
449                events: vec![Event::new("wasm").add_attribute("foo", "bar")],
450            })
451        );
452
453        let result: SubMsgResult = from_json(br#"{"error":"broken"}"#).unwrap();
454        assert_eq!(result, SubMsgResult::Err("broken".to_string()));
455
456        // fails for additional attributes
457        let parse: StdResult<SubMsgResult> = from_json(br#"{"unrelated":321,"error":"broken"}"#);
458        match parse.unwrap_err() {
459            StdError::ParseErr { .. } => {}
460            err => panic!("Unexpected error: {err:?}"),
461        }
462        let parse: StdResult<SubMsgResult> = from_json(br#"{"error":"broken","unrelated":321}"#);
463        match parse.unwrap_err() {
464            StdError::ParseErr { .. } => {}
465            err => panic!("Unexpected error: {err:?}"),
466        }
467    }
468
469    #[test]
470    fn sub_msg_result_unwrap_works() {
471        let response = SubMsgResponse {
472            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
473            msg_responses: vec![MsgResponse {
474                type_url: "URL".to_string(),
475                value: Binary::from_base64("MTIzCg==").unwrap(),
476            }],
477            events: vec![Event::new("wasm").add_attribute("foo", "bar")],
478        };
479        let success = SubMsgResult::Ok(response.clone());
480        assert_eq!(success.unwrap(), response);
481    }
482
483    #[test]
484    #[should_panic]
485    fn sub_msg_result_unwrap_panicks_for_err() {
486        let failure = SubMsgResult::Err("broken".to_string());
487        let _ = failure.unwrap();
488    }
489
490    #[test]
491    fn sub_msg_result_unwrap_err_works() {
492        let failure = SubMsgResult::Err("broken".to_string());
493        assert_eq!(failure.unwrap_err(), "broken");
494    }
495
496    #[test]
497    #[should_panic]
498    fn sub_msg_result_unwrap_err_panics_for_ok() {
499        let response = SubMsgResponse {
500            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
501            events: vec![Event::new("wasm").add_attribute("foo", "bar")],
502            msg_responses: vec![],
503        };
504        let success = SubMsgResult::Ok(response);
505        let _ = success.unwrap_err();
506    }
507
508    #[test]
509    fn sub_msg_result_is_ok_works() {
510        let success = SubMsgResult::Ok(SubMsgResponse {
511            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
512            events: vec![Event::new("wasm").add_attribute("foo", "bar")],
513            msg_responses: vec![],
514        });
515        let failure = SubMsgResult::Err("broken".to_string());
516        assert!(success.is_ok());
517        assert!(!failure.is_ok());
518    }
519
520    #[test]
521    fn sub_msg_result_is_err_works() {
522        let success = SubMsgResult::Ok(SubMsgResponse {
523            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
524            events: vec![Event::new("wasm").add_attribute("foo", "bar")],
525            msg_responses: vec![],
526        });
527        let failure = SubMsgResult::Err("broken".to_string());
528        assert!(failure.is_err());
529        assert!(!success.is_err());
530    }
531
532    #[test]
533    fn sub_msg_result_can_convert_from_core_result() {
534        let original: Result<SubMsgResponse, StdError> = Ok(SubMsgResponse {
535            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
536            events: vec![],
537            msg_responses: vec![],
538        });
539        let converted: SubMsgResult = original.into();
540        assert_eq!(
541            converted,
542            SubMsgResult::Ok(SubMsgResponse {
543                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
544                events: vec![],
545                msg_responses: vec![],
546            })
547        );
548
549        let original: Result<SubMsgResponse, StdError> = Err(StdError::generic_err("broken"));
550        let converted: SubMsgResult = original.into();
551        assert_eq!(
552            converted,
553            SubMsgResult::Err("Generic error: broken".to_string())
554        );
555    }
556
557    #[test]
558    fn sub_msg_result_can_convert_to_core_result() {
559        let original = SubMsgResult::Ok(SubMsgResponse {
560            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
561            events: vec![],
562            msg_responses: vec![],
563        });
564        let converted: Result<SubMsgResponse, String> = original.into();
565        assert_eq!(
566            converted,
567            Ok(SubMsgResponse {
568                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
569                events: vec![],
570                msg_responses: vec![],
571            })
572        );
573
574        let original = SubMsgResult::Err("went wrong".to_string());
575        let converted: Result<SubMsgResponse, String> = original.into();
576        assert_eq!(converted, Err("went wrong".to_string()));
577    }
578
579    #[test]
580    fn reply_deserialization_works() {
581        // 1.x reply without payload (from https://github.com/CosmWasm/cosmwasm/issues/1909)
582        let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
583        assert_eq!(
584            reply,
585            Reply {
586                id: 75,
587                payload: Binary::default(),
588                gas_used: 4312324,
589                result: SubMsgResult::Ok(SubMsgResponse {
590                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
591                    events: vec![Event {
592                        ty: "hi".to_string(),
593                        attributes: vec![Attribute {
594                            key: "si".to_string(),
595                            value: "claro".to_string(),
596                        }]
597                    }],
598                    msg_responses: vec![],
599                })
600            }
601        );
602
603        // with payload (manually added to the above test)
604        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();
605        assert_eq!(
606            reply,
607            Reply {
608                id: 75,
609                payload: Binary::from_base64("3NxjC5U=").unwrap(),
610                gas_used: 4312324,
611                result: SubMsgResult::Ok(SubMsgResponse {
612                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
613                    events: vec![Event {
614                        ty: "hi".to_string(),
615                        attributes: vec![Attribute {
616                            key: "si".to_string(),
617                            value: "claro".to_string(),
618                        }]
619                    }],
620                    msg_responses: vec![],
621                })
622            }
623        );
624    }
625
626    #[test]
627    fn reply_serialization_cosmwasm_1() {
628        // json coming from wasmvm 1.5.0
629        let json = r#"{"id":1234,"result":{"ok":{"events":[{"type":"message","attributes":[{"key":"signer","value":"caller-addr"}]}],"data":"Zm9vYmFy"}}}"#;
630
631        let reply: Reply = from_json(json).unwrap();
632        assert_eq!(reply.id, 1234);
633        assert_eq!(reply.payload, Binary::default());
634        assert_eq!(
635            reply.result,
636            SubMsgResult::Ok(SubMsgResponse {
637                data: Some(Binary::from_base64("Zm9vYmFy").unwrap()),
638                events: vec![Event {
639                    ty: "message".to_string(),
640                    attributes: vec![Attribute {
641                        key: "signer".to_string(),
642                        value: "caller-addr".to_string()
643                    }]
644                }],
645                msg_responses: vec![]
646            })
647        );
648        assert_eq!(reply.gas_used, 0);
649    }
650}