1#![allow(deprecated)]
2
3use core::fmt;
4use derive_more::Debug;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::coin::Coin;
9#[cfg(feature = "stargate")]
10use crate::ibc::IbcMsg;
11use crate::prelude::*;
12#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
13use crate::Decimal;
14use crate::StdResult;
15use crate::{to_json_binary, Binary};
16
17use super::Empty;
18
19pub trait CustomMsg: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema {}
22
23impl CustomMsg for Empty {}
24
25#[non_exhaustive]
26#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
27#[serde(rename_all = "snake_case")]
28pub enum CosmosMsg<T = Empty> {
30 Bank(BankMsg),
31 Custom(T),
34 #[cfg(feature = "staking")]
35 Staking(StakingMsg),
36 #[cfg(feature = "staking")]
37 Distribution(DistributionMsg),
38 #[cfg(feature = "stargate")]
40 #[deprecated = "Use `CosmosMsg::Any` instead (if you only target CosmWasm 2+ chains)"]
41 Stargate {
42 type_url: String,
43 value: Binary,
44 },
45 #[cfg(feature = "cosmwasm_2_0")]
53 Any(AnyMsg),
54 #[cfg(feature = "stargate")]
55 Ibc(IbcMsg),
56 Wasm(WasmMsg),
57 #[cfg(feature = "stargate")]
58 Gov(GovMsg),
59}
60
61impl<T> CosmosMsg<T> {
62 pub fn change_custom<U>(self) -> Option<CosmosMsg<U>> {
67 Some(match self {
68 CosmosMsg::Bank(msg) => CosmosMsg::Bank(msg),
69 CosmosMsg::Custom(_) => return None,
70 #[cfg(feature = "staking")]
71 CosmosMsg::Staking(msg) => CosmosMsg::Staking(msg),
72 #[cfg(feature = "staking")]
73 CosmosMsg::Distribution(msg) => CosmosMsg::Distribution(msg),
74 #[cfg(feature = "stargate")]
75 CosmosMsg::Stargate { type_url, value } => CosmosMsg::Stargate { type_url, value },
76 #[cfg(feature = "cosmwasm_2_0")]
77 CosmosMsg::Any(msg) => CosmosMsg::Any(msg),
78 #[cfg(feature = "stargate")]
79 CosmosMsg::Ibc(msg) => CosmosMsg::Ibc(msg),
80 CosmosMsg::Wasm(msg) => CosmosMsg::Wasm(msg),
81 #[cfg(feature = "stargate")]
82 CosmosMsg::Gov(msg) => CosmosMsg::Gov(msg),
83 })
84 }
85}
86
87#[non_exhaustive]
91#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
92#[serde(rename_all = "snake_case")]
93pub enum BankMsg {
94 Send {
99 to_address: String,
100 amount: Vec<Coin>,
101 },
102 Burn { amount: Vec<Coin> },
106}
107
108#[cfg(feature = "staking")]
112#[non_exhaustive]
113#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
114#[serde(rename_all = "snake_case")]
115pub enum StakingMsg {
116 Delegate { validator: String, amount: Coin },
119 Undelegate { validator: String, amount: Coin },
122 Redelegate {
125 src_validator: String,
126 dst_validator: String,
127 amount: Coin,
128 },
129}
130
131#[cfg(feature = "staking")]
135#[non_exhaustive]
136#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
137#[serde(rename_all = "snake_case")]
138pub enum DistributionMsg {
139 SetWithdrawAddress {
142 address: String,
144 },
145 WithdrawDelegatorReward {
148 validator: String,
150 },
151 #[cfg(feature = "cosmwasm_1_3")]
154 FundCommunityPool {
155 amount: Vec<Coin>,
157 },
158}
159
160#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
163pub struct AnyMsg {
164 pub type_url: String,
165 pub value: Binary,
166}
167
168#[allow(dead_code)]
169struct BinaryToStringEncoder<'a>(&'a Binary);
170
171impl fmt::Display for BinaryToStringEncoder<'_> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match core::str::from_utf8(self.0.as_slice()) {
174 Ok(s) => f.write_str(s),
175 Err(_) => fmt::Debug::fmt(self.0, f),
176 }
177 }
178}
179
180#[non_exhaustive]
184#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
185#[serde(rename_all = "snake_case")]
186pub enum WasmMsg {
187 Execute {
192 contract_addr: String,
193 #[debug("{}", BinaryToStringEncoder(msg))]
195 msg: Binary,
196 funds: Vec<Coin>,
197 },
198 Instantiate {
208 admin: Option<String>,
209 code_id: u64,
210 #[debug("{}", BinaryToStringEncoder(msg))]
212 msg: Binary,
213 funds: Vec<Coin>,
214 label: String,
221 },
222 #[cfg(feature = "cosmwasm_1_2")]
230 Instantiate2 {
231 admin: Option<String>,
232 code_id: u64,
233 label: String,
240 #[debug("{}", BinaryToStringEncoder(msg))]
242 msg: Binary,
243 funds: Vec<Coin>,
244 salt: Binary,
245 },
246 Migrate {
254 contract_addr: String,
255 new_code_id: u64,
257 #[debug("{}", BinaryToStringEncoder(msg))]
259 msg: Binary,
260 },
261 UpdateAdmin {
264 contract_addr: String,
265 admin: String,
266 },
267 ClearAdmin { contract_addr: String },
270}
271
272#[cfg(feature = "stargate")]
342#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
343#[serde(rename_all = "snake_case")]
344pub enum GovMsg {
345 Vote {
347 proposal_id: u64,
348 option: VoteOption,
352 },
353 #[cfg(feature = "cosmwasm_1_2")]
355 VoteWeighted {
356 proposal_id: u64,
357 options: Vec<WeightedVoteOption>,
358 },
359}
360
361#[cfg(feature = "stargate")]
362#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
363#[serde(rename_all = "snake_case")]
364pub enum VoteOption {
365 Yes,
366 No,
367 Abstain,
368 NoWithVeto,
369}
370
371#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
372#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
373pub struct WeightedVoteOption {
374 pub option: VoteOption,
375 pub weight: Decimal,
376}
377
378pub fn wasm_instantiate(
382 code_id: u64,
383 msg: &impl Serialize,
384 funds: Vec<Coin>,
385 label: String,
386) -> StdResult<WasmMsg> {
387 let payload = to_json_binary(msg)?;
388 Ok(WasmMsg::Instantiate {
389 admin: None,
390 code_id,
391 msg: payload,
392 funds,
393 label,
394 })
395}
396
397pub fn wasm_execute(
399 contract_addr: impl Into<String>,
400 msg: &impl Serialize,
401 funds: Vec<Coin>,
402) -> StdResult<WasmMsg> {
403 let payload = to_json_binary(msg)?;
404 Ok(WasmMsg::Execute {
405 contract_addr: contract_addr.into(),
406 msg: payload,
407 funds,
408 })
409}
410
411impl<T> From<BankMsg> for CosmosMsg<T> {
412 fn from(msg: BankMsg) -> Self {
413 CosmosMsg::Bank(msg)
414 }
415}
416
417#[cfg(feature = "staking")]
418impl<T> From<StakingMsg> for CosmosMsg<T> {
419 fn from(msg: StakingMsg) -> Self {
420 CosmosMsg::Staking(msg)
421 }
422}
423
424#[cfg(feature = "staking")]
425impl<T> From<DistributionMsg> for CosmosMsg<T> {
426 fn from(msg: DistributionMsg) -> Self {
427 CosmosMsg::Distribution(msg)
428 }
429}
430
431#[cfg(feature = "cosmwasm_2_0")]
434impl<S: Into<AnyMsg>, T> From<S> for CosmosMsg<T> {
435 fn from(source: S) -> Self {
436 CosmosMsg::<T>::Any(source.into())
437 }
438}
439
440impl<T> From<WasmMsg> for CosmosMsg<T> {
441 fn from(msg: WasmMsg) -> Self {
442 CosmosMsg::Wasm(msg)
443 }
444}
445
446#[cfg(feature = "stargate")]
447impl<T> From<IbcMsg> for CosmosMsg<T> {
448 fn from(msg: IbcMsg) -> Self {
449 CosmosMsg::Ibc(msg)
450 }
451}
452
453#[cfg(feature = "stargate")]
454impl<T> From<GovMsg> for CosmosMsg<T> {
455 fn from(msg: GovMsg) -> Self {
456 CosmosMsg::Gov(msg)
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use crate::{coin, coins};
464 use fmt::Debug;
465
466 #[test]
467 fn from_bank_msg_works() {
468 let to_address = String::from("you");
469 let amount = coins(1015, "earth");
470 let bank = BankMsg::Send { to_address, amount };
471 let msg: CosmosMsg = bank.clone().into();
472 match msg {
473 CosmosMsg::Bank(msg) => assert_eq!(bank, msg),
474 _ => panic!("must encode in Bank variant"),
475 }
476 }
477
478 #[test]
479 #[cfg(feature = "cosmwasm_2_0")]
480 fn from_any_msg_works() {
481 let any = AnyMsg {
483 type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
484 value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
485 };
486 let msg: CosmosMsg = any.clone().into();
487 assert!(matches!(msg, CosmosMsg::Any(a) if a == any));
488
489 struct IntoAny;
491
492 impl From<IntoAny> for AnyMsg {
493 fn from(_: IntoAny) -> Self {
494 AnyMsg {
495 type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
496 value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
497 }
498 }
499 }
500
501 let msg: CosmosMsg = IntoAny.into();
502 assert!(matches!(
503 msg,
504 CosmosMsg::Any(a) if a == any
505 ));
506 }
507
508 #[test]
509 fn wasm_msg_serializes_to_correct_json() {
510 let msg = WasmMsg::Instantiate {
512 admin: Some("king".to_string()),
513 code_id: 7897,
514 msg: br#"{"claim":{}}"#.into(),
515 funds: vec![],
516 label: "my instance".to_string(),
517 };
518 let json = to_json_binary(&msg).unwrap();
519 assert_eq!(
520 String::from_utf8_lossy(&json),
521 r#"{"instantiate":{"admin":"king","code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
522 );
523
524 let msg = WasmMsg::Instantiate {
526 admin: None,
527 code_id: 7897,
528 msg: br#"{"claim":{}}"#.into(),
529 funds: vec![],
530 label: "my instance".to_string(),
531 };
532 let json = to_json_binary(&msg).unwrap();
533 assert_eq!(
534 String::from_utf8_lossy(&json),
535 r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
536 );
537
538 let msg = WasmMsg::Instantiate {
540 admin: None,
541 code_id: 7897,
542 msg: br#"{"claim":{}}"#.into(),
543 funds: vec![coin(321, "stones")],
544 label: "my instance".to_string(),
545 };
546 let json = to_json_binary(&msg).unwrap();
547 assert_eq!(
548 String::from_utf8_lossy(&json),
549 r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"label":"my instance"}}"#,
550 );
551
552 #[cfg(feature = "cosmwasm_1_2")]
554 {
555 let msg = WasmMsg::Instantiate2 {
556 admin: None,
557 code_id: 7897,
558 label: "my instance".to_string(),
559 msg: br#"{"claim":{}}"#.into(),
560 funds: vec![coin(321, "stones")],
561 salt: Binary::from_base64("UkOVazhiwoo=").unwrap(),
562 };
563 let json = to_json_binary(&msg).unwrap();
564 assert_eq!(
565 String::from_utf8_lossy(&json),
566 r#"{"instantiate2":{"admin":null,"code_id":7897,"label":"my instance","msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"salt":"UkOVazhiwoo="}}"#,
567 );
568 }
569 }
570
571 #[test]
572 #[cfg(feature = "cosmwasm_2_0")]
573 fn any_msg_serializes_to_correct_json() {
574 let msg: CosmosMsg = CosmosMsg::Any(AnyMsg {
576 type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
577 value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
578 });
579 let json = crate::to_json_string(&msg).unwrap();
580 assert_eq!(
581 json,
582 r#"{"any":{"type_url":"/cosmos.foo.v1beta.MsgBar","value":"5yu/rQ+HrMcxH1zdga7P5hpGMLE="}}"#,
583 );
584 }
585
586 #[test]
587 #[cfg(feature = "cosmwasm_1_3")]
588 fn msg_distribution_serializes_to_correct_json() {
589 let fund_coins = vec![coin(200, "feathers"), coin(200, "stones")];
591 let fund_msg = DistributionMsg::FundCommunityPool { amount: fund_coins };
592 let fund_json = to_json_binary(&fund_msg).unwrap();
593 assert_eq!(
594 String::from_utf8_lossy(&fund_json),
595 r#"{"fund_community_pool":{"amount":[{"denom":"feathers","amount":"200"},{"denom":"stones","amount":"200"}]}}"#,
596 );
597
598 let set_msg = DistributionMsg::SetWithdrawAddress {
600 address: String::from("withdrawer"),
601 };
602 let set_json = to_json_binary(&set_msg).unwrap();
603 assert_eq!(
604 String::from_utf8_lossy(&set_json),
605 r#"{"set_withdraw_address":{"address":"withdrawer"}}"#,
606 );
607
608 let withdraw_msg = DistributionMsg::WithdrawDelegatorReward {
610 validator: String::from("fancyoperator"),
611 };
612 let withdraw_json = to_json_binary(&withdraw_msg).unwrap();
613 assert_eq!(
614 String::from_utf8_lossy(&withdraw_json),
615 r#"{"withdraw_delegator_reward":{"validator":"fancyoperator"}}"#
616 );
617 }
618
619 #[test]
620 fn wasm_msg_debug_decodes_binary_string_when_possible() {
621 #[cosmwasm_schema::cw_serde]
622 enum ExecuteMsg {
623 Mint { coin: Coin },
624 }
625
626 let msg = WasmMsg::Execute {
627 contract_addr: "joe".to_string(),
628 msg: to_json_binary(&ExecuteMsg::Mint {
629 coin: coin(10, "BTC"),
630 })
631 .unwrap(),
632 funds: vec![],
633 };
634
635 assert_eq!(
636 format!("{msg:?}"),
637 "Execute { contract_addr: \"joe\", msg: {\"mint\":{\"coin\":{\"denom\":\"BTC\",\"amount\":\"10\"}}}, funds: [] }"
638 );
639 }
640
641 #[test]
642 fn wasm_msg_debug_dumps_binary_when_not_utf8() {
643 let msg = WasmMsg::Execute {
644 contract_addr: "joe".to_string(),
645 msg: Binary::from([0, 159, 146, 150]),
646 funds: vec![],
647 };
648
649 assert_eq!(
650 format!("{msg:?}"),
651 "Execute { contract_addr: \"joe\", msg: Binary(009f9296), funds: [] }"
652 );
653 }
654
655 #[test]
656 #[cfg(feature = "stargate")]
657 fn gov_msg_serializes_to_correct_json() {
658 let msg = GovMsg::Vote {
660 proposal_id: 4,
661 option: VoteOption::NoWithVeto,
662 };
663 let json = to_json_binary(&msg).unwrap();
664 assert_eq!(
665 String::from_utf8_lossy(&json),
666 r#"{"vote":{"proposal_id":4,"option":"no_with_veto"}}"#,
667 );
668
669 #[cfg(feature = "cosmwasm_1_2")]
671 {
672 let msg = GovMsg::VoteWeighted {
673 proposal_id: 25,
674 options: vec![
675 WeightedVoteOption {
676 weight: Decimal::percent(25),
677 option: VoteOption::Yes,
678 },
679 WeightedVoteOption {
680 weight: Decimal::percent(25),
681 option: VoteOption::No,
682 },
683 WeightedVoteOption {
684 weight: Decimal::percent(50),
685 option: VoteOption::Abstain,
686 },
687 ],
688 };
689
690 let json = to_json_binary(&msg).unwrap();
691 assert_eq!(
692 String::from_utf8_lossy(&json),
693 r#"{"vote_weighted":{"proposal_id":25,"options":[{"option":"yes","weight":"0.25"},{"option":"no","weight":"0.25"},{"option":"abstain","weight":"0.5"}]}}"#,
694 );
695 }
696 }
697
698 #[test]
699 fn change_custom_works() {
700 #[derive(Debug, PartialEq, Eq, Clone)]
701 struct Custom {
702 _a: i32,
703 }
704 let send = BankMsg::Send {
705 to_address: "you".to_string(),
706 amount: coins(1015, "earth"),
707 };
708 let msg: CosmosMsg<Custom> = send.clone().into();
710 let msg2: CosmosMsg<Empty> = msg.change_custom().unwrap();
711 assert_eq!(msg2, CosmosMsg::Bank(send.clone()));
712 let custom = CosmosMsg::Custom(Custom { _a: 5 });
713 let converted = custom.change_custom::<Empty>();
714 assert_eq!(converted, None);
715
716 let msg: CosmosMsg<Empty> = send.clone().into();
718 let msg2: CosmosMsg<Custom> = msg.change_custom().unwrap();
719 assert_eq!(msg2, CosmosMsg::Bank(send));
720 let empty = CosmosMsg::Custom(Empty {});
721 let converted = empty.change_custom::<Custom>();
722 assert_eq!(converted, None);
723 }
724}