cctp_rs/protocol/
attestation.rs1use alloy_primitives::{hex::FromHex, Bytes};
6use serde::{Deserialize, Deserializer};
7
8pub type AttestationBytes = Vec<u8>;
10
11#[derive(Debug, Deserialize)]
35pub struct V2AttestationResponse {
36 pub messages: Vec<V2Message>,
38}
39
40#[derive(Debug, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct V2Message {
47 pub status: AttestationStatus,
49
50 #[serde(default, deserialize_with = "deserialize_optional_bytes_or_pending")]
52 pub message: Option<Bytes>,
53
54 #[serde(default, deserialize_with = "deserialize_optional_bytes_or_pending")]
56 pub attestation: Option<Bytes>,
57}
58
59#[derive(Debug, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct AttestationResponse {
75 pub status: AttestationStatus,
76 #[serde(default, deserialize_with = "deserialize_optional_bytes_or_pending")]
77 pub attestation: Option<Bytes>,
78}
79
80fn deserialize_optional_bytes_or_pending<'de, D>(deserializer: D) -> Result<Option<Bytes>, D::Error>
90where
91 D: Deserializer<'de>,
92{
93 let opt: Option<String> = Option::deserialize(deserializer)?;
94
95 match opt {
96 None => Ok(None),
97 Some(s) if s.is_empty() => Ok(None),
98 Some(s) if s.eq_ignore_ascii_case("pending") => Ok(None),
99 Some(s) => {
100 let bytes = Bytes::from_hex(s).map_err(serde::de::Error::custom)?;
101 Ok(Some(bytes))
102 }
103 }
104}
105
106#[derive(Debug, Deserialize, PartialEq)]
108#[serde(rename_all = "snake_case")]
109pub enum AttestationStatus {
110 Complete,
111 Pending,
112 PendingConfirmations,
113 Failed,
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_deserialize_attestation_with_valid_hex() {
122 let json = r#"{"status":"complete","attestation":"0x1234abcd"}"#;
123 let response: AttestationResponse = serde_json::from_str(json).unwrap();
124
125 assert_eq!(response.status, AttestationStatus::Complete);
126 assert!(response.attestation.is_some());
127 assert_eq!(
128 response.attestation.unwrap().to_vec(),
129 vec![0x12, 0x34, 0xab, 0xcd]
130 );
131 }
132
133 #[test]
134 fn test_deserialize_attestation_with_pending_string() {
135 let json = r#"{"status":"pending","attestation":"PENDING"}"#;
136 let response: AttestationResponse = serde_json::from_str(json).unwrap();
137
138 assert_eq!(response.status, AttestationStatus::Pending);
139 assert!(response.attestation.is_none());
140 }
141
142 #[test]
143 fn test_deserialize_attestation_with_pending_lowercase() {
144 let json = r#"{"status":"pending","attestation":"pending"}"#;
145 let response: AttestationResponse = serde_json::from_str(json).unwrap();
146
147 assert_eq!(response.status, AttestationStatus::Pending);
148 assert!(response.attestation.is_none());
149 }
150
151 #[test]
152 fn test_deserialize_attestation_with_null() {
153 let json = r#"{"status":"pending","attestation":null}"#;
154 let response: AttestationResponse = serde_json::from_str(json).unwrap();
155
156 assert_eq!(response.status, AttestationStatus::Pending);
157 assert!(response.attestation.is_none());
158 }
159
160 #[test]
161 fn test_deserialize_attestation_missing_field() {
162 let json = r#"{"status":"pending"}"#;
163 let response: AttestationResponse = serde_json::from_str(json).unwrap();
164
165 assert_eq!(response.status, AttestationStatus::Pending);
166 assert!(response.attestation.is_none());
167 }
168
169 #[test]
170 fn test_deserialize_attestation_with_empty_string() {
171 let json = r#"{"status":"pending","attestation":""}"#;
172 let response: AttestationResponse = serde_json::from_str(json).unwrap();
173
174 assert_eq!(response.status, AttestationStatus::Pending);
175 assert!(response.attestation.is_none());
176 }
177
178 #[test]
179 fn test_deserialize_attestation_with_hex_no_prefix() {
180 let json = r#"{"status":"complete","attestation":"deadbeef"}"#;
181 let response: AttestationResponse = serde_json::from_str(json).unwrap();
182
183 assert_eq!(response.status, AttestationStatus::Complete);
184 assert!(response.attestation.is_some());
185 assert_eq!(
186 response.attestation.unwrap().to_vec(),
187 vec![0xde, 0xad, 0xbe, 0xef]
188 );
189 }
190
191 #[test]
192 fn test_deserialize_attestation_with_invalid_hex_fails() {
193 let json = r#"{"status":"complete","attestation":"not_valid_hex"}"#;
194 let result = serde_json::from_str::<AttestationResponse>(json);
195
196 assert!(result.is_err());
197 }
198
199 #[test]
200 fn test_deserialize_all_status_variants() {
201 let complete = r#"{"status":"complete"}"#;
202 let pending = r#"{"status":"pending"}"#;
203 let pending_confirmations = r#"{"status":"pending_confirmations"}"#;
204 let failed = r#"{"status":"failed"}"#;
205
206 assert_eq!(
207 serde_json::from_str::<AttestationResponse>(complete)
208 .unwrap()
209 .status,
210 AttestationStatus::Complete
211 );
212 assert_eq!(
213 serde_json::from_str::<AttestationResponse>(pending)
214 .unwrap()
215 .status,
216 AttestationStatus::Pending
217 );
218 assert_eq!(
219 serde_json::from_str::<AttestationResponse>(pending_confirmations)
220 .unwrap()
221 .status,
222 AttestationStatus::PendingConfirmations
223 );
224 assert_eq!(
225 serde_json::from_str::<AttestationResponse>(failed)
226 .unwrap()
227 .status,
228 AttestationStatus::Failed
229 );
230 }
231
232 #[test]
237 fn test_v2_deserialize_complete_response() {
238 let json = r#"{
239 "messages": [
240 {
241 "status": "complete",
242 "message": "0xdeadbeef",
243 "attestation": "0x1234abcd"
244 }
245 ]
246 }"#;
247 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
248
249 assert_eq!(response.messages.len(), 1);
250 assert_eq!(response.messages[0].status, AttestationStatus::Complete);
251 assert!(response.messages[0].message.is_some());
252 assert!(response.messages[0].attestation.is_some());
253 assert_eq!(
254 response.messages[0].attestation.as_ref().unwrap().to_vec(),
255 vec![0x12, 0x34, 0xab, 0xcd]
256 );
257 assert_eq!(
258 response.messages[0].message.as_ref().unwrap().to_vec(),
259 vec![0xde, 0xad, 0xbe, 0xef]
260 );
261 }
262
263 #[test]
264 fn test_v2_deserialize_pending_response() {
265 let json = r#"{
266 "messages": [
267 {
268 "status": "pending",
269 "message": null,
270 "attestation": null
271 }
272 ]
273 }"#;
274 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
275
276 assert_eq!(response.messages.len(), 1);
277 assert_eq!(response.messages[0].status, AttestationStatus::Pending);
278 assert!(response.messages[0].message.is_none());
279 assert!(response.messages[0].attestation.is_none());
280 }
281
282 #[test]
283 fn test_v2_deserialize_pending_with_string() {
284 let json = r#"{
285 "messages": [
286 {
287 "status": "pending",
288 "message": "PENDING",
289 "attestation": "PENDING"
290 }
291 ]
292 }"#;
293 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
294
295 assert_eq!(response.messages.len(), 1);
296 assert_eq!(response.messages[0].status, AttestationStatus::Pending);
297 assert!(response.messages[0].message.is_none());
298 assert!(response.messages[0].attestation.is_none());
299 }
300
301 #[test]
302 fn test_v2_deserialize_multiple_messages() {
303 let json = r#"{
304 "messages": [
305 {
306 "status": "complete",
307 "message": "0xaa",
308 "attestation": "0xbb"
309 },
310 {
311 "status": "complete",
312 "message": "0xcc",
313 "attestation": "0xdd"
314 }
315 ]
316 }"#;
317 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
318
319 assert_eq!(response.messages.len(), 2);
320 assert_eq!(response.messages[0].status, AttestationStatus::Complete);
321 assert_eq!(response.messages[1].status, AttestationStatus::Complete);
322 }
323
324 #[test]
325 fn test_v2_deserialize_empty_messages() {
326 let json = r#"{"messages": []}"#;
327 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
328
329 assert_eq!(response.messages.len(), 0);
330 }
331
332 #[test]
333 fn test_v2_deserialize_pending_confirmations() {
334 let json = r#"{
335 "messages": [
336 {
337 "status": "pending_confirmations",
338 "message": "0xdeadbeef",
339 "attestation": null
340 }
341 ]
342 }"#;
343 let response: V2AttestationResponse = serde_json::from_str(json).unwrap();
344
345 assert_eq!(response.messages.len(), 1);
346 assert_eq!(
347 response.messages[0].status,
348 AttestationStatus::PendingConfirmations
349 );
350 assert!(response.messages[0].message.is_some());
351 assert!(response.messages[0].attestation.is_none());
352 }
353}