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