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