cosmwasm_std/
ibc.rs

1// The CosmosMsg variants are defined in results/cosmos_msg.rs
2// The rest of the IBC related functionality is defined here
3
4use core::cmp::{Ord, Ordering, PartialOrd};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "ibc3")]
9use crate::addresses::Addr;
10use crate::binary::Binary;
11use crate::coin::Coin;
12use crate::errors::StdResult;
13use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
14use crate::serde::to_json_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, Eq, 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        /// existing 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, Eq, 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, Eq, 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, Eq, JsonSchema)]
118#[non_exhaustive]
119pub struct IbcChannel {
120    pub endpoint: IbcEndpoint,
121    pub counterparty_endpoint: IbcEndpoint,
122    pub order: IbcOrder,
123    /// Note: in ibcv3 this may be "", in the IbcOpenChannel handshake messages
124    pub version: String,
125    /// The connection upon which this channel was created. If this is a multi-hop
126    /// channel, we only expose the first hop.
127    pub connection_id: String,
128}
129
130impl IbcChannel {
131    /// Construct a new IbcChannel.
132    pub fn new(
133        endpoint: IbcEndpoint,
134        counterparty_endpoint: IbcEndpoint,
135        order: IbcOrder,
136        version: impl Into<String>,
137        connection_id: impl Into<String>,
138    ) -> Self {
139        Self {
140            endpoint,
141            counterparty_endpoint,
142            order,
143            version: version.into(),
144            connection_id: connection_id.into(),
145        }
146    }
147}
148
149/// IbcOrder defines if a channel is ORDERED or UNORDERED
150/// Values come from https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/core/channel/v1/channel.proto#L69-L80
151/// Naming comes from the protobuf files and go translations.
152#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
153pub enum IbcOrder {
154    #[serde(rename = "ORDER_UNORDERED")]
155    Unordered,
156    #[serde(rename = "ORDER_ORDERED")]
157    Ordered,
158}
159
160/// IBCTimeoutHeight Height is a monotonically increasing data type
161/// that can be compared against another Height for the purposes of updating and
162/// freezing clients.
163/// Ordering is (revision_number, timeout_height)
164#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
165pub struct IbcTimeoutBlock {
166    /// the version that the client is currently on
167    /// (e.g. after resetting the chain this could increment 1 as height drops to 0)
168    pub revision: u64,
169    /// block height after which the packet times out.
170    /// the height within the given revision
171    pub height: u64,
172}
173
174impl IbcTimeoutBlock {
175    pub fn is_zero(&self) -> bool {
176        self.revision == 0 && self.height == 0
177    }
178}
179
180impl PartialOrd for IbcTimeoutBlock {
181    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
182        Some(self.cmp(other))
183    }
184}
185
186impl Ord for IbcTimeoutBlock {
187    fn cmp(&self, other: &Self) -> Ordering {
188        match self.revision.cmp(&other.revision) {
189            Ordering::Equal => self.height.cmp(&other.height),
190            other => other,
191        }
192    }
193}
194
195#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
196#[non_exhaustive]
197pub struct IbcPacket {
198    /// The raw data sent from the other side in the packet
199    pub data: Binary,
200    /// identifies the channel and port on the sending chain.
201    pub src: IbcEndpoint,
202    /// identifies the channel and port on the receiving chain.
203    pub dest: IbcEndpoint,
204    /// The sequence number of the packet on the given channel
205    pub sequence: u64,
206    pub timeout: IbcTimeout,
207}
208
209impl IbcPacket {
210    /// Construct a new IbcPacket.
211    pub fn new(
212        data: impl Into<Binary>,
213        src: IbcEndpoint,
214        dest: IbcEndpoint,
215        sequence: u64,
216        timeout: IbcTimeout,
217    ) -> Self {
218        Self {
219            data: data.into(),
220            src,
221            dest,
222            sequence,
223            timeout,
224        }
225    }
226}
227
228#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
229#[non_exhaustive]
230pub struct IbcAcknowledgement {
231    pub data: Binary,
232    // we may add more info here in the future (meta-data from the acknowledgement)
233    // there have been proposals to extend this type in core ibc for future versions
234}
235
236impl IbcAcknowledgement {
237    pub fn new(data: impl Into<Binary>) -> Self {
238        IbcAcknowledgement { data: data.into() }
239    }
240
241    pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
242        Ok(IbcAcknowledgement {
243            data: to_json_binary(data)?,
244        })
245    }
246}
247
248/// The message that is passed into `ibc_channel_open`
249#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
250#[serde(rename_all = "snake_case")]
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/// Note that this serializes as "null".
301#[cfg(not(feature = "ibc3"))]
302pub type IbcChannelOpenResponse = ();
303/// This serializes either as "null" or a JSON object.
304#[cfg(feature = "ibc3")]
305pub type IbcChannelOpenResponse = Option<Ibc3ChannelOpenResponse>;
306
307#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
308pub struct Ibc3ChannelOpenResponse {
309    /// We can set the channel version to a different one than we were called with
310    pub version: String,
311}
312
313/// The message that is passed into `ibc_channel_connect`
314#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
315#[serde(rename_all = "snake_case")]
316pub enum IbcChannelConnectMsg {
317    /// The ChanOpenAck step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
318    OpenAck {
319        channel: IbcChannel,
320        counterparty_version: String,
321    },
322    /// The ChanOpenConfirm step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
323    OpenConfirm { channel: IbcChannel },
324}
325
326impl IbcChannelConnectMsg {
327    pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
328        Self::OpenAck {
329            channel,
330            counterparty_version: counterparty_version.into(),
331        }
332    }
333
334    pub fn new_confirm(channel: IbcChannel) -> Self {
335        Self::OpenConfirm { channel }
336    }
337
338    pub fn channel(&self) -> &IbcChannel {
339        match self {
340            Self::OpenAck { channel, .. } => channel,
341            Self::OpenConfirm { channel } => channel,
342        }
343    }
344
345    pub fn counterparty_version(&self) -> Option<&str> {
346        match self {
347            Self::OpenAck {
348                counterparty_version,
349                ..
350            } => Some(counterparty_version),
351            _ => None,
352        }
353    }
354}
355
356impl From<IbcChannelConnectMsg> for IbcChannel {
357    fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
358        match msg {
359            IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
360            IbcChannelConnectMsg::OpenConfirm { channel } => channel,
361        }
362    }
363}
364
365/// The message that is passed into `ibc_channel_close`
366#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
367#[serde(rename_all = "snake_case")]
368pub enum IbcChannelCloseMsg {
369    /// The ChanCloseInit step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
370    CloseInit { channel: IbcChannel },
371    /// The ChanCloseConfirm step from https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management
372    CloseConfirm { channel: IbcChannel }, // pub channel: IbcChannel,
373}
374
375impl IbcChannelCloseMsg {
376    pub fn new_init(channel: IbcChannel) -> Self {
377        Self::CloseInit { channel }
378    }
379
380    pub fn new_confirm(channel: IbcChannel) -> Self {
381        Self::CloseConfirm { channel }
382    }
383
384    pub fn channel(&self) -> &IbcChannel {
385        match self {
386            Self::CloseInit { channel } => channel,
387            Self::CloseConfirm { channel } => channel,
388        }
389    }
390}
391
392impl From<IbcChannelCloseMsg> for IbcChannel {
393    fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
394        match msg {
395            IbcChannelCloseMsg::CloseInit { channel } => channel,
396            IbcChannelCloseMsg::CloseConfirm { channel } => channel,
397        }
398    }
399}
400
401/// The message that is passed into `ibc_packet_receive`
402#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
403#[non_exhaustive]
404pub struct IbcPacketReceiveMsg {
405    pub packet: IbcPacket,
406    #[cfg(feature = "ibc3")]
407    pub relayer: Addr,
408}
409
410impl IbcPacketReceiveMsg {
411    #[cfg(not(feature = "ibc3"))]
412    pub fn new(packet: IbcPacket) -> Self {
413        Self { packet }
414    }
415
416    #[cfg(feature = "ibc3")]
417    pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
418        Self { packet, relayer }
419    }
420}
421
422/// The message that is passed into `ibc_packet_ack`
423#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
424#[non_exhaustive]
425pub struct IbcPacketAckMsg {
426    pub acknowledgement: IbcAcknowledgement,
427    pub original_packet: IbcPacket,
428    #[cfg(feature = "ibc3")]
429    pub relayer: Addr,
430}
431
432impl IbcPacketAckMsg {
433    #[cfg(not(feature = "ibc3"))]
434    pub fn new(acknowledgement: IbcAcknowledgement, original_packet: IbcPacket) -> Self {
435        Self {
436            acknowledgement,
437            original_packet,
438        }
439    }
440
441    #[cfg(feature = "ibc3")]
442    pub fn new(
443        acknowledgement: IbcAcknowledgement,
444        original_packet: IbcPacket,
445        relayer: Addr,
446    ) -> Self {
447        Self {
448            acknowledgement,
449            original_packet,
450            relayer,
451        }
452    }
453}
454
455/// The message that is passed into `ibc_packet_timeout`
456#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
457#[non_exhaustive]
458pub struct IbcPacketTimeoutMsg {
459    pub packet: IbcPacket,
460    #[cfg(feature = "ibc3")]
461    pub relayer: Addr,
462}
463
464impl IbcPacketTimeoutMsg {
465    #[cfg(not(feature = "ibc3"))]
466    pub fn new(packet: IbcPacket) -> Self {
467        Self { packet }
468    }
469
470    #[cfg(feature = "ibc3")]
471    pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
472        Self { packet, relayer }
473    }
474}
475
476/// This is the return value for the majority of the ibc handlers.
477/// That are able to dispatch messages / events on their own,
478/// but have no meaningful return value to the calling code.
479///
480/// Callbacks that have return values (like receive_packet)
481/// or that cannot redispatch messages (like the handshake callbacks)
482/// will use other Response types
483#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
484#[non_exhaustive]
485pub struct IbcBasicResponse<T = Empty> {
486    /// Optional list of messages to pass. These will be executed in order.
487    /// If the ReplyOn member is set, they will invoke this contract's `reply` entry point
488    /// after execution. Otherwise, they act like "fire and forget".
489    /// Use `SubMsg::new` to create messages with the older "fire and forget" semantics.
490    pub messages: Vec<SubMsg<T>>,
491    /// The attributes that will be emitted as part of a `wasm` event.
492    ///
493    /// More info about events (and their attributes) can be found in [*Cosmos SDK* docs].
494    ///
495    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
496    pub attributes: Vec<Attribute>,
497    /// Extra, custom events separate from the main `wasm` one. These will have
498    /// `wasm-` prepended to the type.
499    ///
500    /// More info about events can be found in [*Cosmos SDK* docs].
501    ///
502    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
503    pub events: Vec<Event>,
504}
505
506// Custom implementation in order to implement it for all `T`, even if `T` is not `Default`.
507impl<T> Default for IbcBasicResponse<T> {
508    fn default() -> Self {
509        IbcBasicResponse {
510            messages: vec![],
511            attributes: vec![],
512            events: vec![],
513        }
514    }
515}
516
517impl<T> IbcBasicResponse<T> {
518    pub fn new() -> Self {
519        Self::default()
520    }
521
522    /// Add an attribute included in the main `wasm` event.
523    pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
524        self.attributes.push(Attribute::new(key, value));
525        self
526    }
527
528    /// This creates a "fire and forget" message, by using `SubMsg::new()` to wrap it,
529    /// and adds it to the list of messages to process.
530    pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
531        self.messages.push(SubMsg::new(msg));
532        self
533    }
534
535    /// This takes an explicit SubMsg (creates via e.g. `reply_on_error`)
536    /// and adds it to the list of messages to process.
537    pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
538        self.messages.push(msg);
539        self
540    }
541
542    /// Adds an extra event to the response, separate from the main `wasm` event
543    /// that is always created.
544    ///
545    /// The `wasm-` prefix will be appended by the runtime to the provided type
546    /// of event.
547    pub fn add_event(mut self, event: Event) -> Self {
548        self.events.push(event);
549        self
550    }
551
552    /// Bulk add attributes included in the main `wasm` event.
553    ///
554    /// Anything that can be turned into an iterator and yields something
555    /// that can be converted into an `Attribute` is accepted.
556    ///
557    /// ## Examples
558    ///
559    /// ```
560    /// use cosmwasm_std::{attr, IbcBasicResponse};
561    ///
562    /// let attrs = vec![
563    ///     ("action", "reaction"),
564    ///     ("answer", "42"),
565    ///     ("another", "attribute"),
566    /// ];
567    /// let res: IbcBasicResponse = IbcBasicResponse::new().add_attributes(attrs.clone());
568    /// assert_eq!(res.attributes, attrs);
569    /// ```
570    pub fn add_attributes<A: Into<Attribute>>(
571        mut self,
572        attrs: impl IntoIterator<Item = A>,
573    ) -> Self {
574        self.attributes.extend(attrs.into_iter().map(A::into));
575        self
576    }
577
578    /// Bulk add "fire and forget" messages to the list of messages to process.
579    ///
580    /// ## Examples
581    ///
582    /// ```
583    /// use cosmwasm_std::{CosmosMsg, IbcBasicResponse};
584    ///
585    /// fn make_response_with_msgs(msgs: Vec<CosmosMsg>) -> IbcBasicResponse {
586    ///     IbcBasicResponse::new().add_messages(msgs)
587    /// }
588    /// ```
589    pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
590        self.add_submessages(msgs.into_iter().map(SubMsg::new))
591    }
592
593    /// Bulk add explicit SubMsg structs to the list of messages to process.
594    ///
595    /// ## Examples
596    ///
597    /// ```
598    /// use cosmwasm_std::{SubMsg, IbcBasicResponse};
599    ///
600    /// fn make_response_with_submsgs(msgs: Vec<SubMsg>) -> IbcBasicResponse {
601    ///     IbcBasicResponse::new().add_submessages(msgs)
602    /// }
603    /// ```
604    pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
605        self.messages.extend(msgs);
606        self
607    }
608
609    /// Bulk add custom events to the response. These are separate from the main
610    /// `wasm` event.
611    ///
612    /// The `wasm-` prefix will be appended by the runtime to the provided types
613    /// of events.
614    pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
615        self.events.extend(events);
616        self
617    }
618}
619
620/// This defines the return value on packet response processing.
621/// This "success" case should be returned even in application-level errors,
622/// Where the acknowledgement bytes contain an encoded error message to be returned to
623/// the calling chain. (Returning ContractResult::Err will abort processing of this packet
624/// and not inform the calling chain).
625#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
626#[non_exhaustive]
627pub struct IbcReceiveResponse<T = Empty> {
628    /// The bytes we return to the contract that sent the packet.
629    /// This may represent a success or error of execution
630    pub acknowledgement: Binary,
631    /// Optional list of messages to pass. These will be executed in order.
632    /// If the ReplyOn member is set, they will invoke this contract's `reply` entry point
633    /// after execution. Otherwise, they act like "fire and forget".
634    /// Use `call` or `msg.into()` to create messages with the older "fire and forget" semantics.
635    pub messages: Vec<SubMsg<T>>,
636    /// The attributes that will be emitted as part of a "wasm" event.
637    ///
638    /// More info about events (and their attributes) can be found in [*Cosmos SDK* docs].
639    ///
640    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
641    pub attributes: Vec<Attribute>,
642    /// Extra, custom events separate from the main `wasm` one. These will have
643    /// `wasm-` prepended to the type.
644    ///
645    /// More info about events can be found in [*Cosmos SDK* docs].
646    ///
647    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
648    pub events: Vec<Event>,
649}
650
651// Custom implementation in order to implement it for all `T`, even if `T` is not `Default`.
652impl<T> Default for IbcReceiveResponse<T> {
653    fn default() -> Self {
654        IbcReceiveResponse {
655            acknowledgement: Binary(vec![]),
656            messages: vec![],
657            attributes: vec![],
658            events: vec![],
659        }
660    }
661}
662
663impl<T> IbcReceiveResponse<T> {
664    pub fn new() -> Self {
665        Self::default()
666    }
667
668    /// Set the acknowledgement for this response.
669    ///
670    /// ## Examples
671    ///
672    /// ```
673    /// use cosmwasm_std::{StdAck, IbcReceiveResponse};
674    ///
675    /// fn make_response_with_ack() -> IbcReceiveResponse {
676    ///     let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20.
677    ///     IbcReceiveResponse::new().set_ack(ack)
678    /// }
679    /// ```
680    pub fn set_ack(mut self, ack: impl Into<Binary>) -> Self {
681        self.acknowledgement = ack.into();
682        self
683    }
684
685    /// Add an attribute included in the main `wasm` event.
686    pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
687        self.attributes.push(Attribute::new(key, value));
688        self
689    }
690
691    /// This creates a "fire and forget" message, by using `SubMsg::new()` to wrap it,
692    /// and adds it to the list of messages to process.
693    pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
694        self.messages.push(SubMsg::new(msg));
695        self
696    }
697
698    /// This takes an explicit SubMsg (creates via e.g. `reply_on_error`)
699    /// and adds it to the list of messages to process.
700    pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
701        self.messages.push(msg);
702        self
703    }
704
705    /// Adds an extra event to the response, separate from the main `wasm` event
706    /// that is always created.
707    ///
708    /// The `wasm-` prefix will be appended by the runtime to the provided type
709    /// of event.
710    pub fn add_event(mut self, event: Event) -> Self {
711        self.events.push(event);
712        self
713    }
714
715    /// Bulk add attributes included in the main `wasm` event.
716    ///
717    /// Anything that can be turned into an iterator and yields something
718    /// that can be converted into an `Attribute` is accepted.
719    ///
720    /// ## Examples
721    ///
722    /// ```
723    /// use cosmwasm_std::{attr, IbcReceiveResponse};
724    ///
725    /// let attrs = vec![
726    ///     ("action", "reaction"),
727    ///     ("answer", "42"),
728    ///     ("another", "attribute"),
729    /// ];
730    /// let res: IbcReceiveResponse = IbcReceiveResponse::new().add_attributes(attrs.clone());
731    /// assert_eq!(res.attributes, attrs);
732    /// ```
733    pub fn add_attributes<A: Into<Attribute>>(
734        mut self,
735        attrs: impl IntoIterator<Item = A>,
736    ) -> Self {
737        self.attributes.extend(attrs.into_iter().map(A::into));
738        self
739    }
740
741    /// Bulk add "fire and forget" messages to the list of messages to process.
742    ///
743    /// ## Examples
744    ///
745    /// ```
746    /// use cosmwasm_std::{CosmosMsg, IbcReceiveResponse};
747    ///
748    /// fn make_response_with_msgs(msgs: Vec<CosmosMsg>) -> IbcReceiveResponse {
749    ///     IbcReceiveResponse::new().add_messages(msgs)
750    /// }
751    /// ```
752    pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
753        self.add_submessages(msgs.into_iter().map(SubMsg::new))
754    }
755
756    /// Bulk add explicit SubMsg structs to the list of messages to process.
757    ///
758    /// ## Examples
759    ///
760    /// ```
761    /// use cosmwasm_std::{SubMsg, IbcReceiveResponse};
762    ///
763    /// fn make_response_with_submsgs(msgs: Vec<SubMsg>) -> IbcReceiveResponse {
764    ///     IbcReceiveResponse::new().add_submessages(msgs)
765    /// }
766    /// ```
767    pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
768        self.messages.extend(msgs);
769        self
770    }
771
772    /// Bulk add custom events to the response. These are separate from the main
773    /// `wasm` event.
774    ///
775    /// The `wasm-` prefix will be appended by the runtime to the provided types
776    /// of events.
777    pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
778        self.events.extend(events);
779        self
780    }
781}
782
783#[cfg(test)]
784mod tests {
785    use super::*;
786    use serde_json_wasm::to_string;
787
788    #[test]
789    // added this to check json format for go compat, as I was unsure how some messages are snake encoded
790    fn serialize_msg() {
791        let msg = IbcMsg::Transfer {
792            channel_id: "channel-123".to_string(),
793            to_address: "my-special-addr".into(),
794            amount: Coin::new(12345678, "uatom"),
795            timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
796        };
797        let encoded = to_string(&msg).unwrap();
798        let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"}}}"#;
799        assert_eq!(encoded.as_str(), expected);
800    }
801
802    #[test]
803    fn ibc_timeout_serialize() {
804        let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
805        let expected = r#"{"block":null,"timestamp":"684816844"}"#;
806        assert_eq!(to_string(&timestamp).unwrap(), expected);
807
808        let block = IbcTimeout::with_block(IbcTimeoutBlock {
809            revision: 12,
810            height: 129,
811        });
812        let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
813        assert_eq!(to_string(&block).unwrap(), expected);
814
815        let both = IbcTimeout::with_both(
816            IbcTimeoutBlock {
817                revision: 12,
818                height: 129,
819            },
820            Timestamp::from_nanos(684816844),
821        );
822        let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
823        assert_eq!(to_string(&both).unwrap(), expected);
824    }
825
826    #[test]
827    #[allow(clippy::eq_op)]
828    fn ibc_timeout_block_ord() {
829        let epoch1a = IbcTimeoutBlock {
830            revision: 1,
831            height: 1000,
832        };
833        let epoch1b = IbcTimeoutBlock {
834            revision: 1,
835            height: 3000,
836        };
837        let epoch2a = IbcTimeoutBlock {
838            revision: 2,
839            height: 500,
840        };
841        let epoch2b = IbcTimeoutBlock {
842            revision: 2,
843            height: 2500,
844        };
845
846        // basic checks
847        assert_eq!(epoch1a, epoch1a);
848        assert!(epoch1a < epoch1b);
849        assert!(epoch1b > epoch1a);
850        assert!(epoch2a > epoch1a);
851        assert!(epoch2b > epoch1a);
852
853        // ensure epoch boundaries are correctly handled
854        assert!(epoch1b > epoch1a);
855        assert!(epoch2a > epoch1b);
856        assert!(epoch2b > epoch2a);
857        assert!(epoch2b > epoch1b);
858        // and check the inverse compare
859        assert!(epoch1a < epoch1b);
860        assert!(epoch1b < epoch2a);
861        assert!(epoch2a < epoch2b);
862        assert!(epoch1b < epoch2b);
863    }
864
865    #[test]
866    fn ibc_packet_serialize() {
867        let packet = IbcPacket {
868            data: b"foo".into(),
869            src: IbcEndpoint {
870                port_id: "their-port".to_string(),
871                channel_id: "channel-1234".to_string(),
872            },
873            dest: IbcEndpoint {
874                port_id: "our-port".to_string(),
875                channel_id: "chan33".into(),
876            },
877            sequence: 27,
878            timeout: IbcTimeout::with_both(
879                IbcTimeoutBlock {
880                    revision: 1,
881                    height: 12345678,
882                },
883                Timestamp::from_nanos(4611686018427387904),
884            ),
885        };
886        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"}}"#;
887        assert_eq!(to_string(&packet).unwrap(), expected);
888
889        let no_timestamp = IbcPacket {
890            data: b"foo".into(),
891            src: IbcEndpoint {
892                port_id: "their-port".to_string(),
893                channel_id: "channel-1234".to_string(),
894            },
895            dest: IbcEndpoint {
896                port_id: "our-port".to_string(),
897                channel_id: "chan33".into(),
898            },
899            sequence: 27,
900            timeout: IbcTimeout::with_block(IbcTimeoutBlock {
901                revision: 1,
902                height: 12345678,
903            }),
904        };
905        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}}"#;
906        assert_eq!(to_string(&no_timestamp).unwrap(), expected);
907    }
908}