1use crate::shim::actors::miner::DeadlineInfo;
125use derive_more::From;
126use fvm_shared4::piece::PaddedPieceSize;
127#[cfg(test)]
128use pretty_assertions::assert_eq;
129use schemars::{JsonSchema, Schema, SchemaGenerator};
130use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
131#[cfg(test)]
132use serde_json::json;
133use std::{fmt::Display, str::FromStr};
134use uuid::Uuid;
135
136pub trait HasLotusJson: Sized {
137 type LotusJson: Serialize + DeserializeOwned;
139 #[cfg(test)]
147 fn snapshots() -> Vec<(serde_json::Value, Self)>;
148 fn into_lotus_json(self) -> Self::LotusJson;
149 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self;
150 fn into_lotus_json_value(self) -> serde_json::Result<serde_json::Value> {
151 serde_json::to_value(self.into_lotus_json())
152 }
153 fn into_lotus_json_string(self) -> serde_json::Result<String> {
154 serde_json::to_string(&self.into_lotus_json())
155 }
156 fn into_lotus_json_string_pretty(self) -> serde_json::Result<String> {
157 serde_json::to_string_pretty(&self.into_lotus_json())
158 }
159}
160
161macro_rules! decl_and_test {
162 ($($mod_name:ident for $domain_ty:ty),* $(,)?) => {
163 $(
164 mod $mod_name;
165 )*
166 #[test]
167 fn all_snapshots() {
168 $(
169 print!("test snapshots for {}...", std::any::type_name::<$domain_ty>());
170 std::io::Write::flush(&mut std::io::stdout()).unwrap();
171 assert_all_snapshots::<$domain_ty>();
173 println!("ok.");
174 )*
175 }
176 #[test]
177 fn all_quickchecks() {
178 $(
179 print!("quickcheck for {}...", std::any::type_name::<$domain_ty>());
180 std::io::Write::flush(&mut std::io::stdout()).unwrap();
181 ::quickcheck::quickcheck(assert_unchanged_via_json::<$domain_ty> as fn(_));
183 println!("ok.");
184 )*
185 }
186 }
187}
188#[cfg(doc)]
189pub(crate) use decl_and_test;
190
191decl_and_test!(
192 actor_state for crate::shim::state_tree::ActorState,
193 address for crate::shim::address::Address,
194 beacon_entry for crate::beacon::BeaconEntry,
195 big_int for num::BigInt,
196 block_header for crate::blocks::CachingBlockHeader,
197 cid for ::cid::Cid,
198 duration for std::time::Duration,
199 percent for crate::shim::percent::Percent,
200 election_proof for crate::blocks::ElectionProof,
201 extended_sector_info for crate::shim::sector::ExtendedSectorInfo,
202 gossip_block for crate::blocks::GossipBlock,
203 key_info for crate::key_management::KeyInfo,
204 message for crate::shim::message::Message,
205 po_st_proof for crate::shim::sector::PoStProof,
206 registered_po_st_proof for crate::shim::sector::RegisteredPoStProof,
207 registered_seal_proof for crate::shim::sector::RegisteredSealProof,
208 sector_info for crate::shim::sector::SectorInfo,
209 sector_size for crate::shim::sector::SectorSize,
210 signature for crate::shim::crypto::Signature,
211 signature_type for crate::shim::crypto::SignatureType,
212 signed_message for crate::message::SignedMessage,
213 ticket for crate::blocks::Ticket,
214 tipset_keys for crate::blocks::TipsetKey,
215 token_amount for crate::shim::econ::TokenAmount,
216 vec_u8 for Vec<u8>,
217 vrf_proof for crate::blocks::VRFProof,
218);
219
220mod actors;
223mod allocation;
224mod arc;
225mod beneficiary_term; mod bit_field; mod bytecode_hash;
228mod entry;
229mod filter_estimate;
230mod hash_map;
231mod ipld; mod miner_info; mod miner_power; mod nonempty; mod opt; mod padded_piece_size;
237mod pending_beneficiary_change; mod power_claim; mod raw_bytes; mod receipt; mod token_state;
242mod tombstone;
243mod transient_data;
244mod vec; mod verifreg_claim;
246
247pub use vec::*;
248
249#[macro_export]
250macro_rules! test_snapshots {
251 ($ty:ty) => {
252 pastey::paste! {
253 #[test]
254 fn [<snapshots_ $ty:snake>]() {
255 use super::*;
256 assert_all_snapshots::<$ty>();
257 }
258 }
259 };
260
261 ($module:path: $ty:ident: $($version:literal),+ $(,)?) => {
262 $(
263 pastey::paste! {
264 #[test]
265 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
266 use super::*;
267 assert_all_snapshots::<$module::[<v $version>]::$ty>();
268 }
269 }
270 )+
271 };
272
273 ($module:path: $nested_path:path: $ty:ident: $($version:literal),+ $(,)?) => {
274 $(
275 pastey::paste! {
276 #[test]
277 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
278 use super::*;
279 assert_all_snapshots::<$module::[<v $version>]::$nested_path::$ty>();
280 }
281 }
282 )+
283 };
284}
285
286#[cfg(any(test, doc))]
287pub fn assert_all_snapshots<T>()
288where
289 T: HasLotusJson,
290 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
291{
292 let snapshots = T::snapshots();
293 assert!(!snapshots.is_empty());
294 for (lotus_json, val) in snapshots {
295 assert_one_snapshot(lotus_json, val);
296 }
297}
298
299#[cfg(test)]
300pub fn assert_one_snapshot<T>(lotus_json: serde_json::Value, val: T)
301where
302 T: HasLotusJson,
303 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
304{
305 let val_lotus_json = val.into_lotus_json();
307 let serialized = serde_json::to_value(&val_lotus_json).unwrap();
308 assert_eq!(
309 serialized.to_string(),
310 lotus_json.to_string(),
311 "snapshot failed for {}",
312 std::any::type_name::<T>()
313 );
314
315 let deserialized = match serde_json::from_value::<T::LotusJson>(lotus_json.clone()) {
318 Ok(lotus_json) => T::from_lotus_json(lotus_json).into_lotus_json(),
319 Err(e) => panic!(
320 "couldn't deserialize a {} from {}: {e}",
321 std::any::type_name::<T::LotusJson>(),
322 lotus_json
323 ),
324 };
325 assert_eq!(deserialized, val_lotus_json);
326}
327
328#[cfg(any(test, doc))]
329pub fn assert_unchanged_via_json<T>(val: T)
330where
331 T: HasLotusJson + Clone + PartialEq + std::fmt::Debug,
332 T::LotusJson: Serialize + serde::de::DeserializeOwned,
333{
334 let temp = val.clone().into_lotus_json();
338 let temp = serde_json::to_value(temp).unwrap();
340 let temp = serde_json::from_value::<T::LotusJson>(temp).unwrap();
342 let temp = T::from_lotus_json(temp);
344
345 assert_eq!(val, temp);
346}
347
348pub mod stringify {
350 use super::*;
351
352 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
353 where
354 T: Display,
355 S: Serializer,
356 {
357 serializer.collect_str(value)
358 }
359
360 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
361 where
362 T: FromStr,
363 T::Err: Display,
364 D: Deserializer<'de>,
365 {
366 String::deserialize(deserializer)?
367 .parse()
368 .map_err(serde::de::Error::custom)
369 }
370}
371
372pub mod hexify_bytes {
374 use super::*;
375
376 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
377 where
378 T: AsRef<[u8]>,
379 S: Serializer,
380 {
381 serializer.serialize_str(&crate::utils::encoding::hex::encode_prefixed(value))
384 }
385
386 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
387 where
388 T: FromStr,
389 T::Err: Display,
390 D: Deserializer<'de>,
391 {
392 String::deserialize(deserializer)?
393 .parse()
394 .map_err(serde::de::Error::custom)
395 }
396}
397
398pub mod hexify_vec_bytes {
399 use super::*;
400 use std::borrow::Cow;
401
402 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
403 where
404 S: Serializer,
405 {
406 serializer.serialize_str(&crate::utils::encoding::hex::encode_prefixed(value))
407 }
408
409 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
410 where
411 D: Deserializer<'de>,
412 {
413 let s = String::deserialize(deserializer)?;
414 let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
415
416 let s = if s.len().is_multiple_of(2) {
419 s
420 } else {
421 let mut s = s.into_owned();
422 s.insert(0, '0');
423 Cow::Owned(s)
424 };
425
426 crate::utils::encoding::hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
427 }
428}
429
430pub mod hexify {
432 use super::*;
433 use num_traits::Num;
434 use serde::{Deserializer, Serializer};
435
436 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
437 where
438 T: Num + std::fmt::LowerHex,
439 S: Serializer,
440 {
441 serializer.serialize_str(format!("{value:#x}").as_str())
442 }
443
444 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
445 where
446 T: Num,
447 <T as Num>::FromStrRadixErr: std::fmt::Display,
448 D: Deserializer<'de>,
449 {
450 let s = String::deserialize(deserializer)?;
451 crate::utils::encoding::hex::parse_prefixed_int(&s).map_err(serde::de::Error::custom)
452 }
453}
454
455pub mod base64_standard {
457 use super::*;
458
459 use base64::engine::{Engine as _, general_purpose::STANDARD};
460
461 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
462 where
463 S: Serializer,
464 {
465 STANDARD.encode(value).serialize(serializer)
466 }
467
468 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
469 where
470 D: Deserializer<'de>,
471 {
472 STANDARD
473 .decode(String::deserialize(deserializer)?)
474 .map_err(serde::de::Error::custom)
475 }
476}
477
478pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
480where
481 S: Serializer,
482 T: HasLotusJson + Clone,
483{
484 value.clone().into_lotus_json().serialize(serializer)
485}
486
487pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
489where
490 D: Deserializer<'de>,
491 T: HasLotusJson,
492{
493 Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
494}
495
496#[derive(
498 Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
499)]
500#[serde(bound = "T: HasLotusJson + Clone", transparent)]
501pub struct LotusJson<T>(#[serde(with = "self")] pub T);
502
503impl<T> JsonSchema for LotusJson<T>
504where
505 T: HasLotusJson,
506 T::LotusJson: JsonSchema,
507{
508 fn schema_name() -> std::borrow::Cow<'static, str> {
509 T::LotusJson::schema_name()
510 }
511
512 fn schema_id() -> std::borrow::Cow<'static, str> {
513 T::LotusJson::schema_id()
514 }
515
516 fn json_schema(g: &mut SchemaGenerator) -> Schema {
517 T::LotusJson::json_schema(g)
518 }
519}
520
521impl<T> LotusJson<T> {
522 pub fn into_inner(self) -> T {
523 self.0
524 }
525}
526
527macro_rules! lotus_json_with_self {
528 ($($domain_ty:ty),* $(,)?) => {
529 $(
530 impl $crate::lotus_json::HasLotusJson for $domain_ty {
531 type LotusJson = Self;
532 #[cfg(test)]
533 fn snapshots() -> Vec<(serde_json::Value, Self)> {
534 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
535 }
536 fn into_lotus_json(self) -> Self::LotusJson {
537 self
538 }
539 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
540 lotus_json
541 }
542 }
543 )*
544 }
545}
546pub(crate) use lotus_json_with_self;
547
548lotus_json_with_self!(
549 u32,
550 u64,
551 i64,
552 f64,
553 String,
554 chrono::DateTime<chrono::Utc>,
555 serde_json::Value,
556 (),
557 std::path::PathBuf,
558 bool,
559 DeadlineInfo,
560 PaddedPieceSize,
561 Uuid,
562 std::num::NonZeroU32,
563 std::num::NonZeroUsize,
564);
565
566mod fixme {
567 use super::*;
568
569 impl<T: HasLotusJson> HasLotusJson for (T,) {
570 type LotusJson = (T::LotusJson,);
571 #[cfg(test)]
572 fn snapshots() -> Vec<(serde_json::Value, Self)> {
573 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
574 }
575 fn into_lotus_json(self) -> Self::LotusJson {
576 (self.0.into_lotus_json(),)
577 }
578 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
579 (HasLotusJson::from_lotus_json(lotus_json.0),)
580 }
581 }
582
583 impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
584 type LotusJson = (A::LotusJson, B::LotusJson);
585 #[cfg(test)]
586 fn snapshots() -> Vec<(serde_json::Value, Self)> {
587 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
588 }
589 fn into_lotus_json(self) -> Self::LotusJson {
590 (self.0.into_lotus_json(), self.1.into_lotus_json())
591 }
592 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
593 (
594 HasLotusJson::from_lotus_json(lotus_json.0),
595 HasLotusJson::from_lotus_json(lotus_json.1),
596 )
597 }
598 }
599
600 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
601 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
602 #[cfg(test)]
603 fn snapshots() -> Vec<(serde_json::Value, Self)> {
604 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
605 }
606 fn into_lotus_json(self) -> Self::LotusJson {
607 (
608 self.0.into_lotus_json(),
609 self.1.into_lotus_json(),
610 self.2.into_lotus_json(),
611 )
612 }
613 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
614 (
615 HasLotusJson::from_lotus_json(lotus_json.0),
616 HasLotusJson::from_lotus_json(lotus_json.1),
617 HasLotusJson::from_lotus_json(lotus_json.2),
618 )
619 }
620 }
621
622 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
623 for (A, B, C, D)
624 {
625 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
626 #[cfg(test)]
627 fn snapshots() -> Vec<(serde_json::Value, Self)> {
628 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
629 }
630 fn into_lotus_json(self) -> Self::LotusJson {
631 (
632 self.0.into_lotus_json(),
633 self.1.into_lotus_json(),
634 self.2.into_lotus_json(),
635 self.3.into_lotus_json(),
636 )
637 }
638 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
639 (
640 HasLotusJson::from_lotus_json(lotus_json.0),
641 HasLotusJson::from_lotus_json(lotus_json.1),
642 HasLotusJson::from_lotus_json(lotus_json.2),
643 HasLotusJson::from_lotus_json(lotus_json.3),
644 )
645 }
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652 use ipld_core::serde::SerdeError;
653 use quickcheck_macros::quickcheck;
654 use serde::de::{IntoDeserializer, value::StringDeserializer};
655
656 #[derive(Debug, Deserialize, Serialize, PartialEq)]
657 struct HexifyVecBytesTest {
658 #[serde(with = "hexify_vec_bytes")]
659 value: Vec<u8>,
660 }
661
662 fn matches_legacy_lowerhex<T: AsRef<[u8]> + std::fmt::LowerHex>(value: T) -> bool {
665 #[derive(Serialize)]
666 struct W<T: AsRef<[u8]>>(#[serde(with = "hexify_bytes")] T);
667
668 serde_json::to_string(&W(&value)).unwrap() == format!("\"{value:#x}\"")
669 }
670
671 fn filled<const N: usize>(bytes: Vec<u8>) -> [u8; N] {
672 let mut arr = [0; N];
673 arr.iter_mut().zip(bytes).for_each(|(a, b)| *a = b);
674 arr
675 }
676
677 #[test]
678 fn test_hexify_deserialize() {
679 fn de(input: &str) -> Result<u64, SerdeError> {
680 let deserializer: StringDeserializer<SerdeError> =
681 String::from_str(input).unwrap().into_deserializer();
682 hexify::deserialize(deserializer)
683 }
684
685 self::assert_eq!(de("0x2a").unwrap(), 42);
686 self::assert_eq!(de("0x0").unwrap(), 0);
687 for invalid in ["", "0x", "2a", "0xzz", "cthulhu", "0é", "0x-1"] {
689 assert!(de(invalid).is_err(), "{invalid:?} should be rejected");
690 }
691 }
692
693 #[quickcheck]
694 fn hexify_bytes_matches_legacy_h64(value: u64) -> bool {
695 matches_legacy_lowerhex(ethereum_types::H64::from_low_u64_be(value))
696 }
697
698 #[quickcheck]
699 fn hexify_bytes_matches_legacy_h160(bytes: Vec<u8>) -> bool {
700 matches_legacy_lowerhex(ethereum_types::H160::from(filled::<20>(bytes)))
701 }
702
703 #[quickcheck]
704 fn hexify_bytes_matches_legacy_bloom(bytes: Vec<u8>) -> bool {
705 matches_legacy_lowerhex(ethereum_types::Bloom::from(filled::<256>(bytes)))
706 }
707
708 #[test]
709 fn test_hexify_vec_bytes_serialize() {
710 let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
711
712 for (input, expected) in cases.into_iter() {
713 let hexify = HexifyVecBytesTest { value: input };
714 let serialized = serde_json::to_string(&hexify).unwrap();
715 self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
716 }
717 }
718
719 #[test]
720 fn test_hexify_vec_bytes_deserialize() {
721 let cases = [
722 ("0x", vec![]),
723 ("0x0", vec![0]),
724 ("0xF", vec![15]),
725 ("0x2a42", vec![42, 66]),
726 ("0x2A42", vec![42, 66]),
727 ];
728
729 for (input, expected) in cases.into_iter() {
730 let deserializer: StringDeserializer<SerdeError> =
731 String::from_str(input).unwrap().into_deserializer();
732 let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
733 self::assert_eq!(deserialized, expected);
734 }
735
736 let fail_cases = ["cthulhu", "x", "0xazathoth"];
737 for input in fail_cases.into_iter() {
738 let deserializer: StringDeserializer<SerdeError> =
739 String::from_str(input).unwrap().into_deserializer();
740 let deserialized = hexify_vec_bytes::deserialize(deserializer);
741 assert!(deserialized.is_err());
742 }
743 }
744}