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