Skip to main content

dpp/data_contract/factory/v0/
mod.rs

1use platform_value::Value;
2use platform_version::TryFromPlatformVersioned;
3
4use crate::consensus::basic::decode::SerializedObjectParsingError;
5use crate::consensus::basic::BasicError;
6use crate::consensus::ConsensusError;
7
8use crate::data_contract::config::DataContractConfig;
9#[cfg(feature = "value-conversion")]
10use crate::data_contract::conversion::value::v0::DataContractValueConversionMethodsV0;
11use crate::data_contract::created_data_contract::CreatedDataContract;
12use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
13use crate::data_contract::serialized_version::DataContractInSerializationFormat;
14#[cfg(feature = "value-conversion")]
15use crate::data_contract::v0::DataContractV0;
16use crate::data_contract::{DataContract, INITIAL_DATA_CONTRACT_VERSION};
17use crate::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted;
18#[cfg(feature = "state-transitions")]
19use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
20#[cfg(feature = "state-transitions")]
21use crate::state_transition::data_contract_update_transition::DataContractUpdateTransition;
22
23#[cfg(feature = "value-conversion")]
24use crate::data_contract::v1::DataContractV1;
25use crate::prelude::IdentityNonce;
26use crate::version::PlatformVersion;
27use crate::{errors::ProtocolError, prelude::Identifier};
28
29/// The version 0 implementation of the data contract factory.
30///
31/// This implementation manages the creation, validation, and serialization of data contracts.
32/// It uses a protocol_version, a DataContractValidator, and an EntropyGenerator for its operations.
33pub struct DataContractFactoryV0 {
34    /// The feature version used by this factory.
35    protocol_version: u32,
36}
37
38impl DataContractFactoryV0 {
39    pub fn new(protocol_version: u32) -> Self {
40        Self { protocol_version }
41    }
42
43    /// Create Data Contract
44    pub fn create_with_value_config(
45        &self,
46        owner_id: Identifier,
47        identity_nonce: IdentityNonce,
48        documents: Value,
49        config: Option<Value>,
50        definitions: Option<Value>,
51    ) -> Result<CreatedDataContract, ProtocolError> {
52        let platform_version = PlatformVersion::get(self.protocol_version)?;
53
54        // We need to transform the value into a data contract config
55        let config = config
56            .map(|config_value| DataContractConfig::from_value(config_value, platform_version))
57            .transpose()?;
58        self.create(owner_id, identity_nonce, documents, config, definitions)
59    }
60
61    /// Create Data Contract
62    pub fn create(
63        &self,
64        owner_id: Identifier,
65        identity_nonce: IdentityNonce,
66        documents: Value,
67        config: Option<DataContractConfig>,
68        definitions: Option<Value>,
69    ) -> Result<CreatedDataContract, ProtocolError> {
70        let data_contract_id =
71            DataContract::generate_data_contract_id_v0(owner_id.to_buffer(), identity_nonce);
72
73        self.create_with_id(
74            data_contract_id,
75            owner_id,
76            identity_nonce,
77            documents,
78            config,
79            definitions,
80        )
81    }
82
83    /// Create Data Contract under a given id instead of the one derived from the owner and
84    /// the nonce.
85    ///
86    /// For the system contracts, whose ids are published constants: the document types keep
87    /// the id of the contract they belong to, and the checks that compare against it (a key
88    /// reference's `boundTo`, which requires the key bound to this contract, and a lookup into
89    /// a document type of the same contract) have to see the real one.
90    pub fn create_with_id(
91        &self,
92        data_contract_id: Identifier,
93        owner_id: Identifier,
94        identity_nonce: IdentityNonce,
95        documents: Value,
96        config: Option<DataContractConfig>,
97        definitions: Option<Value>,
98    ) -> Result<CreatedDataContract, ProtocolError> {
99        let platform_version = PlatformVersion::get(self.protocol_version)?;
100
101        let defs = definitions
102            .map(|defs| defs.into_btree_string_map())
103            .transpose()
104            .map_err(ProtocolError::ValueError)?;
105
106        let documents_map = documents
107            .into_btree_string_map()
108            .map_err(ProtocolError::ValueError)?;
109
110        let format = DataContractInSerializationFormat::V0(DataContractInSerializationFormatV0 {
111            id: data_contract_id,
112            config: config.unwrap_or(DataContractConfig::default_for_version(platform_version)?),
113            version: INITIAL_DATA_CONTRACT_VERSION,
114            owner_id,
115            document_schemas: documents_map,
116            schema_defs: defs,
117        });
118
119        let data_contract =
120            DataContract::try_from_platform_versioned(format, true, &mut vec![], platform_version)?;
121
122        CreatedDataContract::from_contract_and_identity_nonce(
123            data_contract,
124            identity_nonce,
125            platform_version,
126        )
127    }
128
129    #[cfg(feature = "value-conversion")]
130    /// Create Data Contract from plain object
131    pub fn create_from_object(
132        &self,
133        data_contract_object: Value,
134        full_validation: bool,
135    ) -> Result<DataContract, ProtocolError> {
136        let platform_version = PlatformVersion::get(self.protocol_version)?;
137        match platform_version
138            .dpp
139            .contract_versions
140            .contract_structure_version
141        {
142            0 => {
143                let v0 = if full_validation {
144                    DataContractV0::from_value(data_contract_object, true, platform_version)?
145                } else {
146                    platform_value::from_value::<DataContractV0>(data_contract_object)
147                        .map_err(ProtocolError::ValueError)?
148                };
149                Ok(v0.into())
150            }
151            1 => {
152                let v1 = if full_validation {
153                    DataContractV1::from_value(data_contract_object, true, platform_version)?
154                } else {
155                    platform_value::from_value::<DataContractV1>(data_contract_object)
156                        .map_err(ProtocolError::ValueError)?
157                };
158                Ok(v1.into())
159            }
160            version => Err(ProtocolError::UnknownVersionMismatch {
161                method: "DataContractFactoryV0::create_from_object".to_string(),
162                known_versions: vec![0, 1],
163                received: version,
164            }),
165        }
166    }
167
168    /// Create Data Contract from buffer
169    pub fn create_from_buffer(
170        &self,
171        buffer: Vec<u8>,
172        #[cfg(feature = "validation")] skip_validation: bool,
173    ) -> Result<DataContract, ProtocolError> {
174        let platform_version = PlatformVersion::get(self.protocol_version)?;
175        #[cfg(not(feature = "validation"))]
176        let skip_validation = true;
177
178        let data_contract: DataContract = DataContract::versioned_deserialize_untrusted(
179            buffer.as_slice(),
180            !skip_validation,
181            platform_version,
182        )
183        .map_err(|e| {
184            ConsensusError::BasicError(BasicError::SerializedObjectParsingError(
185                SerializedObjectParsingError::new(format!("Decode protocol entity: {:#?}", e)),
186            ))
187        })?;
188
189        // TODO: We validate Data Contract on creation.
190        //   Should we disable it when flag is off?
191        // #[cfg(feature = "validation")]
192        // {
193        //     if !skip_validation {
194        //         self.validate_data_contract(&data_contract.to_cleaned_object()?)?;
195        //     }
196        // }
197
198        Ok(data_contract)
199    }
200
201    // TODO: We validate Data Contract on creation.
202    //   Should we disable it when flag is off?
203    // #[cfg(feature = "validation")]
204    // pub fn validate_data_contract(&self, raw_data_contract: &Value) -> Result<(), ProtocolError> {
205    //     let platform_version = PlatformVersion::get(self.protocol_version)?;
206    //     let data_contract = DataContract::from_object(raw_data_contract.clone(), platform_version)?;
207    //     let result = data_contract.validate_schema(platform_version)?;
208    //
209    //     if !result.is_valid() {
210    //         return Err(ProtocolError::InvalidDataContractError(
211    //             InvalidDataContractError::new(result.errors, raw_data_contract.to_owned()),
212    //         ));
213    //     }
214    //
215    //     Ok(())
216    // }
217
218    #[cfg(feature = "state-transitions")]
219    pub fn create_unsigned_data_contract_create_transition(
220        &self,
221        created_data_contract: CreatedDataContract,
222    ) -> Result<DataContractCreateTransition, ProtocolError> {
223        DataContractCreateTransition::try_from_platform_versioned(
224            created_data_contract,
225            PlatformVersion::get(self.protocol_version)?,
226        )
227    }
228
229    #[cfg(feature = "state-transitions")]
230    pub fn create_unsigned_data_contract_update_transition(
231        &self,
232        data_contract: DataContract,
233        identity_contract_nonce: IdentityNonce,
234    ) -> Result<DataContractUpdateTransition, ProtocolError> {
235        DataContractUpdateTransition::try_from_platform_versioned(
236            (data_contract, identity_contract_nonce),
237            PlatformVersion::get(self.protocol_version)?,
238        )
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use crate::data_contract::accessors::v0::DataContractV0Getters;
246    use crate::data_contract::schema::DataContractSchemaMethodsV0;
247
248    use crate::serialization::PlatformSerializableWithPlatformVersion;
249    use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
250    use crate::state_transition::StateTransitionLike;
251    use crate::tests::fixtures::get_data_contract_fixture;
252
253    pub struct TestData {
254        created_data_contract: CreatedDataContract,
255        raw_data_contract: Value,
256        factory: DataContractFactoryV0,
257    }
258
259    fn get_test_data() -> TestData {
260        let platform_version = PlatformVersion::latest();
261        let created_data_contract =
262            get_data_contract_fixture(None, 0, platform_version.protocol_version);
263
264        let raw_data_contract =
265            platform_value::to_value(created_data_contract.data_contract()).unwrap();
266
267        let factory = DataContractFactoryV0::new(platform_version.protocol_version);
268        TestData {
269            created_data_contract,
270            raw_data_contract,
271            factory,
272        }
273    }
274
275    #[test]
276    fn should_create_data_contract_with_specified_name_and_docs_definition() {
277        let TestData {
278            created_data_contract,
279            raw_data_contract,
280            factory,
281        } = get_test_data();
282
283        let data_contract = created_data_contract.data_contract_owned();
284
285        let raw_defs = raw_data_contract
286            .get_value("schemaDefs")
287            .expect("documents property should exist")
288            .clone();
289
290        let raw_documents = raw_data_contract
291            .get_value("documentSchemas")
292            .expect("documents property should exist")
293            .clone();
294
295        let result = factory
296            .create_with_value_config(
297                data_contract.owner_id(),
298                1,
299                raw_documents,
300                None,
301                Some(raw_defs),
302            )
303            .expect("Data Contract should be created")
304            .data_contract_owned();
305
306        assert_eq!(data_contract.version(), result.version());
307        // id is generated based on entropy which is different every time the `create` call is used
308        assert_eq!(data_contract.id().len(), result.id().len());
309        assert_ne!(data_contract.id(), result.id());
310        assert_eq!(data_contract.schema_defs(), result.schema_defs());
311        assert_eq!(data_contract.document_schemas(), result.document_schemas());
312        assert_eq!(data_contract.owner_id(), result.owner_id());
313    }
314
315    #[tokio::test]
316    async fn should_crate_data_contract_from_object() {
317        let _platform_version = PlatformVersion::latest();
318
319        let TestData {
320            created_data_contract,
321            raw_data_contract,
322            factory,
323        } = get_test_data();
324
325        let data_contract = created_data_contract.data_contract_owned();
326
327        let result = factory
328            .create_from_object(raw_data_contract, false)
329            .expect("Data Contract should be created");
330
331        assert_eq!(data_contract.version(), result.version());
332        assert_eq!(data_contract.id(), result.id());
333        assert_eq!(data_contract.owner_id(), result.owner_id());
334        assert_eq!(data_contract.document_types(), result.document_types());
335    }
336
337    #[tokio::test]
338    async fn should_create_data_contract_from_buffer() {
339        let platform_version = PlatformVersion::latest();
340
341        let TestData {
342            created_data_contract,
343            factory,
344            ..
345        } = get_test_data();
346
347        let data_contract = created_data_contract.data_contract_owned();
348
349        let serialized_data_contract = data_contract
350            .serialize_to_bytes_with_platform_version(platform_version)
351            .expect("should be serialized to buffer");
352        let result = factory
353            .create_from_buffer(serialized_data_contract, false)
354            .expect("Data Contract should be created from the buffer");
355
356        assert_eq!(data_contract.version(), result.version());
357        assert_eq!(data_contract.id(), result.id());
358        assert_eq!(data_contract.owner_id(), result.owner_id());
359        assert_eq!(data_contract.document_types(), result.document_types());
360    }
361
362    #[test]
363    fn should_create_data_contract_create_transition_from_data_contract() {
364        let platform_version = PlatformVersion::latest();
365
366        let TestData {
367            created_data_contract,
368            factory,
369            raw_data_contract,
370        } = get_test_data();
371
372        let result = factory
373            .create_unsigned_data_contract_create_transition(created_data_contract.clone())
374            .expect("Data Contract Transition should be created");
375
376        assert_eq!(
377            platform_version
378                .dpp
379                .state_transition_serialization_versions
380                .contract_create_state_transition
381                .default_current_version,
382            result.state_transition_protocol_version()
383        );
384        assert_eq!(
385            created_data_contract.identity_nonce(),
386            result.identity_nonce()
387        );
388
389        let contract_value = platform_value::to_value(
390            DataContract::try_from_platform_versioned(
391                result.data_contract().to_owned(),
392                false,
393                &mut vec![],
394                platform_version,
395            )
396            .unwrap(),
397        )
398        .unwrap();
399
400        assert_eq!(raw_data_contract, contract_value);
401    }
402}