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* to `platform_value::Value`, prefer
128// `DataContractValueConversionMethodsV0::to_value(&dc, pv)` whenever a `PlatformVersion` is
129// in hand — it selects the serialization format from the passed version. The serde-based
130// alternatives (`serde_json::to_value(&dc)?` / `platform_value::to_value(&dc)?`) select the
131// format from the *process-global* current platform version, which other threads may mutate
132// concurrently (e.g. parallel tests building platforms at older protocol versions) — only
133// use them where no explicit version is available.
134// `DataContractInSerializationFormat` (the underlying serialization shape) DOES implement the
135// canonical traits — see `data_contract/serialized_version/mod.rs`.
136
137impl PlatformSerializableWithPlatformVersion for DataContract {
138    type Error = ProtocolError;
139
140    fn serialize_to_bytes_with_platform_version(
141        &self,
142        platform_version: &PlatformVersion,
143    ) -> Result<Vec<u8>, ProtocolError> {
144        let serialization_format: DataContractInSerializationFormat =
145            self.try_into_platform_versioned(platform_version)?;
146        let config = bincode::config::standard()
147            .with_big_endian()
148            .with_no_limit();
149        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
150            PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
151        })
152    }
153
154    fn serialize_consume_to_bytes_with_platform_version(
155        self,
156        platform_version: &PlatformVersion,
157    ) -> Result<Vec<u8>, ProtocolError> {
158        let serialization_format: DataContractInSerializationFormat =
159            self.try_into_platform_versioned(platform_version)?;
160        let config = bincode::config::standard()
161            .with_big_endian()
162            .with_no_limit();
163        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
164            PlatformSerializationError(format!("unable to serialize consume DataContract: {}", e))
165        })
166    }
167}
168
169impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for DataContract {
170    fn versioned_deserialize(
171        data: &[u8],
172        full_validation: bool,
173        platform_version: &PlatformVersion,
174    ) -> Result<Self, ProtocolError>
175    where
176        Self: Sized,
177    {
178        let config = bincode::config::standard()
179            .with_big_endian()
180            .with_no_limit();
181        let data_contract_in_serialization_format: DataContractInSerializationFormat =
182            bincode::borrow_decode_from_slice(data, config)
183                .map_err(|e| {
184                    PlatformDeserializationError(format!(
185                        "unable to deserialize DataContract: {}",
186                        e
187                    ))
188                })?
189                .0;
190        DataContract::try_from_platform_versioned(
191            data_contract_in_serialization_format,
192            full_validation,
193            &mut vec![],
194            platform_version,
195        )
196    }
197}
198
199impl PlatformDeserializableWithBytesLenFromVersionedStructure for DataContract {
200    fn versioned_deserialize_with_bytes_len(
201        data: &[u8],
202        full_validation: bool,
203        platform_version: &PlatformVersion,
204    ) -> Result<(Self, usize), ProtocolError>
205    where
206        Self: Sized,
207    {
208        let config = bincode::config::standard()
209            .with_big_endian()
210            .with_no_limit();
211        let (data_contract_in_serialization_format, len) = bincode::borrow_decode_from_slice::<
212            DataContractInSerializationFormat,
213            Configuration<BigEndian>,
214        >(data, config)
215        .map_err(|e| {
216            PlatformDeserializationError(format!("unable to deserialize DataContract: {}", e))
217        })?;
218        Ok((
219            DataContract::try_from_platform_versioned(
220                data_contract_in_serialization_format,
221                full_validation,
222                &mut vec![],
223                platform_version,
224            )?,
225            len,
226        ))
227    }
228}
229
230impl PlatformLimitDeserializableFromVersionedStructure for DataContract {
231    fn versioned_limit_deserialize(
232        data: &[u8],
233        platform_version: &PlatformVersion,
234    ) -> Result<Self, ProtocolError>
235    where
236        Self: Sized,
237    {
238        let config = bincode::config::standard()
239            .with_big_endian()
240            .with_limit::<CONTRACT_DESERIALIZATION_LIMIT>();
241        let data_contract_in_serialization_format: DataContractInSerializationFormat =
242            bincode::borrow_decode_from_slice(data, config)
243                .map_err(|e| {
244                    PlatformDeserializationError(format!(
245                        "unable to deserialize DataContract with limit: {}",
246                        e
247                    ))
248                })?
249                .0;
250        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
251        DataContract::try_from_platform_versioned(
252            data_contract_in_serialization_format,
253            true,
254            &mut vec![],
255            platform_version,
256        )
257    }
258}
259
260impl DataContract {
261    pub fn as_v0(&self) -> Option<&DataContractV0> {
262        match self {
263            DataContract::V0(v0) => Some(v0),
264            _ => None,
265        }
266    }
267
268    pub fn as_v0_mut(&mut self) -> Option<&mut DataContractV0> {
269        match self {
270            DataContract::V0(v0) => Some(v0),
271            _ => None,
272        }
273    }
274
275    pub fn into_v0(self) -> Option<DataContractV0> {
276        match self {
277            DataContract::V0(v0) => Some(v0),
278            _ => None,
279        }
280    }
281
282    pub fn as_v1(&self) -> Option<&DataContractV1> {
283        match self {
284            DataContract::V1(v1) => Some(v1),
285            _ => None,
286        }
287    }
288
289    pub fn as_v1_mut(&mut self) -> Option<&mut DataContractV1> {
290        match self {
291            DataContract::V1(v1) => Some(v1),
292            _ => None,
293        }
294    }
295
296    pub fn into_v1(self) -> Option<DataContractV1> {
297        match self {
298            DataContract::V1(v1) => Some(v1),
299            _ => None,
300        }
301    }
302
303    /// This should only ever be used in tests, as it will change
304    #[cfg(test)]
305    pub fn into_latest(self) -> Option<DataContractV1> {
306        self.into_v1()
307    }
308
309    /// This should only ever be used in tests, as it will change
310    #[cfg(test)]
311    pub fn as_latest(&self) -> Option<&DataContractV1> {
312        match self {
313            DataContract::V1(v1) => Some(v1),
314            _ => None,
315        }
316    }
317
318    /// This should only ever be used in tests, as it will change
319    #[cfg(test)]
320    pub fn as_latest_mut(&mut self) -> Option<&mut DataContractV1> {
321        match self {
322            DataContract::V1(v1) => Some(v1),
323            _ => None,
324        }
325    }
326
327    pub fn check_version_is_active(
328        protocol_version: u32,
329        data_contract_system_version: FeatureVersion,
330    ) -> Result<bool, ProtocolError> {
331        let platform_version = PlatformVersion::get(protocol_version)?;
332        Ok(platform_version
333            .dpp
334            .contract_versions
335            .contract_structure_version
336            == data_contract_system_version)
337    }
338
339    pub fn hash(&self, platform_version: &PlatformVersion) -> Result<Vec<u8>, ProtocolError> {
340        Ok(hash_double_to_vec(
341            self.serialize_to_bytes_with_platform_version(platform_version)?,
342        ))
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use crate::data_contract::accessors::v0::DataContractV0Getters;
349    use crate::data_contract::config::v0::DataContractConfigGettersV0;
350    use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
351    use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
352    use crate::data_contract::DataContract;
353    use crate::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure;
354    use crate::serialization::PlatformSerializableWithPlatformVersion;
355    use crate::system_data_contracts::load_system_data_contract;
356    use crate::tests::fixtures::{
357        get_dashpay_contract_fixture, get_dashpay_contract_with_generalized_encryption_key_fixture,
358    };
359    use crate::version::PlatformVersion;
360    use data_contracts::SystemDataContract::Dashpay;
361
362    #[test]
363    fn test_contract_serialization() {
364        let platform_version = PlatformVersion::latest();
365        let data_contract = load_system_data_contract(Dashpay, platform_version)
366            .expect("expected dashpay contract");
367        let serialized = data_contract
368            .serialize_to_bytes_with_platform_version(platform_version)
369            .expect("expected to serialize data contract");
370        assert_eq!(
371            serialized[0],
372            platform_version
373                .dpp
374                .contract_versions
375                .contract_serialization_version
376                .default_current_version as u8
377        );
378
379        let unserialized = DataContract::versioned_deserialize(&serialized, true, platform_version)
380            .expect("expected to deserialize data contract");
381
382        assert_eq!(data_contract, unserialized);
383    }
384
385    #[test]
386    fn test_contract_can_have_specialized_contract_encryption_decryption_keys() {
387        let data_contract =
388            get_dashpay_contract_with_generalized_encryption_key_fixture(None, 0, 1)
389                .data_contract_owned();
390        assert_eq!(
391            data_contract
392                .config()
393                .requires_identity_decryption_bounded_key(),
394            Some(StorageKeyRequirements::Unique)
395        );
396        assert_eq!(
397            data_contract
398                .config()
399                .requires_identity_encryption_bounded_key(),
400            Some(StorageKeyRequirements::Unique)
401        );
402    }
403
404    #[test]
405    fn test_contract_document_type_can_have_specialized_contract_encryption_decryption_keys() {
406        let data_contract = get_dashpay_contract_fixture(None, 0, 1).data_contract_owned();
407        assert_eq!(
408            data_contract
409                .document_type_for_name("contactRequest")
410                .expect("expected document type")
411                .requires_identity_decryption_bounded_key(),
412            Some(StorageKeyRequirements::MultipleReferenceToLatest)
413        );
414        assert_eq!(
415            data_contract
416                .document_type_for_name("contactRequest")
417                .expect("expected document type")
418                .requires_identity_encryption_bounded_key(),
419            Some(StorageKeyRequirements::MultipleReferenceToLatest)
420        );
421    }
422}