secret_cosmwasm_std/
ibc.rs

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