#![allow(deprecated)]
use core::fmt;
use derive_more::Debug;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::coin::Coin;
#[cfg(feature = "stargate")]
use crate::ibc::IbcMsg;
#[cfg(feature = "ibc2")]
use crate::ibc2::Ibc2Msg;
use crate::prelude::*;
#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
use crate::Decimal;
use crate::StdResult;
use crate::{to_json_binary, Binary};
use super::Empty;
pub trait CustomMsg: Serialize + Clone + fmt::Debug + PartialEq {}
impl CustomMsg for Empty {}
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum CosmosMsg<T = Empty> {
Bank(BankMsg),
Custom(T),
#[cfg(feature = "staking")]
Staking(StakingMsg),
#[cfg(feature = "staking")]
Distribution(DistributionMsg),
#[cfg(feature = "stargate")]
#[deprecated = "Use `CosmosMsg::Any` instead (if you only target CosmWasm 2+ chains)"]
Stargate {
type_url: String,
value: Binary,
},
#[cfg(feature = "cosmwasm_2_0")]
Any(AnyMsg),
#[cfg(feature = "stargate")]
Ibc(IbcMsg),
Wasm(WasmMsg),
#[cfg(feature = "stargate")]
Gov(GovMsg),
#[cfg(feature = "ibc2")]
Ibc2(Ibc2Msg),
}
impl<T> CosmosMsg<T> {
pub fn change_custom<U>(self) -> Option<CosmosMsg<U>> {
Some(match self {
CosmosMsg::Bank(msg) => CosmosMsg::Bank(msg),
CosmosMsg::Custom(_) => return None,
#[cfg(feature = "staking")]
CosmosMsg::Staking(msg) => CosmosMsg::Staking(msg),
#[cfg(feature = "staking")]
CosmosMsg::Distribution(msg) => CosmosMsg::Distribution(msg),
#[cfg(feature = "stargate")]
CosmosMsg::Stargate { type_url, value } => CosmosMsg::Stargate { type_url, value },
#[cfg(feature = "cosmwasm_2_0")]
CosmosMsg::Any(msg) => CosmosMsg::Any(msg),
#[cfg(feature = "stargate")]
CosmosMsg::Ibc(msg) => CosmosMsg::Ibc(msg),
CosmosMsg::Wasm(msg) => CosmosMsg::Wasm(msg),
#[cfg(feature = "stargate")]
CosmosMsg::Gov(msg) => CosmosMsg::Gov(msg),
#[cfg(feature = "ibc2")]
CosmosMsg::Ibc2(msg) => CosmosMsg::Ibc2(msg),
})
}
}
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum BankMsg {
Send {
to_address: String,
amount: Vec<Coin>,
},
Burn { amount: Vec<Coin> },
}
#[cfg(feature = "staking")]
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum StakingMsg {
Delegate { validator: String, amount: Coin },
Undelegate { validator: String, amount: Coin },
Redelegate {
src_validator: String,
dst_validator: String,
amount: Coin,
},
}
#[cfg(feature = "staking")]
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum DistributionMsg {
SetWithdrawAddress {
address: String,
},
WithdrawDelegatorReward {
validator: String,
},
#[cfg(feature = "cosmwasm_1_3")]
FundCommunityPool {
amount: Vec<Coin>,
},
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct AnyMsg {
pub type_url: String,
pub value: Binary,
}
#[allow(dead_code)]
struct BinaryToStringEncoder<'a>(&'a Binary);
impl fmt::Display for BinaryToStringEncoder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match core::str::from_utf8(self.0.as_slice()) {
Ok(s) => f.write_str(s),
Err(_) => fmt::Debug::fmt(self.0, f),
}
}
}
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum WasmMsg {
Execute {
contract_addr: String,
#[debug("{}", BinaryToStringEncoder(msg))]
msg: Binary,
funds: Vec<Coin>,
},
Instantiate {
admin: Option<String>,
code_id: u64,
#[debug("{}", BinaryToStringEncoder(msg))]
msg: Binary,
funds: Vec<Coin>,
label: String,
},
#[cfg(feature = "cosmwasm_1_2")]
Instantiate2 {
admin: Option<String>,
code_id: u64,
label: String,
#[debug("{}", BinaryToStringEncoder(msg))]
msg: Binary,
funds: Vec<Coin>,
salt: Binary,
},
Migrate {
contract_addr: String,
new_code_id: u64,
#[debug("{}", BinaryToStringEncoder(msg))]
msg: Binary,
},
UpdateAdmin {
contract_addr: String,
admin: String,
},
ClearAdmin { contract_addr: String },
}
#[cfg(feature = "stargate")]
#[non_exhaustive]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum GovMsg {
Vote {
proposal_id: u64,
option: VoteOption,
},
#[cfg(feature = "cosmwasm_1_2")]
VoteWeighted {
proposal_id: u64,
options: Vec<WeightedVoteOption>,
},
}
#[cfg(feature = "stargate")]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum VoteOption {
Yes,
No,
Abstain,
NoWithVeto,
}
#[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))]
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct WeightedVoteOption {
pub option: VoteOption,
pub weight: Decimal,
}
pub fn wasm_instantiate(
code_id: u64,
msg: &impl Serialize,
funds: Vec<Coin>,
label: String,
) -> StdResult<WasmMsg> {
let payload = to_json_binary(msg)?;
Ok(WasmMsg::Instantiate {
admin: None,
code_id,
msg: payload,
funds,
label,
})
}
pub fn wasm_execute(
contract_addr: impl Into<String>,
msg: &impl Serialize,
funds: Vec<Coin>,
) -> StdResult<WasmMsg> {
let payload = to_json_binary(msg)?;
Ok(WasmMsg::Execute {
contract_addr: contract_addr.into(),
msg: payload,
funds,
})
}
impl<T> From<BankMsg> for CosmosMsg<T> {
fn from(msg: BankMsg) -> Self {
CosmosMsg::Bank(msg)
}
}
#[cfg(feature = "staking")]
impl<T> From<StakingMsg> for CosmosMsg<T> {
fn from(msg: StakingMsg) -> Self {
CosmosMsg::Staking(msg)
}
}
#[cfg(feature = "staking")]
impl<T> From<DistributionMsg> for CosmosMsg<T> {
fn from(msg: DistributionMsg) -> Self {
CosmosMsg::Distribution(msg)
}
}
#[cfg(feature = "cosmwasm_2_0")]
impl<S: Into<AnyMsg>, T> From<S> for CosmosMsg<T> {
fn from(source: S) -> Self {
CosmosMsg::<T>::Any(source.into())
}
}
impl<T> From<WasmMsg> for CosmosMsg<T> {
fn from(msg: WasmMsg) -> Self {
CosmosMsg::Wasm(msg)
}
}
#[cfg(feature = "stargate")]
impl<T> From<IbcMsg> for CosmosMsg<T> {
fn from(msg: IbcMsg) -> Self {
CosmosMsg::Ibc(msg)
}
}
#[cfg(feature = "stargate")]
impl<T> From<GovMsg> for CosmosMsg<T> {
fn from(msg: GovMsg) -> Self {
CosmosMsg::Gov(msg)
}
}
#[cfg(feature = "ibc2")]
impl<T> From<Ibc2Msg> for CosmosMsg<T> {
fn from(msg: Ibc2Msg) -> Self {
CosmosMsg::Ibc2(msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{coin, coins};
use fmt::Debug;
#[test]
fn from_bank_msg_works() {
let to_address = String::from("you");
let amount = coins(1015, "earth");
let bank = BankMsg::Send { to_address, amount };
let msg: CosmosMsg = bank.clone().into();
match msg {
CosmosMsg::Bank(msg) => assert_eq!(bank, msg),
_ => panic!("must encode in Bank variant"),
}
}
#[test]
#[cfg(feature = "cosmwasm_2_0")]
fn from_any_msg_works() {
let any = AnyMsg {
type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
};
let msg: CosmosMsg = any.clone().into();
assert!(matches!(msg, CosmosMsg::Any(a) if a == any));
struct IntoAny;
impl From<IntoAny> for AnyMsg {
fn from(_: IntoAny) -> Self {
AnyMsg {
type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
}
}
}
let msg: CosmosMsg = IntoAny.into();
assert!(matches!(
msg,
CosmosMsg::Any(a) if a == any
));
}
#[test]
fn wasm_msg_serializes_to_correct_json() {
let msg = WasmMsg::Instantiate {
admin: Some("king".to_string()),
code_id: 7897,
msg: br#"{"claim":{}}"#.into(),
funds: vec![],
label: "my instance".to_string(),
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"instantiate":{"admin":"king","code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
);
let msg = WasmMsg::Instantiate {
admin: None,
code_id: 7897,
msg: br#"{"claim":{}}"#.into(),
funds: vec![],
label: "my instance".to_string(),
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[],"label":"my instance"}}"#,
);
let msg = WasmMsg::Instantiate {
admin: None,
code_id: 7897,
msg: br#"{"claim":{}}"#.into(),
funds: vec![coin(321, "stones")],
label: "my instance".to_string(),
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"instantiate":{"admin":null,"code_id":7897,"msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"label":"my instance"}}"#,
);
#[cfg(feature = "cosmwasm_1_2")]
{
let msg = WasmMsg::Instantiate2 {
admin: None,
code_id: 7897,
label: "my instance".to_string(),
msg: br#"{"claim":{}}"#.into(),
funds: vec![coin(321, "stones")],
salt: Binary::from_base64("UkOVazhiwoo=").unwrap(),
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"instantiate2":{"admin":null,"code_id":7897,"label":"my instance","msg":"eyJjbGFpbSI6e319","funds":[{"denom":"stones","amount":"321"}],"salt":"UkOVazhiwoo="}}"#,
);
}
}
#[test]
#[cfg(feature = "cosmwasm_2_0")]
fn any_msg_serializes_to_correct_json() {
let msg: CosmosMsg = CosmosMsg::Any(AnyMsg {
type_url: "/cosmos.foo.v1beta.MsgBar".to_string(),
value: Binary::from_base64("5yu/rQ+HrMcxH1zdga7P5hpGMLE=").unwrap(),
});
let json = crate::to_json_string(&msg).unwrap();
assert_eq!(
json,
r#"{"any":{"type_url":"/cosmos.foo.v1beta.MsgBar","value":"5yu/rQ+HrMcxH1zdga7P5hpGMLE="}}"#,
);
}
#[test]
#[cfg(all(feature = "cosmwasm_1_3", feature = "staking"))]
fn msg_distribution_serializes_to_correct_json() {
let fund_coins = vec![coin(200, "feathers"), coin(200, "stones")];
let fund_msg = DistributionMsg::FundCommunityPool { amount: fund_coins };
let fund_json = to_json_binary(&fund_msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&fund_json),
r#"{"fund_community_pool":{"amount":[{"denom":"feathers","amount":"200"},{"denom":"stones","amount":"200"}]}}"#,
);
let set_msg = DistributionMsg::SetWithdrawAddress {
address: String::from("withdrawer"),
};
let set_json = to_json_binary(&set_msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&set_json),
r#"{"set_withdraw_address":{"address":"withdrawer"}}"#,
);
let withdraw_msg = DistributionMsg::WithdrawDelegatorReward {
validator: String::from("fancyoperator"),
};
let withdraw_json = to_json_binary(&withdraw_msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&withdraw_json),
r#"{"withdraw_delegator_reward":{"validator":"fancyoperator"}}"#
);
}
#[test]
fn wasm_msg_debug_decodes_binary_string_when_possible() {
#[cosmwasm_schema::cw_serde]
enum ExecuteMsg {
Mint { coin: Coin },
}
let msg = WasmMsg::Execute {
contract_addr: "joe".to_string(),
msg: to_json_binary(&ExecuteMsg::Mint {
coin: coin(10, "BTC"),
})
.unwrap(),
funds: vec![],
};
assert_eq!(
format!("{msg:?}"),
"Execute { contract_addr: \"joe\", msg: {\"mint\":{\"coin\":{\"denom\":\"BTC\",\"amount\":\"10\"}}}, funds: [] }"
);
}
#[test]
fn wasm_msg_debug_dumps_binary_when_not_utf8() {
let msg = WasmMsg::Execute {
contract_addr: "joe".to_string(),
msg: Binary::from([0, 159, 146, 150]),
funds: vec![],
};
assert_eq!(
format!("{msg:?}"),
"Execute { contract_addr: \"joe\", msg: Binary(009f9296), funds: [] }"
);
}
#[test]
#[cfg(feature = "stargate")]
fn gov_msg_serializes_to_correct_json() {
let msg = GovMsg::Vote {
proposal_id: 4,
option: VoteOption::NoWithVeto,
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"vote":{"proposal_id":4,"option":"no_with_veto"}}"#,
);
#[cfg(feature = "cosmwasm_1_2")]
{
let msg = GovMsg::VoteWeighted {
proposal_id: 25,
options: vec![
WeightedVoteOption {
weight: Decimal::percent(25),
option: VoteOption::Yes,
},
WeightedVoteOption {
weight: Decimal::percent(25),
option: VoteOption::No,
},
WeightedVoteOption {
weight: Decimal::percent(50),
option: VoteOption::Abstain,
},
],
};
let json = to_json_binary(&msg).unwrap();
assert_eq!(
String::from_utf8_lossy(&json),
r#"{"vote_weighted":{"proposal_id":25,"options":[{"option":"yes","weight":"0.25"},{"option":"no","weight":"0.25"},{"option":"abstain","weight":"0.5"}]}}"#,
);
}
}
#[test]
fn change_custom_works() {
#[derive(Debug, PartialEq, Eq, Clone)]
struct Custom {
_a: i32,
}
let send = BankMsg::Send {
to_address: "you".to_string(),
amount: coins(1015, "earth"),
};
let msg: CosmosMsg<Custom> = send.clone().into();
let msg2: CosmosMsg<Empty> = msg.change_custom().unwrap();
assert_eq!(msg2, CosmosMsg::Bank(send.clone()));
let custom = CosmosMsg::Custom(Custom { _a: 5 });
let converted = custom.change_custom::<Empty>();
assert_eq!(converted, None);
let msg: CosmosMsg<Empty> = send.clone().into();
let msg2: CosmosMsg<Custom> = msg.change_custom().unwrap();
assert_eq!(msg2, CosmosMsg::Bank(send));
let empty = CosmosMsg::Custom(Empty {});
let converted = empty.change_custom::<Custom>();
assert_eq!(converted, None);
}
}