cosmwasm_std/results/
cosmos_msg.rs

1use core::fmt;
2use derivative::Derivative;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::binary::Binary;
7use crate::coin::Coin;
8use crate::errors::StdResult;
9#[cfg(feature = "stargate")]
10use crate::ibc::IbcMsg;
11use crate::serde::to_json_binary;
12#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
13use crate::Decimal;
14
15use super::Empty;
16
17/// Like CustomQuery for better type clarity.
18/// Also makes it shorter to use as a trait bound.
19pub trait CustomMsg: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema {}
20
21impl CustomMsg for Empty {}
22
23#[non_exhaustive]
24#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
25#[serde(rename_all = "snake_case")]
26// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T
27pub enum CosmosMsg<T = Empty> {
28    Bank(BankMsg),
29    // by default we use RawMsg, but a contract can override that
30    // to call into more app-specific code (whatever they define)
31    Custom(T),
32    #[cfg(feature = "staking")]
33    Staking(StakingMsg),
34    #[cfg(feature = "staking")]
35    Distribution(DistributionMsg),
36    /// A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto).
37    /// 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)
38    #[cfg(feature = "stargate")]
39    Stargate {
40        type_url: String,
41        value: Binary,
42    },
43    #[cfg(feature = "stargate")]
44    Ibc(IbcMsg),
45    Wasm(WasmMsg),
46    #[cfg(feature = "stargate")]
47    Gov(GovMsg),
48}
49
50/// The message types of the bank module.
51///
52/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto
53#[non_exhaustive]
54#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
55#[serde(rename_all = "snake_case")]
56pub enum BankMsg {
57    /// Sends native tokens from the contract to the given address.
58    ///
59    /// This is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28).
60    /// `from_address` is automatically filled with the current contract's address.
61    Send {
62        to_address: String,
63        amount: Vec<Coin>,
64    },
65    /// This will burn the given coins from the contract's account.
66    /// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper.
67    /// Important if a contract controls significant token supply that must be retired.
68    Burn { amount: Vec<Coin> },
69}
70
71/// The message types of the staking module.
72///
73/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto
74#[cfg(feature = "staking")]
75#[non_exhaustive]
76#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
77#[serde(rename_all = "snake_case")]
78pub enum StakingMsg {
79    /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90).
80    /// `delegator_address` is automatically filled with the current contract's address.
81    Delegate { validator: String, amount: Coin },
82    /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121).
83    /// `delegator_address` is automatically filled with the current contract's address.
84    Undelegate { validator: String, amount: Coin },
85    /// This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105).
86    /// `delegator_address` is automatically filled with the current contract's address.
87    Redelegate {
88        src_validator: String,
89        dst_validator: String,
90        amount: Coin,
91    },
92}
93
94/// The message types of the distribution module.
95///
96/// See https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto
97#[cfg(feature = "staking")]
98#[non_exhaustive]
99#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
100#[serde(rename_all = "snake_case")]
101pub enum DistributionMsg {
102    /// This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37).
103    /// `delegator_address` is automatically filled with the current contract's address.
104    SetWithdrawAddress {
105        /// The `withdraw_address`
106        address: String,
107    },
108    /// This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50).
109    /// `delegator_address` is automatically filled with the current contract's address.
110    WithdrawDelegatorReward {
111        /// The `validator_address`
112        validator: String,
113    },
114    /// This is translated to a [[MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2).
115    /// `depositor` is automatically filled with the current contract's address.
116    #[cfg(feature = "cosmwasm_1_3")]
117    FundCommunityPool {
118        /// The amount to spend
119        amount: Vec<Coin>,
120    },
121}
122
123fn binary_to_string(data: &Binary, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
124    match core::str::from_utf8(data.as_slice()) {
125        Ok(s) => fmt.write_str(s),
126        Err(_) => fmt::Debug::fmt(data, fmt),
127    }
128}
129
130/// The message types of the wasm module.
131///
132/// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto
133#[non_exhaustive]
134#[derive(Serialize, Deserialize, Clone, Derivative, PartialEq, Eq, JsonSchema)]
135#[derivative(Debug)]
136#[serde(rename_all = "snake_case")]
137pub enum WasmMsg {
138    /// Dispatches a call to another contract at a known address (with known ABI).
139    ///
140    /// This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78).
141    /// `sender` is automatically filled with the current contract's address.
142    Execute {
143        contract_addr: String,
144        /// msg is the json-encoded ExecuteMsg struct (as raw Binary)
145        #[derivative(Debug(format_with = "binary_to_string"))]
146        msg: Binary,
147        funds: Vec<Coin>,
148    },
149    /// Instantiates a new contracts from previously uploaded Wasm code.
150    ///
151    /// The contract address is non-predictable. But it is guaranteed that
152    /// when emitting the same Instantiate message multiple times,
153    /// multiple instances on different addresses will be generated. See also
154    /// Instantiate2.
155    ///
156    /// This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71).
157    /// `sender` is automatically filled with the current contract's address.
158    Instantiate {
159        admin: Option<String>,
160        code_id: u64,
161        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
162        #[derivative(Debug(format_with = "binary_to_string"))]
163        msg: Binary,
164        funds: Vec<Coin>,
165        /// A human-readable label for the contract.
166        ///
167        /// Valid values should:
168        /// - not be empty
169        /// - not be bigger than 128 bytes (or some chain-specific limit)
170        /// - not start / end with whitespace
171        label: String,
172    },
173    /// Instantiates a new contracts from previously uploaded Wasm code
174    /// using a predictable address derivation algorithm implemented in
175    /// [`cosmwasm_std::instantiate2_address`].
176    ///
177    /// This is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96).
178    /// `sender` is automatically filled with the current contract's address.
179    /// `fix_msg` is automatically set to false.
180    #[cfg(feature = "cosmwasm_1_2")]
181    Instantiate2 {
182        admin: Option<String>,
183        code_id: u64,
184        /// A human-readable label for the contract.
185        ///
186        /// Valid values should:
187        /// - not be empty
188        /// - not be bigger than 128 bytes (or some chain-specific limit)
189        /// - not start / end with whitespace
190        label: String,
191        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
192        #[derivative(Debug(format_with = "binary_to_string"))]
193        msg: Binary,
194        funds: Vec<Coin>,
195        salt: Binary,
196    },
197    /// Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to
198    /// customize behavior.
199    ///
200    /// Only the contract admin (as defined in wasmd), if any, is able to make this call.
201    ///
202    /// This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96).
203    /// `sender` is automatically filled with the current contract's address.
204    Migrate {
205        contract_addr: String,
206        /// the code_id of the new logic to place in the given contract
207        new_code_id: u64,
208        /// msg is the json-encoded MigrateMsg struct that will be passed to the new code
209        #[derivative(Debug(format_with = "binary_to_string"))]
210        msg: Binary,
211    },
212    /// Sets a new admin (for migrate) on the given contract.
213    /// Fails if this contract is not currently admin of the target contract.
214    UpdateAdmin {
215        contract_addr: String,
216        admin: String,
217    },
218    /// Clears the admin on the given contract, so no more migration possible.
219    /// Fails if this contract is not currently admin of the target contract.
220    ClearAdmin { contract_addr: String },
221}
222
223/// This message type allows the contract interact with the [x/gov] module in order
224/// to cast votes.
225///
226/// [x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov
227///
228/// ## Examples
229///
230/// Cast a simple vote:
231///
232/// ```
233/// # use cosmwasm_std::{
234/// #     HexBinary,
235/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
236/// #     Response, QueryResponse,
237/// # };
238/// # type ExecuteMsg = ();
239/// use cosmwasm_std::{GovMsg, VoteOption};
240///
241/// #[entry_point]
242/// pub fn execute(
243///     deps: DepsMut,
244///     env: Env,
245///     info: MessageInfo,
246///     msg: ExecuteMsg,
247/// ) -> Result<Response, StdError> {
248///     // ...
249///     Ok(Response::new().add_message(GovMsg::Vote {
250///         proposal_id: 4,
251///         vote: VoteOption::Yes,
252///     }))
253/// }
254/// ```
255///
256/// Cast a weighted vote:
257///
258/// ```
259/// # use cosmwasm_std::{
260/// #     HexBinary,
261/// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
262/// #     Response, QueryResponse,
263/// # };
264/// # type ExecuteMsg = ();
265/// # #[cfg(feature = "cosmwasm_1_2")]
266/// use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};
267///
268/// # #[cfg(feature = "cosmwasm_1_2")]
269/// #[entry_point]
270/// pub fn execute(
271///     deps: DepsMut,
272///     env: Env,
273///     info: MessageInfo,
274///     msg: ExecuteMsg,
275/// ) -> Result<Response, StdError> {
276///     // ...
277///     Ok(Response::new().add_message(GovMsg::VoteWeighted {
278///         proposal_id: 4,
279///         options: vec![
280///             WeightedVoteOption {
281///                 option: VoteOption::Yes,
282///                 weight: Decimal::percent(65),
283///             },
284///             WeightedVoteOption {
285///                 option: VoteOption::Abstain,
286///                 weight: Decimal::percent(35),
287///             },
288///         ],
289///     }))
290/// }
291/// ```
292#[cfg(feature = "stargate")]
293#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
294#[serde(rename_all = "snake_case")]
295pub enum GovMsg {
296    /// 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.
297    Vote {
298        proposal_id: u64,
299        /// The vote option.
300        ///
301        /// This should be called "option" for consistency with Cosmos SDK. Sorry for that.
302        /// See <https://github.com/CosmWasm/cosmwasm/issues/1571>.
303        vote: VoteOption,
304    },
305    /// 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.
306    #[cfg(feature = "cosmwasm_1_2")]
307    VoteWeighted {
308        proposal_id: u64,
309        options: Vec<WeightedVoteOption>,
310    },
311}
312
313#[cfg(feature = "stargate")]
314#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
315#[serde(rename_all = "snake_case")]
316pub enum VoteOption {
317    Yes,
318    No,
319    Abstain,
320    NoWithVeto,
321}
322
323#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
324#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
325pub struct WeightedVoteOption {
326    pub option: VoteOption,
327    pub weight: Decimal,
328}
329
330/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code.
331///
332/// When using this, `admin` is always unset. If you need more flexibility, create the message directly.
333pub fn wasm_instantiate(
334    code_id: u64,
335    msg: &impl Serialize,
336    funds: Vec<Coin>,
337    label: String,
338) -> StdResult<WasmMsg> {
339    let payload = to_json_binary(msg)?;
340    Ok(WasmMsg::Instantiate {
341        admin: None,
342        code_id,
343        msg: payload,
344        funds,
345        label,
346    })
347}
348
349/// Shortcut helper as the construction of WasmMsg::Execute can be quite verbose in contract code
350pub fn wasm_execute(
351    contract_addr: impl Into<String>,
352    msg: &impl Serialize,
353    funds: Vec<Coin>,
354) -> StdResult<WasmMsg> {
355    let payload = to_json_binary(msg)?;
356    Ok(WasmMsg::Execute {
357        contract_addr: contract_addr.into(),
358        msg: payload,
359        funds,
360    })
361}
362
363impl<T> From<BankMsg> for CosmosMsg<T> {
364    fn from(msg: BankMsg) -> Self {
365        CosmosMsg::Bank(msg)
366    }
367}
368
369#[cfg(feature = "staking")]
370impl<T> From<StakingMsg> for CosmosMsg<T> {
371    fn from(msg: StakingMsg) -> Self {
372        CosmosMsg::Staking(msg)
373    }
374}
375
376#[cfg(feature = "staking")]
377impl<T> From<DistributionMsg> for CosmosMsg<T> {
378    fn from(msg: DistributionMsg) -> Self {
379        CosmosMsg::Distribution(msg)
380    }
381}
382
383impl<T> From<WasmMsg> for CosmosMsg<T> {
384    fn from(msg: WasmMsg) -> Self {
385        CosmosMsg::Wasm(msg)
386    }
387}
388
389#[cfg(feature = "stargate")]
390impl<T> From<IbcMsg> for CosmosMsg<T> {
391    fn from(msg: IbcMsg) -> Self {
392        CosmosMsg::Ibc(msg)
393    }
394}
395
396#[cfg(feature = "stargate")]
397impl<T> From<GovMsg> for CosmosMsg<T> {
398    fn from(msg: GovMsg) -> Self {
399        CosmosMsg::Gov(msg)
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406    use crate::{coin, coins};
407
408    #[test]
409    fn from_bank_msg_works() {
410        let to_address = String::from("you");
411        let amount = coins(1015, "earth");
412        let bank = BankMsg::Send { to_address, amount };
413        let msg: CosmosMsg = bank.clone().into();
414        match msg {
415            CosmosMsg::Bank(msg) => assert_eq!(bank, msg),
416            _ => panic!("must encode in Bank variant"),
417        }
418    }
419
420    #[test]
421    fn wasm_msg_serializes_to_correct_json() {
422        // Instantiate with admin
423        let msg = WasmMsg::Instantiate {
424            admin: Some("king".to_string()),
425            code_id: 7897,
426            msg: br#"{"claim":{}}"#.into(),
427            funds: vec![],
428            label: "my instance".to_string(),
429        };
430        let json = to_json_binary(&msg).unwrap();
431        assert_eq!(
432            String::from_utf8_lossy(&json),
433            r#"{"instantiate":{"admin":"king","code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
434        );
435
436        // Instantiate without admin
437        let msg = WasmMsg::Instantiate {
438            admin: None,
439            code_id: 7897,
440            msg: br#"{"claim":{}}"#.into(),
441            funds: vec![],
442            label: "my instance".to_string(),
443        };
444        let json = to_json_binary(&msg).unwrap();
445        assert_eq!(
446            String::from_utf8_lossy(&json),
447            r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
448        );
449
450        // Instantiate with funds
451        let msg = WasmMsg::Instantiate {
452            admin: None,
453            code_id: 7897,
454            msg: br#"{"claim":{}}"#.into(),
455            funds: vec![coin(321, "stones")],
456            label: "my instance".to_string(),
457        };
458        let json = to_json_binary(&msg).unwrap();
459        assert_eq!(
460            String::from_utf8_lossy(&json),
461            r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"label":"my instance"}}"#,
462        );
463
464        // Instantiate2
465        #[cfg(feature = "cosmwasm_1_2")]
466        {
467            let msg = WasmMsg::Instantiate2 {
468                admin: None,
469                code_id: 7897,
470                label: "my instance".to_string(),
471                msg: br#"{"claim":{}}"#.into(),
472                funds: vec![coin(321, "stones")],
473                salt: Binary::from_base64("UkOVazhiwoo=").unwrap(),
474            };
475            let json = to_json_binary(&msg).unwrap();
476            assert_eq!(
477                String::from_utf8_lossy(&json),
478                r#"{"instantiate2":{"admin":null,"code_id":7897,"label":"my instance","msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"salt":"UkOVazhiwoo="}}"#,
479            );
480        }
481    }
482
483    #[test]
484    #[cfg(feature = "cosmwasm_1_3")]
485    fn msg_distribution_serializes_to_correct_json() {
486        // FundCommunityPool
487        let fund_coins = vec![coin(200, "feathers"), coin(200, "stones")];
488        let fund_msg = DistributionMsg::FundCommunityPool { amount: fund_coins };
489        let fund_json = to_json_binary(&fund_msg).unwrap();
490        assert_eq!(
491            String::from_utf8_lossy(&fund_json),
492            r#"{"fund_community_pool":{"amount":[{"denom":"feathers","amount":"200"},{"denom":"stones","amount":"200"}]}}"#,
493        );
494
495        // SetWithdrawAddress
496        let set_msg = DistributionMsg::SetWithdrawAddress {
497            address: String::from("withdrawer"),
498        };
499        let set_json = to_json_binary(&set_msg).unwrap();
500        assert_eq!(
501            String::from_utf8_lossy(&set_json),
502            r#"{"set_withdraw_address":{"address":"withdrawer"}}"#,
503        );
504
505        // WithdrawDelegatorRewards
506        let withdraw_msg = DistributionMsg::WithdrawDelegatorReward {
507            validator: String::from("fancyoperator"),
508        };
509        let withdraw_json = to_json_binary(&withdraw_msg).unwrap();
510        assert_eq!(
511            String::from_utf8_lossy(&withdraw_json),
512            r#"{"withdraw_delegator_reward":{"validator":"fancyoperator"}}"#
513        );
514    }
515
516    #[test]
517    fn wasm_msg_debug_decodes_binary_string_when_possible() {
518        #[cosmwasm_schema::cw_serde]
519        enum ExecuteMsg {
520            Mint { coin: Coin },
521        }
522
523        let msg = WasmMsg::Execute {
524            contract_addr: "joe".to_string(),
525            msg: to_json_binary(&ExecuteMsg::Mint {
526                coin: coin(10, "BTC"),
527            })
528            .unwrap(),
529            funds: vec![],
530        };
531
532        assert_eq!(
533            format!("{msg:?}"),
534            "Execute { contract_addr: \"joe\", msg: {\"mint\":{\"coin\":{\"denom\":\"BTC\",\"amount\":\"10\"}}}, funds: [] }"
535        );
536    }
537
538    #[test]
539    fn wasm_msg_debug_dumps_binary_when_not_utf8() {
540        let msg = WasmMsg::Execute {
541            contract_addr: "joe".to_string(),
542            msg: Binary::from([0, 159, 146, 150]),
543            funds: vec![],
544        };
545
546        assert_eq!(
547            format!("{msg:?}"),
548            "Execute { contract_addr: \"joe\", msg: Binary(009f9296), funds: [] }"
549        );
550    }
551
552    #[test]
553    #[cfg(feature = "stargate")]
554    fn gov_msg_serializes_to_correct_json() {
555        // Vote
556        let msg = GovMsg::Vote {
557            proposal_id: 4,
558            vote: VoteOption::NoWithVeto,
559        };
560        let json = to_json_binary(&msg).unwrap();
561        assert_eq!(
562            String::from_utf8_lossy(&json),
563            r#"{"vote":{"proposal_id":4,"vote":"no_with_veto"}}"#,
564        );
565
566        // VoteWeighted
567        #[cfg(feature = "cosmwasm_1_2")]
568        {
569            let msg = GovMsg::VoteWeighted {
570                proposal_id: 25,
571                options: vec![
572                    WeightedVoteOption {
573                        weight: Decimal::percent(25),
574                        option: VoteOption::Yes,
575                    },
576                    WeightedVoteOption {
577                        weight: Decimal::percent(25),
578                        option: VoteOption::No,
579                    },
580                    WeightedVoteOption {
581                        weight: Decimal::percent(50),
582                        option: VoteOption::Abstain,
583                    },
584                ],
585            };
586
587            let json = to_json_binary(&msg).unwrap();
588            assert_eq!(
589                String::from_utf8_lossy(&json),
590                r#"{"vote_weighted":{"proposal_id":25,"options":[{"option":"yes","weight":"0.25"},{"option":"no","weight":"0.25"},{"option":"abstain","weight":"0.5"}]}}"#,
591            );
592        }
593    }
594}