1#![cfg(feature = "stargate")]
2use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::cmp::{Ord, Ordering, PartialOrd};
8use std::fmt;
9
10use crate::binary::Binary;
11use crate::coins::Coin;
12use crate::errors::StdResult;
13use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
14use crate::serde::to_binary;
15use crate::timestamp::Timestamp;
16
17#[non_exhaustive]
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
21#[serde(rename_all = "snake_case")]
22pub enum IbcMsg {
23 Transfer {
29 channel_id: String,
31 to_address: String,
33 amount: Coin,
36 timeout: IbcTimeout,
38 },
39 SendPacket {
43 channel_id: String,
44 data: Binary,
45 timeout: IbcTimeout,
47 },
48 CloseChannel { channel_id: String },
51}
52
53#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
54pub struct IbcEndpoint {
55 pub port_id: String,
56 pub channel_id: String,
57}
58
59#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
63#[serde(rename_all = "snake_case")]
64pub struct IbcTimeout {
65 block: Option<IbcTimeoutBlock>,
67 timestamp: Option<Timestamp>,
68}
69
70impl IbcTimeout {
71 pub fn with_block(block: IbcTimeoutBlock) -> Self {
72 IbcTimeout {
73 block: Some(block),
74 timestamp: None,
75 }
76 }
77
78 pub fn with_timestamp(timestamp: Timestamp) -> Self {
79 IbcTimeout {
80 block: None,
81 timestamp: Some(timestamp),
82 }
83 }
84
85 pub fn with_both(block: IbcTimeoutBlock, timestamp: Timestamp) -> Self {
86 IbcTimeout {
87 block: Some(block),
88 timestamp: Some(timestamp),
89 }
90 }
91
92 pub fn block(&self) -> Option<IbcTimeoutBlock> {
93 self.block
94 }
95
96 pub fn timestamp(&self) -> Option<Timestamp> {
97 self.timestamp
98 }
99}
100
101impl From<Timestamp> for IbcTimeout {
102 fn from(timestamp: Timestamp) -> IbcTimeout {
103 IbcTimeout::with_timestamp(timestamp)
104 }
105}
106
107impl From<IbcTimeoutBlock> for IbcTimeout {
108 fn from(original: IbcTimeoutBlock) -> IbcTimeout {
109 IbcTimeout::with_block(original)
110 }
111}
112
113#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
118#[non_exhaustive]
119pub struct IbcChannel {
120 pub endpoint: IbcEndpoint,
121 pub counterparty_endpoint: IbcEndpoint,
122 pub order: IbcOrder,
123 pub version: String,
124 pub connection_id: String,
127}
128
129impl IbcChannel {
130 pub fn new(
132 endpoint: IbcEndpoint,
133 counterparty_endpoint: IbcEndpoint,
134 order: IbcOrder,
135 version: impl Into<String>,
136 connection_id: impl Into<String>,
137 ) -> Self {
138 Self {
139 endpoint,
140 counterparty_endpoint,
141 order,
142 version: version.into(),
143 connection_id: connection_id.into(),
144 }
145 }
146}
147
148#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
152pub enum IbcOrder {
153 #[serde(rename = "ORDER_UNORDERED")]
154 Unordered,
155 #[serde(rename = "ORDER_ORDERED")]
156 Ordered,
157}
158
159#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
164pub struct IbcTimeoutBlock {
165 pub revision: u64,
168 pub height: u64,
171}
172
173impl IbcTimeoutBlock {
174 pub fn is_zero(&self) -> bool {
175 self.revision == 0 && self.height == 0
176 }
177}
178
179impl PartialOrd for IbcTimeoutBlock {
180 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
181 Some(self.cmp(other))
182 }
183}
184
185impl Ord for IbcTimeoutBlock {
186 fn cmp(&self, other: &Self) -> Ordering {
187 match self.revision.cmp(&other.revision) {
188 Ordering::Equal => self.height.cmp(&other.height),
189 other => other,
190 }
191 }
192}
193
194#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
195#[non_exhaustive]
196pub struct IbcPacket {
197 pub data: Binary,
199 pub src: IbcEndpoint,
201 pub dest: IbcEndpoint,
203 pub sequence: u64,
205 pub timeout: IbcTimeout,
206}
207
208impl IbcPacket {
209 pub fn new(
211 data: impl Into<Binary>,
212 src: IbcEndpoint,
213 dest: IbcEndpoint,
214 sequence: u64,
215 timeout: IbcTimeout,
216 ) -> Self {
217 Self {
218 data: data.into(),
219 src,
220 dest,
221 sequence,
222 timeout,
223 }
224 }
225}
226
227#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
228#[non_exhaustive]
229pub struct IbcAcknowledgement {
230 pub data: Binary,
231 }
234
235impl IbcAcknowledgement {
236 pub fn new(data: impl Into<Binary>) -> Self {
237 IbcAcknowledgement { data: data.into() }
238 }
239
240 pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
241 Ok(IbcAcknowledgement {
242 data: to_binary(data)?,
243 })
244 }
245}
246
247#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
249#[serde(rename_all = "snake_case")]
250#[non_exhaustive]
251pub enum IbcChannelOpenMsg {
252 OpenInit { channel: IbcChannel },
254 OpenTry {
256 channel: IbcChannel,
257 counterparty_version: String,
258 },
259}
260
261impl IbcChannelOpenMsg {
262 pub fn new_init(channel: IbcChannel) -> Self {
263 Self::OpenInit { channel }
264 }
265
266 pub fn new_try(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
267 Self::OpenTry {
268 channel,
269 counterparty_version: counterparty_version.into(),
270 }
271 }
272
273 pub fn channel(&self) -> &IbcChannel {
274 match self {
275 Self::OpenInit { channel } => channel,
276 Self::OpenTry { channel, .. } => channel,
277 }
278 }
279
280 pub fn counterparty_version(&self) -> Option<&str> {
281 match self {
282 Self::OpenTry {
283 counterparty_version,
284 ..
285 } => Some(counterparty_version),
286 _ => None,
287 }
288 }
289}
290
291impl From<IbcChannelOpenMsg> for IbcChannel {
292 fn from(msg: IbcChannelOpenMsg) -> IbcChannel {
293 match msg {
294 IbcChannelOpenMsg::OpenInit { channel } => channel,
295 IbcChannelOpenMsg::OpenTry { channel, .. } => channel,
296 }
297 }
298}
299
300#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
302#[serde(rename_all = "snake_case")]
303#[non_exhaustive]
304pub enum IbcChannelConnectMsg {
305 OpenAck {
307 channel: IbcChannel,
308 counterparty_version: String,
309 },
310 OpenConfirm { channel: IbcChannel },
312}
313
314impl IbcChannelConnectMsg {
315 pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
316 Self::OpenAck {
317 channel,
318 counterparty_version: counterparty_version.into(),
319 }
320 }
321
322 pub fn new_confirm(channel: IbcChannel) -> Self {
323 Self::OpenConfirm { channel }
324 }
325
326 pub fn channel(&self) -> &IbcChannel {
327 match self {
328 Self::OpenAck { channel, .. } => channel,
329 Self::OpenConfirm { channel } => channel,
330 }
331 }
332
333 pub fn counterparty_version(&self) -> Option<&str> {
334 match self {
335 Self::OpenAck {
336 counterparty_version,
337 ..
338 } => Some(counterparty_version),
339 _ => None,
340 }
341 }
342}
343
344impl From<IbcChannelConnectMsg> for IbcChannel {
345 fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
346 match msg {
347 IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
348 IbcChannelConnectMsg::OpenConfirm { channel } => channel,
349 }
350 }
351}
352
353#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
355#[serde(rename_all = "snake_case")]
356#[non_exhaustive]
357pub enum IbcChannelCloseMsg {
358 CloseInit { channel: IbcChannel },
360 CloseConfirm { channel: IbcChannel }, }
363
364impl IbcChannelCloseMsg {
365 pub fn new_init(channel: IbcChannel) -> Self {
366 Self::CloseInit { channel }
367 }
368
369 pub fn new_confirm(channel: IbcChannel) -> Self {
370 Self::CloseConfirm { channel }
371 }
372
373 pub fn channel(&self) -> &IbcChannel {
374 match self {
375 Self::CloseInit { channel } => channel,
376 Self::CloseConfirm { channel } => channel,
377 }
378 }
379}
380
381impl From<IbcChannelCloseMsg> for IbcChannel {
382 fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
383 match msg {
384 IbcChannelCloseMsg::CloseInit { channel } => channel,
385 IbcChannelCloseMsg::CloseConfirm { channel } => channel,
386 }
387 }
388}
389
390#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
392#[non_exhaustive]
393pub struct IbcPacketReceiveMsg {
394 pub packet: IbcPacket,
395}
396
397impl IbcPacketReceiveMsg {
398 pub fn new(packet: IbcPacket) -> Self {
399 Self { packet }
400 }
401}
402
403#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
405#[non_exhaustive]
406pub struct IbcPacketAckMsg {
407 pub acknowledgement: IbcAcknowledgement,
408 pub original_packet: IbcPacket,
409}
410
411impl IbcPacketAckMsg {
412 pub fn new(acknowledgement: IbcAcknowledgement, original_packet: IbcPacket) -> Self {
413 Self {
414 acknowledgement,
415 original_packet,
416 }
417 }
418}
419
420#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
422#[non_exhaustive]
423pub struct IbcPacketTimeoutMsg {
424 pub packet: IbcPacket,
425}
426
427impl IbcPacketTimeoutMsg {
428 pub fn new(packet: IbcPacket) -> Self {
429 Self { packet }
430 }
431}
432
433#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
441#[non_exhaustive]
442pub struct IbcBasicResponse<T = Empty>
443where
444 T: Clone + fmt::Debug + PartialEq + JsonSchema,
445{
446 pub messages: Vec<SubMsg<T>>,
451 pub attributes: Vec<Attribute>,
457 pub events: Vec<Event>,
464}
465
466impl<T> Default for IbcBasicResponse<T>
467where
468 T: Clone + fmt::Debug + PartialEq + JsonSchema,
469{
470 fn default() -> Self {
471 IbcBasicResponse {
472 messages: vec![],
473 attributes: vec![],
474 events: vec![],
475 }
476 }
477}
478
479impl<T> IbcBasicResponse<T>
480where
481 T: Clone + fmt::Debug + PartialEq + JsonSchema,
482{
483 pub fn new() -> Self {
484 Self::default()
485 }
486
487 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
489 self.attributes.push(Attribute::new(key, value));
490 self
491 }
492
493 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
496 self.messages.push(SubMsg::new(msg));
497 self
498 }
499
500 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
503 self.messages.push(msg);
504 self
505 }
506
507 pub fn add_event(mut self, event: Event) -> Self {
513 self.events.push(event);
514 self
515 }
516
517 pub fn add_attributes<A: Into<Attribute>>(
536 mut self,
537 attrs: impl IntoIterator<Item = A>,
538 ) -> Self {
539 self.attributes.extend(attrs.into_iter().map(A::into));
540 self
541 }
542
543 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
555 self.add_submessages(msgs.into_iter().map(SubMsg::new))
556 }
557
558 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
570 self.messages.extend(msgs.into_iter());
571 self
572 }
573
574 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
580 self.events.extend(events.into_iter());
581 self
582 }
583}
584
585#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
591#[non_exhaustive]
592pub struct IbcReceiveResponse<T = Empty>
593where
594 T: Clone + fmt::Debug + PartialEq + JsonSchema,
595{
596 pub acknowledgement: Binary,
599 pub messages: Vec<SubMsg<T>>,
604 pub attributes: Vec<Attribute>,
610 pub events: Vec<Event>,
617}
618
619impl<T> Default for IbcReceiveResponse<T>
620where
621 T: Clone + fmt::Debug + PartialEq + JsonSchema,
622{
623 fn default() -> Self {
624 IbcReceiveResponse {
625 acknowledgement: Binary(vec![]),
626 messages: vec![],
627 attributes: vec![],
628 events: vec![],
629 }
630 }
631}
632
633impl<T> IbcReceiveResponse<T>
634where
635 T: Clone + fmt::Debug + PartialEq + JsonSchema,
636{
637 pub fn new() -> Self {
638 Self::default()
639 }
640
641 pub fn set_ack(mut self, ack: impl Into<Binary>) -> Self {
643 self.acknowledgement = ack.into();
644 self
645 }
646
647 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
649 self.attributes.push(Attribute::new(key, value));
650 self
651 }
652
653 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
656 self.messages.push(SubMsg::new(msg));
657 self
658 }
659
660 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
663 self.messages.push(msg);
664 self
665 }
666
667 pub fn add_event(mut self, event: Event) -> Self {
673 self.events.push(event);
674 self
675 }
676
677 pub fn add_attributes<A: Into<Attribute>>(
696 mut self,
697 attrs: impl IntoIterator<Item = A>,
698 ) -> Self {
699 self.attributes.extend(attrs.into_iter().map(A::into));
700 self
701 }
702
703 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
715 self.add_submessages(msgs.into_iter().map(SubMsg::new))
716 }
717
718 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
730 self.messages.extend(msgs.into_iter());
731 self
732 }
733
734 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
740 self.events.extend(events.into_iter());
741 self
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use serde_json_wasm::to_string;
749
750 #[test]
751 fn serialize_msg() {
753 let msg = IbcMsg::Transfer {
754 channel_id: "channel-123".to_string(),
755 to_address: "my-special-addr".into(),
756 amount: Coin::new(12345678, "uatom"),
757 timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
758 };
759 let encoded = to_string(&msg).unwrap();
760 let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"}}}"#;
761 assert_eq!(encoded.as_str(), expected);
762 }
763
764 #[test]
765 fn ibc_timeout_serialize() {
766 let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
767 let expected = r#"{"block":null,"timestamp":"684816844"}"#;
768 assert_eq!(to_string(×tamp).unwrap(), expected);
769
770 let block = IbcTimeout::with_block(IbcTimeoutBlock {
771 revision: 12,
772 height: 129,
773 });
774 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
775 assert_eq!(to_string(&block).unwrap(), expected);
776
777 let both = IbcTimeout::with_both(
778 IbcTimeoutBlock {
779 revision: 12,
780 height: 129,
781 },
782 Timestamp::from_nanos(684816844),
783 );
784 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
785 assert_eq!(to_string(&both).unwrap(), expected);
786 }
787
788 #[test]
789 fn ibc_timeout_block_ord() {
790 let epoch1a = IbcTimeoutBlock {
791 revision: 1,
792 height: 1000,
793 };
794 let epoch1b = IbcTimeoutBlock {
795 revision: 1,
796 height: 3000,
797 };
798 let epoch2a = IbcTimeoutBlock {
799 revision: 2,
800 height: 500,
801 };
802 let epoch2b = IbcTimeoutBlock {
803 revision: 2,
804 height: 2500,
805 };
806
807 assert!(epoch1a == epoch1a);
809 assert!(epoch1a < epoch1b);
810 assert!(epoch1b > epoch1a);
811 assert!(epoch2a > epoch1a);
812 assert!(epoch2b > epoch1a);
813
814 assert!(epoch1b > epoch1a);
816 assert!(epoch2a > epoch1b);
817 assert!(epoch2b > epoch2a);
818 assert!(epoch2b > epoch1b);
819 assert!(epoch1a < epoch1b);
821 assert!(epoch1b < epoch2a);
822 assert!(epoch2a < epoch2b);
823 assert!(epoch1b < epoch2b);
824 }
825
826 #[test]
827 fn ibc_packet_serialize() {
828 let packet = IbcPacket {
829 data: b"foo".into(),
830 src: IbcEndpoint {
831 port_id: "their-port".to_string(),
832 channel_id: "channel-1234".to_string(),
833 },
834 dest: IbcEndpoint {
835 port_id: "our-port".to_string(),
836 channel_id: "chan33".into(),
837 },
838 sequence: 27,
839 timeout: IbcTimeout::with_both(
840 IbcTimeoutBlock {
841 revision: 1,
842 height: 12345678,
843 },
844 Timestamp::from_nanos(4611686018427387904),
845 ),
846 };
847 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"}}"#;
848 assert_eq!(to_string(&packet).unwrap(), expected);
849
850 let no_timestamp = IbcPacket {
851 data: b"foo".into(),
852 src: IbcEndpoint {
853 port_id: "their-port".to_string(),
854 channel_id: "channel-1234".to_string(),
855 },
856 dest: IbcEndpoint {
857 port_id: "our-port".to_string(),
858 channel_id: "chan33".into(),
859 },
860 sequence: 27,
861 timeout: IbcTimeout::with_block(IbcTimeoutBlock {
862 revision: 1,
863 height: 12345678,
864 }),
865 };
866 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}}"#;
867 assert_eq!(to_string(&no_timestamp).unwrap(), expected);
868 }
869}