cosmwasm_std/
ibc.rs

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