1use super::EMPTY_KEYWORDS;
2use crate::data_contract::accessors::v0::DataContractV0Getters;
3use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
4use crate::data_contract::config::moderation::document_schema_grants_moderator_abilities;
5use crate::data_contract::config::DataContractConfig;
6use crate::data_contract::group::Group;
7use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
8use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
9use crate::data_contract::v0::DataContractV0;
10use crate::data_contract::v1::DataContractV1;
11use crate::data_contract::{
12 DataContract, DefinitionName, DocumentName, GroupContractPosition, TokenContractPosition,
13 EMPTY_GROUPS, EMPTY_TOKENS,
14};
15#[cfg(feature = "json-conversion")]
16use crate::serialization::JsonConvertible;
17#[cfg(feature = "value-conversion")]
18use crate::serialization::ValueConvertible;
19use crate::validation::operations::ProtocolValidationOperation;
20use crate::version::PlatformVersion;
21use crate::ProtocolError;
22use bincode::{Decode, DecodeUntrusted, Encode};
23use derive_more::From;
24use platform_value::{Identifier, Value};
25use platform_version::{IntoPlatformVersioned, TryFromPlatformVersioned};
26use platform_versioning::PlatformVersioned;
27#[cfg(feature = "serde-conversion")]
28use serde::{Deserialize, Serialize};
29use std::collections::BTreeMap;
30use std::fmt;
31
32pub(in crate::data_contract) mod v0;
33pub(in crate::data_contract) mod v1;
34
35pub mod property_names {
36 pub const ID: &str = "id";
37 pub const OWNER_ID: &str = "ownerId";
38 pub const VERSION: &str = "version";
39 pub const DEFINITIONS: &str = "$defs";
40}
41
42pub const CONTRACT_DESERIALIZATION_LIMIT: usize = 15000;
43
44#[derive(Debug, PartialEq, Eq, Clone, Copy)]
50pub enum DataContractMismatch {
51 Id,
53 Config,
55 Version,
57 OwnerId,
59 SchemaDefs,
61 DocumentSchemas,
63 Groups,
65 Tokens,
67 Keywords,
69 Description,
71 FormatVersionMismatch,
73 V0Mismatch,
75}
76
77impl fmt::Display for DataContractMismatch {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let description = match self {
81 DataContractMismatch::Id => "ID fields differ",
82 DataContractMismatch::Config => "Config fields differ",
83 DataContractMismatch::Version => "Version fields differ",
84 DataContractMismatch::OwnerId => "Owner ID fields differ",
85 DataContractMismatch::SchemaDefs => "Schema definitions differ",
86 DataContractMismatch::DocumentSchemas => "Document schemas differ",
87 DataContractMismatch::Groups => "Groups differ",
88 DataContractMismatch::Tokens => "Tokens differ",
89 DataContractMismatch::Keywords => "Keywords differ",
90 DataContractMismatch::Description => "Description fields differ",
91 DataContractMismatch::FormatVersionMismatch => {
92 "Serialization format versions differ (e.g., V0 vs V1)"
93 }
94 DataContractMismatch::V0Mismatch => "V0 versions differ",
95 };
96 write!(f, "{}", description)
97 }
98}
99
100#[cfg_attr(
101 all(feature = "json-conversion", feature = "serde-conversion"),
102 derive(JsonConvertible)
103)]
104#[cfg_attr(
105 all(feature = "value-conversion", feature = "serde-conversion"),
106 derive(ValueConvertible)
107)]
108#[derive(Debug, Clone, Encode, Decode, PartialEq, PlatformVersioned, From, DecodeUntrusted)]
109#[cfg_attr(
110 feature = "serde-conversion",
111 derive(Serialize, Deserialize),
112 serde(tag = "$formatVersion")
113)]
114pub enum DataContractInSerializationFormat {
115 #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
116 V0(DataContractInSerializationFormatV0),
117 #[cfg_attr(feature = "serde-conversion", serde(rename = "1"))]
118 V1(DataContractInSerializationFormatV1),
119}
120
121impl DataContractInSerializationFormat {
122 pub fn id(&self) -> Identifier {
124 match self {
125 DataContractInSerializationFormat::V0(v0) => v0.id,
126 DataContractInSerializationFormat::V1(v1) => v1.id,
127 }
128 }
129
130 pub fn owner_id(&self) -> Identifier {
132 match self {
133 DataContractInSerializationFormat::V0(v0) => v0.owner_id,
134 DataContractInSerializationFormat::V1(v1) => v1.owner_id,
135 }
136 }
137
138 pub fn document_schemas(&self) -> &BTreeMap<DocumentName, Value> {
139 match self {
140 DataContractInSerializationFormat::V0(v0) => &v0.document_schemas,
141 DataContractInSerializationFormat::V1(v1) => &v1.document_schemas,
142 }
143 }
144
145 pub fn has_document_type_with_moderator_abilities(&self) -> bool {
151 self.document_schemas()
152 .values()
153 .any(document_schema_grants_moderator_abilities)
154 }
155
156 pub fn document_schemas_mut(&mut self) -> &mut BTreeMap<DocumentName, Value> {
157 match self {
158 DataContractInSerializationFormat::V0(v0) => &mut v0.document_schemas,
159 DataContractInSerializationFormat::V1(v1) => &mut v1.document_schemas,
160 }
161 }
162
163 pub fn schema_defs(&self) -> Option<&BTreeMap<DefinitionName, Value>> {
164 match self {
165 DataContractInSerializationFormat::V0(v0) => v0.schema_defs.as_ref(),
166 DataContractInSerializationFormat::V1(v1) => v1.schema_defs.as_ref(),
167 }
168 }
169
170 pub fn version(&self) -> u32 {
171 match self {
172 DataContractInSerializationFormat::V0(v0) => v0.version,
173 DataContractInSerializationFormat::V1(v1) => v1.version,
174 }
175 }
176
177 pub fn config(&self) -> &DataContractConfig {
179 match self {
180 DataContractInSerializationFormat::V0(v0) => &v0.config,
181 DataContractInSerializationFormat::V1(v1) => &v1.config,
182 }
183 }
184
185 pub fn groups(&self) -> &BTreeMap<GroupContractPosition, Group> {
186 match self {
187 DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS,
188 DataContractInSerializationFormat::V1(v1) => &v1.groups,
189 }
190 }
191 pub fn tokens(&self) -> &BTreeMap<TokenContractPosition, TokenConfiguration> {
192 match self {
193 DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS,
194 DataContractInSerializationFormat::V1(v1) => &v1.tokens,
195 }
196 }
197
198 pub fn keywords(&self) -> &Vec<String> {
199 match self {
200 DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS,
201 DataContractInSerializationFormat::V1(v1) => &v1.keywords,
202 }
203 }
204
205 pub fn description(&self) -> &Option<String> {
206 match self {
207 DataContractInSerializationFormat::V0(_) => &None,
208 DataContractInSerializationFormat::V1(v1) => &v1.description,
209 }
210 }
211
212 pub fn first_mismatch(&self, other: &Self) -> Option<DataContractMismatch> {
225 match (self, other) {
226 (
227 DataContractInSerializationFormat::V0(v0_self),
228 DataContractInSerializationFormat::V0(v0_other),
229 ) => {
230 if v0_self != v0_other {
231 Some(DataContractMismatch::V0Mismatch)
232 } else {
233 None
234 }
235 }
236 (
237 DataContractInSerializationFormat::V1(v1_self),
238 DataContractInSerializationFormat::V1(v1_other),
239 ) => {
240 if v1_self.id != v1_other.id {
241 Some(DataContractMismatch::Id)
242 } else if v1_self.config != v1_other.config {
243 Some(DataContractMismatch::Config)
244 } else if v1_self.version != v1_other.version {
245 Some(DataContractMismatch::Version)
246 } else if v1_self.owner_id != v1_other.owner_id {
247 Some(DataContractMismatch::OwnerId)
248 } else if v1_self.schema_defs != v1_other.schema_defs {
249 Some(DataContractMismatch::SchemaDefs)
250 } else if v1_self.document_schemas != v1_other.document_schemas {
251 Some(DataContractMismatch::DocumentSchemas)
252 } else if v1_self.groups != v1_other.groups {
253 Some(DataContractMismatch::Groups)
254 } else if v1_self.tokens != v1_other.tokens {
255 Some(DataContractMismatch::Tokens)
256 } else if v1_self.keywords.len() != v1_other.keywords.len()
257 || v1_self
258 .keywords
259 .iter()
260 .zip(v1_other.keywords.iter())
261 .any(|(a, b)| a.to_lowercase() != b.to_lowercase())
262 {
263 Some(DataContractMismatch::Keywords)
264 } else if v1_self.description != v1_other.description {
265 Some(DataContractMismatch::Description)
266 } else {
267 None
268 }
269 }
270 _ => Some(DataContractMismatch::FormatVersionMismatch),
271 }
272 }
273}
274
275impl TryFromPlatformVersioned<DataContractV0> for DataContractInSerializationFormat {
276 type Error = ProtocolError;
277
278 fn try_from_platform_versioned(
279 value: DataContractV0,
280 platform_version: &PlatformVersion,
281 ) -> Result<Self, Self::Error> {
282 value
283 .config
284 .ensure_admitted_by_platform_version(platform_version)?;
285 match platform_version
286 .dpp
287 .contract_versions
288 .contract_serialization_version
289 .default_current_version
290 {
291 0 => {
292 let v0_format: DataContractInSerializationFormatV0 =
293 DataContract::V0(value).into_platform_versioned(platform_version);
294 Ok(v0_format.into())
295 }
296 1 => {
297 let v1_format: DataContractInSerializationFormatV1 =
298 DataContract::V0(value).into_platform_versioned(platform_version);
299 Ok(v1_format.into())
300 }
301 version => Err(ProtocolError::UnknownVersionMismatch {
302 method: "DataContract::serialize_to_default_current_version".to_string(),
303 known_versions: vec![0, 1],
304 received: version,
305 }),
306 }
307 }
308}
309
310impl TryFromPlatformVersioned<&DataContractV0> for DataContractInSerializationFormat {
311 type Error = ProtocolError;
312
313 fn try_from_platform_versioned(
314 value: &DataContractV0,
315 platform_version: &PlatformVersion,
316 ) -> Result<Self, Self::Error> {
317 value
318 .config
319 .ensure_admitted_by_platform_version(platform_version)?;
320 match platform_version
321 .dpp
322 .contract_versions
323 .contract_serialization_version
324 .default_current_version
325 {
326 0 => {
327 let v0_format: DataContractInSerializationFormatV0 =
328 DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
329 Ok(v0_format.into())
330 }
331 1 => {
332 let v1_format: DataContractInSerializationFormatV1 =
333 DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
334 Ok(v1_format.into())
335 }
336 version => Err(ProtocolError::UnknownVersionMismatch {
337 method: "DataContract::serialize_to_default_current_version".to_string(),
338 known_versions: vec![0, 1],
339 received: version,
340 }),
341 }
342 }
343}
344
345impl TryFromPlatformVersioned<DataContractV1> for DataContractInSerializationFormat {
346 type Error = ProtocolError;
347
348 fn try_from_platform_versioned(
349 value: DataContractV1,
350 platform_version: &PlatformVersion,
351 ) -> Result<Self, Self::Error> {
352 value
353 .config
354 .ensure_admitted_by_platform_version(platform_version)?;
355 match platform_version
356 .dpp
357 .contract_versions
358 .contract_serialization_version
359 .default_current_version
360 {
361 0 => {
362 let v0_format: DataContractInSerializationFormatV0 =
363 DataContract::V1(value).into_platform_versioned(platform_version);
364 Ok(v0_format.into())
365 }
366 1 => {
367 let v1_format: DataContractInSerializationFormatV1 =
368 DataContract::V1(value).into_platform_versioned(platform_version);
369 Ok(v1_format.into())
370 }
371 version => Err(ProtocolError::UnknownVersionMismatch {
372 method: "DataContract::serialize_to_default_current_version".to_string(),
373 known_versions: vec![0, 1],
374 received: version,
375 }),
376 }
377 }
378}
379
380impl TryFromPlatformVersioned<&DataContractV1> for DataContractInSerializationFormat {
381 type Error = ProtocolError;
382
383 fn try_from_platform_versioned(
384 value: &DataContractV1,
385 platform_version: &PlatformVersion,
386 ) -> Result<Self, Self::Error> {
387 value
388 .config
389 .ensure_admitted_by_platform_version(platform_version)?;
390 match platform_version
391 .dpp
392 .contract_versions
393 .contract_serialization_version
394 .default_current_version
395 {
396 0 => {
397 let v0_format: DataContractInSerializationFormatV0 =
398 DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
399 Ok(v0_format.into())
400 }
401 1 => {
402 let v1_format: DataContractInSerializationFormatV1 =
403 DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
404 Ok(v1_format.into())
405 }
406 version => Err(ProtocolError::UnknownVersionMismatch {
407 method: "DataContract::serialize_to_default_current_version".to_string(),
408 known_versions: vec![0, 1],
409 received: version,
410 }),
411 }
412 }
413}
414
415impl TryFromPlatformVersioned<&DataContract> for DataContractInSerializationFormat {
416 type Error = ProtocolError;
417
418 fn try_from_platform_versioned(
419 value: &DataContract,
420 platform_version: &PlatformVersion,
421 ) -> Result<Self, Self::Error> {
422 value
423 .config()
424 .ensure_admitted_by_platform_version(platform_version)?;
425 match platform_version
426 .dpp
427 .contract_versions
428 .contract_serialization_version
429 .default_current_version
430 {
431 0 => {
432 let v0_format: DataContractInSerializationFormatV0 =
433 value.clone().into_platform_versioned(platform_version);
434 Ok(v0_format.into())
435 }
436 1 => {
437 let v1_format: DataContractInSerializationFormatV1 =
438 value.clone().into_platform_versioned(platform_version);
439 Ok(v1_format.into())
440 }
441 version => Err(ProtocolError::UnknownVersionMismatch {
442 method: "DataContract::serialize_to_default_current_version".to_string(),
443 known_versions: vec![0, 1],
444 received: version,
445 }),
446 }
447 }
448}
449
450impl TryFromPlatformVersioned<DataContract> for DataContractInSerializationFormat {
451 type Error = ProtocolError;
452
453 fn try_from_platform_versioned(
454 value: DataContract,
455 platform_version: &PlatformVersion,
456 ) -> Result<Self, Self::Error> {
457 value
458 .config()
459 .ensure_admitted_by_platform_version(platform_version)?;
460 match platform_version
461 .dpp
462 .contract_versions
463 .contract_serialization_version
464 .default_current_version
465 {
466 0 => {
467 let v0_format: DataContractInSerializationFormatV0 =
468 value.into_platform_versioned(platform_version);
469 Ok(v0_format.into())
470 }
471 1 => {
472 let v1_format: DataContractInSerializationFormatV1 =
473 value.into_platform_versioned(platform_version);
474 Ok(v1_format.into())
475 }
476 version => Err(ProtocolError::UnknownVersionMismatch {
477 method: "DataContract::serialize_consume_to_default_current_version".to_string(),
478 known_versions: vec![0, 1],
479 received: version,
480 }),
481 }
482 }
483}
484
485impl DataContract {
486 pub fn try_from_platform_versioned(
487 value: DataContractInSerializationFormat,
488 full_validation: bool,
489 validation_operations: &mut Vec<ProtocolValidationOperation>,
490 platform_version: &PlatformVersion,
491 ) -> Result<Self, ProtocolError> {
492 match platform_version
493 .dpp
494 .contract_versions
495 .contract_structure_version
496 {
497 0 => DataContractV0::try_from_platform_versioned(
498 value,
499 full_validation,
500 validation_operations,
501 platform_version,
502 )
503 .map(|contract| contract.into()),
504 1 => DataContractV1::try_from_platform_versioned(
505 value,
506 full_validation,
507 validation_operations,
508 platform_version,
509 )
510 .map(|contract| contract.into()),
511 version => Err(ProtocolError::UnknownVersionMismatch {
512 method: "DataContract::try_from_platform_versioned".to_string(),
513 known_versions: vec![0, 1],
514 received: version,
515 }),
516 }
517 }
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523 use crate::data_contract::accessors::v0::DataContractV0Setters;
524 use crate::data_contract::config::moderation::ContractModerationConfig;
525 use crate::data_contract::config::v0::DataContractConfigV0;
526 use crate::data_contract::config::v1::DataContractConfigV1;
527 use crate::data_contract::group::v0::GroupV0;
528 use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
529 use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
530 use crate::serialization::PlatformSerializableWithPlatformVersion;
531 use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
532 use crate::tests::fixtures::get_data_contract_fixture;
533 use platform_value::Identifier;
534 use std::collections::BTreeMap;
535
536 fn moderated_contract(protocol_version: u32) -> DataContract {
538 let mut contract =
539 get_data_contract_fixture(None, 0, protocol_version).data_contract_owned();
540 let config = contract
541 .config()
542 .clone()
543 .with_moderation(Some(ContractModerationConfig {
544 banlist: true,
545 suspensions: false,
546 moderators: Default::default(),
547 warnings: false,
548 }));
549 contract.set_config(config);
550 contract
551 }
552
553 fn every_conversion(
555 contract: &DataContract,
556 platform_version: &PlatformVersion,
557 ) -> Vec<Result<DataContractInSerializationFormat, ProtocolError>> {
558 let mut conversions = vec![
559 DataContractInSerializationFormat::try_from_platform_versioned(
560 contract.clone(),
561 platform_version,
562 ),
563 DataContractInSerializationFormat::try_from_platform_versioned(
564 contract,
565 platform_version,
566 ),
567 ];
568 match contract {
569 DataContract::V0(v0) => {
570 conversions.push(
571 DataContractInSerializationFormat::try_from_platform_versioned(
572 v0.clone(),
573 platform_version,
574 ),
575 );
576 conversions.push(
577 DataContractInSerializationFormat::try_from_platform_versioned(
578 v0,
579 platform_version,
580 ),
581 );
582 }
583 DataContract::V1(v1) => {
584 conversions.push(
585 DataContractInSerializationFormat::try_from_platform_versioned(
586 v1.clone(),
587 platform_version,
588 ),
589 );
590 conversions.push(
591 DataContractInSerializationFormat::try_from_platform_versioned(
592 v1,
593 platform_version,
594 ),
595 );
596 }
597 }
598 conversions
599 }
600
601 #[test]
602 fn should_refuse_to_serialize_a_moderated_contract_below_protocol_version_14() {
603 let latest = PlatformVersion::latest();
604 let v13 = PlatformVersion::get(13).expect("protocol version 13");
605
606 assert!(matches!(moderated_contract(1), DataContract::V0(_)));
608 assert!(matches!(moderated_contract(14), DataContract::V1(_)));
609 for contract in [moderated_contract(1), moderated_contract(14)] {
610 for conversion in every_conversion(&contract, v13) {
613 assert!(matches!(conversion, Err(ProtocolError::NotSupported(_))));
614 }
615 assert!(matches!(
616 contract.serialize_to_bytes_with_platform_version(v13),
617 Err(ProtocolError::NotSupported(_))
618 ));
619 assert!(matches!(
620 DataContractCreateTransition::try_from_platform_versioned(contract.clone(), v13),
621 Err(ProtocolError::NotSupported(_))
622 ));
623
624 for conversion in every_conversion(&contract, latest) {
625 assert!(conversion.is_ok());
626 }
627 assert!(contract
628 .serialize_to_bytes_with_platform_version(latest)
629 .is_ok());
630 }
631 }
632
633 fn make_v0() -> DataContractInSerializationFormatV0 {
635 DataContractInSerializationFormatV0 {
636 id: Identifier::default(),
637 config: DataContractConfig::V0(DataContractConfigV0::default()),
638 version: 1,
639 owner_id: Identifier::default(),
640 schema_defs: None,
641 document_schemas: BTreeMap::new(),
642 }
643 }
644
645 fn make_v1() -> DataContractInSerializationFormatV1 {
647 DataContractInSerializationFormatV1 {
648 id: Identifier::default(),
649 config: DataContractConfig::V1(DataContractConfigV1::default()),
650 version: 1,
651 owner_id: Identifier::default(),
652 schema_defs: None,
653 document_schemas: BTreeMap::new(),
654 created_at: None,
655 updated_at: None,
656 created_at_block_height: None,
657 updated_at_block_height: None,
658 created_at_epoch: None,
659 updated_at_epoch: None,
660 groups: BTreeMap::new(),
661 tokens: BTreeMap::new(),
662 keywords: vec![],
663 description: None,
664 }
665 }
666
667 #[test]
672 fn first_mismatch_v0_v0_identical_returns_none() {
673 let a = DataContractInSerializationFormat::V0(make_v0());
674 let b = DataContractInSerializationFormat::V0(make_v0());
675 assert_eq!(a.first_mismatch(&b), None);
676 }
677
678 #[test]
679 fn first_mismatch_v0_v0_different_id() {
680 let mut v0_b = make_v0();
681 v0_b.id = Identifier::from([1u8; 32]);
682 let a = DataContractInSerializationFormat::V0(make_v0());
683 let b = DataContractInSerializationFormat::V0(v0_b);
684 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
685 }
686
687 #[test]
688 fn first_mismatch_v0_v0_different_config() {
689 let mut v0_b = make_v0();
690 let mut cfg = DataContractConfigV0::default();
691 cfg.readonly = !cfg.readonly;
692 v0_b.config = DataContractConfig::V0(cfg);
693 let a = DataContractInSerializationFormat::V0(make_v0());
694 let b = DataContractInSerializationFormat::V0(v0_b);
695 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
696 }
697
698 #[test]
699 fn first_mismatch_v0_v0_different_version() {
700 let mut v0_b = make_v0();
701 v0_b.version = 99;
702 let a = DataContractInSerializationFormat::V0(make_v0());
703 let b = DataContractInSerializationFormat::V0(v0_b);
704 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
705 }
706
707 #[test]
708 fn first_mismatch_v0_v0_different_owner_id() {
709 let mut v0_b = make_v0();
710 v0_b.owner_id = Identifier::from([2u8; 32]);
711 let a = DataContractInSerializationFormat::V0(make_v0());
712 let b = DataContractInSerializationFormat::V0(v0_b);
713 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
714 }
715
716 #[test]
717 fn first_mismatch_v0_v0_different_document_schemas() {
718 let mut v0_b = make_v0();
719 v0_b.document_schemas
720 .insert("doc".to_string(), Value::Bool(true));
721 let a = DataContractInSerializationFormat::V0(make_v0());
722 let b = DataContractInSerializationFormat::V0(v0_b);
723 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
724 }
725
726 #[test]
731 fn first_mismatch_v0_v1_returns_format_version_mismatch() {
732 let a = DataContractInSerializationFormat::V0(make_v0());
733 let b = DataContractInSerializationFormat::V1(make_v1());
734 assert_eq!(
735 a.first_mismatch(&b),
736 Some(DataContractMismatch::FormatVersionMismatch)
737 );
738 }
739
740 #[test]
741 fn first_mismatch_v1_v0_returns_format_version_mismatch() {
742 let a = DataContractInSerializationFormat::V1(make_v1());
743 let b = DataContractInSerializationFormat::V0(make_v0());
744 assert_eq!(
745 a.first_mismatch(&b),
746 Some(DataContractMismatch::FormatVersionMismatch)
747 );
748 }
749
750 #[test]
755 fn first_mismatch_v1_v1_identical_returns_none() {
756 let a = DataContractInSerializationFormat::V1(make_v1());
757 let b = DataContractInSerializationFormat::V1(make_v1());
758 assert_eq!(a.first_mismatch(&b), None);
759 }
760
761 #[test]
766 fn first_mismatch_v1_v1_different_id() {
767 let mut v1_b = make_v1();
768 v1_b.id = Identifier::from([1u8; 32]);
769 let a = DataContractInSerializationFormat::V1(make_v1());
770 let b = DataContractInSerializationFormat::V1(v1_b);
771 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
772 }
773
774 #[test]
775 fn first_mismatch_v1_v1_different_config() {
776 let mut v1_b = make_v1();
777 let mut cfg = DataContractConfigV1::default();
778 cfg.readonly = !cfg.readonly;
779 v1_b.config = DataContractConfig::V1(cfg);
780 let a = DataContractInSerializationFormat::V1(make_v1());
781 let b = DataContractInSerializationFormat::V1(v1_b);
782 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Config));
783 }
784
785 #[test]
786 fn first_mismatch_v1_v1_different_version() {
787 let mut v1_b = make_v1();
788 v1_b.version = 42;
789 let a = DataContractInSerializationFormat::V1(make_v1());
790 let b = DataContractInSerializationFormat::V1(v1_b);
791 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Version));
792 }
793
794 #[test]
795 fn first_mismatch_v1_v1_different_owner_id() {
796 let mut v1_b = make_v1();
797 v1_b.owner_id = Identifier::from([3u8; 32]);
798 let a = DataContractInSerializationFormat::V1(make_v1());
799 let b = DataContractInSerializationFormat::V1(v1_b);
800 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::OwnerId));
801 }
802
803 #[test]
804 fn first_mismatch_v1_v1_different_schema_defs() {
805 let mut v1_b = make_v1();
806 let mut defs = BTreeMap::new();
807 defs.insert("someDef".to_string(), Value::Bool(true));
808 v1_b.schema_defs = Some(defs);
809 let a = DataContractInSerializationFormat::V1(make_v1());
810 let b = DataContractInSerializationFormat::V1(v1_b);
811 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::SchemaDefs));
812 }
813
814 #[test]
815 fn first_mismatch_v1_v1_different_document_schemas() {
816 let mut v1_b = make_v1();
817 v1_b.document_schemas
818 .insert("doc".to_string(), Value::U64(1));
819 let a = DataContractInSerializationFormat::V1(make_v1());
820 let b = DataContractInSerializationFormat::V1(v1_b);
821 assert_eq!(
822 a.first_mismatch(&b),
823 Some(DataContractMismatch::DocumentSchemas)
824 );
825 }
826
827 #[test]
828 fn first_mismatch_v1_v1_different_groups() {
829 let mut v1_b = make_v1();
830 v1_b.groups.insert(
831 0,
832 Group::V0(GroupV0 {
833 members: Default::default(),
834 required_power: 1,
835 }),
836 );
837 let a = DataContractInSerializationFormat::V1(make_v1());
838 let b = DataContractInSerializationFormat::V1(v1_b);
839 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Groups));
840 }
841
842 #[test]
843 fn first_mismatch_v1_v1_different_tokens() {
844 let mut v1_b = make_v1();
845 v1_b.tokens.insert(
846 0,
847 TokenConfiguration::V0(
848 crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0::default_most_restrictive(),
849 ),
850 );
851 let a = DataContractInSerializationFormat::V1(make_v1());
852 let b = DataContractInSerializationFormat::V1(v1_b);
853 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Tokens));
854 }
855
856 #[test]
857 fn first_mismatch_v1_v1_different_keywords() {
858 let mut v1_b = make_v1();
859 v1_b.keywords = vec!["test".to_string()];
860 let a = DataContractInSerializationFormat::V1(make_v1());
861 let b = DataContractInSerializationFormat::V1(v1_b);
862 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
863 }
864
865 #[test]
866 fn first_mismatch_v1_v1_keywords_case_insensitive_match() {
867 let mut v1_a = make_v1();
868 v1_a.keywords = vec!["Test".to_string()];
869 let mut v1_b = make_v1();
870 v1_b.keywords = vec!["test".to_string()];
871 let a = DataContractInSerializationFormat::V1(v1_a);
872 let b = DataContractInSerializationFormat::V1(v1_b);
873 assert_eq!(a.first_mismatch(&b), None);
875 }
876
877 #[test]
878 fn first_mismatch_v1_v1_keywords_different_length() {
879 let mut v1_a = make_v1();
880 v1_a.keywords = vec!["a".to_string()];
881 let mut v1_b = make_v1();
882 v1_b.keywords = vec!["a".to_string(), "b".to_string()];
883 let a = DataContractInSerializationFormat::V1(v1_a);
884 let b = DataContractInSerializationFormat::V1(v1_b);
885 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
886 }
887
888 #[test]
889 fn first_mismatch_v1_v1_different_description() {
890 let mut v1_b = make_v1();
891 v1_b.description = Some("a description".to_string());
892 let a = DataContractInSerializationFormat::V1(make_v1());
893 let b = DataContractInSerializationFormat::V1(v1_b);
894 assert_eq!(
895 a.first_mismatch(&b),
896 Some(DataContractMismatch::Description)
897 );
898 }
899
900 #[test]
905 fn first_mismatch_v1_v1_id_takes_priority_over_config() {
906 let mut v1_b = make_v1();
907 v1_b.id = Identifier::from([5u8; 32]);
908 let mut cfg = DataContractConfigV1::default();
909 cfg.readonly = !cfg.readonly;
910 v1_b.config = DataContractConfig::V1(cfg);
911 let a = DataContractInSerializationFormat::V1(make_v1());
912 let b = DataContractInSerializationFormat::V1(v1_b);
913 assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
915 }
916
917 #[test]
922 fn data_contract_mismatch_display() {
923 assert_eq!(format!("{}", DataContractMismatch::Id), "ID fields differ");
924 assert_eq!(
925 format!("{}", DataContractMismatch::FormatVersionMismatch),
926 "Serialization format versions differ (e.g., V0 vs V1)"
927 );
928 assert_eq!(
929 format!("{}", DataContractMismatch::V0Mismatch),
930 "V0 versions differ"
931 );
932 assert_eq!(format!("{}", DataContractMismatch::Tokens), "Tokens differ");
933 assert_eq!(
934 format!("{}", DataContractMismatch::Keywords),
935 "Keywords differ"
936 );
937 assert_eq!(
938 format!("{}", DataContractMismatch::Description),
939 "Description fields differ"
940 );
941 }
942
943 #[test]
948 fn accessor_id_v0() {
949 let v0 = make_v0();
950 let expected_id = v0.id;
951 let format = DataContractInSerializationFormat::V0(v0);
952 assert_eq!(format.id(), expected_id);
953 }
954
955 #[test]
956 fn accessor_id_v1() {
957 let v1 = make_v1();
958 let expected_id = v1.id;
959 let format = DataContractInSerializationFormat::V1(v1);
960 assert_eq!(format.id(), expected_id);
961 }
962
963 #[test]
964 fn accessor_owner_id_v0() {
965 let mut v0 = make_v0();
966 v0.owner_id = Identifier::from([7u8; 32]);
967 let expected = v0.owner_id;
968 let format = DataContractInSerializationFormat::V0(v0);
969 assert_eq!(format.owner_id(), expected);
970 }
971
972 #[test]
973 fn accessor_version_v0() {
974 let mut v0 = make_v0();
975 v0.version = 10;
976 let format = DataContractInSerializationFormat::V0(v0);
977 assert_eq!(format.version(), 10);
978 }
979
980 #[test]
981 fn accessor_version_v1() {
982 let mut v1 = make_v1();
983 v1.version = 20;
984 let format = DataContractInSerializationFormat::V1(v1);
985 assert_eq!(format.version(), 20);
986 }
987
988 #[test]
989 fn accessor_groups_v0_returns_empty() {
990 let format = DataContractInSerializationFormat::V0(make_v0());
991 assert!(format.groups().is_empty());
992 }
993
994 #[test]
995 fn accessor_tokens_v0_returns_empty() {
996 let format = DataContractInSerializationFormat::V0(make_v0());
997 assert!(format.tokens().is_empty());
998 }
999
1000 #[test]
1001 fn accessor_keywords_v0_returns_empty() {
1002 let format = DataContractInSerializationFormat::V0(make_v0());
1003 assert!(format.keywords().is_empty());
1004 }
1005
1006 #[test]
1007 fn accessor_description_v0_returns_none() {
1008 let format = DataContractInSerializationFormat::V0(make_v0());
1009 assert_eq!(format.description(), &None);
1010 }
1011
1012 #[test]
1013 fn accessor_keywords_v1() {
1014 let mut v1 = make_v1();
1015 v1.keywords = vec!["hello".to_string()];
1016 let format = DataContractInSerializationFormat::V1(v1);
1017 assert_eq!(format.keywords(), &vec!["hello".to_string()]);
1018 }
1019
1020 #[test]
1021 fn accessor_description_v1_some() {
1022 let mut v1 = make_v1();
1023 v1.description = Some("desc".to_string());
1024 let format = DataContractInSerializationFormat::V1(v1);
1025 assert_eq!(format.description(), &Some("desc".to_string()));
1026 }
1027
1028 #[test]
1029 fn accessor_document_schemas_v0() {
1030 let mut v0 = make_v0();
1031 v0.document_schemas
1032 .insert("note".to_string(), Value::Bool(true));
1033 let format = DataContractInSerializationFormat::V0(v0);
1034 assert_eq!(format.document_schemas().len(), 1);
1035 assert!(format.document_schemas().contains_key("note"));
1036 }
1037
1038 #[test]
1039 fn accessor_schema_defs_v0_none() {
1040 let format = DataContractInSerializationFormat::V0(make_v0());
1041 assert!(format.schema_defs().is_none());
1042 }
1043
1044 #[test]
1045 fn accessor_schema_defs_v1_some() {
1046 let mut v1 = make_v1();
1047 let mut defs = BTreeMap::new();
1048 defs.insert("def1".to_string(), Value::Null);
1049 v1.schema_defs = Some(defs);
1050 let format = DataContractInSerializationFormat::V1(v1);
1051 assert!(format.schema_defs().is_some());
1052 assert!(format.schema_defs().unwrap().contains_key("def1"));
1053 }
1054
1055 #[test]
1060 fn try_from_platform_versioned_data_contract_v0_version_0() {
1061 let platform_version = PlatformVersion::first();
1062 let v0 = DataContractV0 {
1064 id: Identifier::from([10u8; 32]),
1065 config: DataContractConfig::V0(DataContractConfigV0::default()),
1066 version: 1,
1067 owner_id: Identifier::from([20u8; 32]),
1068 schema_defs: None,
1069 document_types: BTreeMap::new(),
1070 metadata: None,
1071 };
1072 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1073 v0.clone(),
1074 platform_version,
1075 );
1076 assert!(result.is_ok());
1077 let format = result.unwrap();
1078 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1079 assert_eq!(format.id(), Identifier::from([10u8; 32]));
1080 assert_eq!(format.owner_id(), Identifier::from([20u8; 32]));
1081 }
1082
1083 #[test]
1084 fn try_from_platform_versioned_data_contract_v0_ref_version_0() {
1085 let platform_version = PlatformVersion::first();
1086 let v0 = DataContractV0 {
1087 id: Identifier::from([11u8; 32]),
1088 config: DataContractConfig::V0(DataContractConfigV0::default()),
1089 version: 2,
1090 owner_id: Identifier::from([22u8; 32]),
1091 schema_defs: None,
1092 document_types: BTreeMap::new(),
1093 metadata: None,
1094 };
1095 let result =
1096 DataContractInSerializationFormat::try_from_platform_versioned(&v0, platform_version);
1097 assert!(result.is_ok());
1098 let format = result.unwrap();
1099 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1100 assert_eq!(format.version(), 2);
1101 }
1102
1103 #[test]
1104 fn try_from_platform_versioned_data_contract_v0_version_1() {
1105 let platform_version = PlatformVersion::latest();
1106 let v0 = DataContractV0 {
1108 id: Identifier::from([10u8; 32]),
1109 config: DataContractConfig::V0(DataContractConfigV0::default()),
1110 version: 1,
1111 owner_id: Identifier::from([20u8; 32]),
1112 schema_defs: None,
1113 document_types: BTreeMap::new(),
1114 metadata: None,
1115 };
1116 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1117 v0.clone(),
1118 platform_version,
1119 );
1120 assert!(result.is_ok());
1121 let format = result.unwrap();
1122 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1123 }
1124
1125 #[test]
1130 fn try_from_platform_versioned_data_contract_v1_version_0() {
1131 let platform_version = PlatformVersion::first();
1132 let v1 = DataContractV1 {
1133 id: Identifier::from([10u8; 32]),
1134 config: DataContractConfig::V0(DataContractConfigV0::default()),
1135 version: 1,
1136 owner_id: Identifier::from([20u8; 32]),
1137 schema_defs: None,
1138 document_types: BTreeMap::new(),
1139 created_at: None,
1140 updated_at: None,
1141 created_at_block_height: None,
1142 updated_at_block_height: None,
1143 created_at_epoch: None,
1144 updated_at_epoch: None,
1145 groups: BTreeMap::new(),
1146 tokens: BTreeMap::new(),
1147 keywords: vec![],
1148 description: None,
1149 };
1150 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1151 v1.clone(),
1152 platform_version,
1153 );
1154 assert!(result.is_ok());
1155 let format = result.unwrap();
1156 assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1157 }
1158
1159 #[test]
1160 fn try_from_platform_versioned_data_contract_v1_version_1() {
1161 let platform_version = PlatformVersion::latest();
1162 let v1 = DataContractV1 {
1163 id: Identifier::from([10u8; 32]),
1164 config: DataContractConfig::V1(DataContractConfigV1::default()),
1165 version: 1,
1166 owner_id: Identifier::from([20u8; 32]),
1167 schema_defs: None,
1168 document_types: BTreeMap::new(),
1169 created_at: None,
1170 updated_at: None,
1171 created_at_block_height: None,
1172 updated_at_block_height: None,
1173 created_at_epoch: None,
1174 updated_at_epoch: None,
1175 groups: BTreeMap::new(),
1176 tokens: BTreeMap::new(),
1177 keywords: vec![],
1178 description: None,
1179 };
1180 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1181 v1.clone(),
1182 platform_version,
1183 );
1184 assert!(result.is_ok());
1185 let format = result.unwrap();
1186 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1187 }
1188
1189 #[test]
1190 fn try_from_platform_versioned_data_contract_v1_ref_version_1() {
1191 let platform_version = PlatformVersion::latest();
1192 let v1 = DataContractV1 {
1193 id: Identifier::from([10u8; 32]),
1194 config: DataContractConfig::V1(DataContractConfigV1::default()),
1195 version: 3,
1196 owner_id: Identifier::from([20u8; 32]),
1197 schema_defs: None,
1198 document_types: BTreeMap::new(),
1199 created_at: None,
1200 updated_at: None,
1201 created_at_block_height: None,
1202 updated_at_block_height: None,
1203 created_at_epoch: None,
1204 updated_at_epoch: None,
1205 groups: BTreeMap::new(),
1206 tokens: BTreeMap::new(),
1207 keywords: vec![],
1208 description: None,
1209 };
1210 let result =
1211 DataContractInSerializationFormat::try_from_platform_versioned(&v1, platform_version);
1212 assert!(result.is_ok());
1213 let format = result.unwrap();
1214 assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1215 assert_eq!(format.version(), 3);
1216 }
1217
1218 #[test]
1223 fn try_from_platform_versioned_data_contract_ref_version_0() {
1224 let platform_version = PlatformVersion::first();
1225 let contract = DataContract::V0(DataContractV0 {
1226 id: Identifier::from([10u8; 32]),
1227 config: DataContractConfig::V0(DataContractConfigV0::default()),
1228 version: 1,
1229 owner_id: Identifier::from([20u8; 32]),
1230 schema_defs: None,
1231 document_types: BTreeMap::new(),
1232 metadata: None,
1233 });
1234 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1235 &contract,
1236 platform_version,
1237 );
1238 assert!(result.is_ok());
1239 assert!(matches!(
1240 result.unwrap(),
1241 DataContractInSerializationFormat::V0(_)
1242 ));
1243 }
1244
1245 #[test]
1246 fn try_from_platform_versioned_data_contract_owned_version_1() {
1247 let platform_version = PlatformVersion::latest();
1248 let contract = DataContract::V0(DataContractV0 {
1249 id: Identifier::from([10u8; 32]),
1250 config: DataContractConfig::V0(DataContractConfigV0::default()),
1251 version: 1,
1252 owner_id: Identifier::from([20u8; 32]),
1253 schema_defs: None,
1254 document_types: BTreeMap::new(),
1255 metadata: None,
1256 });
1257 let result = DataContractInSerializationFormat::try_from_platform_versioned(
1258 contract,
1259 platform_version,
1260 );
1261 assert!(result.is_ok());
1262 assert!(matches!(
1263 result.unwrap(),
1264 DataContractInSerializationFormat::V1(_)
1265 ));
1266 }
1267
1268 #[test]
1273 fn first_platform_version_uses_serialization_version_0() {
1274 let pv = PlatformVersion::first();
1275 assert_eq!(
1276 pv.dpp
1277 .contract_versions
1278 .contract_serialization_version
1279 .default_current_version,
1280 0
1281 );
1282 }
1283
1284 #[test]
1285 fn latest_platform_version_uses_serialization_version_1() {
1286 let pv = PlatformVersion::latest();
1287 assert_eq!(
1288 pv.dpp
1289 .contract_versions
1290 .contract_serialization_version
1291 .default_current_version,
1292 1
1293 );
1294 }
1295
1296 #[test]
1297 fn first_platform_version_uses_contract_structure_0() {
1298 let pv = PlatformVersion::first();
1299 assert_eq!(pv.dpp.contract_versions.contract_structure_version, 0);
1300 }
1301
1302 #[test]
1303 fn latest_platform_version_uses_contract_structure_1() {
1304 let pv = PlatformVersion::latest();
1305 assert_eq!(pv.dpp.contract_versions.contract_structure_version, 1);
1306 }
1307}
1308
1309#[cfg(all(
1310 test,
1311 feature = "json-conversion",
1312 feature = "value-conversion",
1313 feature = "serde-conversion"
1314))]
1315mod json_convertible_tests {
1316 use super::*;
1317 use crate::data_contract::config::v0::DataContractConfigV0;
1318 use crate::data_contract::config::DataContractConfig;
1319 use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
1320 use platform_value::Identifier;
1321 use std::collections::BTreeMap;
1322
1323 fn fixture() -> DataContractInSerializationFormat {
1324 DataContractInSerializationFormat::V0(DataContractInSerializationFormatV0 {
1325 id: Identifier::new([0xa1; 32]),
1326 config: DataContractConfig::V0(DataContractConfigV0::default()),
1327 version: 1,
1328 owner_id: Identifier::new([0xb2; 32]),
1329 schema_defs: None,
1330 document_schemas: BTreeMap::new(),
1331 })
1332 }
1333
1334 #[test]
1335 fn json_round_trip_with_full_wire_shape() {
1336 use crate::serialization::JsonConvertible;
1337 use serde_json::json;
1338 let original = fixture();
1339 let json = original.to_json().expect("to_json");
1340 assert_eq!(json["$formatVersion"], "0");
1347 assert_eq!(
1348 json["id"],
1349 json!("Bswb3UyeD1pUTaGiE6WvqwFpJZsQSEY1xhJePCDTHdvp")
1350 );
1351 assert_eq!(
1352 json["ownerId"],
1353 json!("D2ZcUbtpG5sKq7XLeB4YnpNnTGSptKCxTddoNeydzJQq")
1354 );
1355 assert_eq!(json["version"], json!(1));
1356 assert_eq!(json["schemaDefs"], json!(null));
1357 assert_eq!(json["documentSchemas"], json!({}));
1358 assert!(json.get("config").is_some(), "config envelope present");
1359 assert_eq!(json["config"]["$formatVersion"], "0");
1360 let recovered = DataContractInSerializationFormat::from_json(json).expect("from_json");
1361 assert_eq!(original, recovered);
1362 }
1363
1364 #[test]
1365 fn value_round_trip_with_full_wire_shape() {
1366 use crate::serialization::ValueConvertible;
1367 use platform_value::Value;
1368 let original = fixture();
1369 let value = original.to_object().expect("to_object");
1370 let map = match &value {
1373 Value::Map(m) => m,
1374 other => panic!("expected Value::Map, got {:?}", other),
1375 };
1376 let get = |k: &str| -> &Value {
1377 map.iter()
1378 .find(|(key, _)| matches!(key, Value::Text(t) if t == k))
1379 .map(|(_, v)| v)
1380 .unwrap_or_else(|| panic!("missing key {k}"))
1381 };
1382 assert_eq!(get("$formatVersion"), &Value::Text("0".to_string()));
1383 assert_eq!(get("id"), &Value::Identifier([0xa1; 32]));
1384 assert_eq!(get("ownerId"), &Value::Identifier([0xb2; 32]));
1385 assert_eq!(get("version"), &Value::U32(1));
1386 assert_eq!(get("schemaDefs"), &Value::Null);
1387 assert!(matches!(get("documentSchemas"), Value::Map(m) if m.is_empty()));
1389 assert!(matches!(get("config"), Value::Map(_)));
1391 let recovered = DataContractInSerializationFormat::from_object(value).expect("from_object");
1392 assert_eq!(original, recovered);
1393 }
1394
1395 #[test]
1396 fn json_preserves_format_version_tag() {
1397 use crate::serialization::JsonConvertible;
1398 let json = fixture().to_json().expect("to_json");
1399 assert_eq!(json["$formatVersion"], "0");
1400 }
1401}