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)]
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 #[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 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 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 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 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 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 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 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 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}