1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::prelude::*;
5use crate::Binary;
6
7use super::{CosmosMsg, Empty, Event};
8
9#[derive(
13 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
14)]
15#[serde(rename_all = "snake_case")]
16pub enum ReplyOn {
17 Always,
19 Error,
21 Success,
23 Never,
25}
26
27#[derive(
34 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
35)]
36pub struct SubMsg<T = Empty> {
37 pub id: u64,
40 #[serde(default)]
53 pub payload: Binary,
54 pub msg: CosmosMsg<T>,
55 pub gas_limit: Option<u64>,
60 pub reply_on: ReplyOn,
61}
62
63pub const UNUSED_MSG_ID: u64 = 0;
65
66impl<T> SubMsg<T> {
67 pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
75 Self::reply_never(msg)
76 }
77
78 pub fn reply_on_success(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
83 Self::reply_on(msg.into(), id, ReplyOn::Success)
84 }
85
86 pub fn reply_on_error(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
91 Self::reply_on(msg.into(), id, ReplyOn::Error)
92 }
93
94 pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
99 Self::reply_on(msg.into(), id, ReplyOn::Always)
100 }
101
102 pub fn reply_never(msg: impl Into<CosmosMsg<T>>) -> Self {
107 Self::reply_on(msg.into(), UNUSED_MSG_ID, ReplyOn::Never)
108 }
109
110 pub fn with_gas_limit(mut self, limit: u64) -> Self {
124 self.gas_limit = Some(limit);
125 self
126 }
127
128 pub fn with_payload(mut self, payload: impl Into<Binary>) -> Self {
142 self.payload = payload.into();
143 self
144 }
145
146 fn reply_on(msg: CosmosMsg<T>, id: u64, reply_on: ReplyOn) -> Self {
147 SubMsg {
148 id,
149 payload: Default::default(),
150 msg,
151 reply_on,
152 gas_limit: None,
153 }
154 }
155
156 pub fn change_custom<U>(self) -> Option<SubMsg<U>> {
161 Some(SubMsg {
162 id: self.id,
163 payload: self.payload,
164 msg: self.msg.change_custom::<U>()?,
165 gas_limit: self.gas_limit,
166 reply_on: self.reply_on,
167 })
168 }
169}
170
171#[derive(
174 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
175)]
176pub struct Reply {
177 pub id: u64,
180 #[serde(default)]
187 pub payload: Binary,
188 #[serde(default)]
194 pub gas_used: u64,
195 pub result: SubMsgResult,
196}
197
198#[derive(
236 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
237)]
238#[serde(rename_all = "snake_case")]
239pub enum SubMsgResult {
240 Ok(SubMsgResponse),
241 #[serde(rename = "error")]
244 Err(String),
245}
246
247impl SubMsgResult {
250 pub fn into_result(self) -> Result<SubMsgResponse, String> {
253 Result::<SubMsgResponse, String>::from(self)
254 }
255
256 pub fn unwrap(self) -> SubMsgResponse {
257 self.into_result().unwrap()
258 }
259
260 pub fn unwrap_err(self) -> String {
261 self.into_result().unwrap_err()
262 }
263
264 pub fn is_ok(&self) -> bool {
265 matches!(self, SubMsgResult::Ok(_))
266 }
267
268 pub fn is_err(&self) -> bool {
269 matches!(self, SubMsgResult::Err(_))
270 }
271}
272
273impl<E: ToString> From<Result<SubMsgResponse, E>> for SubMsgResult {
274 fn from(original: Result<SubMsgResponse, E>) -> SubMsgResult {
275 match original {
276 Ok(value) => SubMsgResult::Ok(value),
277 Err(err) => SubMsgResult::Err(err.to_string()),
278 }
279 }
280}
281
282impl From<SubMsgResult> for Result<SubMsgResponse, String> {
283 fn from(original: SubMsgResult) -> Result<SubMsgResponse, String> {
284 match original {
285 SubMsgResult::Ok(value) => Ok(value),
286 SubMsgResult::Err(err) => Err(err),
287 }
288 }
289}
290
291#[derive(
293 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
294)]
295pub struct SubMsgResponse {
296 pub events: Vec<Event>,
304 #[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."]
305 pub data: Option<Binary>,
306 #[serde(default)]
314 pub msg_responses: Vec<MsgResponse>,
315}
316
317#[derive(
318 Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
319)]
320pub struct MsgResponse {
321 pub type_url: String,
322 pub value: Binary,
323}
324
325#[cfg(test)]
326#[allow(deprecated)]
327mod tests {
328 use super::*;
329 use crate::{
330 coins, errors::ErrorKind, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult,
331 };
332
333 #[test]
334 fn sub_msg_new_works() {
335 let msg = BankMsg::Send {
336 to_address: String::from("you"),
337 amount: coins(1015, "earth"),
338 };
339 let sub_msg: SubMsg = SubMsg::new(msg.clone());
340 assert_eq!(sub_msg.reply_on, ReplyOn::Never);
342 assert_eq!(sub_msg.gas_limit, None);
343 assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
344 }
345
346 #[test]
347 fn sub_msg_reply_never_works() {
348 let msg = BankMsg::Send {
349 to_address: String::from("you"),
350 amount: coins(1015, "earth"),
351 };
352 let sub_msg: SubMsg = SubMsg::reply_never(msg.clone());
353 assert_eq!(sub_msg.reply_on, ReplyOn::Never);
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_reply_always_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_always(msg.clone(), 54);
366 assert_eq!(sub_msg.id, 54);
367 assert_eq!(sub_msg.payload, Binary::default());
368 assert_eq!(sub_msg.reply_on, ReplyOn::Always);
369 assert_eq!(sub_msg.gas_limit, None);
370 assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
371 }
372
373 #[test]
374 fn sub_msg_with_gas_limit_works() {
375 let msg = BankMsg::Send {
376 to_address: String::from("you"),
377 amount: coins(1015, "earth"),
378 };
379 let sub_msg: SubMsg = SubMsg::reply_never(msg);
380 assert_eq!(sub_msg.gas_limit, None);
381 let sub_msg = sub_msg.with_gas_limit(20);
382 assert_eq!(sub_msg.gas_limit, Some(20));
383 }
384
385 #[test]
386 fn sub_msg_with_payload_works() {
387 let msg = BankMsg::Send {
388 to_address: String::from("you"),
389 amount: coins(1015, "earth"),
390 };
391 let sub_msg: SubMsg = SubMsg::reply_never(msg);
392 assert_eq!(sub_msg.payload, Binary::default());
393 let sub_msg = sub_msg.with_payload(vec![0xAA, 3, 5, 1, 2]);
394 assert_eq!(sub_msg.payload, Binary::new(vec![0xAA, 3, 5, 1, 2]));
395 }
396
397 #[test]
398 fn sub_msg_result_serialization_works() {
399 let result = SubMsgResult::Ok(SubMsgResponse {
400 data: None,
401 msg_responses: vec![],
402 events: vec![],
403 });
404 assert_eq!(
405 &to_json_vec(&result).unwrap(),
406 br#"{"ok":{"events":[],"data":null,"msg_responses":[]}}"#
407 );
408
409 let result = SubMsgResult::Ok(SubMsgResponse {
410 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
411 msg_responses: vec![MsgResponse {
412 type_url: "URL".to_string(),
413 value: Binary::from_base64("MTIzCg==").unwrap(),
414 }],
415 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
416 });
417 println!("{}", &crate::to_json_string(&result).unwrap());
418 assert_eq!(
419 &to_json_vec(&result).unwrap(),
420 br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==","msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#
421 );
422
423 let result: SubMsgResult = SubMsgResult::Err("broken".to_string());
424 assert_eq!(&to_json_vec(&result).unwrap(), b"{\"error\":\"broken\"}");
425 }
426
427 #[test]
428 fn sub_msg_result_deserialization_works() {
429 let result: SubMsgResult = from_json(br#"{"ok":{"events":[]}}"#).unwrap();
431 assert_eq!(
432 result,
433 SubMsgResult::Ok(SubMsgResponse {
434 events: vec![],
435 data: None,
436 msg_responses: vec![]
437 })
438 );
439
440 let result: SubMsgResult = from_json(br#"{"ok":{"events":[],"data":"aGk="}}"#).unwrap();
443 assert_eq!(
444 result,
445 SubMsgResult::Ok(SubMsgResponse {
446 events: vec![],
447 data: Some(Binary::from_base64("aGk=").unwrap()),
448 msg_responses: vec![]
449 })
450 );
451
452 let result: SubMsgResult = from_json(
453 br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==",
454 "msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#).unwrap();
455 assert_eq!(
456 result,
457 SubMsgResult::Ok(SubMsgResponse {
458 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
459 msg_responses: vec![MsgResponse {
460 type_url: "URL".to_string(),
461 value: Binary::from_base64("MTIzCg==").unwrap(),
462 }],
463 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
464 })
465 );
466
467 let result: SubMsgResult = from_json(br#"{"error":"broken"}"#).unwrap();
468 assert_eq!(result, SubMsgResult::Err("broken".to_string()));
469
470 let parse: StdResult<SubMsgResult> = from_json(br#"{"unrelated":321,"error":"broken"}"#);
472 match parse.unwrap_err().kind() {
473 ErrorKind::Serialization => {}
474 err => panic!("Unexpected error: {err:?}"),
475 }
476 let parse: StdResult<SubMsgResult> = from_json(br#"{"error":"broken","unrelated":321}"#);
477 match parse.unwrap_err().kind() {
478 ErrorKind::Serialization => {}
479 err => panic!("Unexpected error: {err:?}"),
480 }
481 }
482
483 #[test]
484 fn sub_msg_result_unwrap_works() {
485 let response = SubMsgResponse {
486 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
487 msg_responses: vec![MsgResponse {
488 type_url: "URL".to_string(),
489 value: Binary::from_base64("MTIzCg==").unwrap(),
490 }],
491 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
492 };
493 let success = SubMsgResult::Ok(response.clone());
494 assert_eq!(success.unwrap(), response);
495 }
496
497 #[test]
498 #[should_panic]
499 fn sub_msg_result_unwrap_panicks_for_err() {
500 let failure = SubMsgResult::Err("broken".to_string());
501 let _ = failure.unwrap();
502 }
503
504 #[test]
505 fn sub_msg_result_unwrap_err_works() {
506 let failure = SubMsgResult::Err("broken".to_string());
507 assert_eq!(failure.unwrap_err(), "broken");
508 }
509
510 #[test]
511 #[should_panic]
512 fn sub_msg_result_unwrap_err_panics_for_ok() {
513 let response = SubMsgResponse {
514 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
515 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
516 msg_responses: vec![],
517 };
518 let success = SubMsgResult::Ok(response);
519 let _ = success.unwrap_err();
520 }
521
522 #[test]
523 fn sub_msg_result_is_ok_works() {
524 let success = SubMsgResult::Ok(SubMsgResponse {
525 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
526 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
527 msg_responses: vec![],
528 });
529 let failure = SubMsgResult::Err("broken".to_string());
530 assert!(success.is_ok());
531 assert!(!failure.is_ok());
532 }
533
534 #[test]
535 fn sub_msg_result_is_err_works() {
536 let success = SubMsgResult::Ok(SubMsgResponse {
537 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
538 events: vec![Event::new("wasm").add_attribute("foo", "bar")],
539 msg_responses: vec![],
540 });
541 let failure = SubMsgResult::Err("broken".to_string());
542 assert!(failure.is_err());
543 assert!(!success.is_err());
544 }
545
546 #[test]
547 fn sub_msg_result_can_convert_from_core_result() {
548 let original: Result<SubMsgResponse, StdError> = Ok(SubMsgResponse {
549 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
550 events: vec![],
551 msg_responses: vec![],
552 });
553 let converted: SubMsgResult = original.into();
554 assert_eq!(
555 converted,
556 SubMsgResult::Ok(SubMsgResponse {
557 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
558 events: vec![],
559 msg_responses: vec![],
560 })
561 );
562
563 let original: Result<SubMsgResponse, StdError> = Err(StdError::msg("broken"));
564 let converted: SubMsgResult = original.into();
565 assert_eq!(
566 converted,
567 SubMsgResult::Err("kind: Other, error: broken".to_string())
568 );
569 }
570
571 #[test]
572 fn sub_msg_result_can_convert_to_core_result() {
573 let original = SubMsgResult::Ok(SubMsgResponse {
574 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
575 events: vec![],
576 msg_responses: vec![],
577 });
578 let converted: Result<SubMsgResponse, String> = original.into();
579 assert_eq!(
580 converted,
581 Ok(SubMsgResponse {
582 data: Some(Binary::from_base64("MTIzCg==").unwrap()),
583 events: vec![],
584 msg_responses: vec![],
585 })
586 );
587
588 let original = SubMsgResult::Err("went wrong".to_string());
589 let converted: Result<SubMsgResponse, String> = original.into();
590 assert_eq!(converted, Err("went wrong".to_string()));
591 }
592
593 #[test]
594 fn reply_deserialization_works() {
595 let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
597 assert_eq!(
598 reply,
599 Reply {
600 id: 75,
601 payload: Binary::default(),
602 gas_used: 4312324,
603 result: SubMsgResult::Ok(SubMsgResponse {
604 data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
605 events: vec![Event {
606 ty: "hi".to_string(),
607 attributes: vec![Attribute {
608 key: "si".to_string(),
609 value: "claro".to_string(),
610 }]
611 }],
612 msg_responses: vec![],
613 })
614 }
615 );
616
617 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();
619 assert_eq!(
620 reply,
621 Reply {
622 id: 75,
623 payload: Binary::from_base64("3NxjC5U=").unwrap(),
624 gas_used: 4312324,
625 result: SubMsgResult::Ok(SubMsgResponse {
626 data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
627 events: vec![Event {
628 ty: "hi".to_string(),
629 attributes: vec![Attribute {
630 key: "si".to_string(),
631 value: "claro".to_string(),
632 }]
633 }],
634 msg_responses: vec![],
635 })
636 }
637 );
638 }
639
640 #[test]
641 fn reply_serialization_cosmwasm_1() {
642 let json = r#"{"id":1234,"result":{"ok":{"events":[{"type":"message","attributes":[{"key":"signer","value":"caller-addr"}]}],"data":"Zm9vYmFy"}}}"#;
644
645 let reply: Reply = from_json(json).unwrap();
646 assert_eq!(reply.id, 1234);
647 assert_eq!(reply.payload, Binary::default());
648 assert_eq!(
649 reply.result,
650 SubMsgResult::Ok(SubMsgResponse {
651 data: Some(Binary::from_base64("Zm9vYmFy").unwrap()),
652 events: vec![Event {
653 ty: "message".to_string(),
654 attributes: vec![Attribute {
655 key: "signer".to_string(),
656 value: "caller-addr".to_string()
657 }]
658 }],
659 msg_responses: vec![]
660 })
661 );
662 assert_eq!(reply.gas_used, 0);
663 }
664}