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