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