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