ibc_middleware_packet_forward/
msg.rs

1use alloc::string::{String, ToString};
2use core::fmt;
3use core::str::FromStr;
4
5use ibc_core_host_types::identifiers::{ChannelId, PortId};
6use ibc_primitives::Signer;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9/// Metadata included in ICS-20 packet memos.
10#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
11pub struct PacketMetadata {
12    /// Packet forward middleware metadata.
13    pub forward: ForwardMetadata,
14}
15
16/// Metadata included in ICS-20 packet memos,
17/// related with the packet forwarding middleware.
18#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
19pub struct ForwardMetadata {
20    /// Receiver account on the destination chain.
21    #[serde(deserialize_with = "deserialize_non_empty_signer")]
22    pub receiver: Signer,
23    /// Destination port (usually the `transfer` port).
24    #[serde(deserialize_with = "deserialize_from_str")]
25    pub port: PortId,
26    /// Destination channel.
27    #[serde(deserialize_with = "deserialize_from_str")]
28    pub channel: ChannelId,
29    /// Packet timeout duration.
30    ///
31    /// Formatted as regular time strings (e.g. `"1m20s"`),
32    /// or nanoseconds (e.g. `12345`).
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub timeout: Option<Duration>,
35    /// The number of retries before a packet is invalidated.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub retries: Option<u8>,
38    /// The memo of the next forward transfer. This might be
39    /// another [`ForwardMetadata`] structure, along with
40    /// any additional middleware callbacks.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub next: Option<serde_json::Map<String, serde_json::Value>>,
43}
44
45fn deserialize_non_empty_signer<'de, D>(deserializer: D) -> Result<Signer, D::Error>
46where
47    D: Deserializer<'de>,
48{
49    let s = String::deserialize(deserializer)?;
50    if !s.is_empty() {
51        Ok(s.into())
52    } else {
53        Err(serde::de::Error::custom(
54            "IBC forward receiver cannot be empty",
55        ))
56    }
57}
58
59fn deserialize_from_str<'de, D, T>(deserializer: D) -> Result<T, D::Error>
60where
61    D: Deserializer<'de>,
62    T: FromStr,
63    T::Err: fmt::Display,
64{
65    let s = String::deserialize(deserializer)?;
66    T::from_str(&s).map_err(serde::de::Error::custom)
67}
68
69fn serialize_to_str<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
70where
71    S: Serializer,
72    T: fmt::Display,
73{
74    serializer.serialize_str(&value.to_string())
75}
76
77#[doc(inline)]
78pub use duration::Duration;
79
80mod duration {
81    #[cfg(feature = "borsh")]
82    use borsh::{BorshDeserialize, BorshSerialize};
83
84    use super::*;
85
86    #[derive(Debug, Serialize, Deserialize)]
87    struct DurrDerp(
88        #[serde(deserialize_with = "deserialize_from_str")]
89        #[serde(serialize_with = "serialize_to_str")]
90        dur::Duration,
91    );
92
93    impl From<u64> for U64Dur {
94        fn from(dur: u64) -> Self {
95            Self(DurrDerp(dur::Duration::from_nanos(dur.into())))
96        }
97    }
98
99    #[derive(Debug, Serialize, Deserialize)]
100    #[serde(from = "u64")]
101    struct U64Dur(DurrDerp);
102
103    #[derive(Debug, Serialize, Deserialize)]
104    #[serde(untagged)]
105    enum AllDuration {
106        Dur(DurrDerp),
107        U64(U64Dur),
108    }
109
110    impl From<AllDuration> for Duration {
111        fn from(dur: AllDuration) -> Self {
112            match dur {
113                AllDuration::Dur(DurrDerp(dur)) => Self(dur),
114                AllDuration::U64(U64Dur(DurrDerp(dur))) => Self(dur),
115            }
116        }
117    }
118
119    impl Duration {
120        /// Initialize a new [`Duration`].
121        pub const fn from_dur(dur: dur::Duration) -> Self {
122            Self(dur)
123        }
124    }
125
126    /// Duration type whose serialization routines are compatible with Strangelove's
127    /// PFM JSON forward messages.
128    #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
129    #[serde(from = "AllDuration")]
130    #[repr(transparent)]
131    pub struct Duration(#[serde(serialize_with = "serialize_to_str")] pub dur::Duration);
132
133    #[cfg(feature = "borsh")]
134    impl BorshSerialize for Duration {
135        fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
136            let nanos = self.0.as_nanos().to_le_bytes();
137            writer.write_all(&nanos)
138        }
139    }
140
141    #[cfg(feature = "borsh")]
142    impl BorshDeserialize for Duration {
143        fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Duration> {
144            let mut buf = [0u8; 16];
145            reader.read_exact(&mut buf)?;
146
147            let nanos = u128::from_le_bytes(buf);
148            Ok(Duration(dur::Duration::from_nanos(nanos)))
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn duration_serde_roundtrip_parsing() {
159        const DUR_STR: &str = "\"1m5s\"";
160        const DUR_U64: &str = "1";
161
162        let expected_from_str = Duration(dur::Duration::from_secs(65));
163        let expected_from_u64 = Duration(dur::Duration::from_nanos(1));
164
165        let parsed: Duration = serde_json::from_str(DUR_STR).unwrap();
166        assert_eq!(parsed, expected_from_str);
167
168        let parsed: Duration = serde_json::from_str(DUR_U64).unwrap();
169        assert_eq!(parsed, expected_from_u64);
170    }
171
172    #[test]
173    fn forward_msg_parsing() {
174        struct TestCase {
175            raw_json: &'static str,
176            expected: Result<PacketMetadata, ()>,
177        }
178
179        impl TestCase {
180            fn assert(self) {
181                let parsed = serde_json::from_str::<PacketMetadata>(self.raw_json).map_err(|_| ());
182                assert_eq!(parsed, self.expected);
183            }
184        }
185
186        let cases = [
187            TestCase {
188                raw_json: r#"
189                    {
190                      "forward": {
191                        "channel": "channel-1180",
192                        "port": "transfer",
193                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
194                      },
195                      "ibc_callback": "osmo1ewll8h7up3g0ca2z9ur9e6dv6an64snxg5k8tmzylg6uprkyhgzszjgdzr"
196                    }
197                "#,
198                expected: Ok(PacketMetadata {
199                    forward: ForwardMetadata {
200                        receiver: "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
201                            .to_owned()
202                            .into(),
203                        port: PortId::transfer(),
204                        channel: ChannelId::new(1180),
205                        timeout: None,
206                        retries: None,
207                        next: None,
208                    },
209                }),
210            },
211            TestCase {
212                raw_json: r#"
213                    {
214                      "forward": {
215                        "channel": "channel-1180",
216                        "port": "transfer",
217                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
218                      }
219                    }
220                "#,
221                expected: Ok(PacketMetadata {
222                    forward: ForwardMetadata {
223                        receiver: "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
224                            .to_owned()
225                            .into(),
226                        port: PortId::transfer(),
227                        channel: ChannelId::new(1180),
228                        timeout: None,
229                        retries: None,
230                        next: None,
231                    },
232                }),
233            },
234            TestCase {
235                raw_json: r#"
236                    {
237                      "forward": {
238                        "channel": "channel-1180",
239                        "port": "transfer",
240                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt",
241                        "next": {
242                            "forward": {
243                                "receiver": "noble18st0wqx84av8y6xdlss9d6m2nepyqwj6nfxxuv",
244                                "channel": "channel-1181",
245                                "port": "transfer"
246                            }
247                        }
248                      }
249                    }
250                "#,
251                expected: Ok(PacketMetadata {
252                    forward: ForwardMetadata {
253                        receiver: "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
254                            .to_owned()
255                            .into(),
256                        port: PortId::transfer(),
257                        channel: ChannelId::new(1180),
258                        timeout: None,
259                        retries: None,
260                        next: Some(serde_json::Map::from_iter([(
261                            "forward".to_owned(),
262                            serde_json::Value::Object(serde_json::Map::from_iter([
263                                (
264                                    "receiver".to_owned(),
265                                    serde_json::Value::String(
266                                        "noble18st0wqx84av8y6xdlss9d6m2nepyqwj6nfxxuv".to_owned(),
267                                    ),
268                                ),
269                                (
270                                    "channel".to_owned(),
271                                    serde_json::Value::String("channel-1181".to_owned()),
272                                ),
273                                (
274                                    "port".to_owned(),
275                                    serde_json::Value::String("transfer".to_owned()),
276                                ),
277                            ])),
278                        )])),
279                    },
280                }),
281            },
282            TestCase {
283                raw_json: r#"
284                    {
285                      "forwar": {
286                        "channel": "channel-1180",
287                        "port": "transfer",
288                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
289                      }
290                    }
291                "#,
292                expected: Err(()),
293            },
294            TestCase {
295                raw_json: r#"
296                    {
297                      "forward": {
298                        "channel": "channel-",
299                        "port": "transfer",
300                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
301                      }
302                    }
303                "#,
304                expected: Err(()),
305            },
306            TestCase {
307                raw_json: r#"
308                    {
309                      "forward": {
310                        "channel": "channel-1234",
311                        "port": "transfer",
312                        "receiver": ""
313                      }
314                    }
315                "#,
316                expected: Err(()),
317            },
318            TestCase {
319                raw_json: r#"
320                    {
321                      "forward": {
322                        "channel": "channel-1180",
323                        "port": "transfer",
324                        "timeout": "1m20s",
325                        "receiver": "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
326                      }
327                    }
328                "#,
329                expected: Ok(PacketMetadata {
330                    forward: ForwardMetadata {
331                        receiver: "tnam1qrx3tphxjr9qaznadzykxzt4x76c0cm8ts3pwukt"
332                            .to_owned()
333                            .into(),
334                        port: PortId::transfer(),
335                        channel: ChannelId::new(1180),
336                        timeout: Some(Duration(dur::Duration::from_secs(80))),
337                        retries: None,
338                        next: None,
339                    },
340                }),
341            },
342        ];
343
344        for case in cases {
345            case.assert();
346        }
347    }
348}