jk_cosmwasm_std/
ibc.rs

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