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/// A trait for custom message types which are embedded in `CosmosMsg::Custom(..)`.
20/// Those are messages that the contract and the chain need
21/// to agree on in advance as the chain must be able to deserialize and execute them.
22///
23/// Custom messages are always JSON-encoded when sent from the contract to the environment.
24///
25/// This trait is similar to [`CustomQuery`](crate::CustomQuery) for better type clarity and
26/// makes it shorter to use as a trait bound. It does not require fields or functions to be implemented.
27///
28/// An alternative approach is using [`CosmosMsg::Any`][crate::CosmosMsg#variant.Any]
29/// which provides more flexibility but offers less type-safety.
30///
31/// ## Examples
32///
33/// Some real-world examples of such custom message types are
34/// [TgradeMsg](https://github.com/confio/poe-contracts/blob/v0.17.1/packages/bindings/src/msg.rs#L13),
35/// [ArchwayMsg](https://github.com/archway-network/arch3.rs/blob/bindings/v0.2.1/packages/bindings/src/msg.rs#L22) or
36/// [NeutronMsg](https://github.com/neutron-org/neutron-sdk/blob/v0.11.0/packages/neutron-sdk/src/bindings/msg.rs#L33).
37///
38/// ```
39/// use cosmwasm_schema::cw_serde;
40/// use cosmwasm_std::CustomQuery;
41///
42/// #[cw_serde]
43/// pub enum MyMsg {
44///    // ...
45/// }
46///
47/// impl CustomQuery for MyMsg {}
48/// ```
49pub trait CustomMsg: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema {}
50
51impl CustomMsg for Empty {}
52
53#[non_exhaustive]
54#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
55#[serde(rename_all = "snake_case")]
56// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T
57pub enum CosmosMsg<T = Empty> {
58    Bank(BankMsg),
59    // by default we use RawMsg, but a contract can override that
60    // to call into more app-specific code (whatever they define)
61    Custom(T),
62    #[cfg(feature = "staking")]
63    Staking(StakingMsg),
64    #[cfg(feature = "staking")]
65    Distribution(DistributionMsg),
66    /// 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)
67    #[cfg(feature = "stargate")]
68    #[deprecated = "Use `CosmosMsg::Any` instead (if you only target CosmWasm 2+ chains)"]
69    Stargate {
70        type_url: String,
71        value: Binary,
72    },
73    /// `CosmosMsg::Any` is the replaces the "stargate message" – a message wrapped
74    /// in a [protobuf Any](https://protobuf.dev/programming-guides/proto3/#any)
75    /// that is supported by the chain. It behaves the same as
76    /// `CosmosMsg::Stargate` but has a better name and slightly improved syntax.
77    ///
78    /// This is feature-gated at compile time with `cosmwasm_2_0` because
79    /// a chain running CosmWasm < 2.0 cannot process this.
80    #[cfg(feature = "cosmwasm_2_0")]
81    Any(AnyMsg),
82    #[cfg(feature = "stargate")]
83    Ibc(IbcMsg),
84    Wasm(WasmMsg),
85    #[cfg(feature = "stargate")]
86    Gov(GovMsg),
87}
88
89impl<T> CosmosMsg<T> {
90    /// Convert this this [`CosmosMsg<T>`] to a [`CosmosMsg<U>`] with a different custom message type.
91    /// This allows easier interactions between code written for a specific chain and
92    /// code written for multiple chains.
93    /// If this is the [`CosmosMsg::Custom`] variant, the function returns `None`.
94    pub fn change_custom<U>(self) -> Option<CosmosMsg<U>> {
95        Some(match self {
96            CosmosMsg::Bank(msg) => CosmosMsg::Bank(msg),
97            CosmosMsg::Custom(_) => return None,
98            #[cfg(feature = "staking")]
99            CosmosMsg::Staking(msg) => CosmosMsg::Staking(msg),
100            #[cfg(feature = "staking")]
101            CosmosMsg::Distribution(msg) => CosmosMsg::Distribution(msg),
102            #[cfg(feature = "stargate")]
103            CosmosMsg::Stargate { type_url, value } => CosmosMsg::Stargate { type_url, value },
104            #[cfg(feature = "cosmwasm_2_0")]
105            CosmosMsg::Any(msg) => CosmosMsg::Any(msg),
106            #[cfg(feature = "stargate")]
107            CosmosMsg::Ibc(msg) => CosmosMsg::Ibc(msg),
108            CosmosMsg::Wasm(msg) => CosmosMsg::Wasm(msg),
109            #[cfg(feature = "stargate")]
110            CosmosMsg::Gov(msg) => CosmosMsg::Gov(msg),
111        })
112    }
113}
114
115/// The message types of the bank module.
116///
117/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto
118#[non_exhaustive]
119#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
120#[serde(rename_all = "snake_case")]
121pub enum BankMsg {
122    /// Sends native tokens from the contract to the given address.
123    ///
124    /// This is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28).
125    /// `from_address` is automatically filled with the current contract's address.
126    Send {
127        to_address: String,
128        amount: Vec<Coin>,
129    },
130    /// This will burn the given coins from the contract's account.
131    /// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper.
132    /// Important if a contract controls significant token supply that must be retired.
133    Burn { amount: Vec<Coin> },
134}
135
136/// The message types of the staking module.
137///
138/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto
139#[cfg(feature = "staking")]
140#[non_exhaustive]
141#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
142#[serde(rename_all = "snake_case")]
143pub enum StakingMsg {
144    /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90).
145    /// `delegator_address` is automatically filled with the current contract's address.
146    Delegate { validator: String, amount: Coin },
147    /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121).
148    /// `delegator_address` is automatically filled with the current contract's address.
149    Undelegate { validator: String, amount: Coin },
150    /// This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105).
151    /// `delegator_address` is automatically filled with the current contract's address.
152    Redelegate {
153        src_validator: String,
154        dst_validator: String,
155        amount: Coin,
156    },
157}
158
159/// The message types of the distribution module.
160///
161/// See https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto
162#[cfg(feature = "staking")]
163#[non_exhaustive]
164#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
165#[serde(rename_all = "snake_case")]
166pub enum DistributionMsg {
167    /// This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37).
168    /// `delegator_address` is automatically filled with the current contract's address.
169    SetWithdrawAddress {
170        /// The `withdraw_address`
171        address: String,
172    },
173    /// This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50).
174    /// `delegator_address` is automatically filled with the current contract's address.
175    WithdrawDelegatorReward {
176        /// The `validator_address`
177        validator: String,
178    },
179    /// This is translated to a [[MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2).
180    /// `depositor` is automatically filled with the current contract's address.
181    #[cfg(feature = "cosmwasm_1_3")]
182    FundCommunityPool {
183        /// The amount to spend
184        amount: Vec<Coin>,
185    },
186}
187
188/// A message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto).
189/// 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)
190#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
191pub struct AnyMsg {
192    pub type_url: String,
193    pub value: Binary,
194}
195
196#[allow(dead_code)]
197struct BinaryToStringEncoder<'a>(&'a Binary);
198
199impl fmt::Display for BinaryToStringEncoder<'_> {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match core::str::from_utf8(self.0.as_slice()) {
202            Ok(s) => f.write_str(s),
203            Err(_) => fmt::Debug::fmt(self.0, f),
204        }
205    }
206}
207
208/// The message types of the wasm module.
209///
210/// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto
211#[non_exhaustive]
212#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
213#[serde(rename_all = "snake_case")]
214pub enum WasmMsg {
215    /// Dispatches a call to another contract at a known address (with known ABI).
216    ///
217    /// This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78).
218    /// `sender` is automatically filled with the current contract's address.
219    Execute {
220        contract_addr: String,
221        /// msg is the json-encoded ExecuteMsg struct (as raw Binary)
222        #[debug("{}", BinaryToStringEncoder(msg))]
223        msg: Binary,
224        funds: Vec<Coin>,
225    },
226    /// Instantiates a new contracts from previously uploaded Wasm code.
227    ///
228    /// The contract address is non-predictable. But it is guaranteed that
229    /// when emitting the same Instantiate message multiple times,
230    /// multiple instances on different addresses will be generated. See also
231    /// Instantiate2.
232    ///
233    /// This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71).
234    /// `sender` is automatically filled with the current contract's address.
235    Instantiate {
236        admin: Option<String>,
237        code_id: u64,
238        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
239        #[debug("{}", BinaryToStringEncoder(msg))]
240        msg: Binary,
241        funds: Vec<Coin>,
242        /// A human-readable label for the contract.
243        ///
244        /// Valid values should:
245        /// - not be empty
246        /// - not be bigger than 128 bytes (or some chain-specific limit)
247        /// - not start / end with whitespace
248        label: String,
249    },
250    /// Instantiates a new contracts from previously uploaded Wasm code
251    /// using a predictable address derivation algorithm implemented in
252    /// [`cosmwasm_std::instantiate2_address`].
253    ///
254    /// This is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96).
255    /// `sender` is automatically filled with the current contract's address.
256    /// `fix_msg` is automatically set to false.
257    #[cfg(feature = "cosmwasm_1_2")]
258    Instantiate2 {
259        admin: Option<String>,
260        code_id: u64,
261        /// A human-readable label for the contract.
262        ///
263        /// Valid values should:
264        /// - not be empty
265        /// - not be bigger than 128 bytes (or some chain-specific limit)
266        /// - not start / end with whitespace
267        label: String,
268        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
269        #[debug("{}", BinaryToStringEncoder(msg))]
270        msg: Binary,
271        funds: Vec<Coin>,
272        salt: Binary,
273    },
274    /// Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to
275    /// customize behavior.
276    ///
277    /// Only the contract admin (as defined in wasmd), if any, is able to make this call.
278    ///
279    /// This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96).
280    /// `sender` is automatically filled with the current contract's address.
281    Migrate {
282        contract_addr: String,
283        /// the code_id of the new logic to place in the given contract
284        new_code_id: u64,
285        /// msg is the json-encoded MigrateMsg struct that will be passed to the new code
286        #[debug("{}", BinaryToStringEncoder(msg))]
287        msg: Binary,
288    },
289    /// Sets a new admin (for migrate) on the given contract.
290    /// Fails if this contract is not currently admin of the target contract.
291    UpdateAdmin {
292        contract_addr: String,
293        admin: String,
294    },
295    /// Clears the admin on the given contract, so no more migration possible.
296    /// Fails if this contract is not currently admin of the target contract.
297    ClearAdmin { contract_addr: String },
298}
299
300/// This message type allows the contract interact with the [x/gov] module in order
301/// to cast votes.
302///
303/// [x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov
304///
305/// ## Examples
306///
307/// Cast a simple vote:
308///
309/// ```
310/// # use cosmwasm_std::{
311/// #     HexBinary,
312/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
313/// #     Response, QueryResponse,
314/// # };
315/// # type ExecuteMsg = ();
316/// use cosmwasm_std::{GovMsg, VoteOption};
317///
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::Vote {
327///         proposal_id: 4,
328///         option: VoteOption::Yes,
329///     }))
330/// }
331/// ```
332///
333/// Cast a weighted vote:
334///
335/// ```
336/// # use cosmwasm_std::{
337/// #     HexBinary,
338/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
339/// #     Response, QueryResponse,
340/// # };
341/// # type ExecuteMsg = ();
342/// # #[cfg(feature = "cosmwasm_1_2")]
343/// use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};
344///
345/// # #[cfg(feature = "cosmwasm_1_2")]
346/// #[entry_point]
347/// pub fn execute(
348///     deps: DepsMut,
349///     env: Env,
350///     info: MessageInfo,
351///     msg: ExecuteMsg,
352/// ) -> Result<Response, StdError> {
353///     // ...
354///     Ok(Response::new().add_message(GovMsg::VoteWeighted {
355///         proposal_id: 4,
356///         options: vec![
357///             WeightedVoteOption {
358///                 option: VoteOption::Yes,
359///                 weight: Decimal::percent(65),
360///             },
361///             WeightedVoteOption {
362///                 option: VoteOption::Abstain,
363///                 weight: Decimal::percent(35),
364///             },
365///         ],
366///     }))
367/// }
368/// ```
369#[cfg(feature = "stargate")]
370#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
371#[serde(rename_all = "snake_case")]
372pub enum GovMsg {
373    /// 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.
374    Vote {
375        proposal_id: u64,
376        /// The vote option.
377        ///
378        /// This used to be called "vote", but was changed for consistency with Cosmos SDK.
379        option: VoteOption,
380    },
381    /// 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.
382    #[cfg(feature = "cosmwasm_1_2")]
383    VoteWeighted {
384        proposal_id: u64,
385        options: Vec<WeightedVoteOption>,
386    },
387}
388
389#[cfg(feature = "stargate")]
390#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
391#[serde(rename_all = "snake_case")]
392pub enum VoteOption {
393    Yes,
394    No,
395    Abstain,
396    NoWithVeto,
397}
398
399#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
400#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
401pub struct WeightedVoteOption {
402    pub option: VoteOption,
403    pub weight: Decimal,
404}
405
406/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code.
407///
408/// When using this, `admin` is always unset. If you need more flexibility, create the message directly.
409pub fn wasm_instantiate(
410    code_id: u64,
411    msg: &impl Serialize,
412    funds: Vec<Coin>,
413    label: String,
414) -> StdResult<WasmMsg> {
415    let payload = to_json_binary(msg)?;
416    Ok(WasmMsg::Instantiate {
417        admin: None,
418        code_id,
419        msg: payload,
420        funds,
421        label,
422    })
423}
424
425/// Shortcut helper as the construction of WasmMsg::Execute can be quite verbose in contract code
426pub fn wasm_execute(
427    contract_addr: impl Into<String>,
428    msg: &impl Serialize,
429    funds: Vec<Coin>,
430) -> StdResult<WasmMsg> {
431    let payload = to_json_binary(msg)?;
432    Ok(WasmMsg::Execute {
433        contract_addr: contract_addr.into(),
434        msg: payload,
435        funds,
436    })
437}
438
439impl<T> From<BankMsg> for CosmosMsg<T> {
440    fn from(msg: BankMsg) -> Self {
441        CosmosMsg::Bank(msg)
442    }
443}
444
445#[cfg(feature = "staking")]
446impl<T> From<StakingMsg> for CosmosMsg<T> {
447    fn from(msg: StakingMsg) -> Self {
448        CosmosMsg::Staking(msg)
449    }
450}
451
452#[cfg(feature = "staking")]
453impl<T> From<DistributionMsg> for CosmosMsg<T> {
454    fn from(msg: DistributionMsg) -> Self {
455        CosmosMsg::Distribution(msg)
456    }
457}
458
459// By implementing `From<MyType> for cosmwasm_std::AnyMsg`,
460// you automatically get a MyType -> CosmosMsg conversion.
461#[cfg(feature = "cosmwasm_2_0")]
462impl<S: Into<AnyMsg>, T> From<S> for CosmosMsg<T> {
463    fn from(source: S) -> Self {
464        CosmosMsg::<T>::Any(source.into())
465    }
466}
467
468impl<T> From<WasmMsg> for CosmosMsg<T> {
469    fn from(msg: WasmMsg) -> Self {
470        CosmosMsg::Wasm(msg)
471    }
472}
473
474#[cfg(feature = "stargate")]
475impl<T> From<IbcMsg> for CosmosMsg<T> {
476    fn from(msg: IbcMsg) -> Self {
477        CosmosMsg::Ibc(msg)
478    }
479}
480
481#[cfg(feature = "stargate")]
482impl<T> From<GovMsg> for CosmosMsg<T> {
483    fn from(msg: GovMsg) -> Self {
484        CosmosMsg::Gov(msg)
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491    use crate::{coin, coins};
492    use fmt::Debug;
493
494    #[test]
495    fn from_bank_msg_works() {
496        let to_address = String::from("you");
497        let amount = coins(1015, "earth");
498        let bank = BankMsg::Send { to_address, amount };
499        let msg: CosmosMsg = bank.clone().into();
500        match msg {
501            CosmosMsg::Bank(msg) => assert_eq!(bank, msg),
502            _ => panic!("must encode in Bank variant"),
503        }
504    }
505
506    #[test]
507    #[cfg(feature = "cosmwasm_2_0")]
508    fn from_any_msg_works() {
509        // should work with AnyMsg
510        let any = AnyMsg {
511            type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
512            value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
513        };
514        let msg: CosmosMsg = any.clone().into();
515        assert!(matches!(msg, CosmosMsg::Any(a) if a == any));
516
517        // should work with Into<AnyMsg>
518        struct IntoAny;
519
520        impl From<IntoAny> for AnyMsg {
521            fn from(_: IntoAny) -> Self {
522                AnyMsg {
523                    type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
524                    value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
525                }
526            }
527        }
528
529        let msg: CosmosMsg = IntoAny.into();
530        assert!(matches!(
531            msg,
532            CosmosMsg::Any(a) if a == any
533        ));
534    }
535
536    #[test]
537    fn wasm_msg_serializes_to_correct_json() {
538        // Instantiate with admin
539        let msg = WasmMsg::Instantiate {
540            admin: Some("king".to_string()),
541            code_id: 7897,
542            msg: br#"{"claim":{}}"#.into(),
543            funds: vec![],
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":"king","code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
550        );
551
552        // Instantiate without admin
553        let msg = WasmMsg::Instantiate {
554            admin: None,
555            code_id: 7897,
556            msg: br#"{"claim":{}}"#.into(),
557            funds: vec![],
558            label: "my instance".to_string(),
559        };
560        let json = to_json_binary(&msg).unwrap();
561        assert_eq!(
562            String::from_utf8_lossy(&json),
563            r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
564        );
565
566        // Instantiate with funds
567        let msg = WasmMsg::Instantiate {
568            admin: None,
569            code_id: 7897,
570            msg: br#"{"claim":{}}"#.into(),
571            funds: vec![coin(321, "stones")],
572            label: "my instance".to_string(),
573        };
574        let json = to_json_binary(&msg).unwrap();
575        assert_eq!(
576            String::from_utf8_lossy(&json),
577            r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"label":"my instance"}}"#,
578        );
579
580        // Instantiate2
581        #[cfg(feature = "cosmwasm_1_2")]
582        {
583            let msg = WasmMsg::Instantiate2 {
584                admin: None,
585                code_id: 7897,
586                label: "my instance".to_string(),
587                msg: br#"{"claim":{}}"#.into(),
588                funds: vec![coin(321, "stones")],
589                salt: Binary::from_base64("UkOVazhiwoo=").unwrap(),
590            };
591            let json = to_json_binary(&msg).unwrap();
592            assert_eq!(
593                String::from_utf8_lossy(&json),
594                r#"{"instantiate2":{"admin":null,"code_id":7897,"label":"my instance","msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"salt":"UkOVazhiwoo="}}"#,
595            );
596        }
597    }
598
599    #[test]
600    #[cfg(feature = "cosmwasm_2_0")]
601    fn any_msg_serializes_to_correct_json() {
602        // Same serialization as CosmosMsg::Stargate (see above), except the top level key
603        let msg: CosmosMsg = CosmosMsg::Any(AnyMsg {
604            type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
605            value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
606        });
607        let json = crate::to_json_string(&msg).unwrap();
608        assert_eq!(
609            json,
610            r#"{"any":{"type_url":"/cosmos.foo.v1beta.MsgBar","value":"5yu/rQ+HrMcxH1zdga7P5hpGMLE="}}"#,
611        );
612    }
613
614    #[test]
615    #[cfg(feature = "cosmwasm_1_3")]
616    fn msg_distribution_serializes_to_correct_json() {
617        // FundCommunityPool
618        let fund_coins = vec![coin(200, "feathers"), coin(200, "stones")];
619        let fund_msg = DistributionMsg::FundCommunityPool { amount: fund_coins };
620        let fund_json = to_json_binary(&fund_msg).unwrap();
621        assert_eq!(
622            String::from_utf8_lossy(&fund_json),
623            r#"{"fund_community_pool":{"amount":[{"denom":"feathers","amount":"200"},{"denom":"stones","amount":"200"}]}}"#,
624        );
625
626        // SetWithdrawAddress
627        let set_msg = DistributionMsg::SetWithdrawAddress {
628            address: String::from("withdrawer"),
629        };
630        let set_json = to_json_binary(&set_msg).unwrap();
631        assert_eq!(
632            String::from_utf8_lossy(&set_json),
633            r#"{"set_withdraw_address":{"address":"withdrawer"}}"#,
634        );
635
636        // WithdrawDelegatorRewards
637        let withdraw_msg = DistributionMsg::WithdrawDelegatorReward {
638            validator: String::from("fancyoperator"),
639        };
640        let withdraw_json = to_json_binary(&withdraw_msg).unwrap();
641        assert_eq!(
642            String::from_utf8_lossy(&withdraw_json),
643            r#"{"withdraw_delegator_reward":{"validator":"fancyoperator"}}"#
644        );
645    }
646
647    #[test]
648    fn wasm_msg_debug_decodes_binary_string_when_possible() {
649        #[cosmwasm_schema::cw_serde]
650        enum ExecuteMsg {
651            Mint { coin: Coin },
652        }
653
654        let msg = WasmMsg::Execute {
655            contract_addr: "joe".to_string(),
656            msg: to_json_binary(&ExecuteMsg::Mint {
657                coin: coin(10, "BTC"),
658            })
659            .unwrap(),
660            funds: vec![],
661        };
662
663        assert_eq!(
664            format!("{msg:?}"),
665            "Execute { contract_addr: \"joe\", msg: {\"mint\":{\"coin\":{\"denom\":\"BTC\",\"amount\":\"10\"}}}, funds: [] }"
666        );
667    }
668
669    #[test]
670    fn wasm_msg_debug_dumps_binary_when_not_utf8() {
671        let msg = WasmMsg::Execute {
672            contract_addr: "joe".to_string(),
673            msg: Binary::from([0, 159, 146, 150]),
674            funds: vec![],
675        };
676
677        assert_eq!(
678            format!("{msg:?}"),
679            "Execute { contract_addr: \"joe\", msg: Binary(009f9296), funds: [] }"
680        );
681    }
682
683    #[test]
684    #[cfg(feature = "stargate")]
685    fn gov_msg_serializes_to_correct_json() {
686        // Vote
687        let msg = GovMsg::Vote {
688            proposal_id: 4,
689            option: VoteOption::NoWithVeto,
690        };
691        let json = to_json_binary(&msg).unwrap();
692        assert_eq!(
693            String::from_utf8_lossy(&json),
694            r#"{"vote":{"proposal_id":4,"option":"no_with_veto"}}"#,
695        );
696
697        // VoteWeighted
698        #[cfg(feature = "cosmwasm_1_2")]
699        {
700            let msg = GovMsg::VoteWeighted {
701                proposal_id: 25,
702                options: vec![
703                    WeightedVoteOption {
704                        weight: Decimal::percent(25),
705                        option: VoteOption::Yes,
706                    },
707                    WeightedVoteOption {
708                        weight: Decimal::percent(25),
709                        option: VoteOption::No,
710                    },
711                    WeightedVoteOption {
712                        weight: Decimal::percent(50),
713                        option: VoteOption::Abstain,
714                    },
715                ],
716            };
717
718            let json = to_json_binary(&msg).unwrap();
719            assert_eq!(
720                String::from_utf8_lossy(&json),
721                r#"{"vote_weighted":{"proposal_id":25,"options":[{"option":"yes","weight":"0.25"},{"option":"no","weight":"0.25"},{"option":"abstain","weight":"0.5"}]}}"#,
722            );
723        }
724    }
725
726    #[test]
727    fn change_custom_works() {
728        #[derive(Debug, PartialEq, Eq, Clone)]
729        struct Custom {
730            _a: i32,
731        }
732        let send = BankMsg::Send {
733            to_address: "you".to_string(),
734            amount: coins(1015, "earth"),
735        };
736        // Custom to Empty
737        let msg: CosmosMsg<Custom> = send.clone().into();
738        let msg2: CosmosMsg<Empty> = msg.change_custom().unwrap();
739        assert_eq!(msg2, CosmosMsg::Bank(send.clone()));
740        let custom = CosmosMsg::Custom(Custom { _a: 5 });
741        let converted = custom.change_custom::<Empty>();
742        assert_eq!(converted, None);
743
744        // Empty to Custom
745        let msg: CosmosMsg<Empty> = send.clone().into();
746        let msg2: CosmosMsg<Custom> = msg.change_custom().unwrap();
747        assert_eq!(msg2, CosmosMsg::Bank(send));
748        let empty = CosmosMsg::Custom(Empty {});
749        let converted = empty.change_custom::<Custom>();
750        assert_eq!(converted, None);
751    }
752}