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 bincode::enc::write::SizeWriter;
16use once_cell::sync::Lazy;
17
18pub mod errors;
19pub mod extra;
20
21mod generate_data_contract;
22
23#[cfg(any(feature = "state-transitions", feature = "factories"))]
24pub mod created_data_contract;
25pub mod document_type;
26
27pub mod v0;
28pub mod v1;
29
30#[cfg(feature = "factories")]
31pub mod factory;
32#[cfg(feature = "factories")]
33pub use factory::*;
34#[cfg(any(
35    feature = "value-conversion",
36    feature = "data-contract-cbor-conversion",
37    feature = "json-conversion",
38    feature = "serde-conversion"
39))]
40pub mod conversion;
41#[cfg(feature = "client")]
42mod data_contract_facade;
43#[cfg(feature = "client")]
44pub use data_contract_facade::DataContractFacade;
45mod methods;
46pub mod serialized_version;
47pub use methods::*;
48pub mod accessors;
49pub mod associated_token;
50pub mod change_control_rules;
51pub mod config;
52pub mod group;
53pub mod storage_requirements;
54
55use crate::data_contract::serialized_version::{
56    DataContractInSerializationFormat, CONTRACT_DESERIALIZATION_LIMIT,
57};
58use crate::util::hash::hash_double_to_vec;
59
60#[cfg(feature = "validation")]
61use crate::consensus::basic::data_contract::DataContractCompiledMaxSizeExceededError;
62#[cfg(feature = "validation")]
63use crate::validation::operations::ProtocolValidationOperation;
64#[cfg(feature = "validation")]
65use crate::validation::SimpleConsensusValidationResult;
66use crate::version::{FeatureVersion, PlatformVersion};
67use crate::ProtocolError;
68use crate::ProtocolError::{PlatformDeserializationError, PlatformSerializationError};
69
70use crate::data_contract::accessors::v0::DataContractV0Getters;
71pub use crate::data_contract::associated_token::token_configuration::TokenConfiguration;
72use crate::data_contract::document_type::accessors::DocumentTypeV2Getters;
73use crate::data_contract::group::Group;
74use crate::data_contract::v0::DataContractV0;
75use crate::data_contract::v1::DataContractV1;
76use platform_serialization::PLATFORM_BINCODE_CONFIG;
77use platform_version::TryIntoPlatformVersioned;
78use platform_versioning::PlatformVersioned;
79pub use serde_json::Value as JsonValue;
80
81type JsonSchema = JsonValue;
82type DefinitionName = String;
83pub type DocumentName = String;
84pub type TokenName = String;
85pub type GroupContractPosition = u16;
86pub type TokenContractPosition = u16;
87pub type DataContractWithSerialization = (DataContract, Vec<u8>);
88type PropertyPath = String;
89
90pub const INITIAL_DATA_CONTRACT_VERSION: u32 = 1;
91
92// Define static empty BTreeMaps and Vecs
93static EMPTY_GROUPS: Lazy<BTreeMap<GroupContractPosition, Group>> = Lazy::new(BTreeMap::new);
94static EMPTY_TOKENS: Lazy<BTreeMap<TokenContractPosition, TokenConfiguration>> =
95    Lazy::new(BTreeMap::new);
96static EMPTY_KEYWORDS: Lazy<Vec<String>> = Lazy::new(Vec::new);
97
98/// Understanding Data Contract versioning
99/// Data contract versioning is both for the code structure and for serialization.
100///
101/// The code structure is what is used in code to verify documents and is used in memory
102/// There is generally only one code structure running at any given time, except in the case we
103/// are switching protocol versions.
104///
105/// There can be a lot of serialization versions that are active, and serialization versions
106/// should generally always be supported. This is because when we store something as version 1.
107/// 10 years down the line when we unserialize this contract it will still be in version 1.
108/// Deserialization of a data contract serialized in that version should be translated to the
109/// current code structure version.
110///
111/// There are some scenarios to consider,
112///
113/// One such scenario is that the serialization version does not contain enough information for the
114/// current code structure version.
115///
116/// Depending on the situation one of the following occurs:
117/// - the contract structure can imply missing parts based on default behavior
118/// - the contract structure can disable certain features dependant on missing information
119/// - the contract might be unusable until it is updated by the owner
120#[derive(Debug, Clone, PartialEq, From, PlatformVersioned)]
121pub enum DataContract {
122    V0(DataContractV0),
123    V1(DataContractV1),
124}
125
126// Note: DataContract intentionally does NOT implement JsonConvertible / ValueConvertible.
127// Round-tripping goes through the manual `Serialize` / `Deserialize` impls in
128// `data_contract/conversion/serde/mod.rs`, which thread `DataContractInSerializationFormat`
129// at the *currently active* `PlatformVersion` (see Critical-4 doc there).
130//
131// The two version-aware conversion traits are:
132//   * `DataContractJsonConversionMethodsV0::from_json(value, full_validation, pv)` —
133//     deserialize from JSON, running full schema validation when `full_validation` is
134//     `true` (use on trust boundaries). Pass `false` to reconstruct already-trusted data
135//     (e.g. storage reads) without re-validating. The canonical
136//     `serde_json::from_value::<DataContract>` path also validates (it routes through
137//     this with `full_validation = true`) — see the Critical-4 doc.
138//   * `DataContractValueConversionMethodsV0::from_value(value, full_validation, pv)` —
139//     same shape for `platform_value::Value`.
140//
141// For non-validating *serialization* to `platform_value::Value`, prefer
142// `DataContractValueConversionMethodsV0::to_value(&dc, pv)` whenever a `PlatformVersion` is
143// in hand — it selects the serialization format from the passed version. The serde-based
144// alternatives (`serde_json::to_value(&dc)?` / `platform_value::to_value(&dc)?`) select the
145// format from the *process-global* current platform version, which other threads may mutate
146// concurrently (e.g. parallel tests building platforms at older protocol versions) — only
147// use them where no explicit version is available.
148// `DataContractInSerializationFormat` (the underlying serialization shape) DOES implement the
149// canonical traits — see `data_contract/serialized_version/mod.rs`.
150
151impl PlatformSerializableWithPlatformVersion for DataContract {
152    type Error = ProtocolError;
153
154    fn serialize_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 = PLATFORM_BINCODE_CONFIG;
161        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
162            PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
163        })
164    }
165
166    fn serialize_consume_to_bytes_with_platform_version(
167        self,
168        platform_version: &PlatformVersion,
169    ) -> Result<Vec<u8>, ProtocolError> {
170        let serialization_format: DataContractInSerializationFormat =
171            self.try_into_platform_versioned(platform_version)?;
172        let config = PLATFORM_BINCODE_CONFIG;
173        bincode::encode_to_vec(serialization_format, config).map_err(|e| {
174            PlatformSerializationError(format!("unable to serialize consume DataContract: {}", e))
175        })
176    }
177}
178
179impl DataContract {
180    /// The contract's bytes as a node stores them at `platform_version`
181    /// (`contract_storage_serialization_version`): through protocol version 14 the bytes
182    /// [`PlatformSerializableWithPlatformVersion::serialize_to_bytes_with_platform_version`]
183    /// gives, or format version 2, the compiled form, which only state holds. Every other
184    /// serialization, of a state transition, a query response or a client, keeps
185    /// `contract_serialization_version`.
186    pub fn serialize_to_storage_bytes(
187        &self,
188        platform_version: &PlatformVersion,
189    ) -> Result<Vec<u8>, ProtocolError> {
190        let serialization_format =
191            DataContractInSerializationFormat::try_from_data_contract_for_storage(
192                self,
193                platform_version,
194            )?;
195        bincode::encode_to_vec(serialization_format, no_limit_config()).map_err(|e| {
196            PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
197        })
198    }
199
200    /// Refuses the contract when a node would store it compiled
201    /// (`contract_storage_serialization_version` 2, from protocol version 15) and its compiled
202    /// bytes are over `max_compiled_contract_size`, with a
203    /// [`DataContractCompiledMaxSizeExceededError`]: compiling turns a contract into more bytes
204    /// than its schemas take, and only the format transitions carry is capped by
205    /// `max_serialized_size`. The compile and serialization it takes is recorded in
206    /// `validation_operations`, charged per compiled byte. Refuses, compiles and records
207    /// nothing where contracts are not stored compiled.
208    #[cfg(feature = "validation")]
209    pub fn validate_compiled_size(
210        &self,
211        validation_operations: &mut impl Extend<ProtocolValidationOperation>,
212        platform_version: &PlatformVersion,
213    ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
214        let stored_compiled = platform_version
215            .dpp
216            .contract_versions
217            .contract_storage_serialization_version
218            >= 2;
219        let Some(max_compiled_size) = platform_version.system_limits.max_compiled_contract_size
220        else {
221            return Ok(SimpleConsensusValidationResult::new());
222        };
223        if !stored_compiled {
224            return Ok(SimpleConsensusValidationResult::new());
225        }
226        let compiled_size = self.serialize_to_storage_bytes(platform_version)?.len() as u64;
227        validation_operations.extend(std::iter::once(
228            ProtocolValidationOperation::ContractCompiledSizeValidation(compiled_size),
229        ));
230        if compiled_size > u64::from(max_compiled_size) {
231            return Ok(SimpleConsensusValidationResult::new_with_error(
232                DataContractCompiledMaxSizeExceededError::new(compiled_size, max_compiled_size)
233                    .into(),
234            ));
235        }
236        Ok(SimpleConsensusValidationResult::new())
237    }
238
239    /// [`Self::validate_compiled_size`] as an error: a contract over the cap refused with its
240    /// [`DataContractCompiledMaxSizeExceededError`] as a `ProtocolError::ConsensusError`, as a
241    /// contract parse refuses one, for the callers that turn parse errors into the
242    /// transition's refusal.
243    #[cfg(feature = "validation")]
244    pub fn ensure_compiled_size(
245        &self,
246        validation_operations: &mut impl Extend<ProtocolValidationOperation>,
247        platform_version: &PlatformVersion,
248    ) -> Result<(), ProtocolError> {
249        match self
250            .validate_compiled_size(validation_operations, platform_version)?
251            .errors
252            .into_iter()
253            .next()
254        {
255            Some(error) => Err(ProtocolError::ConsensusError(Box::new(error))),
256            None => Ok(()),
257        }
258    }
259
260    /// [`Self::serialize_to_storage_bytes`], with the length of the contract's bytes in the
261    /// format state transitions carry (`contract_serialization_version`), the bytes the
262    /// protocol caps. Stored compiled, a contract holds those bytes' fields as its base, so
263    /// they are measured rather than serialized again; otherwise it is stored in that format.
264    pub fn serialize_to_storage_bytes_with_carried_size(
265        &self,
266        platform_version: &PlatformVersion,
267    ) -> Result<(Vec<u8>, usize), ProtocolError> {
268        let serialization_format =
269            DataContractInSerializationFormat::try_from_data_contract_for_storage(
270                self,
271                platform_version,
272            )?;
273        let carried_size = match &serialization_format {
274            // Format version 1 bytes are the format version, one byte, then the base's fields
275            DataContractInSerializationFormat::V2(compiled) => {
276                let mut size = SizeWriter::default();
277                bincode::encode_into_writer(&compiled.base, &mut size, no_limit_config()).map_err(
278                    |e| {
279                        PlatformSerializationError(format!("unable to measure DataContract: {}", e))
280                    },
281                )?;
282                Some(1 + size.bytes_written)
283            }
284            _ => None,
285        };
286        let bytes =
287            bincode::encode_to_vec(serialization_format, no_limit_config()).map_err(|e| {
288                PlatformSerializationError(format!("unable to serialize DataContract: {}", e))
289            })?;
290        let carried_size = carried_size.unwrap_or(bytes.len());
291        Ok((bytes, carried_size))
292    }
293}
294
295/// Decodes the stored serialization format with the ordinary decoder: bytes
296/// this node wrote itself (Drive state, wallet storage). A contract in the stored
297/// format is read back from its compiled document types.
298fn decode_serialization_format_trusted<C: Config>(
299    data: &[u8],
300    config: C,
301    what: &str,
302) -> Result<(DataContractInSerializationFormat, usize), ProtocolError> {
303    bincode::borrow_decode_from_slice(data, config)
304        .map_err(|e| PlatformDeserializationError(format!("unable to deserialize {}: {}", what, e)))
305}
306
307/// Decodes the serialization format with the untrusted decoder: bytes from a
308/// peer, a client, a proof or a host caller. The untrusted entry points never take a
309/// contract in the stored format at its compiled types, which nothing vouches for
310/// ([`untrusted_form`]). Proof verification, whose bytes the proof authenticates, reads
311/// them with `DataContract::try_from_platform_versioned`.
312fn decode_serialization_format_untrusted<C: Config>(
313    data: &[u8],
314    config: C,
315    what: &str,
316) -> Result<(DataContractInSerializationFormat, usize), ProtocolError> {
317    bincode::borrow_decode_from_slice_untrusted(data, config)
318        .map_err(|e| PlatformDeserializationError(format!("unable to deserialize {}: {}", what, e)))
319}
320
321/// The format an untrusted entry point reads `format` as: a contract in the stored format
322/// from its schemas ([`DataContractInSerializationFormat::into_schema_form`]), or, fully
323/// validated, from its schemas with its compiled types checked against what they compile to.
324fn untrusted_form(
325    format: DataContractInSerializationFormat,
326    full_validation: bool,
327) -> DataContractInSerializationFormat {
328    if full_validation {
329        format
330    } else {
331        format.into_schema_form()
332    }
333}
334
335fn no_limit_config() -> Configuration<BigEndian, Varint, NoLimit> {
336    PLATFORM_BINCODE_CONFIG
337}
338
339fn contract_limit_config() -> Configuration<BigEndian, Varint, Limit<CONTRACT_DESERIALIZATION_LIMIT>>
340{
341    PLATFORM_BINCODE_CONFIG.with_limit::<CONTRACT_DESERIALIZATION_LIMIT>()
342}
343
344impl PlatformDeserializableWithPotentialValidationFromVersionedStructureTrusted for DataContract {
345    fn versioned_deserialize_trusted(
346        data: &[u8],
347        full_validation: bool,
348        platform_version: &PlatformVersion,
349    ) -> Result<Self, ProtocolError>
350    where
351        Self: Sized,
352    {
353        let (data_contract_in_serialization_format, _) =
354            decode_serialization_format_trusted(data, no_limit_config(), "DataContract")?;
355        DataContract::try_from_platform_versioned(
356            data_contract_in_serialization_format,
357            full_validation,
358            &mut vec![],
359            platform_version,
360        )
361    }
362}
363
364impl PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted for DataContract {
365    fn versioned_deserialize_untrusted(
366        data: &[u8],
367        full_validation: bool,
368        platform_version: &PlatformVersion,
369    ) -> Result<Self, ProtocolError>
370    where
371        Self: Sized,
372    {
373        let (data_contract_in_serialization_format, _) =
374            decode_serialization_format_untrusted(data, no_limit_config(), "DataContract")?;
375        DataContract::try_from_platform_versioned(
376            untrusted_form(data_contract_in_serialization_format, full_validation),
377            full_validation,
378            &mut vec![],
379            platform_version,
380        )
381    }
382}
383
384impl PlatformDeserializableWithBytesLenFromVersionedStructureTrusted for DataContract {
385    fn versioned_deserialize_with_bytes_len_trusted(
386        data: &[u8],
387        full_validation: bool,
388        platform_version: &PlatformVersion,
389    ) -> Result<(Self, usize), ProtocolError>
390    where
391        Self: Sized,
392    {
393        let (data_contract_in_serialization_format, len) =
394            decode_serialization_format_trusted(data, no_limit_config(), "DataContract")?;
395        Ok((
396            DataContract::try_from_platform_versioned(
397                data_contract_in_serialization_format,
398                full_validation,
399                &mut vec![],
400                platform_version,
401            )?,
402            len,
403        ))
404    }
405}
406
407impl PlatformDeserializableWithBytesLenFromVersionedStructureUntrusted for DataContract {
408    fn versioned_deserialize_with_bytes_len_untrusted(
409        data: &[u8],
410        full_validation: bool,
411        platform_version: &PlatformVersion,
412    ) -> Result<(Self, usize), ProtocolError>
413    where
414        Self: Sized,
415    {
416        let (data_contract_in_serialization_format, len) =
417            decode_serialization_format_untrusted(data, no_limit_config(), "DataContract")?;
418        Ok((
419            DataContract::try_from_platform_versioned(
420                untrusted_form(data_contract_in_serialization_format, full_validation),
421                full_validation,
422                &mut vec![],
423                platform_version,
424            )?,
425            len,
426        ))
427    }
428}
429
430impl PlatformLimitDeserializableFromVersionedStructureTrusted for DataContract {
431    fn versioned_limit_deserialize_trusted(
432        data: &[u8],
433        platform_version: &PlatformVersion,
434    ) -> Result<Self, ProtocolError>
435    where
436        Self: Sized,
437    {
438        let (data_contract_in_serialization_format, _) = decode_serialization_format_trusted(
439            data,
440            contract_limit_config(),
441            "DataContract with limit",
442        )?;
443        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
444        DataContract::try_from_platform_versioned(
445            data_contract_in_serialization_format,
446            true,
447            &mut vec![],
448            platform_version,
449        )
450    }
451}
452
453impl PlatformLimitDeserializableFromVersionedStructureUntrusted for DataContract {
454    fn versioned_limit_deserialize_untrusted(
455        data: &[u8],
456        platform_version: &PlatformVersion,
457    ) -> Result<Self, ProtocolError>
458    where
459        Self: Sized,
460    {
461        let (data_contract_in_serialization_format, _) = decode_serialization_format_untrusted(
462            data,
463            contract_limit_config(),
464            "DataContract with limit",
465        )?;
466        // we always want to validate when we have a limit, because limit means the data isn't coming from Drive
467        DataContract::try_from_platform_versioned(
468            data_contract_in_serialization_format,
469            true,
470            &mut vec![],
471            platform_version,
472        )
473    }
474}
475
476impl DataContract {
477    /// Whether the contract keeps the actions its seated moderation team votes on (protocol
478    /// version 14): one of its document types lets the team delete its settled documents
479    /// (`moderatorAbilities.deleteSettled`). Such a contract gets the team actions tree with its
480    /// creation, and no update adds such a type, so this is also whether the tree exists.
481    pub fn keeps_team_actions(&self) -> bool {
482        self.document_types()
483            .values()
484            .any(|document_type| document_type.moderator_settled_deletion().is_some())
485    }
486
487    pub fn as_v0(&self) -> Option<&DataContractV0> {
488        match self {
489            DataContract::V0(v0) => Some(v0),
490            _ => None,
491        }
492    }
493
494    pub fn as_v0_mut(&mut self) -> Option<&mut DataContractV0> {
495        match self {
496            DataContract::V0(v0) => Some(v0),
497            _ => None,
498        }
499    }
500
501    pub fn into_v0(self) -> Option<DataContractV0> {
502        match self {
503            DataContract::V0(v0) => Some(v0),
504            _ => None,
505        }
506    }
507
508    pub fn as_v1(&self) -> Option<&DataContractV1> {
509        match self {
510            DataContract::V1(v1) => Some(v1),
511            _ => None,
512        }
513    }
514
515    pub fn as_v1_mut(&mut self) -> Option<&mut DataContractV1> {
516        match self {
517            DataContract::V1(v1) => Some(v1),
518            _ => None,
519        }
520    }
521
522    pub fn into_v1(self) -> Option<DataContractV1> {
523        match self {
524            DataContract::V1(v1) => Some(v1),
525            _ => None,
526        }
527    }
528
529    /// This should only ever be used in tests, as it will change
530    #[cfg(test)]
531    pub fn into_latest(self) -> Option<DataContractV1> {
532        self.into_v1()
533    }
534
535    /// This should only ever be used in tests, as it will change
536    #[cfg(test)]
537    pub fn as_latest(&self) -> Option<&DataContractV1> {
538        match self {
539            DataContract::V1(v1) => Some(v1),
540            _ => None,
541        }
542    }
543
544    /// This should only ever be used in tests, as it will change
545    #[cfg(test)]
546    pub fn as_latest_mut(&mut self) -> Option<&mut DataContractV1> {
547        match self {
548            DataContract::V1(v1) => Some(v1),
549            _ => None,
550        }
551    }
552
553    pub fn check_version_is_active(
554        protocol_version: u32,
555        data_contract_system_version: FeatureVersion,
556    ) -> Result<bool, ProtocolError> {
557        let platform_version = PlatformVersion::get(protocol_version)?;
558        Ok(platform_version
559            .dpp
560            .contract_versions
561            .contract_structure_version
562            == data_contract_system_version)
563    }
564
565    pub fn hash(&self, platform_version: &PlatformVersion) -> Result<Vec<u8>, ProtocolError> {
566        Ok(hash_double_to_vec(
567            self.serialize_to_bytes_with_platform_version(platform_version)?,
568        ))
569    }
570}
571
572#[cfg(test)]
573mod tests {
574    use crate::data_contract::accessors::v0::DataContractV0Getters;
575    use crate::data_contract::config::v0::DataContractConfigGettersV0;
576    use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
577    use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
578    use crate::data_contract::DataContract;
579    use crate::serialization::{
580        PlatformDeserializableWithBytesLenFromVersionedStructureTrusted,
581        PlatformDeserializableWithBytesLenFromVersionedStructureUntrusted,
582        PlatformDeserializableWithPotentialValidationFromVersionedStructureTrusted,
583        PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted,
584        PlatformLimitDeserializableFromVersionedStructureTrusted,
585        PlatformLimitDeserializableFromVersionedStructureUntrusted,
586        PlatformSerializableWithPlatformVersion,
587    };
588    use crate::system_data_contracts::load_system_data_contract;
589    use crate::tests::fixtures::{
590        get_dashpay_contract_fixture, get_dashpay_contract_with_generalized_encryption_key_fixture,
591    };
592    use crate::version::PlatformVersion;
593    use crate::ProtocolError;
594    use data_contracts::SystemDataContract::Dashpay;
595
596    #[test]
597    fn test_contract_serialization() {
598        let platform_version = PlatformVersion::latest();
599        let data_contract = load_system_data_contract(Dashpay, platform_version)
600            .expect("expected dashpay contract");
601        let serialized = data_contract
602            .serialize_to_bytes_with_platform_version(platform_version)
603            .expect("expected to serialize data contract");
604        assert_eq!(
605            serialized[0],
606            platform_version
607                .dpp
608                .contract_versions
609                .contract_serialization_version
610                .default_current_version as u8
611        );
612
613        let unserialized =
614            DataContract::versioned_deserialize_untrusted(&serialized, true, platform_version)
615                .expect("expected to deserialize data contract");
616
617        assert_eq!(data_contract, unserialized);
618    }
619
620    /// The trusted twins run the ordinary decoder over the same serialization
621    /// format, so on well-formed bytes they must agree with the untrusted
622    /// entry points exactly, consumed length included.
623    #[test]
624    fn trusted_and_untrusted_versioned_deserialize_agree() {
625        let platform_version = PlatformVersion::latest();
626        let data_contract = load_system_data_contract(Dashpay, platform_version)
627            .expect("expected dashpay contract");
628        let serialized = data_contract
629            .serialize_to_bytes_with_platform_version(platform_version)
630            .expect("expected to serialize data contract");
631
632        let trusted =
633            DataContract::versioned_deserialize_trusted(&serialized, true, platform_version)
634                .expect("trusted deserialize");
635        let untrusted =
636            DataContract::versioned_deserialize_untrusted(&serialized, true, platform_version)
637                .expect("untrusted deserialize");
638        assert_eq!(trusted, untrusted);
639        assert_eq!(trusted, data_contract);
640
641        let (trusted, trusted_len) = DataContract::versioned_deserialize_with_bytes_len_trusted(
642            &serialized,
643            true,
644            platform_version,
645        )
646        .expect("trusted deserialize with bytes len");
647        let (untrusted, untrusted_len) =
648            DataContract::versioned_deserialize_with_bytes_len_untrusted(
649                &serialized,
650                true,
651                platform_version,
652            )
653            .expect("untrusted deserialize with bytes len");
654        assert_eq!(trusted, untrusted);
655        assert_eq!(trusted_len, untrusted_len);
656        assert_eq!(trusted_len, serialized.len());
657
658        let trusted =
659            DataContract::versioned_limit_deserialize_trusted(&serialized, platform_version)
660                .expect("trusted limit deserialize");
661        let untrusted =
662            DataContract::versioned_limit_deserialize_untrusted(&serialized, platform_version)
663                .expect("untrusted limit deserialize");
664        assert_eq!(trusted, untrusted);
665        assert_eq!(trusted, data_contract);
666    }
667
668    #[test]
669    fn trusted_versioned_deserialize_rejects_malformed_input() {
670        let platform_version = PlatformVersion::latest();
671        for input in [vec![0xFFu8; 16], vec![]] {
672            assert!(matches!(
673                DataContract::versioned_deserialize_trusted(&input, true, platform_version),
674                Err(ProtocolError::PlatformDeserializationError(_))
675            ));
676            assert!(matches!(
677                DataContract::versioned_deserialize_with_bytes_len_trusted(
678                    &input,
679                    true,
680                    platform_version
681                ),
682                Err(ProtocolError::PlatformDeserializationError(_))
683            ));
684            assert!(matches!(
685                DataContract::versioned_limit_deserialize_trusted(&input, platform_version),
686                Err(ProtocolError::PlatformDeserializationError(_))
687            ));
688        }
689    }
690
691    #[test]
692    fn test_contract_can_have_specialized_contract_encryption_decryption_keys() {
693        let data_contract =
694            get_dashpay_contract_with_generalized_encryption_key_fixture(None, 0, 1)
695                .data_contract_owned();
696        assert_eq!(
697            data_contract
698                .config()
699                .requires_identity_decryption_bounded_key(),
700            Some(StorageKeyRequirements::Unique)
701        );
702        assert_eq!(
703            data_contract
704                .config()
705                .requires_identity_encryption_bounded_key(),
706            Some(StorageKeyRequirements::Unique)
707        );
708    }
709
710    #[test]
711    fn test_contract_document_type_can_have_specialized_contract_encryption_decryption_keys() {
712        let data_contract = get_dashpay_contract_fixture(None, 0, 1).data_contract_owned();
713        assert_eq!(
714            data_contract
715                .document_type_for_name("contactRequest")
716                .expect("expected document type")
717                .requires_identity_decryption_bounded_key(),
718            Some(StorageKeyRequirements::MultipleReferenceToLatest)
719        );
720        assert_eq!(
721            data_contract
722                .document_type_for_name("contactRequest")
723                .expect("expected document type")
724                .requires_identity_encryption_bounded_key(),
725            Some(StorageKeyRequirements::MultipleReferenceToLatest)
726        );
727    }
728}
729
730#[cfg(all(test, feature = "validation"))]
731mod compiled_size_tests {
732    use super::*;
733    use crate::consensus::basic::BasicError;
734    use crate::consensus::ConsensusError;
735    use crate::tests::fixtures::get_data_contract_fixture;
736
737    fn fixture_contract() -> DataContract {
738        get_data_contract_fixture(None, 0, PlatformVersion::latest().protocol_version)
739            .data_contract_owned()
740    }
741
742    /// `platform_version` with a compiled size cap every contract is over.
743    fn with_a_tiny_compiled_size_cap(platform_version: &PlatformVersion) -> PlatformVersion {
744        let mut platform_version = platform_version.clone();
745        platform_version.system_limits.max_compiled_contract_size = Some(100);
746        platform_version
747    }
748
749    #[test]
750    fn should_refuse_a_contract_compiled_over_the_cap_and_charge_for_compiling_it() {
751        let platform_version = with_a_tiny_compiled_size_cap(PlatformVersion::latest());
752        let contract = fixture_contract();
753        let compiled_size = contract
754            .serialize_to_storage_bytes(&platform_version)
755            .expect("expected the contract to compile")
756            .len() as u64;
757        let mut operations = vec![];
758        let refused = contract.ensure_compiled_size(&mut operations, &platform_version);
759        assert!(
760            matches!(
761                &refused,
762                Err(ProtocolError::ConsensusError(error)) if matches!(
763                    error.as_ref(),
764                    ConsensusError::BasicError(
765                        BasicError::DataContractCompiledMaxSizeExceededError(error)
766                    ) if error.max_compiled_size() == 100
767                        && error.compiled_size() == compiled_size
768                )
769            ),
770            "{refused:?}"
771        );
772        assert_eq!(
773            operations,
774            vec![ProtocolValidationOperation::ContractCompiledSizeValidation(
775                compiled_size
776            )]
777        );
778
779        // Under the cap of the latest version, admitted and charged alike
780        let mut operations = vec![];
781        contract
782            .ensure_compiled_size(&mut operations, PlatformVersion::latest())
783            .expect("expected the contract to fit the cap");
784        assert_eq!(operations.len(), 1);
785    }
786
787    #[test]
788    fn should_neither_refuse_nor_charge_where_contracts_are_not_stored_compiled() {
789        let platform_version = with_a_tiny_compiled_size_cap(
790            PlatformVersion::get(14).expect("expected protocol version 14"),
791        );
792        let mut operations = vec![];
793        fixture_contract()
794            .ensure_compiled_size(&mut operations, &platform_version)
795            .expect("expected nothing refused at protocol version 14");
796        assert!(operations.is_empty());
797    }
798}