cosmwasm_std/
ibc.rs

1// The CosmosMsg variants are defined in results/cosmos_msg.rs
2// The rest of the IBC related functionality is defined here
3
4use core::cmp::{Ord, Ordering, PartialOrd};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::coin::Coin;
9use crate::prelude::*;
10use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
11use crate::StdResult;
12use crate::{to_json_binary, Binary};
13use crate::{Addr, Timestamp};
14
15mod callbacks;
16mod transfer_msg_builder;
17
18pub use callbacks::*;
19pub use transfer_msg_builder::*;
20
21/// These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts
22/// (contracts that directly speak the IBC protocol via 6 entry points)
23#[non_exhaustive]
24#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
25#[serde(rename_all = "snake_case")]
26pub enum IbcMsg {
27    /// Sends bank tokens owned by the contract to the given address on another chain.
28    /// The channel must already be established between the ibctransfer module on this chain
29    /// and a matching module on the remote chain.
30    /// We cannot select the port_id, this is whatever the local chain has bound the ibctransfer
31    /// module to.
32    Transfer {
33        /// existing channel to send the tokens over
34        channel_id: String,
35        /// address on the remote chain to receive these tokens
36        to_address: String,
37        /// packet data only supports one coin
38        /// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20
39        amount: Coin,
40        /// when packet times out, measured on remote chain
41        timeout: IbcTimeout,
42        /// An optional memo. See the blog post
43        /// ["Moving Beyond Simple Token Transfers"](https://medium.com/the-interchain-foundation/moving-beyond-simple-token-transfers-d42b2b1dc29b)
44        /// for more information.
45        ///
46        /// There is no difference between setting this to `None` or an empty string.
47        ///
48        /// This field is only supported on chains with CosmWasm >= 2.0 and silently
49        /// ignored on older chains.
50        /// If you need support for both 1.x and 2.x chain with the same codebase,
51        /// it is recommended to use `CosmosMsg::Stargate` with a custom MsgTransfer
52        /// protobuf encoder instead.
53        memo: Option<String>,
54    },
55    /// Sends an IBC packet with given data over the existing channel.
56    /// Data should be encoded in a format defined by the channel version,
57    /// and the module on the other side should know how to parse this.
58    SendPacket {
59        channel_id: String,
60        data: Binary,
61        /// when packet times out, measured on remote chain
62        timeout: IbcTimeout,
63    },
64    /// Acknowledges a packet that this contract received over IBC.
65    /// This allows acknowledging a packet that was not acknowledged yet in the `ibc_packet_receive` call.
66    #[cfg(feature = "cosmwasm_2_1")]
67    WriteAcknowledgement {
68        /// Existing channel where the packet was received
69        channel_id: String,
70        /// Sequence number of the packet that was received
71        packet_sequence: u64,
72        /// The acknowledgement to send back
73        ack: IbcAcknowledgement,
74    },
75    /// This will close an existing channel that is owned by this contract.
76    /// Port is auto-assigned to the contract's IBC port
77    CloseChannel { channel_id: String },
78    /// Incentivizes the next IBC packet sent after this message with a fee.
79    /// Note that this does not necessarily have to be a packet sent by this contract.
80    /// The fees are taken from the contract's balance immediately and locked until the packet is handled.
81    ///
82    /// # Example
83    ///
84    /// Most commonly, you will attach this message to a response right before sending a packet using
85    /// [`IbcMsg::SendPacket`] or [`IbcMsg::Transfer`].
86    ///
87    /// ```rust
88    /// # use cosmwasm_std::{IbcMsg, IbcEndpoint, IbcFee, IbcTimeout, Coin, coins, CosmosMsg, Response, Timestamp};
89    ///
90    /// let incentivize = IbcMsg::PayPacketFee {
91    ///     port_id: "transfer".to_string(),
92    ///     channel_id: "source-channel".to_string(),
93    ///     fee: IbcFee {
94    ///         receive_fee: coins(100, "token"),
95    ///         ack_fee: coins(201, "token"),
96    ///         timeout_fee: coins(200, "token"),
97    ///     },
98    ///     relayers: vec![],
99    /// };
100    /// let transfer = IbcMsg::Transfer {
101    ///     channel_id: "source-channel".to_string(),
102    ///     to_address: "receiver".to_string(),
103    ///     amount: Coin::new(100u32, "token"),
104    ///     timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(0)),
105    ///     memo: None,
106    /// };
107    ///
108    /// # #[cfg(feature = "stargate")]
109    /// let _: Response = Response::new()
110    ///     .add_message(CosmosMsg::Ibc(incentivize))
111    ///     .add_message(CosmosMsg::Ibc(transfer));
112    /// ```
113    #[cfg(feature = "cosmwasm_2_2")]
114    PayPacketFee {
115        /// The port id on the chain where the packet is sent from (this chain).
116        port_id: String,
117        /// The channel id on the chain where the packet is sent from (this chain).
118        channel_id: String,
119        fee: IbcFee,
120        /// Allowlist of relayer addresses that can receive the fee.
121        /// An empty list means that any relayer can receive the fee.
122        ///
123        /// This is currently not implemented and *must* be empty.
124        relayers: Vec<String>,
125    },
126    /// Incentivizes the existing IBC packet with the given port, channel and sequence with a fee.
127    /// Note that this does not necessarily have to be a packet sent by this contract.
128    /// The fees are taken from the contract's balance immediately and locked until the packet is handled.
129    /// They are added to the existing fees on the packet.
130    #[cfg(feature = "cosmwasm_2_2")]
131    PayPacketFeeAsync {
132        /// The port id on the chain where the packet is sent from (this chain).
133        port_id: String,
134        /// The channel id on the chain where the packet is sent from (this chain).
135        channel_id: String,
136        /// The sequence number of the packet that should be incentivized.
137        sequence: u64,
138        fee: IbcFee,
139        /// Allowlist of relayer addresses that can receive the fee.
140        /// An empty list means that any relayer can receive the fee.
141        ///
142        /// This is currently not implemented and *must* be empty.
143        relayers: Vec<String>,
144    },
145}
146
147#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, JsonSchema)]
148pub struct IbcFee {
149    // the packet receive fee
150    pub receive_fee: Vec<Coin>,
151    // the packet acknowledgement fee
152    pub ack_fee: Vec<Coin>,
153    // the packet timeout fee
154    pub timeout_fee: Vec<Coin>,
155}
156
157#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
158pub struct IbcEndpoint {
159    pub port_id: String,
160    pub channel_id: String,
161}
162
163/// In IBC each package must set at least one type of timeout:
164/// the timestamp or the block height. Using this rather complex enum instead of
165/// two timeout fields we ensure that at least one timeout is set.
166#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
167#[serde(rename_all = "snake_case")]
168pub struct IbcTimeout {
169    // use private fields to enforce the use of constructors, which ensure that at least one is set
170    block: Option<IbcTimeoutBlock>,
171    timestamp: Option<Timestamp>,
172}
173
174impl IbcTimeout {
175    pub fn with_block(block: IbcTimeoutBlock) -> Self {
176        IbcTimeout {
177            block: Some(block),
178            timestamp: None,
179        }
180    }
181
182    pub fn with_timestamp(timestamp: Timestamp) -> Self {
183        IbcTimeout {
184            block: None,
185            timestamp: Some(timestamp),
186        }
187    }
188
189    pub fn with_both(block: IbcTimeoutBlock, timestamp: Timestamp) -> Self {
190        IbcTimeout {
191            block: Some(block),
192            timestamp: Some(timestamp),
193        }
194    }
195
196    pub fn block(&self) -> Option<IbcTimeoutBlock> {
197        self.block
198    }
199
200    pub fn timestamp(&self) -> Option<Timestamp> {
201        self.timestamp
202    }
203}
204
205impl From<Timestamp> for IbcTimeout {
206    fn from(timestamp: Timestamp) -> IbcTimeout {
207        IbcTimeout::with_timestamp(timestamp)
208    }
209}
210
211impl From<IbcTimeoutBlock> for IbcTimeout {
212    fn from(original: IbcTimeoutBlock) -> IbcTimeout {
213        IbcTimeout::with_block(original)
214    }
215}
216
217// These are various messages used in the callbacks
218
219/// IbcChannel defines all information on a channel.
220/// This is generally used in the hand-shake process, but can be queried directly.
221#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
222#[non_exhaustive]
223pub struct IbcChannel {
224    pub endpoint: IbcEndpoint,
225    pub counterparty_endpoint: IbcEndpoint,
226    pub order: IbcOrder,
227    /// Note: in ibcv3 this may be "", in the IbcOpenChannel handshake messages
228    pub version: String,
229    /// The connection upon which this channel was created. If this is a multi-hop
230    /// channel, we only expose the first hop.
231    pub connection_id: String,
232}
233
234impl IbcChannel {
235    /// Construct a new IbcChannel.
236    pub fn new(
237        endpoint: IbcEndpoint,
238        counterparty_endpoint: IbcEndpoint,
239        order: IbcOrder,
240        version: impl Into<String>,
241        connection_id: impl Into<String>,
242    ) -> Self {
243        Self {
244            endpoint,
245            counterparty_endpoint,
246            order,
247            version: version.into(),
248            connection_id: connection_id.into(),
249        }
250    }
251}
252
253/// IbcOrder defines if a channel is ORDERED or UNORDERED
254/// Values come from https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/core/channel/v1/channel.proto#L69-L80
255/// Naming comes from the protobuf files and go translations.
256#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
257pub enum IbcOrder {
258    #[serde(rename = "ORDER_UNORDERED")]
259    Unordered,
260    #[serde(rename = "ORDER_ORDERED")]
261    Ordered,
262}
263
264/// IBCTimeoutHeight Height is a monotonically increasing data type
265/// that can be compared against another Height for the purposes of updating and
266/// freezing clients.
267/// Ordering is (revision_number, timeout_height)
268#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
269pub struct IbcTimeoutBlock {
270    /// the version that the client is currently on
271    /// (e.g. after resetting the chain this could increment 1 as height drops to 0)
272    pub revision: u64,
273    /// block height after which the packet times out.
274    /// the height within the given revision
275    pub height: u64,
276}
277
278impl IbcTimeoutBlock {
279    pub fn is_zero(&self) -> bool {
280        self.revision == 0 && self.height == 0
281    }
282}
283
284impl PartialOrd for IbcTimeoutBlock {
285    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
286        Some(self.cmp(other))
287    }
288}
289
290impl Ord for IbcTimeoutBlock {
291    fn cmp(&self, other: &Self) -> Ordering {
292        match self.revision.cmp(&other.revision) {
293            Ordering::Equal => self.height.cmp(&other.height),
294            other => other,
295        }
296    }
297}
298
299#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
300#[non_exhaustive]
301pub struct IbcPacket {
302    /// The raw data sent from the other side in the packet
303    pub data: Binary,
304    /// identifies the channel and port on the sending chain.
305    pub src: IbcEndpoint,
306    /// identifies the channel and port on the receiving chain.
307    pub dest: IbcEndpoint,
308    /// The sequence number of the packet on the given channel
309    pub sequence: u64,
310    pub timeout: IbcTimeout,
311}
312
313impl IbcPacket {
314    /// Construct a new IbcPacket.
315    pub fn new(
316        data: impl Into<Binary>,
317        src: IbcEndpoint,
318        dest: IbcEndpoint,
319        sequence: u64,
320        timeout: IbcTimeout,
321    ) -> Self {
322        Self {
323            data: data.into(),
324            src,
325            dest,
326            sequence,
327            timeout,
328        }
329    }
330}
331
332#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
333#[non_exhaustive]
334pub struct IbcAcknowledgement {
335    pub data: Binary,
336    // we may add more info here in the future (meta-data from the acknowledgement)
337    // there have been proposals to extend this type in core ibc for future versions
338}
339
340impl IbcAcknowledgement {
341    pub fn new(data: impl Into<Binary>) -> Self {
342        IbcAcknowledgement { data: data.into() }
343    }
344
345    pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
346        Ok(IbcAcknowledgement {
347            data: to_json_binary(data)?,
348        })
349    }
350}
351
352/// The message that is passed into `ibc_channel_open`
353#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
354#[serde(rename_all = "snake_case")]
355pub enum IbcChannelOpenMsg {
356    /// The ChanOpenInit step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
357    OpenInit { channel: IbcChannel },
358    /// The ChanOpenTry step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
359    OpenTry {
360        channel: IbcChannel,
361        counterparty_version: String,
362    },
363}
364
365impl IbcChannelOpenMsg {
366    pub fn new_init(channel: IbcChannel) -> Self {
367        Self::OpenInit { channel }
368    }
369
370    pub fn new_try(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
371        Self::OpenTry {
372            channel,
373            counterparty_version: counterparty_version.into(),
374        }
375    }
376
377    pub fn channel(&self) -> &IbcChannel {
378        match self {
379            Self::OpenInit { channel } => channel,
380            Self::OpenTry { channel, .. } => channel,
381        }
382    }
383
384    pub fn counterparty_version(&self) -> Option<&str> {
385        match self {
386            Self::OpenTry {
387                counterparty_version,
388                ..
389            } => Some(counterparty_version),
390            _ => None,
391        }
392    }
393}
394
395impl From<IbcChannelOpenMsg> for IbcChannel {
396    fn from(msg: IbcChannelOpenMsg) -> IbcChannel {
397        match msg {
398            IbcChannelOpenMsg::OpenInit { channel } => channel,
399            IbcChannelOpenMsg::OpenTry { channel, .. } => channel,
400        }
401    }
402}
403
404/// This serializes either as `null` or a JSON object.
405/// Within the response, a channel version can be specified.
406/// If `null` is provided instead, the incoming channel version is accepted.
407pub type IbcChannelOpenResponse = Option<Ibc3ChannelOpenResponse>;
408
409#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
410pub struct Ibc3ChannelOpenResponse {
411    /// We can set the channel version to a different one than we were called with
412    pub version: String,
413}
414
415/// The message that is passed into `ibc_channel_connect`
416#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
417#[serde(rename_all = "snake_case")]
418pub enum IbcChannelConnectMsg {
419    /// The ChanOpenAck step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
420    OpenAck {
421        channel: IbcChannel,
422        counterparty_version: String,
423    },
424    /// The ChanOpenConfirm step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
425    OpenConfirm { channel: IbcChannel },
426}
427
428impl IbcChannelConnectMsg {
429    pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
430        Self::OpenAck {
431            channel,
432            counterparty_version: counterparty_version.into(),
433        }
434    }
435
436    pub fn new_confirm(channel: IbcChannel) -> Self {
437        Self::OpenConfirm { channel }
438    }
439
440    pub fn channel(&self) -> &IbcChannel {
441        match self {
442            Self::OpenAck { channel, .. } => channel,
443            Self::OpenConfirm { channel } => channel,
444        }
445    }
446
447    pub fn counterparty_version(&self) -> Option<&str> {
448        match self {
449            Self::OpenAck {
450                counterparty_version,
451                ..
452            } => Some(counterparty_version),
453            _ => None,
454        }
455    }
456}
457
458impl From<IbcChannelConnectMsg> for IbcChannel {
459    fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
460        match msg {
461            IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
462            IbcChannelConnectMsg::OpenConfirm { channel } => channel,
463        }
464    }
465}
466
467/// The message that is passed into `ibc_channel_close`
468#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
469#[serde(rename_all = "snake_case")]
470pub enum IbcChannelCloseMsg {
471    /// The ChanCloseInit step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
472    CloseInit { channel: IbcChannel },
473    /// The ChanCloseConfirm step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
474    CloseConfirm { channel: IbcChannel }, // pub channel: IbcChannel,
475}
476
477impl IbcChannelCloseMsg {
478    pub fn new_init(channel: IbcChannel) -> Self {
479        Self::CloseInit { channel }
480    }
481
482    pub fn new_confirm(channel: IbcChannel) -> Self {
483        Self::CloseConfirm { channel }
484    }
485
486    pub fn channel(&self) -> &IbcChannel {
487        match self {
488            Self::CloseInit { channel } => channel,
489            Self::CloseConfirm { channel } => channel,
490        }
491    }
492}
493
494impl From<IbcChannelCloseMsg> for IbcChannel {
495    fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
496        match msg {
497            IbcChannelCloseMsg::CloseInit { channel } => channel,
498            IbcChannelCloseMsg::CloseConfirm { channel } => channel,
499        }
500    }
501}
502
503/// The message that is passed into `ibc_packet_receive`
504#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
505#[non_exhaustive]
506pub struct IbcPacketReceiveMsg {
507    pub packet: IbcPacket,
508    pub relayer: Addr,
509}
510
511impl IbcPacketReceiveMsg {
512    pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
513        Self { packet, relayer }
514    }
515}
516
517/// The message that is passed into `ibc_packet_ack`
518#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
519#[non_exhaustive]
520pub struct IbcPacketAckMsg {
521    pub acknowledgement: IbcAcknowledgement,
522    pub original_packet: IbcPacket,
523    pub relayer: Addr,
524}
525
526impl IbcPacketAckMsg {
527    pub fn new(
528        acknowledgement: IbcAcknowledgement,
529        original_packet: IbcPacket,
530        relayer: Addr,
531    ) -> Self {
532        Self {
533            acknowledgement,
534            original_packet,
535            relayer,
536        }
537    }
538}
539
540/// The message that is passed into `ibc_packet_timeout`
541#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
542#[non_exhaustive]
543pub struct IbcPacketTimeoutMsg {
544    pub packet: IbcPacket,
545    pub relayer: Addr,
546}
547
548impl IbcPacketTimeoutMsg {
549    pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
550        Self { packet, relayer }
551    }
552}
553
554/// This is the return value for the majority of the ibc handlers.
555/// That are able to dispatch messages / events on their own,
556/// but have no meaningful return value to the calling code.
557///
558/// Callbacks that have return values (like receive_packet)
559/// or that cannot redispatch messages (like the handshake callbacks)
560/// will use other Response types
561#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
562#[non_exhaustive]
563pub struct IbcBasicResponse<T = Empty> {
564    /// Optional list of messages to pass. These will be executed in order.
565    /// If the ReplyOn member is set, they will invoke this contract's `reply` entry point
566    /// after execution. Otherwise, they act like "fire and forget".
567    /// Use `SubMsg::new` to create messages with the older "fire and forget" semantics.
568    pub messages: Vec<SubMsg<T>>,
569    /// The attributes that will be emitted as part of a `wasm` event.
570    ///
571    /// More info about events (and their attributes) can be found in [*Cosmos SDK* docs].
572    ///
573    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
574    pub attributes: Vec<Attribute>,
575    /// Extra, custom events separate from the main `wasm` one. These will have
576    /// `wasm-` prepended to the type.
577    ///
578    /// More info about events can be found in [*Cosmos SDK* docs].
579    ///
580    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
581    pub events: Vec<Event>,
582}
583
584// Custom implementation in order to implement it for all `T`, even if `T` is not `Default`.
585impl<T> Default for IbcBasicResponse<T> {
586    fn default() -> Self {
587        IbcBasicResponse {
588            messages: vec![],
589            attributes: vec![],
590            events: vec![],
591        }
592    }
593}
594
595impl<T> IbcBasicResponse<T> {
596    pub fn new() -> Self {
597        Self::default()
598    }
599
600    /// Add an attribute included in the main `wasm` event.
601    pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
602        self.attributes.push(Attribute::new(key, value));
603        self
604    }
605
606    /// This creates a "fire and forget" message, by using `SubMsg::new()` to wrap it,
607    /// and adds it to the list of messages to process.
608    pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
609        self.messages.push(SubMsg::new(msg));
610        self
611    }
612
613    /// This takes an explicit SubMsg (creates via e.g. `reply_on_error`)
614    /// and adds it to the list of messages to process.
615    pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
616        self.messages.push(msg);
617        self
618    }
619
620    /// Adds an extra event to the response, separate from the main `wasm` event
621    /// that is always created.
622    ///
623    /// The `wasm-` prefix will be appended by the runtime to the provided type
624    /// of event.
625    pub fn add_event(mut self, event: Event) -> Self {
626        self.events.push(event);
627        self
628    }
629
630    /// Bulk add attributes included in the main `wasm` event.
631    ///
632    /// Anything that can be turned into an iterator and yields something
633    /// that can be converted into an `Attribute` is accepted.
634    ///
635    /// ## Examples
636    ///
637    /// ```
638    /// use cosmwasm_std::{attr, IbcBasicResponse};
639    ///
640    /// let attrs = vec![
641    ///     ("action", "reaction"),
642    ///     ("answer", "42"),
643    ///     ("another", "attribute"),
644    /// ];
645    /// let res: IbcBasicResponse = IbcBasicResponse::new().add_attributes(attrs.clone());
646    /// assert_eq!(res.attributes, attrs);
647    /// ```
648    pub fn add_attributes<A: Into<Attribute>>(
649        mut self,
650        attrs: impl IntoIterator<Item = A>,
651    ) -> Self {
652        self.attributes.extend(attrs.into_iter().map(A::into));
653        self
654    }
655
656    /// Bulk add "fire and forget" messages to the list of messages to process.
657    ///
658    /// ## Examples
659    ///
660    /// ```
661    /// use cosmwasm_std::{CosmosMsg, IbcBasicResponse};
662    ///
663    /// fn make_response_with_msgs(msgs: Vec<CosmosMsg>) -> IbcBasicResponse {
664    ///     IbcBasicResponse::new().add_messages(msgs)
665    /// }
666    /// ```
667    pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
668        self.add_submessages(msgs.into_iter().map(SubMsg::new))
669    }
670
671    /// Bulk add explicit SubMsg structs to the list of messages to process.
672    ///
673    /// ## Examples
674    ///
675    /// ```
676    /// use cosmwasm_std::{SubMsg, IbcBasicResponse};
677    ///
678    /// fn make_response_with_submsgs(msgs: Vec<SubMsg>) -> IbcBasicResponse {
679    ///     IbcBasicResponse::new().add_submessages(msgs)
680    /// }
681    /// ```
682    pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
683        self.messages.extend(msgs);
684        self
685    }
686
687    /// Bulk add custom events to the response. These are separate from the main
688    /// `wasm` event.
689    ///
690    /// The `wasm-` prefix will be appended by the runtime to the provided types
691    /// of events.
692    pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
693        self.events.extend(events);
694        self
695    }
696}
697
698/// This defines the return value on packet response processing.
699/// This "success" case should be returned even in application-level errors,
700/// Where the acknowledgement bytes contain an encoded error message to be returned to
701/// the calling chain. (Returning ContractResult::Err will abort processing of this packet
702/// and not inform the calling chain).
703#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
704#[non_exhaustive]
705pub struct IbcReceiveResponse<T = Empty> {
706    /// The bytes we return to the contract that sent the packet.
707    /// This may represent a success or error of execution.
708    /// In case of `None`, no acknowledgement is written.
709    pub acknowledgement: Option<Binary>,
710    /// Optional list of messages to pass. These will be executed in order.
711    /// If the ReplyOn member is set, they will invoke this contract's `reply` entry point
712    /// after execution. Otherwise, they act like "fire and forget".
713    /// Use `call` or `msg.into()` to create messages with the older "fire and forget" semantics.
714    pub messages: Vec<SubMsg<T>>,
715    /// The attributes that will be emitted as part of a "wasm" event.
716    ///
717    /// More info about events (and their attributes) can be found in [*Cosmos SDK* docs].
718    ///
719    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
720    pub attributes: Vec<Attribute>,
721    /// Extra, custom events separate from the main `wasm` one. These will have
722    /// `wasm-` prepended to the type.
723    ///
724    /// More info about events can be found in [*Cosmos SDK* docs].
725    ///
726    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
727    pub events: Vec<Event>,
728}
729
730impl<T> IbcReceiveResponse<T> {
731    /// Create a new response with the given acknowledgement.
732    ///
733    /// ## Examples
734    ///
735    /// ```
736    /// # use cosmwasm_std::{
737    /// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
738    /// #     Never, Response, QueryResponse, StdAck, IbcPacketReceiveMsg
739    /// # };
740    /// use cosmwasm_std::IbcReceiveResponse;
741    ///
742    /// #[entry_point]
743    /// pub fn ibc_packet_receive(
744    ///     deps: DepsMut,
745    ///     env: Env,
746    ///     msg: IbcPacketReceiveMsg,
747    /// ) -> Result<IbcReceiveResponse, Never> {
748    ///     // ...
749    ///
750    ///     // 0x01 is a FungibleTokenPacketSuccess from ICS-20.
751    ///     Ok(IbcReceiveResponse::new(StdAck::success(b"\x01")))
752    /// }
753    /// ```
754    pub fn new(ack: impl Into<Binary>) -> Self {
755        Self {
756            acknowledgement: Some(ack.into()),
757            messages: vec![],
758            attributes: vec![],
759            events: vec![],
760        }
761    }
762
763    /// Creates a new response without an acknowledgement.
764    ///
765    /// This allows you to send the acknowledgement asynchronously later using
766    /// [`IbcMsg::WriteAcknowledgement`][self::IbcMsg#variant.WriteAcknowledgement].
767    /// If you want to send the acknowledgement immediately, use [`IbcReceiveResponse::new`].
768    ///
769    /// ## Examples
770    ///
771    /// ```
772    /// # use cosmwasm_std::{
773    /// #     Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo,
774    /// #     Never, Response, QueryResponse, StdAck, IbcPacketReceiveMsg
775    /// # };
776    /// use cosmwasm_std::IbcReceiveResponse;
777    ///
778    /// #[entry_point]
779    /// pub fn ibc_packet_receive(
780    ///     deps: DepsMut,
781    ///     env: Env,
782    ///     msg: IbcPacketReceiveMsg,
783    /// ) -> Result<IbcReceiveResponse, Never> {
784    ///     // ...
785    ///
786    ///     Ok(IbcReceiveResponse::without_ack())
787    /// }
788    /// ```
789    pub fn without_ack() -> Self {
790        Self {
791            acknowledgement: None,
792            messages: vec![],
793            attributes: vec![],
794            events: vec![],
795        }
796    }
797
798    /// Add an attribute included in the main `wasm` event.
799    pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
800        self.attributes.push(Attribute::new(key, value));
801        self
802    }
803
804    /// This creates a "fire and forget" message, by using `SubMsg::new()` to wrap it,
805    /// and adds it to the list of messages to process.
806    pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
807        self.messages.push(SubMsg::new(msg));
808        self
809    }
810
811    /// This takes an explicit SubMsg (creates via e.g. `reply_on_error`)
812    /// and adds it to the list of messages to process.
813    pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
814        self.messages.push(msg);
815        self
816    }
817
818    /// Adds an extra event to the response, separate from the main `wasm` event
819    /// that is always created.
820    ///
821    /// The `wasm-` prefix will be appended by the runtime to the provided type
822    /// of event.
823    pub fn add_event(mut self, event: Event) -> Self {
824        self.events.push(event);
825        self
826    }
827
828    /// Bulk add attributes included in the main `wasm` event.
829    ///
830    /// Anything that can be turned into an iterator and yields something
831    /// that can be converted into an `Attribute` is accepted.
832    ///
833    /// ## Examples
834    ///
835    /// ```
836    /// use cosmwasm_std::{attr, IbcReceiveResponse, StdAck};
837    ///
838    /// let attrs = vec![
839    ///     ("action", "reaction"),
840    ///     ("answer", "42"),
841    ///     ("another", "attribute"),
842    /// ];
843    /// let res: IbcReceiveResponse = IbcReceiveResponse::new(StdAck::success(b"\x01")).add_attributes(attrs.clone());
844    /// assert_eq!(res.attributes, attrs);
845    /// ```
846    pub fn add_attributes<A: Into<Attribute>>(
847        mut self,
848        attrs: impl IntoIterator<Item = A>,
849    ) -> Self {
850        self.attributes.extend(attrs.into_iter().map(A::into));
851        self
852    }
853
854    /// Bulk add "fire and forget" messages to the list of messages to process.
855    ///
856    /// ## Examples
857    ///
858    /// ```
859    /// use cosmwasm_std::{CosmosMsg, IbcReceiveResponse, StdAck};
860    ///
861    /// fn make_response_with_msgs(msgs: Vec<CosmosMsg>) -> IbcReceiveResponse {
862    ///     IbcReceiveResponse::new(StdAck::success(b"\x01")).add_messages(msgs)
863    /// }
864    /// ```
865    pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
866        self.add_submessages(msgs.into_iter().map(SubMsg::new))
867    }
868
869    /// Bulk add explicit SubMsg structs to the list of messages to process.
870    ///
871    /// ## Examples
872    ///
873    /// ```
874    /// use cosmwasm_std::{SubMsg, StdAck, IbcReceiveResponse};
875    ///
876    /// fn make_response_with_submsgs(msgs: Vec<SubMsg>) -> IbcReceiveResponse {
877    ///     IbcReceiveResponse::new(StdAck::success(b"\x01")).add_submessages(msgs)
878    /// }
879    /// ```
880    pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
881        self.messages.extend(msgs);
882        self
883    }
884
885    /// Bulk add custom events to the response. These are separate from the main
886    /// `wasm` event.
887    ///
888    /// The `wasm-` prefix will be appended by the runtime to the provided types
889    /// of events.
890    pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
891        self.events.extend(events);
892        self
893    }
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899    use serde_json_wasm::to_string;
900
901    #[test]
902    // added this to check json format for go compat, as I was unsure how some messages are snake encoded
903    fn serialize_msg() {
904        let msg = IbcMsg::Transfer {
905            channel_id: "channel-123".to_string(),
906            to_address: "my-special-addr".into(),
907            amount: Coin::new(12345678u128, "uatom"),
908            timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
909            memo: None,
910        };
911        let encoded = to_string(&msg).unwrap();
912        let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"},"memo":null}}"#;
913        assert_eq!(encoded.as_str(), expected);
914    }
915
916    #[test]
917    fn ibc_timeout_serialize() {
918        let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
919        let expected = r#"{"block":null,"timestamp":"684816844"}"#;
920        assert_eq!(to_string(&timestamp).unwrap(), expected);
921
922        let block = IbcTimeout::with_block(IbcTimeoutBlock {
923            revision: 12,
924            height: 129,
925        });
926        let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
927        assert_eq!(to_string(&block).unwrap(), expected);
928
929        let both = IbcTimeout::with_both(
930            IbcTimeoutBlock {
931                revision: 12,
932                height: 129,
933            },
934            Timestamp::from_nanos(684816844),
935        );
936        let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
937        assert_eq!(to_string(&both).unwrap(), expected);
938    }
939
940    #[test]
941    #[allow(clippy::eq_op)]
942    fn ibc_timeout_block_ord() {
943        let epoch1a = IbcTimeoutBlock {
944            revision: 1,
945            height: 1000,
946        };
947        let epoch1b = IbcTimeoutBlock {
948            revision: 1,
949            height: 3000,
950        };
951        let epoch2a = IbcTimeoutBlock {
952            revision: 2,
953            height: 500,
954        };
955        let epoch2b = IbcTimeoutBlock {
956            revision: 2,
957            height: 2500,
958        };
959
960        // basic checks
961        assert_eq!(epoch1a, epoch1a);
962        assert!(epoch1a < epoch1b);
963        assert!(epoch1b > epoch1a);
964        assert!(epoch2a > epoch1a);
965        assert!(epoch2b > epoch1a);
966
967        // ensure epoch boundaries are correctly handled
968        assert!(epoch1b > epoch1a);
969        assert!(epoch2a > epoch1b);
970        assert!(epoch2b > epoch2a);
971        assert!(epoch2b > epoch1b);
972        // and check the inverse compare
973        assert!(epoch1a < epoch1b);
974        assert!(epoch1b < epoch2a);
975        assert!(epoch2a < epoch2b);
976        assert!(epoch1b < epoch2b);
977    }
978
979    #[test]
980    fn ibc_packet_serialize() {
981        let packet = IbcPacket {
982            data: b"foo".into(),
983            src: IbcEndpoint {
984                port_id: "their-port".to_string(),
985                channel_id: "channel-1234".to_string(),
986            },
987            dest: IbcEndpoint {
988                port_id: "our-port".to_string(),
989                channel_id: "chan33".into(),
990            },
991            sequence: 27,
992            timeout: IbcTimeout::with_both(
993                IbcTimeoutBlock {
994                    revision: 1,
995                    height: 12345678,
996                },
997                Timestamp::from_nanos(4611686018427387904),
998            ),
999        };
1000        let expected = r#"{"data":"Zm9v","src":{"port_id":"their-port","channel_id":"channel-1234"},"dest":{"port_id":"our-port","channel_id":"chan33"},"sequence":27,"timeout":{"block":{"revision":1,"height":12345678},"timestamp":"4611686018427387904"}}"#;
1001        assert_eq!(to_string(&packet).unwrap(), expected);
1002
1003        let no_timestamp = IbcPacket {
1004            data: b"foo".into(),
1005            src: IbcEndpoint {
1006                port_id: "their-port".to_string(),
1007                channel_id: "channel-1234".to_string(),
1008            },
1009            dest: IbcEndpoint {
1010                port_id: "our-port".to_string(),
1011                channel_id: "chan33".into(),
1012            },
1013            sequence: 27,
1014            timeout: IbcTimeout::with_block(IbcTimeoutBlock {
1015                revision: 1,
1016                height: 12345678,
1017            }),
1018        };
1019        let expected = r#"{"data":"Zm9v","src":{"port_id":"their-port","channel_id":"channel-1234"},"dest":{"port_id":"our-port","channel_id":"chan33"},"sequence":27,"timeout":{"block":{"revision":1,"height":12345678},"timestamp":null}}"#;
1020        assert_eq!(to_string(&no_timestamp).unwrap(), expected);
1021    }
1022}