1#![cfg(feature = "stargate")]
2use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::cmp::{Ord, Ordering, PartialOrd};
8
9#[cfg(feature = "ibc3")]
10use crate::addresses::Addr;
11use crate::binary::Binary;
12use crate::coin::Coin;
13use crate::errors::StdResult;
14use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
15use crate::serde::to_binary;
16use crate::timestamp::Timestamp;
17
18#[non_exhaustive]
21#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
22#[serde(rename_all = "snake_case")]
23pub enum IbcMsg {
24 Transfer {
30 channel_id: String,
32 to_address: String,
34 amount: Coin,
37 timeout: IbcTimeout,
39 memo: String,
44 },
45 SendPacket {
49 channel_id: String,
50 data: Binary,
51 timeout: IbcTimeout,
53 },
54 CloseChannel { channel_id: String },
57}
58
59#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
60pub struct IbcEndpoint {
61 pub port_id: String,
62 pub channel_id: String,
63}
64
65#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
69#[serde(rename_all = "snake_case")]
70pub struct IbcTimeout {
71 block: Option<IbcTimeoutBlock>,
73 timestamp: Option<Timestamp>,
74}
75
76impl IbcTimeout {
77 pub fn with_block(block: IbcTimeoutBlock) -> Self {
78 IbcTimeout {
79 block: Some(block),
80 timestamp: None,
81 }
82 }
83
84 pub fn with_timestamp(timestamp: Timestamp) -> Self {
85 IbcTimeout {
86 block: None,
87 timestamp: Some(timestamp),
88 }
89 }
90
91 pub fn with_both(block: IbcTimeoutBlock, timestamp: Timestamp) -> Self {
92 IbcTimeout {
93 block: Some(block),
94 timestamp: Some(timestamp),
95 }
96 }
97
98 pub fn block(&self) -> Option<IbcTimeoutBlock> {
99 self.block
100 }
101
102 pub fn timestamp(&self) -> Option<Timestamp> {
103 self.timestamp
104 }
105}
106
107impl From<Timestamp> for IbcTimeout {
108 fn from(timestamp: Timestamp) -> IbcTimeout {
109 IbcTimeout::with_timestamp(timestamp)
110 }
111}
112
113impl From<IbcTimeoutBlock> for IbcTimeout {
114 fn from(original: IbcTimeoutBlock) -> IbcTimeout {
115 IbcTimeout::with_block(original)
116 }
117}
118
119#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
124#[non_exhaustive]
125pub struct IbcChannel {
126 pub endpoint: IbcEndpoint,
127 pub counterparty_endpoint: IbcEndpoint,
128 pub order: IbcOrder,
129 pub version: String,
131 pub connection_id: String,
134}
135
136impl IbcChannel {
137 pub fn new(
139 endpoint: IbcEndpoint,
140 counterparty_endpoint: IbcEndpoint,
141 order: IbcOrder,
142 version: impl Into<String>,
143 connection_id: impl Into<String>,
144 ) -> Self {
145 Self {
146 endpoint,
147 counterparty_endpoint,
148 order,
149 version: version.into(),
150 connection_id: connection_id.into(),
151 }
152 }
153}
154
155#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
159pub enum IbcOrder {
160 #[serde(rename = "ORDER_UNORDERED")]
161 Unordered,
162 #[serde(rename = "ORDER_ORDERED")]
163 Ordered,
164}
165
166#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
171pub struct IbcTimeoutBlock {
172 pub revision: u64,
175 pub height: u64,
178}
179
180impl IbcTimeoutBlock {
181 pub fn is_zero(&self) -> bool {
182 self.revision == 0 && self.height == 0
183 }
184}
185
186impl PartialOrd for IbcTimeoutBlock {
187 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
188 Some(self.cmp(other))
189 }
190}
191
192impl Ord for IbcTimeoutBlock {
193 fn cmp(&self, other: &Self) -> Ordering {
194 match self.revision.cmp(&other.revision) {
195 Ordering::Equal => self.height.cmp(&other.height),
196 other => other,
197 }
198 }
199}
200
201#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
202#[non_exhaustive]
203pub struct IbcPacket {
204 pub data: Binary,
206 pub src: IbcEndpoint,
208 pub dest: IbcEndpoint,
210 pub sequence: u64,
212 pub timeout: IbcTimeout,
213}
214
215impl IbcPacket {
216 pub fn new(
218 data: impl Into<Binary>,
219 src: IbcEndpoint,
220 dest: IbcEndpoint,
221 sequence: u64,
222 timeout: IbcTimeout,
223 ) -> Self {
224 Self {
225 data: data.into(),
226 src,
227 dest,
228 sequence,
229 timeout,
230 }
231 }
232}
233
234#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
235#[non_exhaustive]
236pub struct IbcAcknowledgement {
237 pub data: Binary,
238 }
241
242impl IbcAcknowledgement {
243 pub fn new(data: impl Into<Binary>) -> Self {
244 IbcAcknowledgement { data: data.into() }
245 }
246
247 pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
248 Ok(IbcAcknowledgement {
249 data: to_binary(data)?,
250 })
251 }
252}
253
254#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
256#[serde(rename_all = "snake_case")]
257pub enum IbcChannelOpenMsg {
258 OpenInit { channel: IbcChannel },
260 OpenTry {
262 channel: IbcChannel,
263 counterparty_version: String,
264 },
265}
266
267impl IbcChannelOpenMsg {
268 pub fn new_init(channel: IbcChannel) -> Self {
269 Self::OpenInit { channel }
270 }
271
272 pub fn new_try(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
273 Self::OpenTry {
274 channel,
275 counterparty_version: counterparty_version.into(),
276 }
277 }
278
279 pub fn channel(&self) -> &IbcChannel {
280 match self {
281 Self::OpenInit { channel } => channel,
282 Self::OpenTry { channel, .. } => channel,
283 }
284 }
285
286 pub fn counterparty_version(&self) -> Option<&str> {
287 match self {
288 Self::OpenTry {
289 counterparty_version,
290 ..
291 } => Some(counterparty_version),
292 _ => None,
293 }
294 }
295}
296
297impl From<IbcChannelOpenMsg> for IbcChannel {
298 fn from(msg: IbcChannelOpenMsg) -> IbcChannel {
299 match msg {
300 IbcChannelOpenMsg::OpenInit { channel } => channel,
301 IbcChannelOpenMsg::OpenTry { channel, .. } => channel,
302 }
303 }
304}
305
306#[cfg(not(feature = "ibc3"))]
308pub type IbcChannelOpenResponse = ();
309#[cfg(feature = "ibc3")]
311pub type IbcChannelOpenResponse = Option<Ibc3ChannelOpenResponse>;
312
313#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
314pub struct Ibc3ChannelOpenResponse {
315 pub version: String,
317}
318
319#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
321#[serde(rename_all = "snake_case")]
322pub enum IbcChannelConnectMsg {
323 OpenAck {
325 channel: IbcChannel,
326 counterparty_version: String,
327 },
328 OpenConfirm { channel: IbcChannel },
330}
331
332impl IbcChannelConnectMsg {
333 pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
334 Self::OpenAck {
335 channel,
336 counterparty_version: counterparty_version.into(),
337 }
338 }
339
340 pub fn new_confirm(channel: IbcChannel) -> Self {
341 Self::OpenConfirm { channel }
342 }
343
344 pub fn channel(&self) -> &IbcChannel {
345 match self {
346 Self::OpenAck { channel, .. } => channel,
347 Self::OpenConfirm { channel } => channel,
348 }
349 }
350
351 pub fn counterparty_version(&self) -> Option<&str> {
352 match self {
353 Self::OpenAck {
354 counterparty_version,
355 ..
356 } => Some(counterparty_version),
357 _ => None,
358 }
359 }
360}
361
362impl From<IbcChannelConnectMsg> for IbcChannel {
363 fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
364 match msg {
365 IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
366 IbcChannelConnectMsg::OpenConfirm { channel } => channel,
367 }
368 }
369}
370
371#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
373#[serde(rename_all = "snake_case")]
374pub enum IbcChannelCloseMsg {
375 CloseInit { channel: IbcChannel },
377 CloseConfirm { channel: IbcChannel }, }
380
381impl IbcChannelCloseMsg {
382 pub fn new_init(channel: IbcChannel) -> Self {
383 Self::CloseInit { channel }
384 }
385
386 pub fn new_confirm(channel: IbcChannel) -> Self {
387 Self::CloseConfirm { channel }
388 }
389
390 pub fn channel(&self) -> &IbcChannel {
391 match self {
392 Self::CloseInit { channel } => channel,
393 Self::CloseConfirm { channel } => channel,
394 }
395 }
396}
397
398impl From<IbcChannelCloseMsg> for IbcChannel {
399 fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
400 match msg {
401 IbcChannelCloseMsg::CloseInit { channel } => channel,
402 IbcChannelCloseMsg::CloseConfirm { channel } => channel,
403 }
404 }
405}
406
407#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
409#[non_exhaustive]
410pub struct IbcPacketReceiveMsg {
411 pub packet: IbcPacket,
412 #[cfg(feature = "ibc3")]
413 pub relayer: Addr,
414}
415
416impl IbcPacketReceiveMsg {
417 #[cfg(not(feature = "ibc3"))]
418 pub fn new(packet: IbcPacket) -> Self {
419 Self { packet }
420 }
421
422 #[cfg(feature = "ibc3")]
423 pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
424 Self { packet, relayer }
425 }
426}
427
428#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
430#[non_exhaustive]
431pub struct IbcPacketAckMsg {
432 pub acknowledgement: IbcAcknowledgement,
433 pub original_packet: IbcPacket,
434 #[cfg(feature = "ibc3")]
435 pub relayer: Addr,
436}
437
438impl IbcPacketAckMsg {
439 #[cfg(not(feature = "ibc3"))]
440 pub fn new(acknowledgement: IbcAcknowledgement, original_packet: IbcPacket) -> Self {
441 Self {
442 acknowledgement,
443 original_packet,
444 }
445 }
446
447 #[cfg(feature = "ibc3")]
448 pub fn new(
449 acknowledgement: IbcAcknowledgement,
450 original_packet: IbcPacket,
451 relayer: Addr,
452 ) -> Self {
453 Self {
454 acknowledgement,
455 original_packet,
456 relayer,
457 }
458 }
459}
460
461#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
463#[non_exhaustive]
464pub struct IbcPacketTimeoutMsg {
465 pub packet: IbcPacket,
466 #[cfg(feature = "ibc3")]
467 pub relayer: Addr,
468}
469
470impl IbcPacketTimeoutMsg {
471 #[cfg(not(feature = "ibc3"))]
472 pub fn new(packet: IbcPacket) -> Self {
473 Self { packet }
474 }
475
476 #[cfg(feature = "ibc3")]
477 pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
478 Self { packet, relayer }
479 }
480}
481
482#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
490#[non_exhaustive]
491pub struct IbcBasicResponse<T = Empty> {
492 pub messages: Vec<SubMsg<T>>,
497 pub attributes: Vec<Attribute>,
503 pub events: Vec<Event>,
510}
511
512impl<T> Default for IbcBasicResponse<T> {
514 fn default() -> Self {
515 IbcBasicResponse {
516 messages: vec![],
517 attributes: vec![],
518 events: vec![],
519 }
520 }
521}
522
523impl<T> IbcBasicResponse<T> {
524 pub fn new() -> Self {
525 Self::default()
526 }
527
528 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
530 self.attributes.push(Attribute::new(key, value));
531 self
532 }
533
534 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
537 self.messages.push(SubMsg::new(msg));
538 self
539 }
540
541 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
544 self.messages.push(msg);
545 self
546 }
547
548 pub fn add_event(mut self, event: Event) -> Self {
554 self.events.push(event);
555 self
556 }
557
558 pub fn add_attributes<A: Into<Attribute>>(
577 mut self,
578 attrs: impl IntoIterator<Item = A>,
579 ) -> Self {
580 self.attributes.extend(attrs.into_iter().map(A::into));
581 self
582 }
583
584 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
596 self.add_submessages(msgs.into_iter().map(SubMsg::new))
597 }
598
599 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
611 self.messages.extend(msgs.into_iter());
612 self
613 }
614
615 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
621 self.events.extend(events.into_iter());
622 self
623 }
624}
625
626#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
632#[non_exhaustive]
633pub struct IbcReceiveResponse<T = Empty> {
634 pub acknowledgement: Binary,
637 pub messages: Vec<SubMsg<T>>,
642 pub attributes: Vec<Attribute>,
648 pub events: Vec<Event>,
655}
656
657impl<T> Default for IbcReceiveResponse<T> {
659 fn default() -> Self {
660 IbcReceiveResponse {
661 acknowledgement: Binary(vec![]),
662 messages: vec![],
663 attributes: vec![],
664 events: vec![],
665 }
666 }
667}
668
669impl<T> IbcReceiveResponse<T> {
670 pub fn new() -> Self {
671 Self::default()
672 }
673
674 pub fn set_ack(mut self, ack: impl Into<Binary>) -> Self {
676 self.acknowledgement = ack.into();
677 self
678 }
679
680 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
682 self.attributes.push(Attribute::new(key, value));
683 self
684 }
685
686 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
689 self.messages.push(SubMsg::new(msg));
690 self
691 }
692
693 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
696 self.messages.push(msg);
697 self
698 }
699
700 pub fn add_event(mut self, event: Event) -> Self {
706 self.events.push(event);
707 self
708 }
709
710 pub fn add_attributes<A: Into<Attribute>>(
729 mut self,
730 attrs: impl IntoIterator<Item = A>,
731 ) -> Self {
732 self.attributes.extend(attrs.into_iter().map(A::into));
733 self
734 }
735
736 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
748 self.add_submessages(msgs.into_iter().map(SubMsg::new))
749 }
750
751 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
763 self.messages.extend(msgs.into_iter());
764 self
765 }
766
767 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
773 self.events.extend(events.into_iter());
774 self
775 }
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781 use serde_json_wasm::to_string;
782
783 #[test]
784 fn serialize_msg() {
786 let msg = IbcMsg::Transfer {
787 channel_id: "channel-123".to_string(),
788 to_address: "my-special-addr".into(),
789 amount: Coin::new(12345678, "uatom"),
790 timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
791 memo: "",
792 };
793 let encoded = to_string(&msg).unwrap();
794 let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"},"memo":""}}"#;
795 assert_eq!(encoded.as_str(), expected);
796 }
797
798 #[test]
799 fn ibc_timeout_serialize() {
800 let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
801 let expected = r#"{"block":null,"timestamp":"684816844"}"#;
802 assert_eq!(to_string(×tamp).unwrap(), expected);
803
804 let block = IbcTimeout::with_block(IbcTimeoutBlock {
805 revision: 12,
806 height: 129,
807 });
808 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
809 assert_eq!(to_string(&block).unwrap(), expected);
810
811 let both = IbcTimeout::with_both(
812 IbcTimeoutBlock {
813 revision: 12,
814 height: 129,
815 },
816 Timestamp::from_nanos(684816844),
817 );
818 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
819 assert_eq!(to_string(&both).unwrap(), expected);
820 }
821
822 #[test]
823 #[allow(clippy::eq_op)]
824 fn ibc_timeout_block_ord() {
825 let epoch1a = IbcTimeoutBlock {
826 revision: 1,
827 height: 1000,
828 };
829 let epoch1b = IbcTimeoutBlock {
830 revision: 1,
831 height: 3000,
832 };
833 let epoch2a = IbcTimeoutBlock {
834 revision: 2,
835 height: 500,
836 };
837 let epoch2b = IbcTimeoutBlock {
838 revision: 2,
839 height: 2500,
840 };
841
842 assert!(epoch1a == epoch1a);
844 assert!(epoch1a < epoch1b);
845 assert!(epoch1b > epoch1a);
846 assert!(epoch2a > epoch1a);
847 assert!(epoch2b > epoch1a);
848
849 assert!(epoch1b > epoch1a);
851 assert!(epoch2a > epoch1b);
852 assert!(epoch2b > epoch2a);
853 assert!(epoch2b > epoch1b);
854 assert!(epoch1a < epoch1b);
856 assert!(epoch1b < epoch2a);
857 assert!(epoch2a < epoch2b);
858 assert!(epoch1b < epoch2b);
859 }
860
861 #[test]
862 fn ibc_packet_serialize() {
863 let packet = IbcPacket {
864 data: b"foo".into(),
865 src: IbcEndpoint {
866 port_id: "their-port".to_string(),
867 channel_id: "channel-1234".to_string(),
868 },
869 dest: IbcEndpoint {
870 port_id: "our-port".to_string(),
871 channel_id: "chan33".into(),
872 },
873 sequence: 27,
874 timeout: IbcTimeout::with_both(
875 IbcTimeoutBlock {
876 revision: 1,
877 height: 12345678,
878 },
879 Timestamp::from_nanos(4611686018427387904),
880 ),
881 };
882 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"}}"#;
883 assert_eq!(to_string(&packet).unwrap(), expected);
884
885 let no_timestamp = IbcPacket {
886 data: b"foo".into(),
887 src: IbcEndpoint {
888 port_id: "their-port".to_string(),
889 channel_id: "channel-1234".to_string(),
890 },
891 dest: IbcEndpoint {
892 port_id: "our-port".to_string(),
893 channel_id: "chan33".into(),
894 },
895 sequence: 27,
896 timeout: IbcTimeout::with_block(IbcTimeoutBlock {
897 revision: 1,
898 height: 12345678,
899 }),
900 };
901 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}}"#;
902 assert_eq!(to_string(&no_timestamp).unwrap(), expected);
903 }
904}