cosmwasm_std/
ibc.rs

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