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::PlatformDeserializableWithPotentialValidationFromVersionedStructure;
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 platform_version = PlatformVersion::get(self.protocol_version)?;
71
72        let data_contract_id =
73            DataContract::generate_data_contract_id_v0(owner_id.to_buffer(), identity_nonce);
74
75        let defs = definitions
76            .map(|defs| defs.into_btree_string_map())
77            .transpose()
78            .map_err(ProtocolError::ValueError)?;
79
80        let documents_map = documents
81            .into_btree_string_map()
82            .map_err(ProtocolError::ValueError)?;
83
84        let format = DataContractInSerializationFormat::V0(DataContractInSerializationFormatV0 {
85            id: data_contract_id,
86            config: config.unwrap_or(DataContractConfig::default_for_version(platform_version)?),
87            version: INITIAL_DATA_CONTRACT_VERSION,
88            owner_id,
89            document_schemas: documents_map,
90            schema_defs: defs,
91        });
92
93        let data_contract =
94            DataContract::try_from_platform_versioned(format, true, &mut vec![], platform_version)?;
95
96        CreatedDataContract::from_contract_and_identity_nonce(
97            data_contract,
98            identity_nonce,
99            platform_version,
100        )
101    }
102
103    #[cfg(feature = "value-conversion")]
104    /// Create Data Contract from plain object
105    pub fn create_from_object(
106        &self,
107        data_contract_object: Value,
108        full_validation: bool,
109    ) -> Result<DataContract, ProtocolError> {
110        let platform_version = PlatformVersion::get(self.protocol_version)?;
111        match platform_version
112            .dpp
113            .contract_versions
114            .contract_structure_version
115        {
116            0 => {
117                let v0 = if full_validation {
118                    DataContractV0::from_value(data_contract_object, true, platform_version)?
119                } else {
120                    platform_value::from_value::<DataContractV0>(data_contract_object)
121                        .map_err(ProtocolError::ValueError)?
122                };
123                Ok(v0.into())
124            }
125            1 => {
126                let v1 = if full_validation {
127                    DataContractV1::from_value(data_contract_object, true, platform_version)?
128                } else {
129                    platform_value::from_value::<DataContractV1>(data_contract_object)
130                        .map_err(ProtocolError::ValueError)?
131                };
132                Ok(v1.into())
133            }
134            version => Err(ProtocolError::UnknownVersionMismatch {
135                method: "DataContractFactoryV0::create_from_object".to_string(),
136                known_versions: vec![0, 1],
137                received: version,
138            }),
139        }
140    }
141
142    /// Create Data Contract from buffer
143    pub fn create_from_buffer(
144        &self,
145        buffer: Vec<u8>,
146        #[cfg(feature = "validation")] skip_validation: bool,
147    ) -> Result<DataContract, ProtocolError> {
148        let platform_version = PlatformVersion::get(self.protocol_version)?;
149        #[cfg(not(feature = "validation"))]
150        let skip_validation = true;
151
152        let data_contract: DataContract = DataContract::versioned_deserialize(
153            buffer.as_slice(),
154            !skip_validation,
155            platform_version,
156        )
157        .map_err(|e| {
158            ConsensusError::BasicError(BasicError::SerializedObjectParsingError(
159                SerializedObjectParsingError::new(format!("Decode protocol entity: {:#?}", e)),
160            ))
161        })?;
162
163        // TODO: We validate Data Contract on creation.
164        //   Should we disable it when flag is off?
165        // #[cfg(feature = "validation")]
166        // {
167        //     if !skip_validation {
168        //         self.validate_data_contract(&data_contract.to_cleaned_object()?)?;
169        //     }
170        // }
171
172        Ok(data_contract)
173    }
174
175    // TODO: We validate Data Contract on creation.
176    //   Should we disable it when flag is off?
177    // #[cfg(feature = "validation")]
178    // pub fn validate_data_contract(&self, raw_data_contract: &Value) -> Result<(), ProtocolError> {
179    //     let platform_version = PlatformVersion::get(self.protocol_version)?;
180    //     let data_contract = DataContract::from_object(raw_data_contract.clone(), platform_version)?;
181    //     let result = data_contract.validate_schema(platform_version)?;
182    //
183    //     if !result.is_valid() {
184    //         return Err(ProtocolError::InvalidDataContractError(
185    //             InvalidDataContractError::new(result.errors, raw_data_contract.to_owned()),
186    //         ));
187    //     }
188    //
189    //     Ok(())
190    // }
191
192    #[cfg(feature = "state-transitions")]
193    pub fn create_unsigned_data_contract_create_transition(
194        &self,
195        created_data_contract: CreatedDataContract,
196    ) -> Result<DataContractCreateTransition, ProtocolError> {
197        DataContractCreateTransition::try_from_platform_versioned(
198            created_data_contract,
199            PlatformVersion::get(self.protocol_version)?,
200        )
201    }
202
203    #[cfg(feature = "state-transitions")]
204    pub fn create_unsigned_data_contract_update_transition(
205        &self,
206        data_contract: DataContract,
207        identity_contract_nonce: IdentityNonce,
208    ) -> Result<DataContractUpdateTransition, ProtocolError> {
209        DataContractUpdateTransition::try_from_platform_versioned(
210            (data_contract, identity_contract_nonce),
211            PlatformVersion::get(self.protocol_version)?,
212        )
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::data_contract::accessors::v0::DataContractV0Getters;
220    use crate::data_contract::schema::DataContractSchemaMethodsV0;
221
222    use crate::serialization::PlatformSerializableWithPlatformVersion;
223    use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
224    use crate::state_transition::StateTransitionLike;
225    use crate::tests::fixtures::get_data_contract_fixture;
226
227    pub struct TestData {
228        created_data_contract: CreatedDataContract,
229        raw_data_contract: Value,
230        factory: DataContractFactoryV0,
231    }
232
233    fn get_test_data() -> TestData {
234        let platform_version = PlatformVersion::latest();
235        let created_data_contract =
236            get_data_contract_fixture(None, 0, platform_version.protocol_version);
237
238        let raw_data_contract =
239            platform_value::to_value(created_data_contract.data_contract()).unwrap();
240
241        let factory = DataContractFactoryV0::new(platform_version.protocol_version);
242        TestData {
243            created_data_contract,
244            raw_data_contract,
245            factory,
246        }
247    }
248
249    #[test]
250    fn should_create_data_contract_with_specified_name_and_docs_definition() {
251        let TestData {
252            created_data_contract,
253            raw_data_contract,
254            factory,
255        } = get_test_data();
256
257        let data_contract = created_data_contract.data_contract_owned();
258
259        let raw_defs = raw_data_contract
260            .get_value("schemaDefs")
261            .expect("documents property should exist")
262            .clone();
263
264        let raw_documents = raw_data_contract
265            .get_value("documentSchemas")
266            .expect("documents property should exist")
267            .clone();
268
269        let result = factory
270            .create_with_value_config(
271                data_contract.owner_id(),
272                1,
273                raw_documents,
274                None,
275                Some(raw_defs),
276            )
277            .expect("Data Contract should be created")
278            .data_contract_owned();
279
280        assert_eq!(data_contract.version(), result.version());
281        // id is generated based on entropy which is different every time the `create` call is used
282        assert_eq!(data_contract.id().len(), result.id().len());
283        assert_ne!(data_contract.id(), result.id());
284        assert_eq!(data_contract.schema_defs(), result.schema_defs());
285        assert_eq!(data_contract.document_schemas(), result.document_schemas());
286        assert_eq!(data_contract.owner_id(), result.owner_id());
287    }
288
289    #[tokio::test]
290    async fn should_crate_data_contract_from_object() {
291        let _platform_version = PlatformVersion::latest();
292
293        let TestData {
294            created_data_contract,
295            raw_data_contract,
296            factory,
297        } = get_test_data();
298
299        let data_contract = created_data_contract.data_contract_owned();
300
301        let result = factory
302            .create_from_object(raw_data_contract, false)
303            .expect("Data Contract should be created");
304
305        assert_eq!(data_contract.version(), result.version());
306        assert_eq!(data_contract.id(), result.id());
307        assert_eq!(data_contract.owner_id(), result.owner_id());
308        assert_eq!(data_contract.document_types(), result.document_types());
309    }
310
311    #[tokio::test]
312    async fn should_create_data_contract_from_buffer() {
313        let platform_version = PlatformVersion::latest();
314
315        let TestData {
316            created_data_contract,
317            factory,
318            ..
319        } = get_test_data();
320
321        let data_contract = created_data_contract.data_contract_owned();
322
323        let serialized_data_contract = data_contract
324            .serialize_to_bytes_with_platform_version(platform_version)
325            .expect("should be serialized to buffer");
326        let result = factory
327            .create_from_buffer(serialized_data_contract, false)
328            .expect("Data Contract should be created from the buffer");
329
330        assert_eq!(data_contract.version(), result.version());
331        assert_eq!(data_contract.id(), result.id());
332        assert_eq!(data_contract.owner_id(), result.owner_id());
333        assert_eq!(data_contract.document_types(), result.document_types());
334    }
335
336    #[test]
337    fn should_create_data_contract_create_transition_from_data_contract() {
338        let platform_version = PlatformVersion::latest();
339
340        let TestData {
341            created_data_contract,
342            factory,
343            raw_data_contract,
344        } = get_test_data();
345
346        let result = factory
347            .create_unsigned_data_contract_create_transition(created_data_contract.clone())
348            .expect("Data Contract Transition should be created");
349
350        assert_eq!(0, result.state_transition_protocol_version());
351        assert_eq!(
352            created_data_contract.identity_nonce(),
353            result.identity_nonce()
354        );
355
356        let contract_value = platform_value::to_value(
357            &DataContract::try_from_platform_versioned(
358                result.data_contract().to_owned(),
359                false,
360                &mut vec![],
361                platform_version,
362            )
363            .unwrap(),
364        )
365        .unwrap();
366
367        assert_eq!(raw_data_contract, contract_value);
368    }
369}