Skip to main content

dpp/data_contract/
mod.rs

1use crate::serialization::{
2    PlatformDeserializableWithBytesLenFromVersionedStructureTrusted,
3    PlatformDeserializableWithBytesLenFromVersionedStructureUntrusted,
4    PlatformDeserializableWithPotentialValidationFromVersionedStructureTrusted,
5    PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted,
6    PlatformLimitDeserializableFromVersionedStructureTrusted,
7    PlatformLimitDeserializableFromVersionedStructureUntrusted,
8    PlatformSerializableWithPlatformVersion,
9};
10use std::collections::BTreeMap;
11
12use derive_more::From;
13
14use bincode::config::{BigEndian, Config, Configuration, Limit, NoLimit, Varint};
15use once_cell::sync::Lazy;
16
17pub mod errors;
18pub mod extra;
19
20mod generate_data_contract;
21
22#[cfg(any(feature = "state-transitions", feature = "factories"))]
23pub mod created_data_contract;
24pub mod document_type;
25
26pub mod v0;
27pub mod v1;
28
29#[cfg(feature = "factories")]
30pub mod factory;
31#[cfg(feature = "factories")]
32pub use factory::*;
33#[cfg(any(
34    feature = "value-conversion",
35    feature = "data-contract-cbor-conversion",
36    feature = "json-conversion",
37    feature = "serde-conversion"
38))]
39pub mod conversion;
40#[cfg(feature = "client")]
41mod data_contract_facade;
42#[cfg(feature = "client")]
43pub use data_contract_facade::DataContractFacade;
44mod methods;
45pub mod serialized_version;
46pub use methods::*;
47pub mod accessors;
48pub mod associated_token;
49pub mod change_control_rules;
50pub mod config;
51pub mod group;
52pub mod storage_requirements;
53
54use crate::data_contract::serialized_version::{
55    DataContractInSerializationFormat, CONTRACT_DESERIALIZATION_LIMIT,
56};
57use crate::util::hash::hash_double_to_vec;
58
59use crate::version::{FeatureVersion, PlatformVersion};
60use crate::ProtocolError;
61use crate::ProtocolError::{PlatformDeserializationError, PlatformSerializationError};
62
63pub use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
64use crate::data_contract::group::Group;
65use crate::data_contract::v0::DataContractV0;
66use crate::data_contract::v1::DataContractV1;
67use platform_version::TryIntoPlatformVersioned;
68use platform_versioning::PlatformVersioned;
69pub use serde_json::Value as JsonValue;
70
71type JsonSchema = JsonValue;
72type DefinitionName = String;
73pub type DocumentName = String;
74pub type TokenName = String;
75pub type GroupContractPosition = u16;
76pub type TokenContractPosition = u16;
77pub type DataContractWithSerialization = (DataContract, Vec<u8>);
78type PropertyPath = String;
79
80pub const INITIAL_DATA_CONTRACT_VERSION: u32 = 1;
81
82// Define static empty BTreeMaps and Vecs
83static EMPTY_GROUPS: Lazy<BTreeMap<GroupContractPosition, Group>> = Lazy::new(BTreeMap::new);
84static EMPTY_TOKENS: Lazy<BTreeMap<TokenContractPosition, TokenConfiguration>> =
85    Lazy::new(BTreeMap::new);
86static EMPTY_KEYWORDS: Lazy<Vec<String>> = Lazy::new(Vec::new);
87
88/// Understanding Data Contract versioning
89/// Data contract versioning is both for the code structure and for serialization.
90///
91/// The code structure is what is used in code to verify documents and is used in memory
92/// There is generally only one code structure running at any given time, except in the case we
93/// are switching protocol versions.
94///
95/// There can be a lot of serialization versions that are active, and serialization versions
96/// should generally always be supported. This is because when we store something as version 1.
97/// 10 years down the line when we unserialize this contract it will still be in version 1.
98/// Deserialization of a data contract serialized in that version should be translated to the
99/// current code structure version.
100///
101/// There are some scenarios to consider,
102///
103/// One such scenario is that the serialization version does not contain enough information for the
104/// current code structure version.
105///
106/// Depending on the situation one of the following occurs:
107/// - the contract structure can imply missing parts based on default behavior
108/// - the contract structure can disable certain features dependant on missing information
109/// - the contract might be unusable until it is updated by the owner
110#[derive(Debug, Clone, PartialEq, From, PlatformVersioned)]
111pub enum DataContract {
112    V0(DataContractV0),
113    V1(DataContractV1),
114}
115
116// Note: DataContract intentionally does NOT implement JsonConvertible / ValueConvertible.
117// Round-tripping goes through the manual `Serialize` / `Deserialize` impls in
118// `data_contract/conversion/serde/mod.rs`, which thread `DataContractInSerializationFormat`
119// at the *currently active* `PlatformVersion` (see Critical-4 doc there).
120//
121// The two version-aware conversion traits are:
122//   * `DataContractJsonConversionMethodsV0::from_json(value, full_validation, pv)` —
123//     deserialize from JSON, running full schema validation when `full_validation` is
124//     `true` (use on trust boundaries). Pass `false` to reconstruct already-trusted data
125//     (e.g. storage reads) without re-validating. The canonical
126//     `serde_json::from_value::<DataContract>` path also validates (it routes through
127//     this with `full_validation = true`) — see the Critical-4 doc.
128//   * `DataContractValueConversionMethodsV0::from_value(value, full_validation, pv)` —
129//     same shape for `platform_value::Value`.
130//
131// For non-validating *serialization* to `platform_value::Value`, prefer
132// `DataContractValueConversionMethodsV0::to_value(&dc, pv)` whenever a `PlatformVersion` is
133// in hand — it selects the serialization format from the passed version. The serde-based
134// alternatives (`serde_json::to_value(&dc)?` / `platform_value::to_value(&dc)?`) select the
135// format from the *process-global* current platform version, which other threads may mutate
136// concurrently (e.g. parallel tests building platforms at older protocol versions) — only
137// use them where no explicit version is available.
138// `DataContractInSerializationFormat` (the underlying serialization shape) DOES implement the
139// canonical traits — see `data_contract/serialized_version/mod.rs`.
140
141impl PlatformSerializableWithPlatformVersion for DataContract {
142    type Error = ProtocolError;
143
144    fn serialize_to_bytes_with_platform_version(
145        &self,
146        platform_version: &PlatformVersion,
147    ) -> Result<Vec<u8>, ProtocolError> {
148        let serialization_format: DataContractInSerializationFormat =
149            self.try_into_platform_versioned(platform_version)?;
150        let config = bincode::config::standard()
151            .with_big_endian()
152            .with_no_limit();
153        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
154            PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
155        })
156    }
157
158    fn serialize_consume_to_bytes_with_platform_version(
159        self,
160        platform_version: &PlatformVersion,
161    ) -> Result<Vec<u8>, ProtocolError> {
162        let serialization_format: DataContractInSerializationFormat =
163            self.try_into_platform_versioned(platform_version)?;
164        let config = bincode::config::standard()
165            .with_big_endian()
166            .with_no_limit();
167        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
168            PlatformSerializationError(format!("unable to serialize consume DataContract: {}", e))
169        })
170    }
171}
172
173/// Decodes the stored serialization format with the ordinary decoder: bytes
174/// this node wrote itself (Drive state, wallet storage).
175fn decode_serialization_format_trusted<C: Config>(
176    data: &[u8],
177    config: C,
178    what: &str,
179) -> Result<(DataContractInSerializationFormat, usize), ProtocolError> {
180    bincode::borrow_decode_from_slice(data, config)
181        .map_err(|e| PlatformDeserializationError(format!("unable to deserialize {}: {}", what, e)))
182}
183
184/// Decodes the serialization format with the untrusted decoder: bytes from a
185/// peer, a client, a proof or a host caller.
186fn decode_serialization_format_untrusted<C: Config>(
187    data: &[u8],
188    config: C,
189    what: &str,
190) -> Result<(DataContractInSerializationFormat, usize), ProtocolError> {
191    bincode::borrow_decode_from_slice_untrusted(data, config)
192        .map_err(|e| PlatformDeserializationError(format!("unable to deserialize {}: {}", what, e)))
193}
194
195fn no_limit_config() -> Configuration<BigEndian, Varint, NoLimit> {
196    bincode::config::standard()
197        .with_big_endian()
198        .with_no_limit()
199}
200
201fn contract_limit_config() -> Configuration<BigEndian, Varint, Limit<CONTRACT_DESERIALIZATION_LIMIT>>
202{
203    bincode::config::standard()
204        .with_big_endian()
205        .with_limit::<CONTRACT_DESERIALIZATION_LIMIT>()
206}
207
208impl PlatformDeserializableWithPotentialValidationFromVersionedStructureTrusted for DataContract {
209    fn versioned_deserialize_trusted(
210        data: &[u8],
211        full_validation: bool,
212        platform_version: &PlatformVersion,
213    ) -> Result<Self, ProtocolError>
214    where
215        Self: Sized,
216    {
217        let (data_contract_in_serialization_format, _) =
218            decode_serialization_format_trusted(data, no_limit_config(), "DataContract")?;
219        DataContract::try_from_platform_versioned(
220            data_contract_in_serialization_format,
221            full_validation,
222            &mut vec![],
223            platform_version,
224        )
225    }
226}
227
228impl PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted for DataContract {
229    fn versioned_deserialize_untrusted(
230        data: &[u8],
231        full_validation: bool,
232        platform_version: &PlatformVersion,
233    ) -> Result<Self, ProtocolError>
234    where
235        Self: Sized,
236    {
237        let (data_contract_in_serialization_format, _) =
238            decode_serialization_format_untrusted(data, no_limit_config(), "DataContract")?;
239        DataContract::try_from_platform_versioned(
240            data_contract_in_serialization_format,
241            full_validation,
242            &mut vec![],
243            platform_version,
244        )
245    }
246}
247
248impl PlatformDeserializableWithBytesLenFromVersionedStructureTrusted for DataContract {
249    fn versioned_deserialize_with_bytes_len_trusted(
250        data: &[u8],
251        full_validation: bool,
252        platform_version: &PlatformVersion,
253    ) -> Result<(Self, usize), ProtocolError>
254    where
255        Self: Sized,
256    {
257        let (data_contract_in_serialization_format, len) =
258            decode_serialization_format_trusted(data, no_limit_config(), "DataContract")?;
259        Ok((
260            DataContract::try_from_platform_versioned(
261                data_contract_in_serialization_format,
262                full_validation,
263                &mut vec![],
264                platform_version,
265            )?,
266            len,
267        ))
268    }
269}
270
271impl PlatformDeserializableWithBytesLenFromVersionedStructureUntrusted for DataContract {
272    fn versioned_deserialize_with_bytes_len_untrusted(
273        data: &[u8],
274        full_validation: bool,
275        platform_version: &PlatformVersion,
276    ) -> Result<(Self, usize), ProtocolError>
277    where
278        Self: Sized,
279    {
280        let (data_contract_in_serialization_format, len) =
281            decode_serialization_format_untrusted(data, no_limit_config(), "DataContract")?;
282        Ok((
283            DataContract::try_from_platform_versioned(
284                data_contract_in_serialization_format,
285                full_validation,
286                &mut vec![],
287                platform_version,
288            )?,
289            len,
290        ))
291    }
292}
293
294impl PlatformLimitDeserializableFromVersionedStructureTrusted for DataContract {
295    fn versioned_limit_deserialize_trusted(
296        data: &[u8],
297        platform_version: &PlatformVersion,
298    ) -> Result<Self, ProtocolError>
299    where
300        Self: Sized,
301    {
302        let (data_contract_in_serialization_format, _) = decode_serialization_format_trusted(
303            data,
304            contract_limit_config(),
305            "DataContract with limit",
306        )?;
307        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
308        DataContract::try_from_platform_versioned(
309            data_contract_in_serialization_format,
310            true,
311            &mut vec![],
312            platform_version,
313        )
314    }
315}
316
317impl PlatformLimitDeserializableFromVersionedStructureUntrusted for DataContract {
318    fn versioned_limit_deserialize_untrusted(
319        data: &[u8],
320        platform_version: &PlatformVersion,
321    ) -> Result<Self, ProtocolError>
322    where
323        Self: Sized,
324    {
325        let (data_contract_in_serialization_format, _) = decode_serialization_format_untrusted(
326            data,
327            contract_limit_config(),
328            "DataContract with limit",
329        )?;
330        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
331        DataContract::try_from_platform_versioned(
332            data_contract_in_serialization_format,
333            true,
334            &mut vec![],
335            platform_version,
336        )
337    }
338}
339
340impl DataContract {
341    pub fn as_v0(&self) -> Option<&DataContractV0> {
342        match self {
343            DataContract::V0(v0) => Some(v0),
344            _ => None,
345        }
346    }
347
348    pub fn as_v0_mut(&mut self) -> Option<&mut DataContractV0> {
349        match self {
350            DataContract::V0(v0) => Some(v0),
351            _ => None,
352        }
353    }
354
355    pub fn into_v0(self) -> Option<DataContractV0> {
356        match self {
357            DataContract::V0(v0) => Some(v0),
358            _ => None,
359        }
360    }
361
362    pub fn as_v1(&self) -> Option<&DataContractV1> {
363        match self {
364            DataContract::V1(v1) => Some(v1),
365            _ => None,
366        }
367    }
368
369    pub fn as_v1_mut(&mut self) -> Option<&mut DataContractV1> {
370        match self {
371            DataContract::V1(v1) => Some(v1),
372            _ => None,
373        }
374    }
375
376    pub fn into_v1(self) -> Option<DataContractV1> {
377        match self {
378            DataContract::V1(v1) => Some(v1),
379            _ => None,
380        }
381    }
382
383    /// This should only ever be used in tests, as it will change
384    #[cfg(test)]
385    pub fn into_latest(self) -> Option<DataContractV1> {
386        self.into_v1()
387    }
388
389    /// This should only ever be used in tests, as it will change
390    #[cfg(test)]
391    pub fn as_latest(&self) -> Option<&DataContractV1> {
392        match self {
393            DataContract::V1(v1) => Some(v1),
394            _ => None,
395        }
396    }
397
398    /// This should only ever be used in tests, as it will change
399    #[cfg(test)]
400    pub fn as_latest_mut(&mut self) -> Option<&mut DataContractV1> {
401        match self {
402            DataContract::V1(v1) => Some(v1),
403            _ => None,
404        }
405    }
406
407    pub fn check_version_is_active(
408        protocol_version: u32,
409        data_contract_system_version: FeatureVersion,
410    ) -> Result<bool, ProtocolError> {
411        let platform_version = PlatformVersion::get(protocol_version)?;
412        Ok(platform_version
413            .dpp
414            .contract_versions
415            .contract_structure_version
416            == data_contract_system_version)
417    }
418
419    pub fn hash(&self, platform_version: &PlatformVersion) -> Result<Vec<u8>, ProtocolError> {
420        Ok(hash_double_to_vec(
421            self.serialize_to_bytes_with_platform_version(platform_version)?,
422        ))
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    use crate::data_contract::accessors::v0::DataContractV0Getters;
429    use crate::data_contract::config::v0::DataContractConfigGettersV0;
430    use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
431    use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
432    use crate::data_contract::DataContract;
433    use crate::serialization::{
434        PlatformDeserializableWithBytesLenFromVersionedStructureTrusted,
435        PlatformDeserializableWithBytesLenFromVersionedStructureUntrusted,
436        PlatformDeserializableWithPotentialValidationFromVersionedStructureTrusted,
437        PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted,
438        PlatformLimitDeserializableFromVersionedStructureTrusted,
439        PlatformLimitDeserializableFromVersionedStructureUntrusted,
440        PlatformSerializableWithPlatformVersion,
441    };
442    use crate::system_data_contracts::load_system_data_contract;
443    use crate::tests::fixtures::{
444        get_dashpay_contract_fixture, get_dashpay_contract_with_generalized_encryption_key_fixture,
445    };
446    use crate::version::PlatformVersion;
447    use crate::ProtocolError;
448    use data_contracts::SystemDataContract::Dashpay;
449
450    #[test]
451    fn test_contract_serialization() {
452        let platform_version = PlatformVersion::latest();
453        let data_contract = load_system_data_contract(Dashpay, platform_version)
454            .expect("expected dashpay contract");
455        let serialized = data_contract
456            .serialize_to_bytes_with_platform_version(platform_version)
457            .expect("expected to serialize data contract");
458        assert_eq!(
459            serialized[0],
460            platform_version
461                .dpp
462                .contract_versions
463                .contract_serialization_version
464                .default_current_version as u8
465        );
466
467        let unserialized =
468            DataContract::versioned_deserialize_untrusted(&serialized, true, platform_version)
469                .expect("expected to deserialize data contract");
470
471        assert_eq!(data_contract, unserialized);
472    }
473
474    /// The trusted twins run the ordinary decoder over the same serialization
475    /// format, so on well-formed bytes they must agree with the untrusted
476    /// entry points exactly, consumed length included.
477    #[test]
478    fn trusted_and_untrusted_versioned_deserialize_agree() {
479        let platform_version = PlatformVersion::latest();
480        let data_contract = load_system_data_contract(Dashpay, platform_version)
481            .expect("expected dashpay contract");
482        let serialized = data_contract
483            .serialize_to_bytes_with_platform_version(platform_version)
484            .expect("expected to serialize data contract");
485
486        let trusted =
487            DataContract::versioned_deserialize_trusted(&serialized, true, platform_version)
488                .expect("trusted deserialize");
489        let untrusted =
490            DataContract::versioned_deserialize_untrusted(&serialized, true, platform_version)
491                .expect("untrusted deserialize");
492        assert_eq!(trusted, untrusted);
493        assert_eq!(trusted, data_contract);
494
495        let (trusted, trusted_len) = DataContract::versioned_deserialize_with_bytes_len_trusted(
496            &serialized,
497            true,
498            platform_version,
499        )
500        .expect("trusted deserialize with bytes len");
501        let (untrusted, untrusted_len) =
502            DataContract::versioned_deserialize_with_bytes_len_untrusted(
503                &serialized,
504                true,
505                platform_version,
506            )
507            .expect("untrusted deserialize with bytes len");
508        assert_eq!(trusted, untrusted);
509        assert_eq!(trusted_len, untrusted_len);
510        assert_eq!(trusted_len, serialized.len());
511
512        let trusted =
513            DataContract::versioned_limit_deserialize_trusted(&serialized, platform_version)
514                .expect("trusted limit deserialize");
515        let untrusted =
516            DataContract::versioned_limit_deserialize_untrusted(&serialized, platform_version)
517                .expect("untrusted limit deserialize");
518        assert_eq!(trusted, untrusted);
519        assert_eq!(trusted, data_contract);
520    }
521
522    #[test]
523    fn trusted_versioned_deserialize_rejects_malformed_input() {
524        let platform_version = PlatformVersion::latest();
525        for input in [vec![0xFFu8; 16], vec![]] {
526            assert!(matches!(
527                DataContract::versioned_deserialize_trusted(&input, true, platform_version),
528                Err(ProtocolError::PlatformDeserializationError(_))
529            ));
530            assert!(matches!(
531                DataContract::versioned_deserialize_with_bytes_len_trusted(
532                    &input,
533                    true,
534                    platform_version
535                ),
536                Err(ProtocolError::PlatformDeserializationError(_))
537            ));
538            assert!(matches!(
539                DataContract::versioned_limit_deserialize_trusted(&input, platform_version),
540                Err(ProtocolError::PlatformDeserializationError(_))
541            ));
542        }
543    }
544
545    #[test]
546    fn test_contract_can_have_specialized_contract_encryption_decryption_keys() {
547        let data_contract =
548            get_dashpay_contract_with_generalized_encryption_key_fixture(None, 0, 1)
549                .data_contract_owned();
550        assert_eq!(
551            data_contract
552                .config()
553                .requires_identity_decryption_bounded_key(),
554            Some(StorageKeyRequirements::Unique)
555        );
556        assert_eq!(
557            data_contract
558                .config()
559                .requires_identity_encryption_bounded_key(),
560            Some(StorageKeyRequirements::Unique)
561        );
562    }
563
564    #[test]
565    fn test_contract_document_type_can_have_specialized_contract_encryption_decryption_keys() {
566        let data_contract = get_dashpay_contract_fixture(None, 0, 1).data_contract_owned();
567        assert_eq!(
568            data_contract
569                .document_type_for_name("contactRequest")
570                .expect("expected document type")
571                .requires_identity_decryption_bounded_key(),
572            Some(StorageKeyRequirements::MultipleReferenceToLatest)
573        );
574        assert_eq!(
575            data_contract
576                .document_type_for_name("contactRequest")
577                .expect("expected document type")
578                .requires_identity_encryption_bounded_key(),
579            Some(StorageKeyRequirements::MultipleReferenceToLatest)
580        );
581    }
582}