1#![cfg(feature = "stargate")]
2use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::cmp::{Ord, Ordering, PartialOrd};
8
9use crate::binary::Binary;
10use crate::coins::Coin;
11use crate::errors::StdResult;
12use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
13use crate::serde::to_binary;
14use crate::timestamp::Timestamp;
15
16#[non_exhaustive]
19#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
20#[serde(rename_all = "snake_case")]
21pub enum IbcMsg {
22 Transfer {
28 channel_id: String,
30 to_address: String,
32 amount: Coin,
35 timeout: IbcTimeout,
37 },
38 SendPacket {
42 channel_id: String,
43 data: Binary,
44 timeout: IbcTimeout,
46 },
47 CloseChannel { channel_id: String },
50}
51
52#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
53pub struct IbcEndpoint {
54 pub port_id: String,
55 pub channel_id: String,
56}
57
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
62#[serde(rename_all = "snake_case")]
63pub struct IbcTimeout {
64 block: Option<IbcTimeoutBlock>,
66 timestamp: Option<Timestamp>,
67}
68
69impl IbcTimeout {
70 pub fn with_block(block: IbcTimeoutBlock) -> Self {
71 IbcTimeout {
72 block: Some(block),
73 timestamp: None,
74 }
75 }
76
77 pub fn with_timestamp(timestamp: Timestamp) -> Self {
78 IbcTimeout {
79 block: None,
80 timestamp: Some(timestamp),
81 }
82 }
83
84 pub fn with_both(block: IbcTimeoutBlock, timestamp: Timestamp) -> Self {
85 IbcTimeout {
86 block: Some(block),
87 timestamp: Some(timestamp),
88 }
89 }
90
91 pub fn block(&self) -> Option<IbcTimeoutBlock> {
92 self.block
93 }
94
95 pub fn timestamp(&self) -> Option<Timestamp> {
96 self.timestamp
97 }
98}
99
100impl From<Timestamp> for IbcTimeout {
101 fn from(timestamp: Timestamp) -> IbcTimeout {
102 IbcTimeout::with_timestamp(timestamp)
103 }
104}
105
106impl From<IbcTimeoutBlock> for IbcTimeout {
107 fn from(original: IbcTimeoutBlock) -> IbcTimeout {
108 IbcTimeout::with_block(original)
109 }
110}
111
112#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
117#[non_exhaustive]
118pub struct IbcChannel {
119 pub endpoint: IbcEndpoint,
120 pub counterparty_endpoint: IbcEndpoint,
121 pub order: IbcOrder,
122 pub version: String,
123 pub connection_id: String,
126}
127
128impl IbcChannel {
129 pub fn new(
131 endpoint: IbcEndpoint,
132 counterparty_endpoint: IbcEndpoint,
133 order: IbcOrder,
134 version: impl Into<String>,
135 connection_id: impl Into<String>,
136 ) -> Self {
137 Self {
138 endpoint,
139 counterparty_endpoint,
140 order,
141 version: version.into(),
142 connection_id: connection_id.into(),
143 }
144 }
145}
146
147#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
151pub enum IbcOrder {
152 #[serde(rename = "ORDER_UNORDERED")]
153 Unordered,
154 #[serde(rename = "ORDER_ORDERED")]
155 Ordered,
156}
157
158#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
163pub struct IbcTimeoutBlock {
164 pub revision: u64,
167 pub height: u64,
170}
171
172impl IbcTimeoutBlock {
173 pub fn is_zero(&self) -> bool {
174 self.revision == 0 && self.height == 0
175 }
176}
177
178impl PartialOrd for IbcTimeoutBlock {
179 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
180 Some(self.cmp(other))
181 }
182}
183
184impl Ord for IbcTimeoutBlock {
185 fn cmp(&self, other: &Self) -> Ordering {
186 match self.revision.cmp(&other.revision) {
187 Ordering::Equal => self.height.cmp(&other.height),
188 other => other,
189 }
190 }
191}
192
193#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
194#[non_exhaustive]
195pub struct IbcPacket {
196 pub data: Binary,
198 pub src: IbcEndpoint,
200 pub dest: IbcEndpoint,
202 pub sequence: u64,
204 pub timeout: IbcTimeout,
205}
206
207impl IbcPacket {
208 pub fn new(
210 data: impl Into<Binary>,
211 src: IbcEndpoint,
212 dest: IbcEndpoint,
213 sequence: u64,
214 timeout: IbcTimeout,
215 ) -> Self {
216 Self {
217 data: data.into(),
218 src,
219 dest,
220 sequence,
221 timeout,
222 }
223 }
224}
225
226#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
227#[non_exhaustive]
228pub struct IbcAcknowledgement {
229 pub data: Binary,
230 }
233
234impl IbcAcknowledgement {
235 pub fn new(data: impl Into<Binary>) -> Self {
236 IbcAcknowledgement { data: data.into() }
237 }
238
239 pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
240 Ok(IbcAcknowledgement {
241 data: to_binary(data)?,
242 })
243 }
244}
245
246#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
248#[serde(rename_all = "snake_case")]
249#[non_exhaustive]
250pub enum IbcChannelOpenMsg {
251 OpenInit { channel: IbcChannel },
253 OpenTry {
255 channel: IbcChannel,
256 counterparty_version: String,
257 },
258}
259
260impl IbcChannelOpenMsg {
261 pub fn new_init(channel: IbcChannel) -> Self {
262 Self::OpenInit { channel }
263 }
264
265 pub fn new_try(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
266 Self::OpenTry {
267 channel,
268 counterparty_version: counterparty_version.into(),
269 }
270 }
271
272 pub fn channel(&self) -> &IbcChannel {
273 match self {
274 Self::OpenInit { channel } => channel,
275 Self::OpenTry { channel, .. } => channel,
276 }
277 }
278
279 pub fn counterparty_version(&self) -> Option<&str> {
280 match self {
281 Self::OpenTry {
282 counterparty_version,
283 ..
284 } => Some(counterparty_version),
285 _ => None,
286 }
287 }
288}
289
290impl From<IbcChannelOpenMsg> for IbcChannel {
291 fn from(msg: IbcChannelOpenMsg) -> IbcChannel {
292 match msg {
293 IbcChannelOpenMsg::OpenInit { channel } => channel,
294 IbcChannelOpenMsg::OpenTry { channel, .. } => channel,
295 }
296 }
297}
298
299#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
301#[serde(rename_all = "snake_case")]
302#[non_exhaustive]
303pub enum IbcChannelConnectMsg {
304 OpenAck {
306 channel: IbcChannel,
307 counterparty_version: String,
308 },
309 OpenConfirm { channel: IbcChannel },
311}
312
313impl IbcChannelConnectMsg {
314 pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
315 Self::OpenAck {
316 channel,
317 counterparty_version: counterparty_version.into(),
318 }
319 }
320
321 pub fn new_confirm(channel: IbcChannel) -> Self {
322 Self::OpenConfirm { channel }
323 }
324
325 pub fn channel(&self) -> &IbcChannel {
326 match self {
327 Self::OpenAck { channel, .. } => channel,
328 Self::OpenConfirm { channel } => channel,
329 }
330 }
331
332 pub fn counterparty_version(&self) -> Option<&str> {
333 match self {
334 Self::OpenAck {
335 counterparty_version,
336 ..
337 } => Some(counterparty_version),
338 _ => None,
339 }
340 }
341}
342
343impl From<IbcChannelConnectMsg> for IbcChannel {
344 fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
345 match msg {
346 IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
347 IbcChannelConnectMsg::OpenConfirm { channel } => channel,
348 }
349 }
350}
351
352#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
354#[serde(rename_all = "snake_case")]
355#[non_exhaustive]
356pub enum IbcChannelCloseMsg {
357 CloseInit { channel: IbcChannel },
359 CloseConfirm { channel: IbcChannel }, }
362
363impl IbcChannelCloseMsg {
364 pub fn new_init(channel: IbcChannel) -> Self {
365 Self::CloseInit { channel }
366 }
367
368 pub fn new_confirm(channel: IbcChannel) -> Self {
369 Self::CloseConfirm { channel }
370 }
371
372 pub fn channel(&self) -> &IbcChannel {
373 match self {
374 Self::CloseInit { channel } => channel,
375 Self::CloseConfirm { channel } => channel,
376 }
377 }
378}
379
380impl From<IbcChannelCloseMsg> for IbcChannel {
381 fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
382 match msg {
383 IbcChannelCloseMsg::CloseInit { channel } => channel,
384 IbcChannelCloseMsg::CloseConfirm { channel } => channel,
385 }
386 }
387}
388
389#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
391#[non_exhaustive]
392pub struct IbcPacketReceiveMsg {
393 pub packet: IbcPacket,
394}
395
396impl IbcPacketReceiveMsg {
397 pub fn new(packet: IbcPacket) -> Self {
398 Self { packet }
399 }
400}
401
402#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
404#[non_exhaustive]
405pub struct IbcPacketAckMsg {
406 pub acknowledgement: IbcAcknowledgement,
407 pub original_packet: IbcPacket,
408}
409
410impl IbcPacketAckMsg {
411 pub fn new(acknowledgement: IbcAcknowledgement, original_packet: IbcPacket) -> Self {
412 Self {
413 acknowledgement,
414 original_packet,
415 }
416 }
417}
418
419#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
421#[non_exhaustive]
422pub struct IbcPacketTimeoutMsg {
423 pub packet: IbcPacket,
424}
425
426impl IbcPacketTimeoutMsg {
427 pub fn new(packet: IbcPacket) -> Self {
428 Self { packet }
429 }
430}
431
432#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
440#[non_exhaustive]
441pub struct IbcBasicResponse<T = Empty> {
442 pub messages: Vec<SubMsg<T>>,
447 pub attributes: Vec<Attribute>,
453 pub events: Vec<Event>,
460}
461
462impl<T> Default for IbcBasicResponse<T> {
464 fn default() -> Self {
465 IbcBasicResponse {
466 messages: vec![],
467 attributes: vec![],
468 events: vec![],
469 }
470 }
471}
472
473impl<T> IbcBasicResponse<T> {
474 pub fn new() -> Self {
475 Self::default()
476 }
477
478 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
480 self.attributes.push(Attribute::new(key, value));
481 self
482 }
483
484 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
487 self.messages.push(SubMsg::new(msg));
488 self
489 }
490
491 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
494 self.messages.push(msg);
495 self
496 }
497
498 pub fn add_event(mut self, event: Event) -> Self {
504 self.events.push(event);
505 self
506 }
507
508 pub fn add_attributes<A: Into<Attribute>>(
527 mut self,
528 attrs: impl IntoIterator<Item = A>,
529 ) -> Self {
530 self.attributes.extend(attrs.into_iter().map(A::into));
531 self
532 }
533
534 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
546 self.add_submessages(msgs.into_iter().map(SubMsg::new))
547 }
548
549 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
561 self.messages.extend(msgs.into_iter());
562 self
563 }
564
565 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
571 self.events.extend(events.into_iter());
572 self
573 }
574}
575
576#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
582#[non_exhaustive]
583pub struct IbcReceiveResponse<T = Empty> {
584 pub acknowledgement: Binary,
587 pub messages: Vec<SubMsg<T>>,
592 pub attributes: Vec<Attribute>,
598 pub events: Vec<Event>,
605}
606
607impl<T> Default for IbcReceiveResponse<T> {
609 fn default() -> Self {
610 IbcReceiveResponse {
611 acknowledgement: Binary(vec![]),
612 messages: vec![],
613 attributes: vec![],
614 events: vec![],
615 }
616 }
617}
618
619impl<T> IbcReceiveResponse<T> {
620 pub fn new() -> Self {
621 Self::default()
622 }
623
624 pub fn set_ack(mut self, ack: impl Into<Binary>) -> Self {
626 self.acknowledgement = ack.into();
627 self
628 }
629
630 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
632 self.attributes.push(Attribute::new(key, value));
633 self
634 }
635
636 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
639 self.messages.push(SubMsg::new(msg));
640 self
641 }
642
643 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
646 self.messages.push(msg);
647 self
648 }
649
650 pub fn add_event(mut self, event: Event) -> Self {
656 self.events.push(event);
657 self
658 }
659
660 pub fn add_attributes<A: Into<Attribute>>(
679 mut self,
680 attrs: impl IntoIterator<Item = A>,
681 ) -> Self {
682 self.attributes.extend(attrs.into_iter().map(A::into));
683 self
684 }
685
686 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
698 self.add_submessages(msgs.into_iter().map(SubMsg::new))
699 }
700
701 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
713 self.messages.extend(msgs.into_iter());
714 self
715 }
716
717 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
723 self.events.extend(events.into_iter());
724 self
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731 use serde_json_wasm::to_string;
732
733 #[test]
734 fn serialize_msg() {
736 let msg = IbcMsg::Transfer {
737 channel_id: "channel-123".to_string(),
738 to_address: "my-special-addr".into(),
739 amount: Coin::new(12345678, "uatom"),
740 timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
741 };
742 let encoded = to_string(&msg).unwrap();
743 let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"}}}"#;
744 assert_eq!(encoded.as_str(), expected);
745 }
746
747 #[test]
748 fn ibc_timeout_serialize() {
749 let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
750 let expected = r#"{"block":null,"timestamp":"684816844"}"#;
751 assert_eq!(to_string(×tamp).unwrap(), expected);
752
753 let block = IbcTimeout::with_block(IbcTimeoutBlock {
754 revision: 12,
755 height: 129,
756 });
757 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
758 assert_eq!(to_string(&block).unwrap(), expected);
759
760 let both = IbcTimeout::with_both(
761 IbcTimeoutBlock {
762 revision: 12,
763 height: 129,
764 },
765 Timestamp::from_nanos(684816844),
766 );
767 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
768 assert_eq!(to_string(&both).unwrap(), expected);
769 }
770
771 #[test]
772 #[allow(clippy::eq_op)]
773 fn ibc_timeout_block_ord() {
774 let epoch1a = IbcTimeoutBlock {
775 revision: 1,
776 height: 1000,
777 };
778 let epoch1b = IbcTimeoutBlock {
779 revision: 1,
780 height: 3000,
781 };
782 let epoch2a = IbcTimeoutBlock {
783 revision: 2,
784 height: 500,
785 };
786 let epoch2b = IbcTimeoutBlock {
787 revision: 2,
788 height: 2500,
789 };
790
791 assert!(epoch1a == epoch1a);
793 assert!(epoch1a < epoch1b);
794 assert!(epoch1b > epoch1a);
795 assert!(epoch2a > epoch1a);
796 assert!(epoch2b > epoch1a);
797
798 assert!(epoch1b > epoch1a);
800 assert!(epoch2a > epoch1b);
801 assert!(epoch2b > epoch2a);
802 assert!(epoch2b > epoch1b);
803 assert!(epoch1a < epoch1b);
805 assert!(epoch1b < epoch2a);
806 assert!(epoch2a < epoch2b);
807 assert!(epoch1b < epoch2b);
808 }
809
810 #[test]
811 fn ibc_packet_serialize() {
812 let packet = IbcPacket {
813 data: b"foo".into(),
814 src: IbcEndpoint {
815 port_id: "their-port".to_string(),
816 channel_id: "channel-1234".to_string(),
817 },
818 dest: IbcEndpoint {
819 port_id: "our-port".to_string(),
820 channel_id: "chan33".into(),
821 },
822 sequence: 27,
823 timeout: IbcTimeout::with_both(
824 IbcTimeoutBlock {
825 revision: 1,
826 height: 12345678,
827 },
828 Timestamp::from_nanos(4611686018427387904),
829 ),
830 };
831 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"}}"#;
832 assert_eq!(to_string(&packet).unwrap(), expected);
833
834 let no_timestamp = IbcPacket {
835 data: b"foo".into(),
836 src: IbcEndpoint {
837 port_id: "their-port".to_string(),
838 channel_id: "channel-1234".to_string(),
839 },
840 dest: IbcEndpoint {
841 port_id: "our-port".to_string(),
842 channel_id: "chan33".into(),
843 },
844 sequence: 27,
845 timeout: IbcTimeout::with_block(IbcTimeoutBlock {
846 revision: 1,
847 height: 12345678,
848 }),
849 };
850 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}}"#;
851 assert_eq!(to_string(&no_timestamp).unwrap(), expected);
852 }
853}