secret_cosmwasm_std/results/
submessages.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::Binary;
5
6use super::{CosmosMsg, Empty, Event};
7
8/// Use this to define when the contract gets a response callback.
9/// If you only need it for errors or success you can select just those in order
10/// to save gas.
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
12#[serde(rename_all = "snake_case")]
13pub enum ReplyOn {
14    /// Always perform a callback after SubMsg is processed
15    Always,
16    /// Only callback if SubMsg returned an error, no callback on success case
17    Error,
18    /// Only callback if SubMsg was successful, no callback on error case
19    Success,
20    /// Never make a callback - this is like the original CosmosMsg semantics
21    Never,
22}
23
24/// A submessage that will guarantee a `reply` call on success or error, depending on
25/// the `reply_on` setting. If you do not need to process the result, use regular messages instead.
26///
27/// Note: On error the submessage execution will revert any partial state changes due to this message,
28/// but not revert any state changes in the calling contract. If this is required, it must be done
29/// manually in the `reply` entry point.
30#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
31pub struct SubMsg<T = Empty> {
32    /// An arbitrary ID chosen by the contract.
33    /// This is typically used to match `Reply`s in the `reply` entry point to the submessage.
34    pub id: u64,
35    pub msg: CosmosMsg<T>,
36    /// Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
37    pub gas_limit: Option<u64>,
38    pub reply_on: ReplyOn,
39}
40
41/// This is used for cases when we use ReplyOn::Never and the id doesn't matter
42pub const UNUSED_MSG_ID: u64 = 0;
43
44impl<T> SubMsg<T> {
45    /// new creates a "fire and forget" message with the pre-0.14 semantics
46    pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
47        SubMsg {
48            id: UNUSED_MSG_ID,
49            msg: msg.into(),
50            reply_on: ReplyOn::Never,
51            gas_limit: None,
52        }
53    }
54
55    /// create a `SubMsg` that will provide a `reply` with the given id if the message returns `Ok`
56    pub fn reply_on_success(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
57        Self::reply_on(msg.into(), id, ReplyOn::Success)
58    }
59
60    /// create a `SubMsg` that will provide a `reply` with the given id if the message returns `Err`
61    pub fn reply_on_error(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
62        Self::reply_on(msg.into(), id, ReplyOn::Error)
63    }
64
65    /// create a `SubMsg` that will always provide a `reply` with the given id
66    pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
67        Self::reply_on(msg.into(), id, ReplyOn::Always)
68    }
69
70    /// Add a gas limit to the message.
71    /// This gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
72    ///
73    /// ## Examples
74    ///
75    /// ```
76    /// # use secret_cosmwasm_std::{coins, BankMsg, ReplyOn, SubMsg};
77    /// # let msg = BankMsg::Send { to_address: String::from("you"), amount: coins(1015, "earth") };
78    /// let sub_msg: SubMsg = SubMsg::reply_always(msg, 1234).with_gas_limit(60_000);
79    /// assert_eq!(sub_msg.id, 1234);
80    /// assert_eq!(sub_msg.gas_limit, Some(60_000));
81    /// assert_eq!(sub_msg.reply_on, ReplyOn::Always);
82    /// ```
83    pub fn with_gas_limit(mut self, limit: u64) -> Self {
84        self.gas_limit = Some(limit);
85        self
86    }
87
88    fn reply_on(msg: CosmosMsg<T>, id: u64, reply_on: ReplyOn) -> Self {
89        SubMsg {
90            id,
91            msg,
92            reply_on,
93            gas_limit: None,
94        }
95    }
96}
97
98/// The result object returned to `reply`. We always get the ID from the submessage
99/// back and then must handle success and error cases ourselves.
100#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
101pub struct Reply {
102    /// The ID that the contract set when emitting the `SubMsg`.
103    /// Use this to identify which submessage triggered the `reply`.
104    pub id: u64,
105    pub result: SubMsgResult,
106}
107
108/// This is the result type that is returned from a sub message execution.
109///
110/// We use a custom type here instead of Rust's Result because we want to be able to
111/// define the serialization, which is a public interface. Every language that compiles
112/// to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.
113///
114/// Until version 1.0.0-beta5, `ContractResult<SubMsgResponse>` was used instead
115/// of this type. Once serialized, the two types are the same. However, in the Rust type
116/// system we want different types for clarity and documenation reasons.
117///
118/// # Examples
119///
120/// Success:
121///
122/// ```
123/// # use secret_cosmwasm_std::{to_vec, Binary, Event, SubMsgResponse, SubMsgResult};
124/// let response = SubMsgResponse {
125///     data: Some(Binary::from_base64("MTIzCg==").unwrap()),
126///     events: vec![Event::new("wasm").add_attribute("fo", "ba")],
127/// };
128/// let result: SubMsgResult = SubMsgResult::Ok(response);
129/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba","encrypted":true}]}],"data":"MTIzCg=="}}"#);
130/// ```
131///
132/// Failure:
133///
134/// ```
135/// # use secret_cosmwasm_std::{to_vec, SubMsgResult, Response};
136/// let error_msg = String::from("Something went wrong");
137/// let result = SubMsgResult::Err(error_msg);
138/// assert_eq!(to_vec(&result).unwrap(), br#"{"error":"Something went wrong"}"#);
139/// ```
140#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
141#[serde(rename_all = "snake_case")]
142pub enum SubMsgResult {
143    Ok(SubMsgResponse),
144    /// An error type that every custom error created by contract developers can be converted to.
145    /// This could potientially have more structure, but String is the easiest.
146    #[serde(rename = "error")]
147    Err(String),
148}
149
150// Implementations here mimic the Result API and should be implemented via a conversion to Result
151// to ensure API consistency
152impl SubMsgResult {
153    /// Converts a `SubMsgResult<S>` to a `Result<S, String>` as a convenient way
154    /// to access the full Result API.
155    pub fn into_result(self) -> Result<SubMsgResponse, String> {
156        Result::<SubMsgResponse, String>::from(self)
157    }
158
159    pub fn unwrap(self) -> SubMsgResponse {
160        self.into_result().unwrap()
161    }
162
163    pub fn unwrap_err(self) -> String {
164        self.into_result().unwrap_err()
165    }
166
167    pub fn is_ok(&self) -> bool {
168        matches!(self, SubMsgResult::Ok(_))
169    }
170
171    pub fn is_err(&self) -> bool {
172        matches!(self, SubMsgResult::Err(_))
173    }
174}
175
176impl<E: ToString> From<Result<SubMsgResponse, E>> for SubMsgResult {
177    fn from(original: Result<SubMsgResponse, E>) -> SubMsgResult {
178        match original {
179            Ok(value) => SubMsgResult::Ok(value),
180            Err(err) => SubMsgResult::Err(err.to_string()),
181        }
182    }
183}
184
185impl From<SubMsgResult> for Result<SubMsgResponse, String> {
186    fn from(original: SubMsgResult) -> Result<SubMsgResponse, String> {
187        match original {
188            SubMsgResult::Ok(value) => Ok(value),
189            SubMsgResult::Err(err) => Err(err),
190        }
191    }
192}
193
194/// The information we get back from a successful sub message execution,
195/// with full Cosmos SDK events.
196#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
197pub struct SubMsgResponse {
198    pub events: Vec<Event>,
199    pub data: Option<Binary>,
200}
201
202#[deprecated(note = "Renamed to SubMsgResponse")]
203pub type SubMsgExecutionResponse = SubMsgResponse;
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::{from_slice, to_vec, StdError, StdResult};
209
210    #[test]
211    fn sub_msg_result_serialization_works() {
212        let result = SubMsgResult::Ok(SubMsgResponse {
213            data: None,
214            events: vec![],
215        });
216        assert_eq!(
217            &to_vec(&result).unwrap(),
218            br#"{"ok":{"events":[],"data":null}}"#
219        );
220
221        let result = SubMsgResult::Ok(SubMsgResponse {
222            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
223            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
224        });
225
226        println!("deubgggg: {:?}", result);
227
228        assert_eq!(
229            &to_vec(&result).unwrap(),
230            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba","encrypted":true}]}],"data":"MTIzCg=="}}"#
231        );
232
233        let result: SubMsgResult = SubMsgResult::Err("broken".to_string());
234        assert_eq!(&to_vec(&result).unwrap(), b"{\"error\":\"broken\"}");
235    }
236
237    #[test]
238    fn sub_msg_result_deserialization_works() {
239        let result: SubMsgResult = from_slice(br#"{"ok":{"events":[],"data":null}}"#).unwrap();
240        assert_eq!(
241            result,
242            SubMsgResult::Ok(SubMsgResponse {
243                events: vec![],
244                data: None,
245            })
246        );
247
248        let result: SubMsgResult = from_slice(
249            br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba","encrypted":true}]}],"data":"MTIzCg=="}}"#).unwrap();
250        assert_eq!(
251            result,
252            SubMsgResult::Ok(SubMsgResponse {
253                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
254                events: vec![Event::new("wasm").add_attribute("fo", "ba")],
255            })
256        );
257
258        let result: SubMsgResult = from_slice(br#"{"error":"broken"}"#).unwrap();
259        assert_eq!(result, SubMsgResult::Err("broken".to_string()));
260
261        // fails for additional attributes
262        let parse: StdResult<SubMsgResult> = from_slice(br#"{"unrelated":321,"error":"broken"}"#);
263        match parse.unwrap_err() {
264            StdError::ParseErr { .. } => {}
265            err => panic!("Unexpected error: {:?}", err),
266        }
267        let parse: StdResult<SubMsgResult> = from_slice(br#"{"error":"broken","unrelated":321}"#);
268        match parse.unwrap_err() {
269            StdError::ParseErr { .. } => {}
270            err => panic!("Unexpected error: {:?}", err),
271        }
272    }
273
274    #[test]
275    fn sub_msg_result_unwrap_works() {
276        let response = SubMsgResponse {
277            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
278            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
279        };
280        let success = SubMsgResult::Ok(response.clone());
281        assert_eq!(success.unwrap(), response);
282    }
283
284    #[test]
285    #[should_panic]
286    fn sub_msg_result_unwrap_panicks_for_err() {
287        let failure = SubMsgResult::Err("broken".to_string());
288        let _ = failure.unwrap();
289    }
290
291    #[test]
292    fn sub_msg_result_unwrap_err_works() {
293        let failure = SubMsgResult::Err("broken".to_string());
294        assert_eq!(failure.unwrap_err(), "broken");
295    }
296
297    #[test]
298    #[should_panic]
299    fn sub_msg_result_unwrap_err_panics_for_ok() {
300        let response = SubMsgResponse {
301            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
302            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
303        };
304        let success = SubMsgResult::Ok(response);
305        let _ = success.unwrap_err();
306    }
307
308    #[test]
309    fn sub_msg_result_is_ok_works() {
310        let success = SubMsgResult::Ok(SubMsgResponse {
311            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
312            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
313        });
314        let failure = SubMsgResult::Err("broken".to_string());
315        assert!(success.is_ok());
316        assert!(!failure.is_ok());
317    }
318
319    #[test]
320    fn sub_msg_result_is_err_works() {
321        let success = SubMsgResult::Ok(SubMsgResponse {
322            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
323            events: vec![Event::new("wasm").add_attribute("fo", "ba")],
324        });
325        let failure = SubMsgResult::Err("broken".to_string());
326        assert!(failure.is_err());
327        assert!(!success.is_err());
328    }
329
330    #[test]
331    fn sub_msg_result_can_convert_from_core_result() {
332        let original: Result<SubMsgResponse, StdError> = Ok(SubMsgResponse {
333            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
334            events: vec![],
335        });
336        let converted: SubMsgResult = original.into();
337        assert_eq!(
338            converted,
339            SubMsgResult::Ok(SubMsgResponse {
340                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
341                events: vec![],
342            })
343        );
344
345        let original: Result<SubMsgResponse, StdError> = Err(StdError::generic_err("broken"));
346        let converted: SubMsgResult = original.into();
347        assert_eq!(
348            converted,
349            SubMsgResult::Err("Generic error: broken".to_string())
350        );
351    }
352
353    #[test]
354    fn sub_msg_result_can_convert_to_core_result() {
355        let original = SubMsgResult::Ok(SubMsgResponse {
356            data: Some(Binary::from_base64("MTIzCg==").unwrap()),
357            events: vec![],
358        });
359        let converted: Result<SubMsgResponse, String> = original.into();
360        assert_eq!(
361            converted,
362            Ok(SubMsgResponse {
363                data: Some(Binary::from_base64("MTIzCg==").unwrap()),
364                events: vec![],
365            })
366        );
367
368        let original = SubMsgResult::Err("went wrong".to_string());
369        let converted: Result<SubMsgResponse, String> = original.into();
370        assert_eq!(converted, Err("went wrong".to_string()));
371    }
372}