Skip to main content

dpp/data_contract/v1/serialization/
mod.rs

1use crate::data_contract::document_type::class_methods::consensus_or_protocol_required_fields_error;
2use crate::data_contract::document_type::validate_required_since_within_contract_version;
3use crate::data_contract::document_type::DocumentType;
4use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
5use crate::data_contract::serialized_version::DataContractInSerializationFormat;
6use crate::data_contract::{DataContract, DataContractV1};
7use crate::version::{PlatformVersion, PlatformVersionCurrentVersion};
8use crate::ProtocolError;
9use std::collections::BTreeMap;
10
11use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
12use crate::validation::operations::ProtocolValidationOperation;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14
15impl Serialize for DataContractV1 {
16    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
17    where
18        S: Serializer,
19    {
20        let data_contract: DataContract = self.clone().into();
21        let serialization_format = DataContractInSerializationFormatV1::from(data_contract);
22        serialization_format.serialize(serializer)
23    }
24}
25
26impl<'de> Deserialize<'de> for DataContractV1 {
27    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28    where
29        D: Deserializer<'de>,
30    {
31        let serialization_format = DataContractInSerializationFormatV1::deserialize(deserializer)?;
32        let current_version = PlatformVersion::get_version_or_current_or_latest(None)
33            .map_err(|e| serde::de::Error::custom(e.to_string()))?;
34        // when deserializing from json/platform_value/cbor we always want to validate (as this is not coming from the state)
35        DataContractV1::try_from_platform_versioned_v1(
36            serialization_format,
37            true,
38            &mut vec![],
39            current_version,
40        )
41        .map_err(serde::de::Error::custom)
42    }
43}
44
45impl DataContractV1 {
46    pub(in crate::data_contract) fn try_from_platform_versioned(
47        value: DataContractInSerializationFormat,
48        full_validation: bool,
49        validation_operations: &mut Vec<ProtocolValidationOperation>,
50        platform_version: &PlatformVersion,
51    ) -> Result<Self, ProtocolError> {
52        match value {
53            DataContractInSerializationFormat::V0(serialization_format_v0) => {
54                let data_contract = DataContractV1::try_from_platform_versioned_v0(
55                    serialization_format_v0,
56                    full_validation,
57                    validation_operations,
58                    platform_version,
59                )?;
60
61                Ok(data_contract)
62            }
63            DataContractInSerializationFormat::V1(serialization_format_v1) => {
64                let data_contract = DataContractV1::try_from_platform_versioned_v1(
65                    serialization_format_v1,
66                    full_validation,
67                    validation_operations,
68                    platform_version,
69                )?;
70
71                Ok(data_contract)
72            }
73        }
74    }
75
76    pub(in crate::data_contract) fn try_from_platform_versioned_v0(
77        data_contract_data: DataContractInSerializationFormatV0,
78        full_validation: bool,
79        validation_operations: &mut Vec<ProtocolValidationOperation>,
80        platform_version: &PlatformVersion,
81    ) -> Result<Self, ProtocolError> {
82        let DataContractInSerializationFormatV0 {
83            id,
84            config,
85            version,
86            owner_id,
87            document_schemas,
88            schema_defs,
89        } = data_contract_data;
90
91        let document_types = DocumentType::create_document_types_from_document_schemas(
92            id,
93            1,
94            data_contract_data.config.version(),
95            document_schemas,
96            schema_defs.as_ref(),
97            &BTreeMap::new(),
98            &config,
99            full_validation,
100            false,
101            validation_operations,
102            platform_version,
103        )?;
104
105        validate_required_since_within_contract_version(&document_types, version)
106            .map_err(consensus_or_protocol_required_fields_error)?;
107
108        let data_contract = DataContractV1 {
109            id,
110            version,
111            owner_id,
112            document_types,
113            config,
114            schema_defs,
115            created_at: None,
116            updated_at: None,
117            created_at_block_height: None,
118            updated_at_block_height: None,
119            created_at_epoch: None,
120            updated_at_epoch: None,
121            groups: Default::default(),
122            tokens: Default::default(),
123            keywords: Default::default(),
124            description: None,
125        };
126
127        Ok(data_contract)
128    }
129
130    pub(in crate::data_contract) fn try_from_platform_versioned_v1(
131        data_contract_data: DataContractInSerializationFormatV1,
132        full_validation: bool,
133        validation_operations: &mut Vec<ProtocolValidationOperation>,
134        platform_version: &PlatformVersion,
135    ) -> Result<Self, ProtocolError> {
136        let DataContractInSerializationFormatV1 {
137            id,
138            config,
139            version,
140            owner_id,
141            document_schemas,
142            schema_defs,
143            created_at,
144            updated_at,
145            created_at_block_height,
146            updated_at_block_height,
147            created_at_epoch,
148            updated_at_epoch,
149            groups,
150            tokens,
151            keywords,
152            description,
153        } = data_contract_data;
154
155        let document_types = DocumentType::create_document_types_from_document_schemas(
156            id,
157            1,
158            data_contract_data.config.version(),
159            document_schemas,
160            schema_defs.as_ref(),
161            &tokens,
162            &config,
163            full_validation,
164            !tokens.is_empty(),
165            validation_operations,
166            platform_version,
167        )?;
168
169        validate_required_since_within_contract_version(&document_types, version)
170            .map_err(consensus_or_protocol_required_fields_error)?;
171
172        let data_contract = DataContractV1 {
173            id,
174            version,
175            owner_id,
176            document_types,
177            config,
178            schema_defs,
179            created_at,
180            updated_at,
181            created_at_block_height,
182            updated_at_block_height,
183            created_at_epoch,
184            updated_at_epoch,
185            groups,
186            tokens,
187            keywords: keywords
188                .into_iter()
189                .map(|keyword| keyword.to_lowercase())
190                .collect(),
191            description,
192        };
193
194        Ok(data_contract)
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::data_contract::DataContract;
201    use crate::identity::accessors::IdentityGettersV0;
202    use crate::identity::Identity;
203    use crate::serialization::{
204        PlatformDeserializableWithPotentialValidationFromVersionedStructure,
205        PlatformSerializableWithPlatformVersion,
206    };
207    use crate::tests::fixtures::get_data_contract_fixture;
208    use crate::version::PlatformVersion;
209    use platform_version::version::LATEST_PLATFORM_VERSION;
210
211    #[test]
212    #[cfg(feature = "random-identities")]
213    fn data_contract_ser_de() {
214        // V1 of the contract is first present in protocol version 7
215        let platform_version = PlatformVersion::get(7).expect("expected protocol version 7");
216        let identity = Identity::random_identity(5, Some(5), platform_version)
217            .expect("expected a random identity");
218        let contract =
219            get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version)
220                .data_contract_owned();
221        let bytes = contract
222            .serialize_to_bytes_with_platform_version(LATEST_PLATFORM_VERSION)
223            .expect("expected to serialize");
224        let recovered_contract =
225            DataContract::versioned_deserialize(&bytes, false, platform_version)
226                .expect("expected to deserialize state transition");
227        assert_eq!(contract, recovered_contract);
228    }
229
230    #[test]
231    #[cfg(feature = "random-identities")]
232    fn data_contract_v1_serde_json_roundtrip() {
233        use crate::data_contract::accessors::v0::DataContractV0Getters;
234        use crate::data_contract::DataContractV1;
235
236        // V1 contracts are first produced at platform version 9
237        let platform_version = PlatformVersion::get(9).expect("expected protocol version 9");
238        let identity = Identity::random_identity(5, Some(5), platform_version)
239            .expect("expected a random identity");
240        let contract =
241            get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version)
242                .data_contract_owned();
243        let v1 = contract.into_v1().expect("expected V1 contract");
244
245        let json = serde_json::to_string(&v1).expect("expected to serialize to JSON");
246        let recovered: DataContractV1 =
247            serde_json::from_str(&json).expect("expected to deserialize from JSON");
248
249        // Schema normalization during deserialization means full equality may differ;
250        // verify stable identity fields to confirm a successful roundtrip.
251        assert_eq!(v1.id(), recovered.id());
252        assert_eq!(v1.owner_id(), recovered.owner_id());
253        assert_eq!(v1.version(), recovered.version());
254    }
255}