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