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