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