cosmwasm_std/results/
cosmos_msg.rs

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
19/// Like CustomQuery for better type clarity.
20/// Also makes it shorter to use as a trait bound.
21pub 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")]
28// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T
29pub enum CosmosMsg<T = Empty> {
30    Bank(BankMsg),
31    // by default we use RawMsg, but a contract can override that
32    // to call into more app-specific code (whatever they define)
33    Custom(T),
34    #[cfg(feature = "staking")]
35    Staking(StakingMsg),
36    #[cfg(feature = "staking")]
37    Distribution(DistributionMsg),
38    /// This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)
39    #[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    /// `CosmosMsg::Any` is the replaces the "stargate message" – a message wrapped
46    /// in a [protobuf Any](https://protobuf.dev/programming-guides/proto3/#any)
47    /// that is suppored by the chain. It behaves the same as
48    /// `CosmosMsg::Stargate` but has a better name and slightly improved syntax.
49    ///
50    /// This is feature-gated at compile time with `cosmwasm_2_0` because
51    /// a chain running CosmWasm < 2.0 cannot process this.
52    #[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    /// Convert this this [`CosmosMsg<T>`] to a [`CosmosMsg<U>`] with a different custom message type.
63    /// This allows easier interactions between code written for a specific chain and
64    /// code written for multiple chains.
65    /// If this is the [`CosmosMsg::Custom`] variant, the function returns `None`.
66    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/// The message types of the bank module.
88///
89/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto
90#[non_exhaustive]
91#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
92#[serde(rename_all = "snake_case")]
93pub enum BankMsg {
94    /// Sends native tokens from the contract to the given address.
95    ///
96    /// This is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28).
97    /// `from_address` is automatically filled with the current contract's address.
98    Send {
99        to_address: String,
100        amount: Vec<Coin>,
101    },
102    /// This will burn the given coins from the contract's account.
103    /// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper.
104    /// Important if a contract controls significant token supply that must be retired.
105    Burn { amount: Vec<Coin> },
106}
107
108/// The message types of the staking module.
109///
110/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto
111#[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    /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90).
117    /// `delegator_address` is automatically filled with the current contract's address.
118    Delegate { validator: String, amount: Coin },
119    /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121).
120    /// `delegator_address` is automatically filled with the current contract's address.
121    Undelegate { validator: String, amount: Coin },
122    /// This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105).
123    /// `delegator_address` is automatically filled with the current contract's address.
124    Redelegate {
125        src_validator: String,
126        dst_validator: String,
127        amount: Coin,
128    },
129}
130
131/// The message types of the distribution module.
132///
133/// See https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto
134#[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    /// This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37).
140    /// `delegator_address` is automatically filled with the current contract's address.
141    SetWithdrawAddress {
142        /// The `withdraw_address`
143        address: String,
144    },
145    /// This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50).
146    /// `delegator_address` is automatically filled with the current contract's address.
147    WithdrawDelegatorReward {
148        /// The `validator_address`
149        validator: String,
150    },
151    /// This is translated to a [[MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2).
152    /// `depositor` is automatically filled with the current contract's address.
153    #[cfg(feature = "cosmwasm_1_3")]
154    FundCommunityPool {
155        /// The amount to spend
156        amount: Vec<Coin>,
157    },
158}
159
160/// A message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto).
161/// This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)
162#[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/// The message types of the wasm module.
181///
182/// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto
183#[non_exhaustive]
184#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
185#[serde(rename_all = "snake_case")]
186pub enum WasmMsg {
187    /// Dispatches a call to another contract at a known address (with known ABI).
188    ///
189    /// This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78).
190    /// `sender` is automatically filled with the current contract's address.
191    Execute {
192        contract_addr: String,
193        /// msg is the json-encoded ExecuteMsg struct (as raw Binary)
194        #[debug("{}", BinaryToStringEncoder(msg))]
195        msg: Binary,
196        funds: Vec<Coin>,
197    },
198    /// Instantiates a new contracts from previously uploaded Wasm code.
199    ///
200    /// The contract address is non-predictable. But it is guaranteed that
201    /// when emitting the same Instantiate message multiple times,
202    /// multiple instances on different addresses will be generated. See also
203    /// Instantiate2.
204    ///
205    /// This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71).
206    /// `sender` is automatically filled with the current contract's address.
207    Instantiate {
208        admin: Option<String>,
209        code_id: u64,
210        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
211        #[debug("{}", BinaryToStringEncoder(msg))]
212        msg: Binary,
213        funds: Vec<Coin>,
214        /// A human-readable label for the contract.
215        ///
216        /// Valid values should:
217        /// - not be empty
218        /// - not be bigger than 128 bytes (or some chain-specific limit)
219        /// - not start / end with whitespace
220        label: String,
221    },
222    /// Instantiates a new contracts from previously uploaded Wasm code
223    /// using a predictable address derivation algorithm implemented in
224    /// [`cosmwasm_std::instantiate2_address`].
225    ///
226    /// This is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96).
227    /// `sender` is automatically filled with the current contract's address.
228    /// `fix_msg` is automatically set to false.
229    #[cfg(feature = "cosmwasm_1_2")]
230    Instantiate2 {
231        admin: Option<String>,
232        code_id: u64,
233        /// A human-readable label for the contract.
234        ///
235        /// Valid values should:
236        /// - not be empty
237        /// - not be bigger than 128 bytes (or some chain-specific limit)
238        /// - not start / end with whitespace
239        label: String,
240        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
241        #[debug("{}", BinaryToStringEncoder(msg))]
242        msg: Binary,
243        funds: Vec<Coin>,
244        salt: Binary,
245    },
246    /// Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to
247    /// customize behavior.
248    ///
249    /// Only the contract admin (as defined in wasmd), if any, is able to make this call.
250    ///
251    /// This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96).
252    /// `sender` is automatically filled with the current contract's address.
253    Migrate {
254        contract_addr: String,
255        /// the code_id of the new logic to place in the given contract
256        new_code_id: u64,
257        /// msg is the json-encoded MigrateMsg struct that will be passed to the new code
258        #[debug("{}", BinaryToStringEncoder(msg))]
259        msg: Binary,
260    },
261    /// Sets a new admin (for migrate) on the given contract.
262    /// Fails if this contract is not currently admin of the target contract.
263    UpdateAdmin {
264        contract_addr: String,
265        admin: String,
266    },
267    /// Clears the admin on the given contract, so no more migration possible.
268    /// Fails if this contract is not currently admin of the target contract.
269    ClearAdmin { contract_addr: String },
270}
271
272/// This message type allows the contract interact with the [x/gov] module in order
273/// to cast votes.
274///
275/// [x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov
276///
277/// ## Examples
278///
279/// Cast a simple vote:
280///
281/// ```
282/// # use cosmwasm_std::{
283/// #     HexBinary,
284/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
285/// #     Response, QueryResponse,
286/// # };
287/// # type ExecuteMsg = ();
288/// use cosmwasm_std::{GovMsg, VoteOption};
289///
290/// #[entry_point]
291/// pub fn execute(
292///     deps: DepsMut,
293///     env: Env,
294///     info: MessageInfo,
295///     msg: ExecuteMsg,
296/// ) -> Result<Response, StdError> {
297///     // ...
298///     Ok(Response::new().add_message(GovMsg::Vote {
299///         proposal_id: 4,
300///         option: VoteOption::Yes,
301///     }))
302/// }
303/// ```
304///
305/// Cast a weighted vote:
306///
307/// ```
308/// # use cosmwasm_std::{
309/// #     HexBinary,
310/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
311/// #     Response, QueryResponse,
312/// # };
313/// # type ExecuteMsg = ();
314/// # #[cfg(feature = "cosmwasm_1_2")]
315/// use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};
316///
317/// # #[cfg(feature = "cosmwasm_1_2")]
318/// #[entry_point]
319/// pub fn execute(
320///     deps: DepsMut,
321///     env: Env,
322///     info: MessageInfo,
323///     msg: ExecuteMsg,
324/// ) -> Result<Response, StdError> {
325///     // ...
326///     Ok(Response::new().add_message(GovMsg::VoteWeighted {
327///         proposal_id: 4,
328///         options: vec![
329///             WeightedVoteOption {
330///                 option: VoteOption::Yes,
331///                 weight: Decimal::percent(65),
332///             },
333///             WeightedVoteOption {
334///                 option: VoteOption::Abstain,
335///                 weight: Decimal::percent(35),
336///             },
337///         ],
338///     }))
339/// }
340/// ```
341#[cfg(feature = "stargate")]
342#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
343#[serde(rename_all = "snake_case")]
344pub enum GovMsg {
345    /// This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.
346    Vote {
347        proposal_id: u64,
348        /// The vote option.
349        ///
350        /// This used to be called "vote", but was changed for consistency with Cosmos SDK.
351        option: VoteOption,
352    },
353    /// This maps directly to [MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/proto/cosmos/gov/v1beta1/tx.proto#L66-L78) in the Cosmos SDK with voter set to the contract address.
354    #[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
378/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code.
379///
380/// When using this, `admin` is always unset. If you need more flexibility, create the message directly.
381pub 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
397/// Shortcut helper as the construction of WasmMsg::Execute can be quite verbose in contract code
398pub 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// By implementing `From<MyType> for cosmwasm_std::AnyMsg`,
432// you automatically get a MyType -> CosmosMsg conversion.
433#[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        // should work with AnyMsg
482        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        // should work with Into<AnyMsg>
490        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        // Instantiate with admin
511        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        // Instantiate without admin
525        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        // Instantiate with funds
539        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        // Instantiate2
553        #[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        // Same serialization as CosmosMsg::Stargate (see above), except the top level key
575        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        // FundCommunityPool
590        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        // SetWithdrawAddress
599        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        // WithdrawDelegatorRewards
609        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        // Vote
659        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        // VoteWeighted
670        #[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        // Custom to Empty
709        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        // Empty to Custom
717        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}