Skip to main content

dpp/data_contract/
mod.rs

1use crate::serialization::{
2    PlatformDeserializableWithBytesLenFromVersionedStructure,
3    PlatformDeserializableWithPotentialValidationFromVersionedStructure,
4    PlatformLimitDeserializableFromVersionedStructure, PlatformSerializableWithPlatformVersion,
5};
6use std::collections::BTreeMap;
7
8use derive_more::From;
9
10use bincode::config::{BigEndian, Configuration};
11use once_cell::sync::Lazy;
12
13pub mod errors;
14pub mod extra;
15
16mod generate_data_contract;
17
18#[cfg(any(feature = "state-transitions", feature = "factories"))]
19pub mod created_data_contract;
20pub mod document_type;
21
22pub mod v0;
23pub mod v1;
24
25#[cfg(feature = "factories")]
26pub mod factory;
27#[cfg(feature = "factories")]
28pub use factory::*;
29#[cfg(any(
30    feature = "value-conversion",
31    feature = "data-contract-cbor-conversion",
32    feature = "json-conversion",
33    feature = "serde-conversion"
34))]
35pub mod conversion;
36#[cfg(feature = "client")]
37mod data_contract_facade;
38#[cfg(feature = "client")]
39pub use data_contract_facade::DataContractFacade;
40mod methods;
41pub mod serialized_version;
42pub use methods::*;
43pub mod accessors;
44pub mod associated_token;
45pub mod change_control_rules;
46pub mod config;
47pub mod group;
48pub mod storage_requirements;
49
50use crate::data_contract::serialized_version::{
51    DataContractInSerializationFormat, CONTRACT_DESERIALIZATION_LIMIT,
52};
53use crate::util::hash::hash_double_to_vec;
54
55use crate::version::{FeatureVersion, PlatformVersion};
56use crate::ProtocolError;
57use crate::ProtocolError::{PlatformDeserializationError, PlatformSerializationError};
58
59pub use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
60use crate::data_contract::group::Group;
61use crate::data_contract::v0::DataContractV0;
62use crate::data_contract::v1::DataContractV1;
63use platform_version::TryIntoPlatformVersioned;
64use platform_versioning::PlatformVersioned;
65pub use serde_json::Value as JsonValue;
66
67type JsonSchema = JsonValue;
68type DefinitionName = String;
69pub type DocumentName = String;
70pub type TokenName = String;
71pub type GroupContractPosition = u16;
72pub type TokenContractPosition = u16;
73pub type DataContractWithSerialization = (DataContract, Vec<u8>);
74type PropertyPath = String;
75
76pub const INITIAL_DATA_CONTRACT_VERSION: u32 = 1;
77
78// Define static empty BTreeMaps and Vecs
79static EMPTY_GROUPS: Lazy<BTreeMap<GroupContractPosition, Group>> = Lazy::new(BTreeMap::new);
80static EMPTY_TOKENS: Lazy<BTreeMap<TokenContractPosition, TokenConfiguration>> =
81    Lazy::new(BTreeMap::new);
82static EMPTY_KEYWORDS: Lazy<Vec<String>> = Lazy::new(Vec::new);
83
84/// Understanding Data Contract versioning
85/// Data contract versioning is both for the code structure and for serialization.
86///
87/// The code structure is what is used in code to verify documents and is used in memory
88/// There is generally only one code structure running at any given time, except in the case we
89/// are switching protocol versions.
90///
91/// There can be a lot of serialization versions that are active, and serialization versions
92/// should generally always be supported. This is because when we store something as version 1.
93/// 10 years down the line when we unserialize this contract it will still be in version 1.
94/// Deserialization of a data contract serialized in that version should be translated to the
95/// current code structure version.
96///
97/// There are some scenarios to consider,
98///
99/// One such scenario is that the serialization version does not contain enough information for the
100/// current code structure version.
101///
102/// Depending on the situation one of the following occurs:
103/// - the contract structure can imply missing parts based on default behavior
104/// - the contract structure can disable certain features dependant on missing information
105/// - the contract might be unusable until it is updated by the owner
106#[derive(Debug, Clone, PartialEq, From, PlatformVersioned)]
107pub enum DataContract {
108    V0(DataContractV0),
109    V1(DataContractV1),
110}
111
112// Note: DataContract intentionally does NOT implement JsonConvertible / ValueConvertible.
113// Round-tripping goes through the manual `Serialize` / `Deserialize` impls in
114// `data_contract/conversion/serde/mod.rs`, which thread `DataContractInSerializationFormat`
115// at the *currently active* `PlatformVersion` (see Critical-4 doc there).
116//
117// The two version-aware conversion traits are:
118//   * `DataContractJsonConversionMethodsV0::from_json(value, full_validation, pv)` —
119//     deserialize from JSON, running full schema validation when `full_validation` is
120//     `true` (use on trust boundaries). Pass `false` to reconstruct already-trusted data
121//     (e.g. storage reads) without re-validating. The canonical
122//     `serde_json::from_value::<DataContract>` path also validates (it routes through
123//     this with `full_validation = true`) — see the Critical-4 doc.
124//   * `DataContractValueConversionMethodsV0::from_value(value, full_validation, pv)` —
125//     same shape for `platform_value::Value`.
126//
127// For non-validating *serialization*, just use `serde_json::to_value(&dc)?` /
128// `platform_value::to_value(&dc)?` — the manual Serialize impl handles versioning.
129// `DataContractInSerializationFormat` (the underlying serialization shape) DOES implement the
130// canonical traits — see `data_contract/serialized_version/mod.rs`.
131
132impl PlatformSerializableWithPlatformVersion for DataContract {
133    type Error = ProtocolError;
134
135    fn serialize_to_bytes_with_platform_version(
136        &self,
137        platform_version: &PlatformVersion,
138    ) -> Result<Vec<u8>, ProtocolError> {
139        let serialization_format: DataContractInSerializationFormat =
140            self.try_into_platform_versioned(platform_version)?;
141        let config = bincode::config::standard()
142            .with_big_endian()
143            .with_no_limit();
144        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
145            PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
146        })
147    }
148
149    fn serialize_consume_to_bytes_with_platform_version(
150        self,
151        platform_version: &PlatformVersion,
152    ) -> Result<Vec<u8>, ProtocolError> {
153        let serialization_format: DataContractInSerializationFormat =
154            self.try_into_platform_versioned(platform_version)?;
155        let config = bincode::config::standard()
156            .with_big_endian()
157            .with_no_limit();
158        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
159            PlatformSerializationError(format!("unable to serialize consume DataContract: {}", e))
160        })
161    }
162}
163
164impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for DataContract {
165    fn versioned_deserialize(
166        data: &[u8],
167        full_validation: bool,
168        platform_version: &PlatformVersion,
169    ) -> Result<Self, ProtocolError>
170    where
171        Self: Sized,
172    {
173        let config = bincode::config::standard()
174            .with_big_endian()
175            .with_no_limit();
176        let data_contract_in_serialization_format: DataContractInSerializationFormat =
177            bincode::borrow_decode_from_slice(data, config)
178                .map_err(|e| {
179                    PlatformDeserializationError(format!(
180                        "unable to deserialize DataContract: {}",
181                        e
182                    ))
183                })?
184                .0;
185        DataContract::try_from_platform_versioned(
186            data_contract_in_serialization_format,
187            full_validation,
188            &mut vec![],
189            platform_version,
190        )
191    }
192}
193
194impl PlatformDeserializableWithBytesLenFromVersionedStructure for DataContract {
195    fn versioned_deserialize_with_bytes_len(
196        data: &[u8],
197        full_validation: bool,
198        platform_version: &PlatformVersion,
199    ) -> Result<(Self, usize), ProtocolError>
200    where
201        Self: Sized,
202    {
203        let config = bincode::config::standard()
204            .with_big_endian()
205            .with_no_limit();
206        let (data_contract_in_serialization_format, len) = bincode::borrow_decode_from_slice::<
207            DataContractInSerializationFormat,
208            Configuration<BigEndian>,
209        >(data, config)
210        .map_err(|e| {
211            PlatformDeserializationError(format!("unable to deserialize DataContract: {}", e))
212        })?;
213        Ok((
214            DataContract::try_from_platform_versioned(
215                data_contract_in_serialization_format,
216                full_validation,
217                &mut vec![],
218                platform_version,
219            )?,
220            len,
221        ))
222    }
223}
224
225impl PlatformLimitDeserializableFromVersionedStructure for DataContract {
226    fn versioned_limit_deserialize(
227        data: &[u8],
228        platform_version: &PlatformVersion,
229    ) -> Result<Self, ProtocolError>
230    where
231        Self: Sized,
232    {
233        let config = bincode::config::standard()
234            .with_big_endian()
235            .with_limit::<CONTRACT_DESERIALIZATION_LIMIT>();
236        let data_contract_in_serialization_format: DataContractInSerializationFormat =
237            bincode::borrow_decode_from_slice(data, config)
238                .map_err(|e| {
239                    PlatformDeserializationError(format!(
240                        "unable to deserialize DataContract with limit: {}",
241                        e
242                    ))
243                })?
244                .0;
245        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
246        DataContract::try_from_platform_versioned(
247            data_contract_in_serialization_format,
248            true,
249            &mut vec![],
250            platform_version,
251        )
252    }
253}
254
255impl DataContract {
256    pub fn as_v0(&self) -> Option<&DataContractV0> {
257        match self {
258            DataContract::V0(v0) => Some(v0),
259            _ => None,
260        }
261    }
262
263    pub fn as_v0_mut(&mut self) -> Option<&mut DataContractV0> {
264        match self {
265            DataContract::V0(v0) => Some(v0),
266            _ => None,
267        }
268    }
269
270    pub fn into_v0(self) -> Option<DataContractV0> {
271        match self {
272            DataContract::V0(v0) => Some(v0),
273            _ => None,
274        }
275    }
276
277    pub fn as_v1(&self) -> Option<&DataContractV1> {
278        match self {
279            DataContract::V1(v1) => Some(v1),
280            _ => None,
281        }
282    }
283
284    pub fn as_v1_mut(&mut self) -> Option<&mut DataContractV1> {
285        match self {
286            DataContract::V1(v1) => Some(v1),
287            _ => None,
288        }
289    }
290
291    pub fn into_v1(self) -> Option<DataContractV1> {
292        match self {
293            DataContract::V1(v1) => Some(v1),
294            _ => None,
295        }
296    }
297
298    /// This should only ever be used in tests, as it will change
299    #[cfg(test)]
300    pub fn into_latest(self) -> Option<DataContractV1> {
301        self.into_v1()
302    }
303
304    /// This should only ever be used in tests, as it will change
305    #[cfg(test)]
306    pub fn as_latest(&self) -> Option<&DataContractV1> {
307        match self {
308            DataContract::V1(v1) => Some(v1),
309            _ => None,
310        }
311    }
312
313    /// This should only ever be used in tests, as it will change
314    #[cfg(test)]
315    pub fn as_latest_mut(&mut self) -> Option<&mut DataContractV1> {
316        match self {
317            DataContract::V1(v1) => Some(v1),
318            _ => None,
319        }
320    }
321
322    pub fn check_version_is_active(
323        protocol_version: u32,
324        data_contract_system_version: FeatureVersion,
325    ) -> Result<bool, ProtocolError> {
326        let platform_version = PlatformVersion::get(protocol_version)?;
327        Ok(platform_version
328            .dpp
329            .contract_versions
330            .contract_structure_version
331            == data_contract_system_version)
332    }
333
334    pub fn hash(&self, platform_version: &PlatformVersion) -> Result<Vec<u8>, ProtocolError> {
335        Ok(hash_double_to_vec(
336            self.serialize_to_bytes_with_platform_version(platform_version)?,
337        ))
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use crate::data_contract::accessors::v0::DataContractV0Getters;
344    use crate::data_contract::config::v0::DataContractConfigGettersV0;
345    use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
346    use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
347    use crate::data_contract::DataContract;
348    use crate::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure;
349    use crate::serialization::PlatformSerializableWithPlatformVersion;
350    use crate::system_data_contracts::load_system_data_contract;
351    use crate::tests::fixtures::{
352        get_dashpay_contract_fixture, get_dashpay_contract_with_generalized_encryption_key_fixture,
353    };
354    use crate::version::PlatformVersion;
355    use data_contracts::SystemDataContract::Dashpay;
356
357    #[test]
358    fn test_contract_serialization() {
359        let platform_version = PlatformVersion::latest();
360        let data_contract = load_system_data_contract(Dashpay, platform_version)
361            .expect("expected dashpay contract");
362        let serialized = data_contract
363            .serialize_to_bytes_with_platform_version(platform_version)
364            .expect("expected to serialize data contract");
365        assert_eq!(
366            serialized[0],
367            platform_version
368                .dpp
369                .contract_versions
370                .contract_serialization_version
371                .default_current_version as u8
372        );
373
374        let unserialized = DataContract::versioned_deserialize(&serialized, true, platform_version)
375            .expect("expected to deserialize data contract");
376
377        assert_eq!(data_contract, unserialized);
378    }
379
380    #[test]
381    fn test_contract_can_have_specialized_contract_encryption_decryption_keys() {
382        let data_contract =
383            get_dashpay_contract_with_generalized_encryption_key_fixture(None, 0, 1)
384                .data_contract_owned();
385        assert_eq!(
386            data_contract
387                .config()
388                .requires_identity_decryption_bounded_key(),
389            Some(StorageKeyRequirements::Unique)
390        );
391        assert_eq!(
392            data_contract
393                .config()
394                .requires_identity_encryption_bounded_key(),
395            Some(StorageKeyRequirements::Unique)
396        );
397    }
398
399    #[test]
400    fn test_contract_document_type_can_have_specialized_contract_encryption_decryption_keys() {
401        let data_contract = get_dashpay_contract_fixture(None, 0, 1).data_contract_owned();
402        assert_eq!(
403            data_contract
404                .document_type_for_name("contactRequest")
405                .expect("expected document type")
406                .requires_identity_decryption_bounded_key(),
407            Some(StorageKeyRequirements::MultipleReferenceToLatest)
408        );
409        assert_eq!(
410            data_contract
411                .document_type_for_name("contactRequest")
412                .expect("expected document type")
413                .requires_identity_encryption_bounded_key(),
414            Some(StorageKeyRequirements::MultipleReferenceToLatest)
415        );
416    }
417}