1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::prelude::*;
5use crate::Binary;
6
7use super::{CosmosMsg, Empty, Event};
8
9#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
13#[serde(rename_all = "snake_case")]
14pub enum ReplyOn {
15 Always,
17 Error,
19 Success,
21 Never,
23}
24
25#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
32pub struct SubMsg<T = Empty> {
33 pub id: u64,
36 #[serde(default)]
49 pub payload: Binary,
50 pub msg: CosmosMsg<T>,
51 pub gas_limit: Option<u64>,
56 pub reply_on: ReplyOn,
57}
58
59pub const UNUSED_MSG_ID: u64 = 0;
61
62impl<T> SubMsg<T> {
63 pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
71 Self::reply_never(msg)
72 }
73
74 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 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 pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
95 Self::reply_on(msg.into(), id, ReplyOn::Always)
96 }
97
98 pub fn reply_never(msg: impl Into<CosmosMsg<T>>) -> Self {
103 Self::reply_on(msg.into(), UNUSED_MSG_ID, ReplyOn::Never)
104 }
105
106 pub fn with_gas_limit(mut self, limit: u64) -> Self {
120 self.gas_limit = Some(limit);
121 self
122 }
123
124 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 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
170pub struct Reply {
171 pub id: u64,
174 #[serde(default)]
181 pub payload: Binary,
182 #[serde(default)]
188 pub gas_used: u64,
189 pub result: SubMsgResult,
190}
191
192#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
230#[serde(rename_all = "snake_case")]
231pub enum SubMsgResult {
232 Ok(SubMsgResponse),
233 #[serde(rename = "error")]
236 Err(String),
237}
238
239impl SubMsgResult {
242 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
285pub struct SubMsgResponse {
286 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 #[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 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 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 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 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 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 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 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 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}