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