ibc_types_transfer/
acknowledgement.rs

1use core::fmt::{Display, Error as FmtError, Formatter};
2
3use crate::prelude::*;
4
5/// A string constant included in error acknowledgements.
6/// NOTE: Changing this const is state machine breaking as acknowledgements are written into state
7pub const ACK_ERR_STR: &str = "error handling packet on destination chain: see events for details";
8
9/// A successful acknowledgement, equivalent to `base64::encode(0x01)`.
10pub const ACK_SUCCESS_B64: &str = "AQ==";
11
12#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum ConstAckSuccess {
15    #[cfg_attr(feature = "with_serde", serde(rename = "AQ=="))]
16    Success,
17}
18
19#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum TokenTransferAcknowledgement {
22    /// Successful Acknowledgement
23    /// e.g. `{"result":"AQ=="}`
24    #[cfg_attr(feature = "with_serde", serde(rename = "result"))]
25    Success(ConstAckSuccess),
26    /// Error Acknowledgement
27    /// e.g. `{"error":"cannot unmarshal ICS-20 transfer packet data"}`
28    #[cfg_attr(feature = "with_serde", serde(rename = "error"))]
29    Error(String),
30}
31
32impl TokenTransferAcknowledgement {
33    pub fn success() -> Self {
34        Self::Success(ConstAckSuccess::Success)
35    }
36
37    pub fn is_successful(&self) -> bool {
38        matches!(self, TokenTransferAcknowledgement::Success(_))
39    }
40}
41
42impl Display for TokenTransferAcknowledgement {
43    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
44        match self {
45            TokenTransferAcknowledgement::Success(_) => write!(f, "{ACK_SUCCESS_B64}"),
46            TokenTransferAcknowledgement::Error(err_str) => write!(f, "{err_str}"),
47        }
48    }
49}
50
51impl From<TokenTransferAcknowledgement> for Vec<u8> {
52    fn from(ack: TokenTransferAcknowledgement) -> Self {
53        // WARNING: Make sure all branches always return a non-empty vector.
54        // Otherwise, the conversion to `Acknowledgement` will panic.
55        match ack {
56            TokenTransferAcknowledgement::Success(_) => r#"{"result":"AQ=="}"#.as_bytes().into(),
57            TokenTransferAcknowledgement::Error(s) => alloc::format!(r#"{{"error":"{s}"}}"#).into(),
58        }
59    }
60}
61
62#[cfg(test)]
63mod test {
64    use super::*;
65
66    #[cfg(feature = "with_serde")]
67    #[test]
68    fn test_ack_ser() {
69        use crate::alloc::borrow::ToOwned;
70        fn ser_json_assert_eq(ack: TokenTransferAcknowledgement, json_str: &str) {
71            let ser = serde_json::to_string(&ack).unwrap();
72            assert_eq!(ser, json_str)
73        }
74
75        ser_json_assert_eq(
76            TokenTransferAcknowledgement::success(),
77            r#"{"result":"AQ=="}"#,
78        );
79        ser_json_assert_eq(
80            TokenTransferAcknowledgement::Error(
81                "cannot unmarshal ICS-20 transfer packet data".to_owned(),
82            ),
83            r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#,
84        );
85    }
86
87    #[cfg(feature = "with_serde")]
88    #[test]
89    fn test_ack_success_to_vec() {
90        let ack_success: Vec<u8> = TokenTransferAcknowledgement::success().into();
91
92        // Check that it's the same output as ibc-go
93        // Note: this also implicitly checks that the ack bytes are non-empty,
94        // which would make the conversion to `Acknowledgement` panic
95        assert_eq!(ack_success, r#"{"result":"AQ=="}"#.as_bytes());
96    }
97
98    #[cfg(feature = "with_serde")]
99    #[test]
100    fn test_ack_error_to_vec() {
101        use crate::alloc::string::ToString;
102        let ack_error: Vec<u8> = TokenTransferAcknowledgement::Error(
103            "cannot unmarshal ICS-20 transfer packet data".to_string(),
104        )
105        .into();
106
107        // Check that it's the same output as ibc-go
108        // Note: this also implicitly checks that the ack bytes are non-empty,
109        // which would make the conversion to `Acknowledgement` panic
110        assert_eq!(
111            ack_error,
112            r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#.as_bytes()
113        );
114    }
115
116    #[cfg(feature = "with_serde")]
117    #[test]
118    fn test_ack_de() {
119        use crate::alloc::borrow::ToOwned;
120        fn de_json_assert_eq(json_str: &str, ack: TokenTransferAcknowledgement) {
121            let de = serde_json::from_str::<TokenTransferAcknowledgement>(json_str).unwrap();
122            assert_eq!(de, ack)
123        }
124
125        de_json_assert_eq(
126            r#"{"result":"AQ=="}"#,
127            TokenTransferAcknowledgement::success(),
128        );
129        de_json_assert_eq(
130            r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#,
131            TokenTransferAcknowledgement::Error(
132                "cannot unmarshal ICS-20 transfer packet data".to_owned(),
133            ),
134        );
135
136        assert!(
137            serde_json::from_str::<TokenTransferAcknowledgement>(r#"{"result":"AQ="}"#).is_err()
138        );
139        assert!(
140            serde_json::from_str::<TokenTransferAcknowledgement>(r#"{"success":"AQ=="}"#).is_err()
141        );
142    }
143}