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}