secret_cosmwasm_std/results/
cosmos_msg.rs

1use derivative::Derivative;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::fmt;
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_binary;
12
13use super::Empty;
14
15/// Like CustomQuery for better type clarity.
16/// Also makes it shorter to use as a trait bound.
17pub trait CustomMsg: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema {}
18
19impl CustomMsg for Empty {}
20
21#[non_exhaustive]
22#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
23#[serde(rename_all = "snake_case")]
24// See https://github.com/serde-rs/serde/issues/1296 why we cannot add De-Serialize trait bounds to T
25pub enum CosmosMsg<T = Empty> {
26    Bank(BankMsg),
27    // by default we use RawMsg, but a contract can override that
28    // to call into more app-specific code (whatever they define)
29    Custom(T),
30    #[cfg(feature = "staking")]
31    Staking(StakingMsg),
32    #[cfg(feature = "staking")]
33    Distribution(DistributionMsg),
34    /// A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto).
35    /// 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)
36    #[cfg(feature = "stargate")]
37    Stargate {
38        /// this is the fully qualified msg path used for routing,
39        /// e.g. /cosmos.bank.v1beta1.MsgSend
40        /// NOTE: the type_url can be changed after a chain upgrade
41        type_url: String,
42        value: Binary,
43    },
44    #[cfg(feature = "stargate")]
45    Ibc(IbcMsg),
46    Wasm(WasmMsg),
47    #[cfg(feature = "stargate")]
48    Gov(GovMsg),
49    FinalizeTx(Empty),
50}
51
52impl<T> CosmosMsg<T> {
53    pub fn finalize_tx() -> Self {
54        CosmosMsg::FinalizeTx(Empty {})
55    }
56}
57
58/// The message types of the bank module.
59///
60/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto
61#[non_exhaustive]
62#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
63#[serde(rename_all = "snake_case")]
64pub enum BankMsg {
65    /// Sends native tokens from the contract to the given address.
66    ///
67    /// This is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28).
68    /// `from_address` is automatically filled with the current contract's address.
69    Send {
70        to_address: String,
71        amount: Vec<Coin>,
72    },
73    /// This will burn the given coins from the contract's account.
74    /// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper.
75    /// Important if a contract controls significant token supply that must be retired.
76    Burn { amount: Vec<Coin> },
77}
78
79/// The message types of the staking module.
80///
81/// See https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto
82#[cfg(feature = "staking")]
83#[non_exhaustive]
84#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
85#[serde(rename_all = "snake_case")]
86pub enum StakingMsg {
87    /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90).
88    /// `delegator_address` is automatically filled with the current contract's address.
89    Delegate { validator: String, amount: Coin },
90    /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121).
91    /// `delegator_address` is automatically filled with the current contract's address.
92    Undelegate { validator: String, amount: Coin },
93    /// This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105).
94    /// `delegator_address` is automatically filled with the current contract's address.
95    Redelegate {
96        src_validator: String,
97        dst_validator: String,
98        amount: Coin,
99    },
100}
101
102/// The message types of the distribution module.
103///
104/// See https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto
105#[cfg(feature = "staking")]
106#[non_exhaustive]
107#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
108#[serde(rename_all = "snake_case")]
109pub enum DistributionMsg {
110    /// This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37).
111    /// `delegator_address` is automatically filled with the current contract's address.
112    SetWithdrawAddress {
113        /// The `withdraw_address`
114        address: String,
115    },
116    /// This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50).
117    /// `delegator_address` is automatically filled with the current contract's address.
118    WithdrawDelegatorReward {
119        /// The `validator_address`
120        validator: String,
121    },
122}
123
124fn binary_to_string(data: &Binary, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
125    match std::str::from_utf8(data.as_slice()) {
126        Ok(s) => fmt.write_str(s),
127        Err(_) => write!(fmt, "{:?}", data),
128    }
129}
130
131/// The message types of the wasm module.
132///
133/// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto
134#[non_exhaustive]
135#[derive(Serialize, Deserialize, Clone, Derivative, PartialEq, Eq, JsonSchema)]
136#[derivative(Debug)]
137#[serde(rename_all = "snake_case")]
138pub enum WasmMsg {
139    /// Dispatches a call to another contract at a known address (with known ABI).
140    ///
141    /// This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78).
142    /// `sender` is automatically filled with the current contract's address.
143    Execute {
144        contract_addr: String,
145        /// code_hash is the hex encoded hash of the code. This is used by Secret Network to harden against replaying the contract
146        /// It is used to bind the request to a destination contract in a stronger way than just the contract address which can be faked
147        code_hash: String,
148        /// msg is the json-encoded ExecuteMsg struct (as raw Binary)
149        #[derivative(Debug(format_with = "binary_to_string"))]
150        msg: Binary,
151        #[serde(rename = "send")]
152        funds: Vec<Coin>,
153    },
154    /// Instantiates a new contracts from previously uploaded Wasm code.
155    ///
156    /// This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61).
157    /// `sender` is automatically filled with the current contract's address.
158    Instantiate {
159        admin: Option<String>,
160        code_id: u64,
161        /// code_hash is the hex encoded hash of the code. This is used by Secret Network to harden against replaying the contract
162        /// It is used to bind the request to a destination contract in a stronger way than just the contract address which can be faked
163        code_hash: String,
164        /// msg is the JSON-encoded InstantiateMsg struct (as raw Binary)
165        #[derivative(Debug(format_with = "binary_to_string"))]
166        msg: Binary,
167        #[serde(rename = "send")]
168        funds: Vec<Coin>,
169        /// A human-readbale label for the contract, must be unique across all contracts
170        label: String,
171    },
172    /// Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to
173    /// customize behavior.
174    ///
175    /// Only the contract admin (as defined in wasmd), if any, is able to make this call.
176    ///
177    /// This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96).
178    /// `sender` is automatically filled with the current contract's address.
179    Migrate {
180        contract_addr: String,
181        /// code_hash is the hex encoded hash of the **new** code. This is used by Secret Network to harden against replaying the contract
182        /// It is used to bind the request to a destination contract in a stronger way than just the contract address which can be faked
183        code_hash: String,
184        /// the code_id of the **new** logic to place in the given contract
185        code_id: u64,
186        /// msg is the json-encoded MigrateMsg struct that will be passed to the new code
187        #[derivative(Debug(format_with = "binary_to_string"))]
188        msg: Binary,
189    },
190    /// Sets a new admin (for migrate) on the given contract.
191    /// Fails if this contract is not currently admin of the target contract.
192    UpdateAdmin {
193        contract_addr: String,
194        admin: String,
195    },
196    /// Clears the admin on the given contract, so no more migration possible.
197    /// Fails if this contract is not currently admin of the target contract.
198    ClearAdmin { contract_addr: String },
199}
200
201#[cfg(feature = "stargate")]
202#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
203#[serde(rename_all = "snake_case")]
204pub enum GovMsg {
205    /// 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.
206    Vote { proposal_id: u64, vote: VoteOption },
207}
208
209#[cfg(feature = "stargate")]
210#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
211#[serde(rename_all = "snake_case")]
212pub enum VoteOption {
213    Yes,
214    No,
215    Abstain,
216    NoWithVeto,
217}
218
219
220/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code.
221///
222/// When using this, `admin` is always unset. If you need more flexibility, create the message directly.
223pub fn wasm_instantiate(
224    code_id: u64,
225    code_hash: impl Into<String>,
226    msg: &impl Serialize,
227    funds: Vec<Coin>,
228    label: String,
229) -> StdResult<WasmMsg> {
230    let payload = to_binary(msg)?;
231    Ok(WasmMsg::Instantiate {
232        admin: None,
233        code_id,
234        code_hash: code_hash.into(),
235        msg: payload,
236        funds,
237        label,
238    })
239}
240
241/// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code
242pub fn wasm_execute(
243    contract_addr: impl Into<String>,
244    code_hash: impl Into<String>,
245    msg: &impl Serialize,
246    funds: Vec<Coin>,
247) -> StdResult<WasmMsg> {
248    let payload = to_binary(msg)?;
249    Ok(WasmMsg::Execute {
250        contract_addr: contract_addr.into(),
251        code_hash: code_hash.into(),
252        msg: payload,
253        funds,
254    })
255}
256
257impl<T> From<BankMsg> for CosmosMsg<T> {
258    fn from(msg: BankMsg) -> Self {
259        CosmosMsg::Bank(msg)
260    }
261}
262
263#[cfg(feature = "staking")]
264impl<T> From<StakingMsg> for CosmosMsg<T> {
265    fn from(msg: StakingMsg) -> Self {
266        CosmosMsg::Staking(msg)
267    }
268}
269
270#[cfg(feature = "staking")]
271impl<T> From<DistributionMsg> for CosmosMsg<T> {
272    fn from(msg: DistributionMsg) -> Self {
273        CosmosMsg::Distribution(msg)
274    }
275}
276
277impl<T> From<WasmMsg> for CosmosMsg<T> {
278    fn from(msg: WasmMsg) -> Self {
279        CosmosMsg::Wasm(msg)
280    }
281}
282
283#[cfg(feature = "stargate")]
284impl<T> From<IbcMsg> for CosmosMsg<T> {
285    fn from(msg: IbcMsg) -> Self {
286        CosmosMsg::Ibc(msg)
287    }
288}
289
290#[cfg(feature = "stargate")]
291impl<T> From<GovMsg> for CosmosMsg<T> {
292    fn from(msg: GovMsg) -> Self {
293        CosmosMsg::Gov(msg)
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::{coin, coins};
301
302    #[test]
303    fn from_bank_msg_works() {
304        let to_address = String::from("you");
305        let amount = coins(1015, "earth");
306        let bank = BankMsg::Send { to_address, amount };
307        let msg: CosmosMsg = bank.clone().into();
308        match msg {
309            CosmosMsg::Bank(msg) => assert_eq!(bank, msg),
310            _ => panic!("must encode in Bank variant"),
311        }
312    }
313
314    #[cosmwasm_schema::cw_serde]
315    enum ExecuteMsg {
316        Mint { coin: Coin },
317    }
318
319    #[test]
320    fn wasm_msg_debug_decodes_binary_string_when_possible() {
321        let msg = WasmMsg::Execute {
322            contract_addr: "joe".to_string(),
323            code_hash: "aaaa".to_string(),
324            msg: to_binary(&ExecuteMsg::Mint {
325                coin: coin(10, "BTC"),
326            })
327            .unwrap(),
328            funds: vec![],
329        };
330
331        assert_eq!(
332            format!("{:?}", msg),
333            "Execute { contract_addr: \"joe\", code_hash: \"aaaa\", msg: {\"mint\":{\"coin\":{\"denom\":\"BTC\",\"amount\":\"10\"}}}, funds: [] }"
334        );
335    }
336
337    #[test]
338    fn wasm_msg_debug_dumps_binary_when_not_utf8() {
339        let msg = WasmMsg::Execute {
340            contract_addr: "joe".to_string(),
341            code_hash: "aaaa".to_string(),
342            msg: Binary::from([0, 159, 146, 150]),
343            funds: vec![],
344        };
345
346        assert_eq!(
347            format!("{:?}", msg),
348            "Execute { contract_addr: \"joe\", code_hash: \"aaaa\", msg: Binary(009f9296), funds: [] }"
349        );
350    }
351}