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