Skip to main content

dpp/data_contract/serialized_version/
mod.rs

1use super::EMPTY_KEYWORDS;
2use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
3use crate::data_contract::config::DataContractConfig;
4use crate::data_contract::group::Group;
5use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
6use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
7use crate::data_contract::v0::DataContractV0;
8use crate::data_contract::v1::DataContractV1;
9use crate::data_contract::{
10    DataContract, DefinitionName, DocumentName, GroupContractPosition, TokenContractPosition,
11    EMPTY_GROUPS, EMPTY_TOKENS,
12};
13#[cfg(feature = "json-conversion")]
14use crate::serialization::JsonConvertible;
15use crate::validation::operations::ProtocolValidationOperation;
16use crate::version::PlatformVersion;
17use crate::ProtocolError;
18use bincode::{Decode, Encode};
19use derive_more::From;
20use platform_value::{Identifier, Value};
21use platform_version::{IntoPlatformVersioned, TryFromPlatformVersioned};
22use platform_versioning::PlatformVersioned;
23#[cfg(feature = "serde-conversion")]
24use serde::{Deserialize, Serialize};
25use std::collections::BTreeMap;
26use std::fmt;
27
28pub(in crate::data_contract) mod v0;
29pub(in crate::data_contract) mod v1;
30
31pub mod property_names {
32    pub const ID: &str = "id";
33    pub const OWNER_ID: &str = "ownerId";
34    pub const VERSION: &str = "version";
35    pub const DEFINITIONS: &str = "$defs";
36}
37
38pub const CONTRACT_DESERIALIZATION_LIMIT: usize = 15000;
39
40/// Represents a field mismatch between two `DataContractInSerializationFormat::V1`
41/// variants, or indicates a format version mismatch.
42///
43/// Used to diagnose why two data contracts are not considered equal
44/// when ignoring auto-generated fields.
45#[derive(Debug, PartialEq, Eq, Clone, Copy)]
46pub enum DataContractMismatch {
47    /// The `id` fields are not equal.
48    Id,
49    /// The `config` fields are not equal.
50    Config,
51    /// The `version` fields are not equal.
52    Version,
53    /// The `owner_id` fields are not equal.
54    OwnerId,
55    /// The `schema_defs` fields are not equal.
56    SchemaDefs,
57    /// The `document_schemas` fields are not equal.
58    DocumentSchemas,
59    /// The `groups` fields are not equal.
60    Groups,
61    /// The `tokens` fields are not equal.
62    Tokens,
63    /// The `keywords` fields are not equal.
64    Keywords,
65    /// The `description` fields are not equal.
66    Description,
67    /// The two variants are of different serialization formats (e.g., V0 vs V1).
68    FormatVersionMismatch,
69    /// The two variants are different in V0.
70    V0Mismatch,
71}
72
73impl fmt::Display for DataContractMismatch {
74    /// Formats the enum into a human-readable string describing the mismatch.
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        let description = match self {
77            DataContractMismatch::Id => "ID fields differ",
78            DataContractMismatch::Config => "Config fields differ",
79            DataContractMismatch::Version => "Version fields differ",
80            DataContractMismatch::OwnerId => "Owner ID fields differ",
81            DataContractMismatch::SchemaDefs => "Schema definitions differ",
82            DataContractMismatch::DocumentSchemas => "Document schemas differ",
83            DataContractMismatch::Groups => "Groups differ",
84            DataContractMismatch::Tokens => "Tokens differ",
85            DataContractMismatch::Keywords => "Keywords differ",
86            DataContractMismatch::Description => "Description fields differ",
87            DataContractMismatch::FormatVersionMismatch => {
88                "Serialization format versions differ (e.g., V0 vs V1)"
89            }
90            DataContractMismatch::V0Mismatch => "V0 versions differ",
91        };
92        write!(f, "{}", description)
93    }
94}
95
96#[cfg_attr(
97    all(feature = "json-conversion", feature = "serde-conversion"),
98    derive(JsonConvertible)
99)]
100#[derive(Debug, Clone, Encode, Decode, PartialEq, PlatformVersioned, From)]
101#[cfg_attr(
102    feature = "serde-conversion",
103    derive(Serialize, Deserialize),
104    serde(tag = "$formatVersion")
105)]
106pub enum DataContractInSerializationFormat {
107    #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
108    V0(DataContractInSerializationFormatV0),
109    #[cfg_attr(feature = "serde-conversion", serde(rename = "1"))]
110    V1(DataContractInSerializationFormatV1),
111}
112
113impl DataContractInSerializationFormat {
114    /// Returns the unique identifier for the data contract.
115    pub fn id(&self) -> Identifier {
116        match self {
117            DataContractInSerializationFormat::V0(v0) => v0.id,
118            DataContractInSerializationFormat::V1(v1) => v1.id,
119        }
120    }
121
122    /// Returns the owner identifier for the data contract.
123    pub fn owner_id(&self) -> Identifier {
124        match self {
125            DataContractInSerializationFormat::V0(v0) => v0.owner_id,
126            DataContractInSerializationFormat::V1(v1) => v1.owner_id,
127        }
128    }
129
130    pub fn document_schemas(&self) -> &BTreeMap<DocumentName, Value> {
131        match self {
132            DataContractInSerializationFormat::V0(v0) => &v0.document_schemas,
133            DataContractInSerializationFormat::V1(v1) => &v1.document_schemas,
134        }
135    }
136
137    pub fn document_schemas_mut(&mut self) -> &mut BTreeMap<DocumentName, Value> {
138        match self {
139            DataContractInSerializationFormat::V0(v0) => &mut v0.document_schemas,
140            DataContractInSerializationFormat::V1(v1) => &mut v1.document_schemas,
141        }
142    }
143
144    pub fn schema_defs(&self) -> Option<&BTreeMap<DefinitionName, Value>> {
145        match self {
146            DataContractInSerializationFormat::V0(v0) => v0.schema_defs.as_ref(),
147            DataContractInSerializationFormat::V1(v1) => v1.schema_defs.as_ref(),
148        }
149    }
150
151    pub fn version(&self) -> u32 {
152        match self {
153            DataContractInSerializationFormat::V0(v0) => v0.version,
154            DataContractInSerializationFormat::V1(v1) => v1.version,
155        }
156    }
157
158    /// Returns the config for the data contract.
159    pub fn config(&self) -> &DataContractConfig {
160        match self {
161            DataContractInSerializationFormat::V0(v0) => &v0.config,
162            DataContractInSerializationFormat::V1(v1) => &v1.config,
163        }
164    }
165
166    pub fn groups(&self) -> &BTreeMap<GroupContractPosition, Group> {
167        match self {
168            DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS,
169            DataContractInSerializationFormat::V1(v1) => &v1.groups,
170        }
171    }
172    pub fn tokens(&self) -> &BTreeMap<TokenContractPosition, TokenConfiguration> {
173        match self {
174            DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS,
175            DataContractInSerializationFormat::V1(v1) => &v1.tokens,
176        }
177    }
178
179    pub fn keywords(&self) -> &Vec<String> {
180        match self {
181            DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS,
182            DataContractInSerializationFormat::V1(v1) => &v1.keywords,
183        }
184    }
185
186    pub fn description(&self) -> &Option<String> {
187        match self {
188            DataContractInSerializationFormat::V0(_) => &None,
189            DataContractInSerializationFormat::V1(v1) => &v1.description,
190        }
191    }
192
193    /// Compares `self` to another `DataContractInSerializationFormat` instance
194    /// and returns the first mismatching field, if any.
195    ///
196    /// This comparison ignores auto-generated fields and is only sensitive to
197    /// significant differences in contract content. For V0 formats, any difference
198    /// results in a generic mismatch. For differing format versions (V0 vs V1),
199    /// a `FormatVersionMismatch` is returned.
200    ///
201    /// # Returns
202    ///
203    /// - `None` if the contracts are equal according to the relevant fields.
204    /// - `Some(DataContractMismatch)` indicating the first field where they differ.
205    pub fn first_mismatch(&self, other: &Self) -> Option<DataContractMismatch> {
206        match (self, other) {
207            (
208                DataContractInSerializationFormat::V0(v0_self),
209                DataContractInSerializationFormat::V0(v0_other),
210            ) => {
211                if v0_self != v0_other {
212                    Some(DataContractMismatch::V0Mismatch)
213                } else {
214                    None
215                }
216            }
217            (
218                DataContractInSerializationFormat::V1(v1_self),
219                DataContractInSerializationFormat::V1(v1_other),
220            ) => {
221                if v1_self.id != v1_other.id {
222                    Some(DataContractMismatch::Id)
223                } else if v1_self.config != v1_other.config {
224                    Some(DataContractMismatch::Config)
225                } else if v1_self.version != v1_other.version {
226                    Some(DataContractMismatch::Version)
227                } else if v1_self.owner_id != v1_other.owner_id {
228                    Some(DataContractMismatch::OwnerId)
229                } else if v1_self.schema_defs != v1_other.schema_defs {
230                    Some(DataContractMismatch::SchemaDefs)
231                } else if v1_self.document_schemas != v1_other.document_schemas {
232                    Some(DataContractMismatch::DocumentSchemas)
233                } else if v1_self.groups != v1_other.groups {
234                    Some(DataContractMismatch::Groups)
235                } else if v1_self.tokens != v1_other.tokens {
236                    Some(DataContractMismatch::Tokens)
237                } else if v1_self.keywords.len() != v1_other.keywords.len()
238                    || v1_self
239                        .keywords
240                        .iter()
241                        .zip(v1_other.keywords.iter())
242                        .any(|(a, b)| a.to_lowercase() != b.to_lowercase())
243                {
244                    Some(DataContractMismatch::Keywords)
245                } else if v1_self.description != v1_other.description {
246                    Some(DataContractMismatch::Description)
247                } else {
248                    None
249                }
250            }
251            _ => Some(DataContractMismatch::FormatVersionMismatch),
252        }
253    }
254}
255
256impl TryFromPlatformVersioned<DataContractV0> for DataContractInSerializationFormat {
257    type Error = ProtocolError;
258
259    fn try_from_platform_versioned(
260        value: DataContractV0,
261        platform_version: &PlatformVersion,
262    ) -> Result<Self, Self::Error> {
263        match platform_version
264            .dpp
265            .contract_versions
266            .contract_serialization_version
267            .default_current_version
268        {
269            0 => {
270                let v0_format: DataContractInSerializationFormatV0 =
271                    DataContract::V0(value).into_platform_versioned(platform_version);
272                Ok(v0_format.into())
273            }
274            1 => {
275                let v1_format: DataContractInSerializationFormatV1 =
276                    DataContract::V0(value).into_platform_versioned(platform_version);
277                Ok(v1_format.into())
278            }
279            version => Err(ProtocolError::UnknownVersionMismatch {
280                method: "DataContract::serialize_to_default_current_version".to_string(),
281                known_versions: vec![0, 1],
282                received: version,
283            }),
284        }
285    }
286}
287
288impl TryFromPlatformVersioned<&DataContractV0> for DataContractInSerializationFormat {
289    type Error = ProtocolError;
290
291    fn try_from_platform_versioned(
292        value: &DataContractV0,
293        platform_version: &PlatformVersion,
294    ) -> Result<Self, Self::Error> {
295        match platform_version
296            .dpp
297            .contract_versions
298            .contract_serialization_version
299            .default_current_version
300        {
301            0 => {
302                let v0_format: DataContractInSerializationFormatV0 =
303                    DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
304                Ok(v0_format.into())
305            }
306            1 => {
307                let v1_format: DataContractInSerializationFormatV1 =
308                    DataContract::V0(value.to_owned()).into_platform_versioned(platform_version);
309                Ok(v1_format.into())
310            }
311            version => Err(ProtocolError::UnknownVersionMismatch {
312                method: "DataContract::serialize_to_default_current_version".to_string(),
313                known_versions: vec![0, 1],
314                received: version,
315            }),
316        }
317    }
318}
319
320impl TryFromPlatformVersioned<DataContractV1> for DataContractInSerializationFormat {
321    type Error = ProtocolError;
322
323    fn try_from_platform_versioned(
324        value: DataContractV1,
325        platform_version: &PlatformVersion,
326    ) -> Result<Self, Self::Error> {
327        match platform_version
328            .dpp
329            .contract_versions
330            .contract_serialization_version
331            .default_current_version
332        {
333            0 => {
334                let v0_format: DataContractInSerializationFormatV0 =
335                    DataContract::V1(value).into_platform_versioned(platform_version);
336                Ok(v0_format.into())
337            }
338            1 => {
339                let v1_format: DataContractInSerializationFormatV1 =
340                    DataContract::V1(value).into_platform_versioned(platform_version);
341                Ok(v1_format.into())
342            }
343            version => Err(ProtocolError::UnknownVersionMismatch {
344                method: "DataContract::serialize_to_default_current_version".to_string(),
345                known_versions: vec![0, 1],
346                received: version,
347            }),
348        }
349    }
350}
351
352impl TryFromPlatformVersioned<&DataContractV1> for DataContractInSerializationFormat {
353    type Error = ProtocolError;
354
355    fn try_from_platform_versioned(
356        value: &DataContractV1,
357        platform_version: &PlatformVersion,
358    ) -> Result<Self, Self::Error> {
359        match platform_version
360            .dpp
361            .contract_versions
362            .contract_serialization_version
363            .default_current_version
364        {
365            0 => {
366                let v0_format: DataContractInSerializationFormatV0 =
367                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
368                Ok(v0_format.into())
369            }
370            1 => {
371                let v1_format: DataContractInSerializationFormatV1 =
372                    DataContract::V1(value.to_owned()).into_platform_versioned(platform_version);
373                Ok(v1_format.into())
374            }
375            version => Err(ProtocolError::UnknownVersionMismatch {
376                method: "DataContract::serialize_to_default_current_version".to_string(),
377                known_versions: vec![0, 1],
378                received: version,
379            }),
380        }
381    }
382}
383
384impl TryFromPlatformVersioned<&DataContract> for DataContractInSerializationFormat {
385    type Error = ProtocolError;
386
387    fn try_from_platform_versioned(
388        value: &DataContract,
389        platform_version: &PlatformVersion,
390    ) -> Result<Self, Self::Error> {
391        match platform_version
392            .dpp
393            .contract_versions
394            .contract_serialization_version
395            .default_current_version
396        {
397            0 => {
398                let v0_format: DataContractInSerializationFormatV0 =
399                    value.clone().into_platform_versioned(platform_version);
400                Ok(v0_format.into())
401            }
402            1 => {
403                let v1_format: DataContractInSerializationFormatV1 =
404                    value.clone().into_platform_versioned(platform_version);
405                Ok(v1_format.into())
406            }
407            version => Err(ProtocolError::UnknownVersionMismatch {
408                method: "DataContract::serialize_to_default_current_version".to_string(),
409                known_versions: vec![0, 1],
410                received: version,
411            }),
412        }
413    }
414}
415
416impl TryFromPlatformVersioned<DataContract> for DataContractInSerializationFormat {
417    type Error = ProtocolError;
418
419    fn try_from_platform_versioned(
420        value: DataContract,
421        platform_version: &PlatformVersion,
422    ) -> Result<Self, Self::Error> {
423        match platform_version
424            .dpp
425            .contract_versions
426            .contract_serialization_version
427            .default_current_version
428        {
429            0 => {
430                let v0_format: DataContractInSerializationFormatV0 =
431                    value.into_platform_versioned(platform_version);
432                Ok(v0_format.into())
433            }
434            1 => {
435                let v1_format: DataContractInSerializationFormatV1 =
436                    value.into_platform_versioned(platform_version);
437                Ok(v1_format.into())
438            }
439            version => Err(ProtocolError::UnknownVersionMismatch {
440                method: "DataContract::serialize_consume_to_default_current_version".to_string(),
441                known_versions: vec![0, 1],
442                received: version,
443            }),
444        }
445    }
446}
447
448impl DataContract {
449    pub fn try_from_platform_versioned(
450        value: DataContractInSerializationFormat,
451        full_validation: bool,
452        validation_operations: &mut Vec<ProtocolValidationOperation>,
453        platform_version: &PlatformVersion,
454    ) -> Result<Self, ProtocolError> {
455        match platform_version
456            .dpp
457            .contract_versions
458            .contract_structure_version
459        {
460            0 => DataContractV0::try_from_platform_versioned(
461                value,
462                full_validation,
463                validation_operations,
464                platform_version,
465            )
466            .map(|contract| contract.into()),
467            1 => DataContractV1::try_from_platform_versioned(
468                value,
469                full_validation,
470                validation_operations,
471                platform_version,
472            )
473            .map(|contract| contract.into()),
474            version => Err(ProtocolError::UnknownVersionMismatch {
475                method: "DataContract::try_from_platform_versioned".to_string(),
476                known_versions: vec![0, 1],
477                received: version,
478            }),
479        }
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486    use crate::data_contract::config::v0::DataContractConfigV0;
487    use crate::data_contract::config::v1::DataContractConfigV1;
488    use crate::data_contract::group::v0::GroupV0;
489    use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
490    use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
491    use platform_value::Identifier;
492    use std::collections::BTreeMap;
493
494    /// Helper to create a default V0 serialization format.
495    fn make_v0() -> DataContractInSerializationFormatV0 {
496        DataContractInSerializationFormatV0 {
497            id: Identifier::default(),
498            config: DataContractConfig::V0(DataContractConfigV0::default()),
499            version: 1,
500            owner_id: Identifier::default(),
501            schema_defs: None,
502            document_schemas: BTreeMap::new(),
503        }
504    }
505
506    /// Helper to create a default V1 serialization format.
507    fn make_v1() -> DataContractInSerializationFormatV1 {
508        DataContractInSerializationFormatV1 {
509            id: Identifier::default(),
510            config: DataContractConfig::V1(DataContractConfigV1::default()),
511            version: 1,
512            owner_id: Identifier::default(),
513            schema_defs: None,
514            document_schemas: BTreeMap::new(),
515            created_at: None,
516            updated_at: None,
517            created_at_block_height: None,
518            updated_at_block_height: None,
519            created_at_epoch: None,
520            updated_at_epoch: None,
521            groups: BTreeMap::new(),
522            tokens: BTreeMap::new(),
523            keywords: vec![],
524            description: None,
525        }
526    }
527
528    // -----------------------------------------------------------------------
529    // first_mismatch: V0-V0
530    // -----------------------------------------------------------------------
531
532    #[test]
533    fn first_mismatch_v0_v0_identical_returns_none() {
534        let a = DataContractInSerializationFormat::V0(make_v0());
535        let b = DataContractInSerializationFormat::V0(make_v0());
536        assert_eq!(a.first_mismatch(&b), None);
537    }
538
539    #[test]
540    fn first_mismatch_v0_v0_different_id() {
541        let mut v0_b = make_v0();
542        v0_b.id = Identifier::from([1u8; 32]);
543        let a = DataContractInSerializationFormat::V0(make_v0());
544        let b = DataContractInSerializationFormat::V0(v0_b);
545        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
546    }
547
548    #[test]
549    fn first_mismatch_v0_v0_different_config() {
550        let mut v0_b = make_v0();
551        let mut cfg = DataContractConfigV0::default();
552        cfg.readonly = !cfg.readonly;
553        v0_b.config = DataContractConfig::V0(cfg);
554        let a = DataContractInSerializationFormat::V0(make_v0());
555        let b = DataContractInSerializationFormat::V0(v0_b);
556        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
557    }
558
559    #[test]
560    fn first_mismatch_v0_v0_different_version() {
561        let mut v0_b = make_v0();
562        v0_b.version = 99;
563        let a = DataContractInSerializationFormat::V0(make_v0());
564        let b = DataContractInSerializationFormat::V0(v0_b);
565        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
566    }
567
568    #[test]
569    fn first_mismatch_v0_v0_different_owner_id() {
570        let mut v0_b = make_v0();
571        v0_b.owner_id = Identifier::from([2u8; 32]);
572        let a = DataContractInSerializationFormat::V0(make_v0());
573        let b = DataContractInSerializationFormat::V0(v0_b);
574        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
575    }
576
577    #[test]
578    fn first_mismatch_v0_v0_different_document_schemas() {
579        let mut v0_b = make_v0();
580        v0_b.document_schemas
581            .insert("doc".to_string(), Value::Bool(true));
582        let a = DataContractInSerializationFormat::V0(make_v0());
583        let b = DataContractInSerializationFormat::V0(v0_b);
584        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::V0Mismatch));
585    }
586
587    // -----------------------------------------------------------------------
588    // first_mismatch: format mismatch (V0 vs V1)
589    // -----------------------------------------------------------------------
590
591    #[test]
592    fn first_mismatch_v0_v1_returns_format_version_mismatch() {
593        let a = DataContractInSerializationFormat::V0(make_v0());
594        let b = DataContractInSerializationFormat::V1(make_v1());
595        assert_eq!(
596            a.first_mismatch(&b),
597            Some(DataContractMismatch::FormatVersionMismatch)
598        );
599    }
600
601    #[test]
602    fn first_mismatch_v1_v0_returns_format_version_mismatch() {
603        let a = DataContractInSerializationFormat::V1(make_v1());
604        let b = DataContractInSerializationFormat::V0(make_v0());
605        assert_eq!(
606            a.first_mismatch(&b),
607            Some(DataContractMismatch::FormatVersionMismatch)
608        );
609    }
610
611    // -----------------------------------------------------------------------
612    // first_mismatch: V1-V1 identical
613    // -----------------------------------------------------------------------
614
615    #[test]
616    fn first_mismatch_v1_v1_identical_returns_none() {
617        let a = DataContractInSerializationFormat::V1(make_v1());
618        let b = DataContractInSerializationFormat::V1(make_v1());
619        assert_eq!(a.first_mismatch(&b), None);
620    }
621
622    // -----------------------------------------------------------------------
623    // first_mismatch: V1-V1 field-by-field mismatches
624    // -----------------------------------------------------------------------
625
626    #[test]
627    fn first_mismatch_v1_v1_different_id() {
628        let mut v1_b = make_v1();
629        v1_b.id = Identifier::from([1u8; 32]);
630        let a = DataContractInSerializationFormat::V1(make_v1());
631        let b = DataContractInSerializationFormat::V1(v1_b);
632        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
633    }
634
635    #[test]
636    fn first_mismatch_v1_v1_different_config() {
637        let mut v1_b = make_v1();
638        let mut cfg = DataContractConfigV1::default();
639        cfg.readonly = !cfg.readonly;
640        v1_b.config = DataContractConfig::V1(cfg);
641        let a = DataContractInSerializationFormat::V1(make_v1());
642        let b = DataContractInSerializationFormat::V1(v1_b);
643        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Config));
644    }
645
646    #[test]
647    fn first_mismatch_v1_v1_different_version() {
648        let mut v1_b = make_v1();
649        v1_b.version = 42;
650        let a = DataContractInSerializationFormat::V1(make_v1());
651        let b = DataContractInSerializationFormat::V1(v1_b);
652        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Version));
653    }
654
655    #[test]
656    fn first_mismatch_v1_v1_different_owner_id() {
657        let mut v1_b = make_v1();
658        v1_b.owner_id = Identifier::from([3u8; 32]);
659        let a = DataContractInSerializationFormat::V1(make_v1());
660        let b = DataContractInSerializationFormat::V1(v1_b);
661        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::OwnerId));
662    }
663
664    #[test]
665    fn first_mismatch_v1_v1_different_schema_defs() {
666        let mut v1_b = make_v1();
667        let mut defs = BTreeMap::new();
668        defs.insert("someDef".to_string(), Value::Bool(true));
669        v1_b.schema_defs = Some(defs);
670        let a = DataContractInSerializationFormat::V1(make_v1());
671        let b = DataContractInSerializationFormat::V1(v1_b);
672        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::SchemaDefs));
673    }
674
675    #[test]
676    fn first_mismatch_v1_v1_different_document_schemas() {
677        let mut v1_b = make_v1();
678        v1_b.document_schemas
679            .insert("doc".to_string(), Value::U64(1));
680        let a = DataContractInSerializationFormat::V1(make_v1());
681        let b = DataContractInSerializationFormat::V1(v1_b);
682        assert_eq!(
683            a.first_mismatch(&b),
684            Some(DataContractMismatch::DocumentSchemas)
685        );
686    }
687
688    #[test]
689    fn first_mismatch_v1_v1_different_groups() {
690        let mut v1_b = make_v1();
691        v1_b.groups.insert(
692            0,
693            Group::V0(GroupV0 {
694                members: Default::default(),
695                required_power: 1,
696            }),
697        );
698        let a = DataContractInSerializationFormat::V1(make_v1());
699        let b = DataContractInSerializationFormat::V1(v1_b);
700        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Groups));
701    }
702
703    #[test]
704    fn first_mismatch_v1_v1_different_tokens() {
705        let mut v1_b = make_v1();
706        v1_b.tokens.insert(
707            0,
708            TokenConfiguration::V0(
709                crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0::default_most_restrictive(),
710            ),
711        );
712        let a = DataContractInSerializationFormat::V1(make_v1());
713        let b = DataContractInSerializationFormat::V1(v1_b);
714        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Tokens));
715    }
716
717    #[test]
718    fn first_mismatch_v1_v1_different_keywords() {
719        let mut v1_b = make_v1();
720        v1_b.keywords = vec!["test".to_string()];
721        let a = DataContractInSerializationFormat::V1(make_v1());
722        let b = DataContractInSerializationFormat::V1(v1_b);
723        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
724    }
725
726    #[test]
727    fn first_mismatch_v1_v1_keywords_case_insensitive_match() {
728        let mut v1_a = make_v1();
729        v1_a.keywords = vec!["Test".to_string()];
730        let mut v1_b = make_v1();
731        v1_b.keywords = vec!["test".to_string()];
732        let a = DataContractInSerializationFormat::V1(v1_a);
733        let b = DataContractInSerializationFormat::V1(v1_b);
734        // The comparison uses to_lowercase, so "Test" and "test" should match
735        assert_eq!(a.first_mismatch(&b), None);
736    }
737
738    #[test]
739    fn first_mismatch_v1_v1_keywords_different_length() {
740        let mut v1_a = make_v1();
741        v1_a.keywords = vec!["a".to_string()];
742        let mut v1_b = make_v1();
743        v1_b.keywords = vec!["a".to_string(), "b".to_string()];
744        let a = DataContractInSerializationFormat::V1(v1_a);
745        let b = DataContractInSerializationFormat::V1(v1_b);
746        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Keywords));
747    }
748
749    #[test]
750    fn first_mismatch_v1_v1_different_description() {
751        let mut v1_b = make_v1();
752        v1_b.description = Some("a description".to_string());
753        let a = DataContractInSerializationFormat::V1(make_v1());
754        let b = DataContractInSerializationFormat::V1(v1_b);
755        assert_eq!(
756            a.first_mismatch(&b),
757            Some(DataContractMismatch::Description)
758        );
759    }
760
761    // -----------------------------------------------------------------------
762    // first_mismatch: priority ordering in V1 (id detected before config, etc.)
763    // -----------------------------------------------------------------------
764
765    #[test]
766    fn first_mismatch_v1_v1_id_takes_priority_over_config() {
767        let mut v1_b = make_v1();
768        v1_b.id = Identifier::from([5u8; 32]);
769        let mut cfg = DataContractConfigV1::default();
770        cfg.readonly = !cfg.readonly;
771        v1_b.config = DataContractConfig::V1(cfg);
772        let a = DataContractInSerializationFormat::V1(make_v1());
773        let b = DataContractInSerializationFormat::V1(v1_b);
774        // Id is checked before config
775        assert_eq!(a.first_mismatch(&b), Some(DataContractMismatch::Id));
776    }
777
778    // -----------------------------------------------------------------------
779    // DataContractMismatch Display
780    // -----------------------------------------------------------------------
781
782    #[test]
783    fn data_contract_mismatch_display() {
784        assert_eq!(format!("{}", DataContractMismatch::Id), "ID fields differ");
785        assert_eq!(
786            format!("{}", DataContractMismatch::FormatVersionMismatch),
787            "Serialization format versions differ (e.g., V0 vs V1)"
788        );
789        assert_eq!(
790            format!("{}", DataContractMismatch::V0Mismatch),
791            "V0 versions differ"
792        );
793        assert_eq!(format!("{}", DataContractMismatch::Tokens), "Tokens differ");
794        assert_eq!(
795            format!("{}", DataContractMismatch::Keywords),
796            "Keywords differ"
797        );
798        assert_eq!(
799            format!("{}", DataContractMismatch::Description),
800            "Description fields differ"
801        );
802    }
803
804    // -----------------------------------------------------------------------
805    // Accessor methods
806    // -----------------------------------------------------------------------
807
808    #[test]
809    fn accessor_id_v0() {
810        let v0 = make_v0();
811        let expected_id = v0.id;
812        let format = DataContractInSerializationFormat::V0(v0);
813        assert_eq!(format.id(), expected_id);
814    }
815
816    #[test]
817    fn accessor_id_v1() {
818        let v1 = make_v1();
819        let expected_id = v1.id;
820        let format = DataContractInSerializationFormat::V1(v1);
821        assert_eq!(format.id(), expected_id);
822    }
823
824    #[test]
825    fn accessor_owner_id_v0() {
826        let mut v0 = make_v0();
827        v0.owner_id = Identifier::from([7u8; 32]);
828        let expected = v0.owner_id;
829        let format = DataContractInSerializationFormat::V0(v0);
830        assert_eq!(format.owner_id(), expected);
831    }
832
833    #[test]
834    fn accessor_version_v0() {
835        let mut v0 = make_v0();
836        v0.version = 10;
837        let format = DataContractInSerializationFormat::V0(v0);
838        assert_eq!(format.version(), 10);
839    }
840
841    #[test]
842    fn accessor_version_v1() {
843        let mut v1 = make_v1();
844        v1.version = 20;
845        let format = DataContractInSerializationFormat::V1(v1);
846        assert_eq!(format.version(), 20);
847    }
848
849    #[test]
850    fn accessor_groups_v0_returns_empty() {
851        let format = DataContractInSerializationFormat::V0(make_v0());
852        assert!(format.groups().is_empty());
853    }
854
855    #[test]
856    fn accessor_tokens_v0_returns_empty() {
857        let format = DataContractInSerializationFormat::V0(make_v0());
858        assert!(format.tokens().is_empty());
859    }
860
861    #[test]
862    fn accessor_keywords_v0_returns_empty() {
863        let format = DataContractInSerializationFormat::V0(make_v0());
864        assert!(format.keywords().is_empty());
865    }
866
867    #[test]
868    fn accessor_description_v0_returns_none() {
869        let format = DataContractInSerializationFormat::V0(make_v0());
870        assert_eq!(format.description(), &None);
871    }
872
873    #[test]
874    fn accessor_keywords_v1() {
875        let mut v1 = make_v1();
876        v1.keywords = vec!["hello".to_string()];
877        let format = DataContractInSerializationFormat::V1(v1);
878        assert_eq!(format.keywords(), &vec!["hello".to_string()]);
879    }
880
881    #[test]
882    fn accessor_description_v1_some() {
883        let mut v1 = make_v1();
884        v1.description = Some("desc".to_string());
885        let format = DataContractInSerializationFormat::V1(v1);
886        assert_eq!(format.description(), &Some("desc".to_string()));
887    }
888
889    #[test]
890    fn accessor_document_schemas_v0() {
891        let mut v0 = make_v0();
892        v0.document_schemas
893            .insert("note".to_string(), Value::Bool(true));
894        let format = DataContractInSerializationFormat::V0(v0);
895        assert_eq!(format.document_schemas().len(), 1);
896        assert!(format.document_schemas().contains_key("note"));
897    }
898
899    #[test]
900    fn accessor_schema_defs_v0_none() {
901        let format = DataContractInSerializationFormat::V0(make_v0());
902        assert!(format.schema_defs().is_none());
903    }
904
905    #[test]
906    fn accessor_schema_defs_v1_some() {
907        let mut v1 = make_v1();
908        let mut defs = BTreeMap::new();
909        defs.insert("def1".to_string(), Value::Null);
910        v1.schema_defs = Some(defs);
911        let format = DataContractInSerializationFormat::V1(v1);
912        assert!(format.schema_defs().is_some());
913        assert!(format.schema_defs().unwrap().contains_key("def1"));
914    }
915
916    // -----------------------------------------------------------------------
917    // TryFromPlatformVersioned: DataContractV0 -> DataContractInSerializationFormat
918    // -----------------------------------------------------------------------
919
920    #[test]
921    fn try_from_platform_versioned_data_contract_v0_version_0() {
922        let platform_version = PlatformVersion::first();
923        // V1 contract versions use default_current_version: 0
924        let v0 = DataContractV0 {
925            id: Identifier::from([10u8; 32]),
926            config: DataContractConfig::V0(DataContractConfigV0::default()),
927            version: 1,
928            owner_id: Identifier::from([20u8; 32]),
929            schema_defs: None,
930            document_types: BTreeMap::new(),
931            metadata: None,
932        };
933        let result = DataContractInSerializationFormat::try_from_platform_versioned(
934            v0.clone(),
935            platform_version,
936        );
937        assert!(result.is_ok());
938        let format = result.unwrap();
939        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
940        assert_eq!(format.id(), Identifier::from([10u8; 32]));
941        assert_eq!(format.owner_id(), Identifier::from([20u8; 32]));
942    }
943
944    #[test]
945    fn try_from_platform_versioned_data_contract_v0_ref_version_0() {
946        let platform_version = PlatformVersion::first();
947        let v0 = DataContractV0 {
948            id: Identifier::from([11u8; 32]),
949            config: DataContractConfig::V0(DataContractConfigV0::default()),
950            version: 2,
951            owner_id: Identifier::from([22u8; 32]),
952            schema_defs: None,
953            document_types: BTreeMap::new(),
954            metadata: None,
955        };
956        let result =
957            DataContractInSerializationFormat::try_from_platform_versioned(&v0, platform_version);
958        assert!(result.is_ok());
959        let format = result.unwrap();
960        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
961        assert_eq!(format.version(), 2);
962    }
963
964    #[test]
965    fn try_from_platform_versioned_data_contract_v0_version_1() {
966        let platform_version = PlatformVersion::latest();
967        // Latest uses default_current_version: 1
968        let v0 = DataContractV0 {
969            id: Identifier::from([10u8; 32]),
970            config: DataContractConfig::V0(DataContractConfigV0::default()),
971            version: 1,
972            owner_id: Identifier::from([20u8; 32]),
973            schema_defs: None,
974            document_types: BTreeMap::new(),
975            metadata: None,
976        };
977        let result = DataContractInSerializationFormat::try_from_platform_versioned(
978            v0.clone(),
979            platform_version,
980        );
981        assert!(result.is_ok());
982        let format = result.unwrap();
983        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
984    }
985
986    // -----------------------------------------------------------------------
987    // TryFromPlatformVersioned: DataContractV1 -> DataContractInSerializationFormat
988    // -----------------------------------------------------------------------
989
990    #[test]
991    fn try_from_platform_versioned_data_contract_v1_version_0() {
992        let platform_version = PlatformVersion::first();
993        let v1 = DataContractV1 {
994            id: Identifier::from([10u8; 32]),
995            config: DataContractConfig::V0(DataContractConfigV0::default()),
996            version: 1,
997            owner_id: Identifier::from([20u8; 32]),
998            schema_defs: None,
999            document_types: BTreeMap::new(),
1000            created_at: None,
1001            updated_at: None,
1002            created_at_block_height: None,
1003            updated_at_block_height: None,
1004            created_at_epoch: None,
1005            updated_at_epoch: None,
1006            groups: BTreeMap::new(),
1007            tokens: BTreeMap::new(),
1008            keywords: vec![],
1009            description: None,
1010        };
1011        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1012            v1.clone(),
1013            platform_version,
1014        );
1015        assert!(result.is_ok());
1016        let format = result.unwrap();
1017        assert!(matches!(format, DataContractInSerializationFormat::V0(_)));
1018    }
1019
1020    #[test]
1021    fn try_from_platform_versioned_data_contract_v1_version_1() {
1022        let platform_version = PlatformVersion::latest();
1023        let v1 = DataContractV1 {
1024            id: Identifier::from([10u8; 32]),
1025            config: DataContractConfig::V1(DataContractConfigV1::default()),
1026            version: 1,
1027            owner_id: Identifier::from([20u8; 32]),
1028            schema_defs: None,
1029            document_types: BTreeMap::new(),
1030            created_at: None,
1031            updated_at: None,
1032            created_at_block_height: None,
1033            updated_at_block_height: None,
1034            created_at_epoch: None,
1035            updated_at_epoch: None,
1036            groups: BTreeMap::new(),
1037            tokens: BTreeMap::new(),
1038            keywords: vec![],
1039            description: None,
1040        };
1041        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1042            v1.clone(),
1043            platform_version,
1044        );
1045        assert!(result.is_ok());
1046        let format = result.unwrap();
1047        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1048    }
1049
1050    #[test]
1051    fn try_from_platform_versioned_data_contract_v1_ref_version_1() {
1052        let platform_version = PlatformVersion::latest();
1053        let v1 = DataContractV1 {
1054            id: Identifier::from([10u8; 32]),
1055            config: DataContractConfig::V1(DataContractConfigV1::default()),
1056            version: 3,
1057            owner_id: Identifier::from([20u8; 32]),
1058            schema_defs: None,
1059            document_types: BTreeMap::new(),
1060            created_at: None,
1061            updated_at: None,
1062            created_at_block_height: None,
1063            updated_at_block_height: None,
1064            created_at_epoch: None,
1065            updated_at_epoch: None,
1066            groups: BTreeMap::new(),
1067            tokens: BTreeMap::new(),
1068            keywords: vec![],
1069            description: None,
1070        };
1071        let result =
1072            DataContractInSerializationFormat::try_from_platform_versioned(&v1, platform_version);
1073        assert!(result.is_ok());
1074        let format = result.unwrap();
1075        assert!(matches!(format, DataContractInSerializationFormat::V1(_)));
1076        assert_eq!(format.version(), 3);
1077    }
1078
1079    // -----------------------------------------------------------------------
1080    // TryFromPlatformVersioned: DataContract -> DataContractInSerializationFormat
1081    // -----------------------------------------------------------------------
1082
1083    #[test]
1084    fn try_from_platform_versioned_data_contract_ref_version_0() {
1085        let platform_version = PlatformVersion::first();
1086        let contract = DataContract::V0(DataContractV0 {
1087            id: Identifier::from([10u8; 32]),
1088            config: DataContractConfig::V0(DataContractConfigV0::default()),
1089            version: 1,
1090            owner_id: Identifier::from([20u8; 32]),
1091            schema_defs: None,
1092            document_types: BTreeMap::new(),
1093            metadata: None,
1094        });
1095        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1096            &contract,
1097            platform_version,
1098        );
1099        assert!(result.is_ok());
1100        assert!(matches!(
1101            result.unwrap(),
1102            DataContractInSerializationFormat::V0(_)
1103        ));
1104    }
1105
1106    #[test]
1107    fn try_from_platform_versioned_data_contract_owned_version_1() {
1108        let platform_version = PlatformVersion::latest();
1109        let contract = DataContract::V0(DataContractV0 {
1110            id: Identifier::from([10u8; 32]),
1111            config: DataContractConfig::V0(DataContractConfigV0::default()),
1112            version: 1,
1113            owner_id: Identifier::from([20u8; 32]),
1114            schema_defs: None,
1115            document_types: BTreeMap::new(),
1116            metadata: None,
1117        });
1118        let result = DataContractInSerializationFormat::try_from_platform_versioned(
1119            contract,
1120            platform_version,
1121        );
1122        assert!(result.is_ok());
1123        assert!(matches!(
1124            result.unwrap(),
1125            DataContractInSerializationFormat::V1(_)
1126        ));
1127    }
1128
1129    // -----------------------------------------------------------------------
1130    // Verify serialization version routing
1131    // -----------------------------------------------------------------------
1132
1133    #[test]
1134    fn first_platform_version_uses_serialization_version_0() {
1135        let pv = PlatformVersion::first();
1136        assert_eq!(
1137            pv.dpp
1138                .contract_versions
1139                .contract_serialization_version
1140                .default_current_version,
1141            0
1142        );
1143    }
1144
1145    #[test]
1146    fn latest_platform_version_uses_serialization_version_1() {
1147        let pv = PlatformVersion::latest();
1148        assert_eq!(
1149            pv.dpp
1150                .contract_versions
1151                .contract_serialization_version
1152                .default_current_version,
1153            1
1154        );
1155    }
1156
1157    #[test]
1158    fn first_platform_version_uses_contract_structure_0() {
1159        let pv = PlatformVersion::first();
1160        assert_eq!(pv.dpp.contract_versions.contract_structure_version, 0);
1161    }
1162
1163    #[test]
1164    fn latest_platform_version_uses_contract_structure_1() {
1165        let pv = PlatformVersion::latest();
1166        assert_eq!(pv.dpp.contract_versions.contract_structure_version, 1);
1167    }
1168}