1use core::cmp::{Ord, Ordering, PartialOrd};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "ibc3")]
9use crate::addresses::Addr;
10use crate::binary::Binary;
11use crate::coin::Coin;
12use crate::errors::StdResult;
13use crate::results::{Attribute, CosmosMsg, Empty, Event, SubMsg};
14use crate::serde::to_json_binary;
15use crate::timestamp::Timestamp;
16
17#[non_exhaustive]
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, 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, Eq, JsonSchema)]
54pub struct IbcEndpoint {
55 pub port_id: String,
56 pub channel_id: String,
57}
58
59#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, 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, Eq, JsonSchema)]
118#[non_exhaustive]
119pub struct IbcChannel {
120 pub endpoint: IbcEndpoint,
121 pub counterparty_endpoint: IbcEndpoint,
122 pub order: IbcOrder,
123 pub version: String,
125 pub connection_id: String,
128}
129
130impl IbcChannel {
131 pub fn new(
133 endpoint: IbcEndpoint,
134 counterparty_endpoint: IbcEndpoint,
135 order: IbcOrder,
136 version: impl Into<String>,
137 connection_id: impl Into<String>,
138 ) -> Self {
139 Self {
140 endpoint,
141 counterparty_endpoint,
142 order,
143 version: version.into(),
144 connection_id: connection_id.into(),
145 }
146 }
147}
148
149#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
153pub enum IbcOrder {
154 #[serde(rename = "ORDER_UNORDERED")]
155 Unordered,
156 #[serde(rename = "ORDER_ORDERED")]
157 Ordered,
158}
159
160#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
165pub struct IbcTimeoutBlock {
166 pub revision: u64,
169 pub height: u64,
172}
173
174impl IbcTimeoutBlock {
175 pub fn is_zero(&self) -> bool {
176 self.revision == 0 && self.height == 0
177 }
178}
179
180impl PartialOrd for IbcTimeoutBlock {
181 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
182 Some(self.cmp(other))
183 }
184}
185
186impl Ord for IbcTimeoutBlock {
187 fn cmp(&self, other: &Self) -> Ordering {
188 match self.revision.cmp(&other.revision) {
189 Ordering::Equal => self.height.cmp(&other.height),
190 other => other,
191 }
192 }
193}
194
195#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
196#[non_exhaustive]
197pub struct IbcPacket {
198 pub data: Binary,
200 pub src: IbcEndpoint,
202 pub dest: IbcEndpoint,
204 pub sequence: u64,
206 pub timeout: IbcTimeout,
207}
208
209impl IbcPacket {
210 pub fn new(
212 data: impl Into<Binary>,
213 src: IbcEndpoint,
214 dest: IbcEndpoint,
215 sequence: u64,
216 timeout: IbcTimeout,
217 ) -> Self {
218 Self {
219 data: data.into(),
220 src,
221 dest,
222 sequence,
223 timeout,
224 }
225 }
226}
227
228#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
229#[non_exhaustive]
230pub struct IbcAcknowledgement {
231 pub data: Binary,
232 }
235
236impl IbcAcknowledgement {
237 pub fn new(data: impl Into<Binary>) -> Self {
238 IbcAcknowledgement { data: data.into() }
239 }
240
241 pub fn encode_json(data: &impl Serialize) -> StdResult<Self> {
242 Ok(IbcAcknowledgement {
243 data: to_json_binary(data)?,
244 })
245 }
246}
247
248#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
250#[serde(rename_all = "snake_case")]
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#[cfg(not(feature = "ibc3"))]
302pub type IbcChannelOpenResponse = ();
303#[cfg(feature = "ibc3")]
305pub type IbcChannelOpenResponse = Option<Ibc3ChannelOpenResponse>;
306
307#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
308pub struct Ibc3ChannelOpenResponse {
309 pub version: String,
311}
312
313#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
315#[serde(rename_all = "snake_case")]
316pub enum IbcChannelConnectMsg {
317 OpenAck {
319 channel: IbcChannel,
320 counterparty_version: String,
321 },
322 OpenConfirm { channel: IbcChannel },
324}
325
326impl IbcChannelConnectMsg {
327 pub fn new_ack(channel: IbcChannel, counterparty_version: impl Into<String>) -> Self {
328 Self::OpenAck {
329 channel,
330 counterparty_version: counterparty_version.into(),
331 }
332 }
333
334 pub fn new_confirm(channel: IbcChannel) -> Self {
335 Self::OpenConfirm { channel }
336 }
337
338 pub fn channel(&self) -> &IbcChannel {
339 match self {
340 Self::OpenAck { channel, .. } => channel,
341 Self::OpenConfirm { channel } => channel,
342 }
343 }
344
345 pub fn counterparty_version(&self) -> Option<&str> {
346 match self {
347 Self::OpenAck {
348 counterparty_version,
349 ..
350 } => Some(counterparty_version),
351 _ => None,
352 }
353 }
354}
355
356impl From<IbcChannelConnectMsg> for IbcChannel {
357 fn from(msg: IbcChannelConnectMsg) -> IbcChannel {
358 match msg {
359 IbcChannelConnectMsg::OpenAck { channel, .. } => channel,
360 IbcChannelConnectMsg::OpenConfirm { channel } => channel,
361 }
362 }
363}
364
365#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
367#[serde(rename_all = "snake_case")]
368pub enum IbcChannelCloseMsg {
369 CloseInit { channel: IbcChannel },
371 CloseConfirm { channel: IbcChannel }, }
374
375impl IbcChannelCloseMsg {
376 pub fn new_init(channel: IbcChannel) -> Self {
377 Self::CloseInit { channel }
378 }
379
380 pub fn new_confirm(channel: IbcChannel) -> Self {
381 Self::CloseConfirm { channel }
382 }
383
384 pub fn channel(&self) -> &IbcChannel {
385 match self {
386 Self::CloseInit { channel } => channel,
387 Self::CloseConfirm { channel } => channel,
388 }
389 }
390}
391
392impl From<IbcChannelCloseMsg> for IbcChannel {
393 fn from(msg: IbcChannelCloseMsg) -> IbcChannel {
394 match msg {
395 IbcChannelCloseMsg::CloseInit { channel } => channel,
396 IbcChannelCloseMsg::CloseConfirm { channel } => channel,
397 }
398 }
399}
400
401#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
403#[non_exhaustive]
404pub struct IbcPacketReceiveMsg {
405 pub packet: IbcPacket,
406 #[cfg(feature = "ibc3")]
407 pub relayer: Addr,
408}
409
410impl IbcPacketReceiveMsg {
411 #[cfg(not(feature = "ibc3"))]
412 pub fn new(packet: IbcPacket) -> Self {
413 Self { packet }
414 }
415
416 #[cfg(feature = "ibc3")]
417 pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
418 Self { packet, relayer }
419 }
420}
421
422#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
424#[non_exhaustive]
425pub struct IbcPacketAckMsg {
426 pub acknowledgement: IbcAcknowledgement,
427 pub original_packet: IbcPacket,
428 #[cfg(feature = "ibc3")]
429 pub relayer: Addr,
430}
431
432impl IbcPacketAckMsg {
433 #[cfg(not(feature = "ibc3"))]
434 pub fn new(acknowledgement: IbcAcknowledgement, original_packet: IbcPacket) -> Self {
435 Self {
436 acknowledgement,
437 original_packet,
438 }
439 }
440
441 #[cfg(feature = "ibc3")]
442 pub fn new(
443 acknowledgement: IbcAcknowledgement,
444 original_packet: IbcPacket,
445 relayer: Addr,
446 ) -> Self {
447 Self {
448 acknowledgement,
449 original_packet,
450 relayer,
451 }
452 }
453}
454
455#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
457#[non_exhaustive]
458pub struct IbcPacketTimeoutMsg {
459 pub packet: IbcPacket,
460 #[cfg(feature = "ibc3")]
461 pub relayer: Addr,
462}
463
464impl IbcPacketTimeoutMsg {
465 #[cfg(not(feature = "ibc3"))]
466 pub fn new(packet: IbcPacket) -> Self {
467 Self { packet }
468 }
469
470 #[cfg(feature = "ibc3")]
471 pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
472 Self { packet, relayer }
473 }
474}
475
476#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
484#[non_exhaustive]
485pub struct IbcBasicResponse<T = Empty> {
486 pub messages: Vec<SubMsg<T>>,
491 pub attributes: Vec<Attribute>,
497 pub events: Vec<Event>,
504}
505
506impl<T> Default for IbcBasicResponse<T> {
508 fn default() -> Self {
509 IbcBasicResponse {
510 messages: vec![],
511 attributes: vec![],
512 events: vec![],
513 }
514 }
515}
516
517impl<T> IbcBasicResponse<T> {
518 pub fn new() -> Self {
519 Self::default()
520 }
521
522 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
524 self.attributes.push(Attribute::new(key, value));
525 self
526 }
527
528 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
531 self.messages.push(SubMsg::new(msg));
532 self
533 }
534
535 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
538 self.messages.push(msg);
539 self
540 }
541
542 pub fn add_event(mut self, event: Event) -> Self {
548 self.events.push(event);
549 self
550 }
551
552 pub fn add_attributes<A: Into<Attribute>>(
571 mut self,
572 attrs: impl IntoIterator<Item = A>,
573 ) -> Self {
574 self.attributes.extend(attrs.into_iter().map(A::into));
575 self
576 }
577
578 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
590 self.add_submessages(msgs.into_iter().map(SubMsg::new))
591 }
592
593 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
605 self.messages.extend(msgs);
606 self
607 }
608
609 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
615 self.events.extend(events);
616 self
617 }
618}
619
620#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
626#[non_exhaustive]
627pub struct IbcReceiveResponse<T = Empty> {
628 pub acknowledgement: Binary,
631 pub messages: Vec<SubMsg<T>>,
636 pub attributes: Vec<Attribute>,
642 pub events: Vec<Event>,
649}
650
651impl<T> Default for IbcReceiveResponse<T> {
653 fn default() -> Self {
654 IbcReceiveResponse {
655 acknowledgement: Binary(vec![]),
656 messages: vec![],
657 attributes: vec![],
658 events: vec![],
659 }
660 }
661}
662
663impl<T> IbcReceiveResponse<T> {
664 pub fn new() -> Self {
665 Self::default()
666 }
667
668 pub fn set_ack(mut self, ack: impl Into<Binary>) -> Self {
681 self.acknowledgement = ack.into();
682 self
683 }
684
685 pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
687 self.attributes.push(Attribute::new(key, value));
688 self
689 }
690
691 pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
694 self.messages.push(SubMsg::new(msg));
695 self
696 }
697
698 pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
701 self.messages.push(msg);
702 self
703 }
704
705 pub fn add_event(mut self, event: Event) -> Self {
711 self.events.push(event);
712 self
713 }
714
715 pub fn add_attributes<A: Into<Attribute>>(
734 mut self,
735 attrs: impl IntoIterator<Item = A>,
736 ) -> Self {
737 self.attributes.extend(attrs.into_iter().map(A::into));
738 self
739 }
740
741 pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
753 self.add_submessages(msgs.into_iter().map(SubMsg::new))
754 }
755
756 pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
768 self.messages.extend(msgs);
769 self
770 }
771
772 pub fn add_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
778 self.events.extend(events);
779 self
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use serde_json_wasm::to_string;
787
788 #[test]
789 fn serialize_msg() {
791 let msg = IbcMsg::Transfer {
792 channel_id: "channel-123".to_string(),
793 to_address: "my-special-addr".into(),
794 amount: Coin::new(12345678, "uatom"),
795 timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
796 };
797 let encoded = to_string(&msg).unwrap();
798 let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"}}}"#;
799 assert_eq!(encoded.as_str(), expected);
800 }
801
802 #[test]
803 fn ibc_timeout_serialize() {
804 let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
805 let expected = r#"{"block":null,"timestamp":"684816844"}"#;
806 assert_eq!(to_string(×tamp).unwrap(), expected);
807
808 let block = IbcTimeout::with_block(IbcTimeoutBlock {
809 revision: 12,
810 height: 129,
811 });
812 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
813 assert_eq!(to_string(&block).unwrap(), expected);
814
815 let both = IbcTimeout::with_both(
816 IbcTimeoutBlock {
817 revision: 12,
818 height: 129,
819 },
820 Timestamp::from_nanos(684816844),
821 );
822 let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
823 assert_eq!(to_string(&both).unwrap(), expected);
824 }
825
826 #[test]
827 #[allow(clippy::eq_op)]
828 fn ibc_timeout_block_ord() {
829 let epoch1a = IbcTimeoutBlock {
830 revision: 1,
831 height: 1000,
832 };
833 let epoch1b = IbcTimeoutBlock {
834 revision: 1,
835 height: 3000,
836 };
837 let epoch2a = IbcTimeoutBlock {
838 revision: 2,
839 height: 500,
840 };
841 let epoch2b = IbcTimeoutBlock {
842 revision: 2,
843 height: 2500,
844 };
845
846 assert_eq!(epoch1a, epoch1a);
848 assert!(epoch1a < epoch1b);
849 assert!(epoch1b > epoch1a);
850 assert!(epoch2a > epoch1a);
851 assert!(epoch2b > epoch1a);
852
853 assert!(epoch1b > epoch1a);
855 assert!(epoch2a > epoch1b);
856 assert!(epoch2b > epoch2a);
857 assert!(epoch2b > epoch1b);
858 assert!(epoch1a < epoch1b);
860 assert!(epoch1b < epoch2a);
861 assert!(epoch2a < epoch2b);
862 assert!(epoch1b < epoch2b);
863 }
864
865 #[test]
866 fn ibc_packet_serialize() {
867 let packet = IbcPacket {
868 data: b"foo".into(),
869 src: IbcEndpoint {
870 port_id: "their-port".to_string(),
871 channel_id: "channel-1234".to_string(),
872 },
873 dest: IbcEndpoint {
874 port_id: "our-port".to_string(),
875 channel_id: "chan33".into(),
876 },
877 sequence: 27,
878 timeout: IbcTimeout::with_both(
879 IbcTimeoutBlock {
880 revision: 1,
881 height: 12345678,
882 },
883 Timestamp::from_nanos(4611686018427387904),
884 ),
885 };
886 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"}}"#;
887 assert_eq!(to_string(&packet).unwrap(), expected);
888
889 let no_timestamp = IbcPacket {
890 data: b"foo".into(),
891 src: IbcEndpoint {
892 port_id: "their-port".to_string(),
893 channel_id: "channel-1234".to_string(),
894 },
895 dest: IbcEndpoint {
896 port_id: "our-port".to_string(),
897 channel_id: "chan33".into(),
898 },
899 sequence: 27,
900 timeout: IbcTimeout::with_block(IbcTimeoutBlock {
901 revision: 1,
902 height: 12345678,
903 }),
904 };
905 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}}"#;
906 assert_eq!(to_string(&no_timestamp).unwrap(), expected);
907 }
908}