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