1pub use fields::{property_names, IDENTIFIER_FIELDS};
2
3mod accessors;
4pub mod document_event;
5#[cfg(feature = "client")]
6mod document_facade;
7#[cfg(feature = "factories")]
8pub mod document_factory;
9pub mod document_methods;
10mod document_patch;
11pub mod errors;
12#[cfg(feature = "extended-document")]
13pub mod extended_document;
14mod fields;
15pub mod generate_document_id;
16pub mod serialization_traits;
17#[cfg(feature = "factories")]
18pub mod specialized_document_factory;
19pub mod transfer;
20mod v0;
21
22pub use accessors::*;
23pub use v0::*;
24
25#[cfg(feature = "extended-document")]
26pub use extended_document::property_names as extended_document_property_names;
27#[cfg(feature = "extended-document")]
28pub use extended_document::ExtendedDocument;
29#[cfg(feature = "extended-document")]
30pub use extended_document::IDENTIFIER_FIELDS as EXTENDED_DOCUMENT_IDENTIFIER_FIELDS;
31
32pub const INITIAL_REVISION: u64 = 1;
34
35use crate::data_contract::document_type::DocumentTypeRef;
36use crate::data_contract::DataContract;
37use crate::document::document_methods::{
38 DocumentGetRawForContractV0, DocumentGetRawForDocumentTypeV0, DocumentHashV0Method,
39 DocumentIsEqualIgnoringTimestampsV0, DocumentMethodsV0,
40};
41use crate::document::errors::DocumentError;
42use crate::version::PlatformVersion;
43use crate::ProtocolError;
44use derive_more::From;
45
46use std::fmt;
47use std::fmt::Formatter;
48
49#[derive(Clone, Debug, PartialEq, From)]
50#[cfg_attr(
51 feature = "serde-conversion",
52 derive(serde::Serialize, serde::Deserialize),
53 serde(tag = "$formatVersion")
54)]
55pub enum Document {
56 #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
57 V0(DocumentV0),
58}
59
60#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
61impl crate::serialization::JsonConvertible for Document {}
62
63#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
64impl crate::serialization::ValueConvertible for Document {}
65
66impl fmt::Display for Document {
67 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
68 match self {
69 Document::V0(v0) => {
70 write!(f, "v0 : {} ", v0)?;
71 }
72 }
73 Ok(())
74 }
75}
76
77impl DocumentMethodsV0 for Document {
78 fn get_raw_for_contract(
80 &self,
81 key: &str,
82 document_type_name: &str,
83 contract: &DataContract,
84 owner_id: Option<[u8; 32]>,
85 platform_version: &PlatformVersion,
86 ) -> Result<Option<Vec<u8>>, ProtocolError> {
87 match self {
88 Document::V0(document_v0) => {
89 match platform_version
90 .dpp
91 .document_versions
92 .document_method_versions
93 .get_raw_for_contract
94 {
95 0 => document_v0.get_raw_for_contract_v0(
96 key,
97 document_type_name,
98 contract,
99 owner_id,
100 platform_version,
101 ),
102 version => Err(ProtocolError::UnknownVersionMismatch {
103 method: "DocumentMethodV0::get_raw_for_contract".to_string(),
104 known_versions: vec![0],
105 received: version,
106 }),
107 }
108 }
109 }
110 }
111
112 fn get_raw_for_document_type(
114 &self,
115 key_path: &str,
116 document_type: DocumentTypeRef,
117 owner_id: Option<[u8; 32]>,
118 platform_version: &PlatformVersion,
119 ) -> Result<Option<Vec<u8>>, ProtocolError> {
120 match self {
121 Document::V0(document_v0) => {
122 match platform_version
123 .dpp
124 .document_versions
125 .document_method_versions
126 .get_raw_for_document_type
127 {
128 0 => document_v0.get_raw_for_document_type_v0(
129 key_path,
130 document_type,
131 owner_id,
132 platform_version,
133 ),
134 version => Err(ProtocolError::UnknownVersionMismatch {
135 method: "DocumentMethodV0::get_raw_for_document_type".to_string(),
136 known_versions: vec![0],
137 received: version,
138 }),
139 }
140 }
141 }
142 }
143
144 fn hash(
145 &self,
146 contract: &DataContract,
147 document_type: DocumentTypeRef,
148 platform_version: &PlatformVersion,
149 ) -> Result<Vec<u8>, ProtocolError> {
150 match self {
151 Document::V0(document_v0) => {
152 match platform_version
153 .dpp
154 .document_versions
155 .document_method_versions
156 .hash
157 {
158 0 => document_v0.hash_v0(contract, document_type, platform_version),
159 version => Err(ProtocolError::UnknownVersionMismatch {
160 method: "DocumentMethodV0::hash".to_string(),
161 known_versions: vec![0],
162 received: version,
163 }),
164 }
165 }
166 }
167 }
168
169 fn increment_revision(&mut self) -> Result<(), ProtocolError> {
170 let Some(revision) = self.revision() else {
171 return Err(ProtocolError::Document(Box::new(
172 DocumentError::DocumentNoRevisionError {
173 document: Box::new(self.clone()),
174 },
175 )));
176 };
177
178 let new_revision = revision
179 .checked_add(1)
180 .ok_or(ProtocolError::Overflow("overflow when adding 1"))?;
181
182 self.set_revision(Some(new_revision));
183
184 Ok(())
185 }
186
187 fn is_equal_ignoring_time_based_fields(
188 &self,
189 rhs: &Self,
190 also_ignore_fields: Option<Vec<&str>>,
191 platform_version: &PlatformVersion,
192 ) -> Result<bool, ProtocolError> {
193 match (self, rhs) {
194 (Document::V0(document_v0), Document::V0(rhs_v0)) => {
195 match platform_version
196 .dpp
197 .document_versions
198 .document_method_versions
199 .is_equal_ignoring_timestamps
200 {
201 0 => Ok(document_v0
202 .is_equal_ignoring_time_based_fields_v0(rhs_v0, also_ignore_fields)),
203 version => Err(ProtocolError::UnknownVersionMismatch {
204 method: "DocumentMethodV0::is_equal_ignoring_time_based_fields".to_string(),
205 known_versions: vec![0],
206 received: version,
207 }),
208 }
209 }
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::data_contract::accessors::v0::DataContractV0Getters;
218 use crate::data_contract::document_type::random_document::CreateRandomDocument;
219 use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
220 use crate::tests::json_document::json_document_to_contract;
221
222 use regex::Regex;
223
224 #[test]
225 fn test_document_display() {
226 let platform_version = PlatformVersion::first();
227 let contract = json_document_to_contract(
228 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
229 false,
230 platform_version,
231 )
232 .expect("expected to get contract");
233
234 let document_type = contract
235 .document_type_for_name("profile")
236 .expect("expected to get profile document type");
237 let document = document_type
238 .random_document(Some(3333), platform_version)
239 .expect("expected to get a random document");
240
241 let document_string = format!("{}", document);
242 let pattern = r"v\d+ : id:45ZNwGcxeMpLpYmiVEKKBKXbZfinrhjZLkau1GWizPFX owner_id:2vq574DjKi7ZD8kJ6dMHxT5wu6ZKD2bW5xKAyKAGW7qZ created_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) updated_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) avatarUrl:string y8RD1DbW18RuyblDX7hx\[...\(670\)\] displayName:string y94Itl6mn1yBE publicMessage:string SvAQrzsslj0ESc15GQBQ\[...\(105\)\] .*";
243 let re = Regex::new(pattern).unwrap();
244 assert!(
245 re.is_match(document_string.as_str()),
246 "pattern: {} does not match {}",
247 pattern,
248 document_string
249 );
250 }
251
252 #[test]
253 fn test_serialization_and_deserialization() {
254 let platform_version = PlatformVersion::latest();
255 let contract = json_document_to_contract(
256 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
257 false,
258 platform_version,
259 )
260 .expect("expected to get contract");
261
262 let document_type = contract
263 .document_type_for_name("domain")
264 .expect("expected to get document type");
265 for _ in 0..20 {
266 let document = document_type
267 .random_document(None, platform_version)
268 .expect("expected a document");
269 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
270 &document,
271 document_type,
272 &contract,
273 platform_version,
274 )
275 .expect("should serialize");
276 let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
277 .expect("expected to deserialize domain document");
278 }
279 }
280
281 #[test]
282 fn test_serialize_deserialize_over_different_versions_of_document_type() {
283 let platform_version = PlatformVersion::latest();
284 let contract = json_document_to_contract(
285 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
286 false,
287 platform_version,
288 )
289 .expect("expected to get contract");
290
291 let updated_contract = json_document_to_contract(
292 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract-update-v2-test.json",
293 false,
294 platform_version,
295 )
296 .expect("expected to get contract");
297
298 let document_type = contract
299 .document_type_for_name("domain")
300 .expect("expected to get document type");
301
302 let updated_document_type = updated_contract
303 .document_type_for_name("domain")
304 .expect("expected to get document type");
305
306 for _ in 0..20 {
308 let document = document_type
309 .random_document(None, platform_version)
310 .expect("expected a document");
311 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
312 &document,
313 document_type,
314 &contract,
315 platform_version,
316 )
317 .expect("should serialize");
318 let _deserialized =
319 Document::from_bytes(&serialized, updated_document_type, platform_version)
320 .expect("expected to deserialize domain document");
321 }
322
323 for _ in 0..20 {
325 let document = updated_document_type
326 .random_document(None, platform_version)
327 .expect("expected a document");
328 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
329 &document,
330 document_type,
331 &contract,
332 platform_version,
333 )
334 .expect("should serialize");
335 let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
336 .expect("expected to deserialize domain document");
337 }
338 }
339
340 #[test]
345 fn display_document_with_no_properties() {
346 let doc = Document::V0(DocumentV0 {
347 contract_version: None,
348 id: platform_value::Identifier::new([0xAA; 32]),
349 owner_id: platform_value::Identifier::new([0xBB; 32]),
350 properties: Default::default(),
351 revision: None,
352 created_at: None,
353 updated_at: None,
354 transferred_at: None,
355 created_at_block_height: None,
356 updated_at_block_height: None,
357 transferred_at_block_height: None,
358 created_at_core_block_height: None,
359 updated_at_core_block_height: None,
360 transferred_at_core_block_height: None,
361 creator_id: None,
362 });
363
364 let s = format!("{}", doc);
365 assert!(
366 s.contains("no properties"),
367 "should say 'no properties' when the BTreeMap is empty, got: {}",
368 s
369 );
370 }
371
372 #[test]
373 fn display_document_shows_transferred_at_fields() {
374 let doc = Document::V0(DocumentV0 {
375 contract_version: None,
376 id: platform_value::Identifier::new([1u8; 32]),
377 owner_id: platform_value::Identifier::new([2u8; 32]),
378 properties: Default::default(),
379 revision: None,
380 created_at: None,
381 updated_at: None,
382 transferred_at: Some(1_700_000_000_000),
383 created_at_block_height: None,
384 updated_at_block_height: None,
385 transferred_at_block_height: Some(500),
386 created_at_core_block_height: None,
387 updated_at_core_block_height: None,
388 transferred_at_core_block_height: Some(42),
389 creator_id: None,
390 });
391
392 let s = format!("{}", doc);
393 assert!(
394 s.contains("transferred_at:"),
395 "should contain transferred_at, got: {}",
396 s
397 );
398 assert!(
399 s.contains("transferred_at_block_height:500"),
400 "should contain transferred_at_block_height:500, got: {}",
401 s
402 );
403 assert!(
404 s.contains("transferred_at_core_block_height:42"),
405 "should contain transferred_at_core_block_height:42, got: {}",
406 s
407 );
408 }
409
410 #[test]
411 fn display_document_shows_creator_id() {
412 let creator = platform_value::Identifier::new([0xCC; 32]);
413 let doc = Document::V0(DocumentV0 {
414 contract_version: None,
415 id: platform_value::Identifier::new([1u8; 32]),
416 owner_id: platform_value::Identifier::new([2u8; 32]),
417 properties: Default::default(),
418 revision: None,
419 created_at: None,
420 updated_at: None,
421 transferred_at: None,
422 created_at_block_height: None,
423 updated_at_block_height: None,
424 transferred_at_block_height: None,
425 created_at_core_block_height: None,
426 updated_at_core_block_height: None,
427 transferred_at_core_block_height: None,
428 creator_id: Some(creator),
429 });
430
431 let s = format!("{}", doc);
432 assert!(
433 s.contains("creator_id:"),
434 "should contain creator_id, got: {}",
435 s
436 );
437 }
438
439 #[test]
440 fn display_document_shows_block_height_fields() {
441 let doc = Document::V0(DocumentV0 {
442 contract_version: None,
443 id: platform_value::Identifier::new([1u8; 32]),
444 owner_id: platform_value::Identifier::new([2u8; 32]),
445 properties: Default::default(),
446 revision: None,
447 created_at: None,
448 updated_at: None,
449 transferred_at: None,
450 created_at_block_height: Some(100),
451 updated_at_block_height: Some(200),
452 transferred_at_block_height: None,
453 created_at_core_block_height: Some(50),
454 updated_at_core_block_height: Some(60),
455 transferred_at_core_block_height: None,
456 creator_id: None,
457 });
458
459 let s = format!("{}", doc);
460 assert!(s.contains("created_at_block_height:100"), "got: {}", s);
461 assert!(s.contains("updated_at_block_height:200"), "got: {}", s);
462 assert!(s.contains("created_at_core_block_height:50"), "got: {}", s);
463 assert!(s.contains("updated_at_core_block_height:60"), "got: {}", s);
464 }
465
466 #[test]
471 fn increment_revision_works_on_mutable_document() {
472 let mut doc = Document::V0(DocumentV0 {
473 contract_version: None,
474 id: platform_value::Identifier::new([1u8; 32]),
475 owner_id: platform_value::Identifier::new([2u8; 32]),
476 properties: Default::default(),
477 revision: Some(1),
478 created_at: None,
479 updated_at: None,
480 transferred_at: None,
481 created_at_block_height: None,
482 updated_at_block_height: None,
483 transferred_at_block_height: None,
484 created_at_core_block_height: None,
485 updated_at_core_block_height: None,
486 transferred_at_core_block_height: None,
487 creator_id: None,
488 });
489
490 doc.increment_revision()
491 .expect("increment_revision should succeed");
492 assert_eq!(doc.revision(), Some(2));
493 }
494
495 #[test]
496 fn increment_revision_fails_when_no_revision() {
497 let mut doc = Document::V0(DocumentV0 {
498 contract_version: None,
499 id: platform_value::Identifier::new([1u8; 32]),
500 owner_id: platform_value::Identifier::new([2u8; 32]),
501 properties: Default::default(),
502 revision: None,
503 created_at: None,
504 updated_at: None,
505 transferred_at: None,
506 created_at_block_height: None,
507 updated_at_block_height: None,
508 transferred_at_block_height: None,
509 created_at_core_block_height: None,
510 updated_at_core_block_height: None,
511 transferred_at_core_block_height: None,
512 creator_id: None,
513 });
514
515 let result = doc.increment_revision();
516 assert!(
517 result.is_err(),
518 "increment_revision should fail when revision is None"
519 );
520 }
521
522 #[test]
527 fn is_equal_ignoring_time_based_fields_dispatches_correctly() {
528 let platform_version = PlatformVersion::latest();
529 let contract = json_document_to_contract(
530 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
531 false,
532 platform_version,
533 )
534 .expect("expected to get contract");
535
536 let document_type = contract
537 .document_type_for_name("profile")
538 .expect("expected to get profile document type");
539
540 let doc1 = document_type
541 .random_document(Some(42), platform_version)
542 .expect("expected random document");
543
544 let mut doc2 = doc1.clone();
545 doc2.set_created_at(Some(9_999_999));
547 doc2.set_updated_at(Some(8_888_888));
548
549 let result = doc1
550 .is_equal_ignoring_time_based_fields(&doc2, None, platform_version)
551 .expect("should succeed");
552 assert!(
553 result,
554 "same document with different timestamps should be equal ignoring time fields"
555 );
556 }
557
558 #[test]
563 fn get_raw_for_contract_dispatches_to_v0() {
564 let platform_version = PlatformVersion::latest();
565 let contract = json_document_to_contract(
566 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
567 false,
568 platform_version,
569 )
570 .expect("expected to get contract");
571
572 let document_type = contract
573 .document_type_for_name("profile")
574 .expect("expected to get profile document type");
575
576 let document = document_type
577 .random_document(Some(7), platform_version)
578 .expect("expected random document");
579
580 let raw_id = document
581 .get_raw_for_contract("$id", "profile", &contract, None, platform_version)
582 .expect("should succeed");
583 assert_eq!(raw_id, Some(document.id().to_vec()));
584 }
585
586 #[test]
591 fn document_hash_is_deterministic() {
592 let platform_version = PlatformVersion::latest();
593 let contract = json_document_to_contract(
594 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
595 false,
596 platform_version,
597 )
598 .expect("expected to get contract");
599
600 let document_type = contract
601 .document_type_for_name("profile")
602 .expect("expected to get profile document type");
603
604 let document = document_type
605 .random_document(Some(42), platform_version)
606 .expect("expected random document");
607
608 let hash1 = document
609 .hash(&contract, document_type, platform_version)
610 .expect("hash should succeed");
611 let hash2 = document
612 .hash(&contract, document_type, platform_version)
613 .expect("hash should succeed");
614 assert_eq!(hash1, hash2, "hash should be deterministic");
615 assert!(!hash1.is_empty(), "hash should not be empty");
616 }
617
618 #[test]
625 fn increment_revision_errors_on_overflow() {
626 let mut doc = Document::V0(DocumentV0 {
627 contract_version: None,
628 id: platform_value::Identifier::new([1u8; 32]),
629 owner_id: platform_value::Identifier::new([2u8; 32]),
630 properties: Default::default(),
631 revision: Some(crate::prelude::Revision::MAX),
632 created_at: None,
633 updated_at: None,
634 transferred_at: None,
635 created_at_block_height: None,
636 updated_at_block_height: None,
637 transferred_at_block_height: None,
638 created_at_core_block_height: None,
639 updated_at_core_block_height: None,
640 transferred_at_core_block_height: None,
641 creator_id: None,
642 });
643 let err = doc.increment_revision().expect_err("MAX + 1 must overflow");
644 match err {
645 ProtocolError::Overflow(_) => {}
646 other => panic!("expected ProtocolError::Overflow, got {:?}", other),
647 }
648 }
649
650 #[test]
655 fn from_document_v0_produces_v0_variant() {
656 let v0 = DocumentV0 {
657 contract_version: None,
658 id: platform_value::Identifier::new([1u8; 32]),
659 owner_id: platform_value::Identifier::new([2u8; 32]),
660 properties: Default::default(),
661 revision: Some(7),
662 created_at: None,
663 updated_at: None,
664 transferred_at: None,
665 created_at_block_height: None,
666 updated_at_block_height: None,
667 transferred_at_block_height: None,
668 created_at_core_block_height: None,
669 updated_at_core_block_height: None,
670 transferred_at_core_block_height: None,
671 creator_id: None,
672 };
673 let document: Document = v0.clone().into();
674 match document {
675 Document::V0(inner) => assert_eq!(inner, v0),
676 }
677 }
678
679 #[test]
685 fn document_display_has_version_prefix() {
686 let doc = Document::V0(DocumentV0 {
687 contract_version: None,
688 id: platform_value::Identifier::new([1u8; 32]),
689 owner_id: platform_value::Identifier::new([2u8; 32]),
690 properties: Default::default(),
691 revision: None,
692 created_at: None,
693 updated_at: None,
694 transferred_at: None,
695 created_at_block_height: None,
696 updated_at_block_height: None,
697 transferred_at_block_height: None,
698 created_at_core_block_height: None,
699 updated_at_core_block_height: None,
700 transferred_at_core_block_height: None,
701 creator_id: None,
702 });
703 let s = format!("{}", doc);
704 assert!(
705 s.starts_with("v0 : "),
706 "Display should prefix with version, got: {s}"
707 );
708 }
709
710 #[test]
716 fn get_raw_for_document_type_dispatch_path_returns_id() {
717 let platform_version = PlatformVersion::latest();
718 let contract = json_document_to_contract(
719 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
720 false,
721 platform_version,
722 )
723 .expect("expected contract");
724 let document_type = contract
725 .document_type_for_name("profile")
726 .expect("expected document type");
727
728 let document = document_type
729 .random_document(Some(11), platform_version)
730 .expect("expected random document");
731
732 let raw = document
733 .get_raw_for_document_type("$id", document_type, None, platform_version)
734 .expect("should succeed");
735 assert_eq!(raw, Some(document.id().to_vec()));
736 }
737}
738
739#[cfg(all(
740 test,
741 feature = "json-conversion",
742 feature = "value-conversion",
743 feature = "serde-conversion"
744))]
745mod json_convertible_tests {
746 use super::*;
747
748 use platform_value::{platform_value, Identifier};
749 use serde_json::json;
750 use std::collections::BTreeMap;
751
752 fn fixture() -> Document {
753 Document::V0(DocumentV0 {
754 contract_version: None,
755 id: Identifier::new([0xa1; 32]),
756 owner_id: Identifier::new([0xb2; 32]),
757 properties: BTreeMap::new(),
758 revision: Some(2),
759 created_at: Some(1_700_000_000_000),
760 updated_at: Some(1_700_000_001_000),
761 transferred_at: None,
762 created_at_block_height: Some(100),
763 updated_at_block_height: Some(101),
764 transferred_at_block_height: None,
765 created_at_core_block_height: Some(50),
766 updated_at_core_block_height: Some(51),
767 transferred_at_core_block_height: None,
768 creator_id: Some(Identifier::new([0xc3; 32])),
769 })
770 }
771
772 #[test]
773 fn json_round_trip_with_full_wire_shape() {
774 use crate::serialization::JsonConvertible;
775 let original = fixture();
776 let json = original.to_json().expect("to_json");
777 assert_eq!(
784 json,
785 json!({
786 "$formatVersion": "0",
787 "$id": Identifier::new([0xa1; 32]),
788 "$ownerId": Identifier::new([0xb2; 32]),
789 "$revision": 2,
790 "$createdAt": 1_700_000_000_000u64,
791 "$updatedAt": 1_700_000_001_000u64,
792 "$transferredAt": serde_json::Value::Null,
793 "$createdAtBlockHeight": 100,
794 "$updatedAtBlockHeight": 101,
795 "$transferredAtBlockHeight": serde_json::Value::Null,
796 "$createdAtCoreBlockHeight": 50,
797 "$updatedAtCoreBlockHeight": 51,
798 "$transferredAtCoreBlockHeight": serde_json::Value::Null,
799 "$creatorId": Identifier::new([0xc3; 32]),
800 })
801 );
802 let recovered = Document::from_json(json).expect("from_json");
803 assert_eq!(original, recovered);
804 }
805
806 #[test]
807 fn value_round_trip_with_full_wire_shape() {
808 use crate::serialization::ValueConvertible;
809 let original = fixture();
810 let value = original.to_object().expect("to_object");
811 assert_eq!(
814 value,
815 platform_value!({
816 "$formatVersion": "0",
817 "$id": Identifier::new([0xa1; 32]),
818 "$ownerId": Identifier::new([0xb2; 32]),
819 "$revision": 2u64,
820 "$createdAt": 1_700_000_000_000u64,
821 "$updatedAt": 1_700_000_001_000u64,
822 "$transferredAt": platform_value::Value::Null,
823 "$createdAtBlockHeight": 100u64,
824 "$updatedAtBlockHeight": 101u64,
825 "$transferredAtBlockHeight": platform_value::Value::Null,
826 "$createdAtCoreBlockHeight": 50u32,
827 "$updatedAtCoreBlockHeight": 51u32,
828 "$transferredAtCoreBlockHeight": platform_value::Value::Null,
829 "$creatorId": Identifier::new([0xc3; 32]),
830 })
831 );
832 let recovered = Document::from_object(value).expect("from_object");
833 assert_eq!(original, recovered);
834 }
835}