ibc_middleware_packet_forward/
msg.rs1use 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#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
11pub struct PacketMetadata {
12 pub forward: ForwardMetadata,
14}
15
16#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
19pub struct ForwardMetadata {
20 #[serde(deserialize_with = "deserialize_non_empty_signer")]
22 pub receiver: Signer,
23 #[serde(deserialize_with = "deserialize_from_str")]
25 pub port: PortId,
26 #[serde(deserialize_with = "deserialize_from_str")]
28 pub channel: ChannelId,
29 #[serde(skip_serializing_if = "Option::is_none")]
34 pub timeout: Option<Duration>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub retries: Option<u8>,
38 #[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 pub const fn from_dur(dur: dur::Duration) -> Self {
122 Self(dur)
123 }
124 }
125
126 #[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}