1mod fields;
2mod methods;
3pub mod moderation;
4pub mod v0;
5pub mod v1;
6pub mod v2;
7
8use crate::data_contract::config::moderation::ContractModerationConfig;
9use crate::data_contract::config::v1::{
10 DataContractConfigGettersV1, DataContractConfigSettersV1, DataContractConfigV1,
11};
12use crate::data_contract::config::v2::{DataContractConfigGettersV2, DataContractConfigV2};
13use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
14#[cfg(feature = "json-conversion")]
15use crate::serialization::JsonConvertible;
16#[cfg(feature = "value-conversion")]
17use crate::serialization::ValueConvertible;
18use crate::version::PlatformVersion;
19use crate::ProtocolError;
20use bincode::{Decode, DecodeUntrusted, Encode};
21use derive_more::From;
22pub use fields::*;
23use platform_value::Value;
24use serde::{Deserialize, Serialize};
25use std::collections::BTreeMap;
26use v0::{DataContractConfigGettersV0, DataContractConfigSettersV0, DataContractConfigV0};
27
28#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
29#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
30#[derive(
31 Serialize, Deserialize, Encode, Decode, Debug, Clone, PartialEq, Eq, From, DecodeUntrusted,
32)]
33#[serde(tag = "$formatVersion")]
34pub enum DataContractConfig {
35 #[serde(rename = "0")]
36 V0(DataContractConfigV0),
37 #[serde(rename = "1")]
38 V1(DataContractConfigV1),
39 #[serde(rename = "2")]
41 V2(DataContractConfigV2),
42}
43
44impl DataContractConfig {
45 pub fn version(&self) -> u16 {
46 match self {
47 DataContractConfig::V0(_) => 0,
48 DataContractConfig::V1(_) => 1,
49 DataContractConfig::V2(_) => 2,
50 }
51 }
52
53 pub fn default_for_version(
54 platform_version: &PlatformVersion,
55 ) -> Result<DataContractConfig, ProtocolError> {
56 match platform_version
57 .dpp
58 .contract_versions
59 .config
60 .default_current_version
61 {
62 0 => Ok(DataContractConfigV0::default().into()),
63 1 => Ok(DataContractConfigV1::default().into()),
64 2 => Ok(DataContractConfigV2::default().into()),
65 version => Err(ProtocolError::UnknownVersionMismatch {
66 method: "DataContractConfig::default_for_version".to_string(),
67 known_versions: vec![0, 1, 2],
68 received: version,
69 }),
70 }
71 }
72
73 pub fn with_moderation(self, moderation: Option<ContractModerationConfig>) -> Self {
76 let mut v2: DataContractConfigV2 = match self {
77 DataContractConfig::V0(v0) => DataContractConfigV1 {
78 can_be_deleted: v0.can_be_deleted,
79 readonly: v0.readonly,
80 keeps_history: v0.keeps_history,
81 documents_keep_history_contract_default: v0.documents_keep_history_contract_default,
82 documents_mutable_contract_default: v0.documents_mutable_contract_default,
83 documents_can_be_deleted_contract_default: v0
84 .documents_can_be_deleted_contract_default,
85 requires_identity_encryption_bounded_key: v0
86 .requires_identity_encryption_bounded_key,
87 requires_identity_decryption_bounded_key: v0
88 .requires_identity_decryption_bounded_key,
89 sized_integer_types: false,
90 }
91 .into(),
92 DataContractConfig::V1(v1) => v1.into(),
93 DataContractConfig::V2(v2) => v2,
94 };
95 v2.moderation = moderation;
96 DataContractConfig::V2(v2)
97 }
98
99 pub fn ensure_admitted_by_platform_version(
103 &self,
104 platform_version: &PlatformVersion,
105 ) -> Result<(), ProtocolError> {
106 if self.moderation().is_some()
107 && platform_version.dpp.contract_versions.config.max_version < 2
108 {
109 return Err(ProtocolError::NotSupported(format!(
110 "contract moderation is not supported at protocol version {}",
111 platform_version.protocol_version
112 )));
113 }
114 Ok(())
115 }
116
117 pub fn config_valid_for_platform_version(
129 self,
130 platform_version: &PlatformVersion,
131 ) -> DataContractConfig {
132 let max_version = platform_version.dpp.contract_versions.config.max_version;
133 match self {
134 DataContractConfig::V0(v0) => DataContractConfig::V0(v0),
135 DataContractConfig::V1(v1) => {
136 if max_version == 0 {
137 DataContractConfig::V0(v1.into())
138 } else {
139 DataContractConfig::V1(v1)
140 }
141 }
142 DataContractConfig::V2(v2) => {
143 if max_version < 2 {
144 DataContractConfig::V1(v2.into())
145 .config_valid_for_platform_version(platform_version)
146 } else {
147 DataContractConfig::V2(v2)
148 }
149 }
150 }
151 }
152
153 fn refuse_moderation_the_version_can_not_parse(
156 declares_moderation: bool,
157 platform_version: &PlatformVersion,
158 ) -> Result<(), ProtocolError> {
159 if declares_moderation
160 && platform_version
161 .dpp
162 .contract_versions
163 .config
164 .default_current_version
165 < 2
166 {
167 return Err(ProtocolError::NotSupported(format!(
168 "contract moderation is not supported at protocol version {}",
169 platform_version.protocol_version
170 )));
171 }
172 Ok(())
173 }
174
175 pub fn from_value(
181 value: Value,
182 platform_version: &PlatformVersion,
183 ) -> Result<DataContractConfig, ProtocolError> {
184 if let Value::Map(map) = &value {
185 let declares_moderation = map.iter().any(|(key, value)| {
186 key.as_text() == Some(property::MODERATION) && !value.is_null()
187 });
188 Self::refuse_moderation_the_version_can_not_parse(
189 declares_moderation,
190 platform_version,
191 )?;
192 }
193 match platform_version
194 .dpp
195 .contract_versions
196 .config
197 .default_current_version
198 {
199 0 => {
200 let config: DataContractConfigV0 = platform_value::from_value(value)?;
201 Ok(config.into())
202 }
203 1 => {
204 let config: DataContractConfigV1 = platform_value::from_value(value)?;
205 Ok(config.into())
206 }
207 2 => {
208 let config: DataContractConfigV2 = platform_value::from_value(value)?;
209 Ok(config.into())
210 }
211 version => Err(ProtocolError::UnknownVersionMismatch {
212 method: "DataContractConfig::from_value".to_string(),
213 known_versions: vec![0, 1, 2],
214 received: version,
215 }),
216 }
217 }
218
219 pub(in crate::data_contract) fn get_contract_configuration_properties(
239 contract: &BTreeMap<String, Value>,
240 platform_version: &PlatformVersion,
241 ) -> Result<DataContractConfig, ProtocolError> {
242 Self::refuse_moderation_the_version_can_not_parse(
243 contract
244 .get(property::MODERATION)
245 .is_some_and(|value| !value.is_null()),
246 platform_version,
247 )?;
248 match platform_version
249 .dpp
250 .contract_versions
251 .config
252 .default_current_version
253 {
254 0 => Ok(
255 DataContractConfigV0::get_contract_configuration_properties_v0(contract)?.into(),
256 ),
257 1 => Ok(
258 DataContractConfigV1::get_contract_configuration_properties_v1(contract)?.into(),
259 ),
260 2 => Ok(
261 DataContractConfigV2::get_contract_configuration_properties_v2(contract)?.into(),
262 ),
263 version => Err(ProtocolError::UnknownVersionMismatch {
264 method: "DataContractConfig::get_contract_configuration_properties".to_string(),
265 known_versions: vec![0, 1, 2],
266 received: version,
267 }),
268 }
269 }
270}
271
272impl DataContractConfigGettersV0 for DataContractConfig {
273 fn can_be_deleted(&self) -> bool {
274 match self {
275 DataContractConfig::V0(v0) => v0.can_be_deleted,
276 DataContractConfig::V1(v1) => v1.can_be_deleted,
277 DataContractConfig::V2(v2) => v2.can_be_deleted,
278 }
279 }
280
281 fn readonly(&self) -> bool {
282 match self {
283 DataContractConfig::V0(v0) => v0.readonly,
284 DataContractConfig::V1(v1) => v1.readonly,
285 DataContractConfig::V2(v2) => v2.readonly,
286 }
287 }
288
289 fn keeps_history(&self) -> bool {
290 match self {
291 DataContractConfig::V0(v0) => v0.keeps_history,
292 DataContractConfig::V1(v1) => v1.keeps_history,
293 DataContractConfig::V2(v2) => v2.keeps_history,
294 }
295 }
296
297 fn documents_keep_history_contract_default(&self) -> bool {
298 match self {
299 DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default,
300 DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default,
301 DataContractConfig::V2(v2) => v2.documents_keep_history_contract_default,
302 }
303 }
304
305 fn documents_mutable_contract_default(&self) -> bool {
306 match self {
307 DataContractConfig::V0(v0) => v0.documents_mutable_contract_default,
308 DataContractConfig::V1(v1) => v1.documents_mutable_contract_default,
309 DataContractConfig::V2(v2) => v2.documents_mutable_contract_default,
310 }
311 }
312
313 fn documents_can_be_deleted_contract_default(&self) -> bool {
314 match self {
315 DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default,
316 DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default,
317 DataContractConfig::V2(v2) => v2.documents_can_be_deleted_contract_default,
318 }
319 }
320
321 fn requires_identity_encryption_bounded_key(&self) -> Option<StorageKeyRequirements> {
323 match self {
324 DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key,
325 DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key,
326 DataContractConfig::V2(v2) => v2.requires_identity_encryption_bounded_key,
327 }
328 }
329
330 fn requires_identity_decryption_bounded_key(&self) -> Option<StorageKeyRequirements> {
332 match self {
333 DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key,
334 DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key,
335 DataContractConfig::V2(v2) => v2.requires_identity_decryption_bounded_key,
336 }
337 }
338}
339
340impl DataContractConfigSettersV0 for DataContractConfig {
341 fn set_can_be_deleted(&mut self, value: bool) {
342 match self {
343 DataContractConfig::V0(v0) => v0.can_be_deleted = value,
344 DataContractConfig::V1(v1) => v1.can_be_deleted = value,
345 DataContractConfig::V2(v2) => v2.can_be_deleted = value,
346 }
347 }
348
349 fn set_readonly(&mut self, value: bool) {
350 match self {
351 DataContractConfig::V0(v0) => v0.readonly = value,
352 DataContractConfig::V1(v1) => v1.readonly = value,
353 DataContractConfig::V2(v2) => v2.readonly = value,
354 }
355 }
356
357 fn set_keeps_history(&mut self, value: bool) {
358 match self {
359 DataContractConfig::V0(v0) => v0.keeps_history = value,
360 DataContractConfig::V1(v1) => v1.keeps_history = value,
361 DataContractConfig::V2(v2) => v2.keeps_history = value,
362 }
363 }
364
365 fn set_documents_keep_history_contract_default(&mut self, value: bool) {
366 match self {
367 DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default = value,
368 DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default = value,
369 DataContractConfig::V2(v2) => v2.documents_keep_history_contract_default = value,
370 }
371 }
372
373 fn set_documents_can_be_deleted_contract_default(&mut self, value: bool) {
374 match self {
375 DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default = value,
376 DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default = value,
377 DataContractConfig::V2(v2) => v2.documents_can_be_deleted_contract_default = value,
378 }
379 }
380
381 fn set_documents_mutable_contract_default(&mut self, value: bool) {
382 match self {
383 DataContractConfig::V0(v0) => v0.documents_mutable_contract_default = value,
384 DataContractConfig::V1(v1) => v1.documents_mutable_contract_default = value,
385 DataContractConfig::V2(v2) => v2.documents_mutable_contract_default = value,
386 }
387 }
388
389 fn set_requires_identity_encryption_bounded_key(
390 &mut self,
391 value: Option<StorageKeyRequirements>,
392 ) {
393 match self {
394 DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key = value,
395 DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key = value,
396 DataContractConfig::V2(v2) => v2.requires_identity_encryption_bounded_key = value,
397 }
398 }
399
400 fn set_requires_identity_decryption_bounded_key(
401 &mut self,
402 value: Option<StorageKeyRequirements>,
403 ) {
404 match self {
405 DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key = value,
406 DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key = value,
407 DataContractConfig::V2(v2) => v2.requires_identity_decryption_bounded_key = value,
408 }
409 }
410}
411
412impl DataContractConfigGettersV1 for DataContractConfig {
413 fn sized_integer_types(&self) -> bool {
414 match self {
415 DataContractConfig::V0(_) => false,
416 DataContractConfig::V1(v1) => v1.sized_integer_types,
417 DataContractConfig::V2(v2) => v2.sized_integer_types,
418 }
419 }
420}
421
422impl DataContractConfigSettersV1 for DataContractConfig {
423 fn set_sized_integer_types_enabled(&mut self, enable: bool) {
424 match self {
425 DataContractConfig::V0(_) => {}
426 DataContractConfig::V1(v1) => v1.sized_integer_types = enable,
427 DataContractConfig::V2(v2) => v2.sized_integer_types = enable,
428 }
429 }
430}
431
432impl DataContractConfigGettersV2 for DataContractConfig {
433 fn moderation(&self) -> Option<&ContractModerationConfig> {
434 match self {
435 DataContractConfig::V0(_) | DataContractConfig::V1(_) => None,
436 DataContractConfig::V2(v2) => v2.moderation.as_ref(),
437 }
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use crate::data_contract::config::v0::DataContractConfigV0;
445 use crate::data_contract::config::v1::DataContractConfigV1;
446 use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
447 use platform_version::version::PlatformVersion;
448
449 mod default_for_version {
450 use super::*;
451
452 #[test]
453 fn default_for_latest_platform_version() {
454 let platform_version = PlatformVersion::latest();
455 let config = DataContractConfig::default_for_version(platform_version)
456 .expect("should create config for latest version");
457
458 let expected_version = platform_version
460 .dpp
461 .contract_versions
462 .config
463 .default_current_version;
464
465 assert_eq!(config.version(), expected_version);
466 }
467
468 #[test]
469 fn default_for_first_platform_version() {
470 let platform_version = PlatformVersion::first();
471 let config = DataContractConfig::default_for_version(platform_version)
472 .expect("should create config for first version");
473
474 let expected_version = platform_version
475 .dpp
476 .contract_versions
477 .config
478 .default_current_version;
479
480 assert_eq!(config.version(), expected_version);
481 }
482 }
483
484 mod version_method {
485 use super::*;
486
487 #[test]
488 fn v0_reports_version_0() {
489 let config = DataContractConfig::V0(DataContractConfigV0::default());
490 assert_eq!(config.version(), 0);
491 }
492
493 #[test]
494 fn v1_reports_version_1() {
495 let config = DataContractConfig::V1(DataContractConfigV1::default());
496 assert_eq!(config.version(), 1);
497 }
498 }
499
500 mod from_conversions {
501 use super::*;
502
503 #[test]
504 fn v0_into_config() {
505 let v0 = DataContractConfigV0::default();
506 let config: DataContractConfig = v0.into();
507 assert_eq!(config.version(), 0);
508 }
509
510 #[test]
511 fn v1_into_config() {
512 let v1 = DataContractConfigV1::default();
513 let config: DataContractConfig = v1.into();
514 assert_eq!(config.version(), 1);
515 }
516
517 #[test]
518 fn v1_to_v0_conversion_preserves_fields() {
519 let v1 = DataContractConfigV1 {
520 can_be_deleted: true,
521 readonly: true,
522 keeps_history: true,
523 documents_keep_history_contract_default: true,
524 documents_mutable_contract_default: false,
525 documents_can_be_deleted_contract_default: false,
526 requires_identity_encryption_bounded_key: None,
527 requires_identity_decryption_bounded_key: None,
528 sized_integer_types: true,
529 };
530 let v0: DataContractConfigV0 = v1.into();
531 assert!(v0.can_be_deleted);
532 assert!(v0.readonly);
533 assert!(v0.keeps_history);
534 assert!(v0.documents_keep_history_contract_default);
535 assert!(!v0.documents_mutable_contract_default);
536 assert!(!v0.documents_can_be_deleted_contract_default);
537 }
538 }
539
540 mod getters_v0 {
541 use super::*;
542
543 #[test]
544 fn default_v0_getter_values() {
545 let config = DataContractConfig::V0(DataContractConfigV0::default());
546 assert_eq!(config.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
547 assert_eq!(config.readonly(), !DEFAULT_CONTRACT_MUTABILITY);
548 assert_eq!(config.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
549 assert_eq!(
550 config.documents_keep_history_contract_default(),
551 DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY
552 );
553 assert_eq!(
554 config.documents_mutable_contract_default(),
555 DEFAULT_CONTRACT_DOCUMENT_MUTABILITY
556 );
557 assert_eq!(
558 config.documents_can_be_deleted_contract_default(),
559 DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED
560 );
561 assert!(config.requires_identity_encryption_bounded_key().is_none());
562 assert!(config.requires_identity_decryption_bounded_key().is_none());
563 }
564
565 #[test]
566 fn default_v1_getter_values() {
567 let config = DataContractConfig::V1(DataContractConfigV1::default());
568 assert_eq!(config.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
569 assert_eq!(config.readonly(), !DEFAULT_CONTRACT_MUTABILITY);
570 assert_eq!(config.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
571 assert_eq!(
572 config.documents_keep_history_contract_default(),
573 DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY
574 );
575 assert_eq!(
576 config.documents_mutable_contract_default(),
577 DEFAULT_CONTRACT_DOCUMENT_MUTABILITY
578 );
579 assert_eq!(
580 config.documents_can_be_deleted_contract_default(),
581 DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED
582 );
583 }
584 }
585
586 mod setters_v0 {
587 use super::*;
588
589 #[test]
590 fn set_can_be_deleted_on_v0() {
591 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
592 config.set_can_be_deleted(true);
593 assert!(config.can_be_deleted());
594 config.set_can_be_deleted(false);
595 assert!(!config.can_be_deleted());
596 }
597
598 #[test]
599 fn set_readonly_on_v1() {
600 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
601 config.set_readonly(true);
602 assert!(config.readonly());
603 config.set_readonly(false);
604 assert!(!config.readonly());
605 }
606
607 #[test]
608 fn set_keeps_history() {
609 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
610 config.set_keeps_history(true);
611 assert!(config.keeps_history());
612 }
613
614 #[test]
615 fn set_documents_keep_history() {
616 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
617 config.set_documents_keep_history_contract_default(true);
618 assert!(config.documents_keep_history_contract_default());
619 }
620
621 #[test]
622 fn set_documents_mutable() {
623 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
624 config.set_documents_mutable_contract_default(false);
625 assert!(!config.documents_mutable_contract_default());
626 }
627
628 #[test]
629 fn set_documents_can_be_deleted() {
630 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
631 config.set_documents_can_be_deleted_contract_default(false);
632 assert!(!config.documents_can_be_deleted_contract_default());
633 }
634
635 #[test]
636 fn set_encryption_key_requirements() {
637 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
638 config
639 .set_requires_identity_encryption_bounded_key(Some(StorageKeyRequirements::Unique));
640 assert_eq!(
641 config.requires_identity_encryption_bounded_key(),
642 Some(StorageKeyRequirements::Unique)
643 );
644 }
645
646 #[test]
647 fn set_decryption_key_requirements() {
648 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
649 config
650 .set_requires_identity_decryption_bounded_key(Some(StorageKeyRequirements::Unique));
651 assert_eq!(
652 config.requires_identity_decryption_bounded_key(),
653 Some(StorageKeyRequirements::Unique)
654 );
655 }
656 }
657
658 mod getters_setters_v1 {
659 use super::*;
660
661 #[test]
662 fn sized_integer_types_default_v1() {
663 let config = DataContractConfig::V1(DataContractConfigV1::default());
664 assert!(config.sized_integer_types());
666 }
667
668 #[test]
669 fn sized_integer_types_v0_always_false() {
670 let config = DataContractConfig::V0(DataContractConfigV0::default());
671 assert!(!config.sized_integer_types());
672 }
673
674 #[test]
675 fn set_sized_integer_types_on_v1() {
676 let mut config = DataContractConfig::V1(DataContractConfigV1::default());
677 config.set_sized_integer_types_enabled(false);
678 assert!(!config.sized_integer_types());
679 config.set_sized_integer_types_enabled(true);
680 assert!(config.sized_integer_types());
681 }
682
683 #[test]
684 fn set_sized_integer_types_on_v0_is_noop() {
685 let mut config = DataContractConfig::V0(DataContractConfigV0::default());
686 config.set_sized_integer_types_enabled(true);
687 assert!(!config.sized_integer_types());
689 }
690 }
691
692 mod config_valid_for_platform_version {
693 use super::*;
694
695 #[test]
696 fn v0_stays_v0_regardless_of_platform() {
697 let config = DataContractConfig::V0(DataContractConfigV0::default());
698 let result = config.config_valid_for_platform_version(PlatformVersion::latest());
699 assert_eq!(result.version(), 0);
700 }
701
702 #[test]
703 fn v1_downgraded_to_v0_when_max_version_is_0() {
704 let config = DataContractConfig::V1(DataContractConfigV1 {
705 can_be_deleted: true,
706 readonly: false,
707 keeps_history: true,
708 documents_keep_history_contract_default: false,
709 documents_mutable_contract_default: true,
710 documents_can_be_deleted_contract_default: true,
711 requires_identity_encryption_bounded_key: None,
712 requires_identity_decryption_bounded_key: None,
713 sized_integer_types: true,
714 });
715
716 let platform_version = PlatformVersion::first();
718 if platform_version.dpp.contract_versions.config.max_version == 0 {
719 let result = config.config_valid_for_platform_version(platform_version);
720 assert_eq!(result.version(), 0);
721 assert!(result.can_be_deleted());
723 }
724 }
725
726 #[test]
727 fn v2_follows_the_platform_version_whatever_it_declares() {
728 let moderated = DataContractConfig::V2(DataContractConfigV2 {
729 moderation: Some(ContractModerationConfig {
730 banlist: true,
731 suspensions: false,
732 moderators: Default::default(),
733 warnings: false,
734 }),
735 ..DataContractConfigV2::default()
736 });
737 let unmoderated = DataContractConfig::V2(DataContractConfigV2::default());
738
739 let latest = PlatformVersion::latest();
742 for config in [moderated.clone(), unmoderated.clone()] {
743 assert_eq!(
744 config.config_valid_for_platform_version(latest).version(),
745 2
746 );
747 }
748 assert_eq!(
749 DataContractConfig::default_for_version(latest)
750 .expect("default config")
751 .version(),
752 2
753 );
754
755 let v13 = PlatformVersion::get(13).expect("protocol version 13");
757 for config in [moderated, unmoderated] {
758 assert_eq!(config.config_valid_for_platform_version(v13).version(), 1);
759 }
760 }
761
762 #[test]
763 fn a_moderation_declaration_is_refused_where_the_version_can_not_carry_it() {
764 let moderated = DataContractConfig::V2(DataContractConfigV2 {
765 moderation: Some(ContractModerationConfig {
766 banlist: true,
767 suspensions: false,
768 moderators: Default::default(),
769 warnings: false,
770 }),
771 ..DataContractConfigV2::default()
772 });
773 let unmoderated = DataContractConfig::V2(DataContractConfigV2::default());
774 let latest = PlatformVersion::latest();
775 let v13 = PlatformVersion::get(13).expect("protocol version 13");
776
777 assert!(moderated
779 .ensure_admitted_by_platform_version(latest)
780 .is_ok());
781 assert!(matches!(
782 moderated.ensure_admitted_by_platform_version(v13),
783 Err(ProtocolError::NotSupported(_))
784 ));
785 assert!(unmoderated.ensure_admitted_by_platform_version(v13).is_ok());
786
787 let value = platform_value::platform_value!({
789 "moderation": { "banlist": true },
790 });
791 assert!(matches!(
792 DataContractConfig::from_value(value.clone(), v13),
793 Err(ProtocolError::NotSupported(_))
794 ));
795 assert!(DataContractConfig::from_value(value, latest)
796 .expect("from_value")
797 .moderation()
798 .is_some());
799 }
800
801 #[test]
802 fn v1_stays_v1_when_max_version_is_1_or_higher() {
803 let config = DataContractConfig::V1(DataContractConfigV1::default());
804 let platform_version = PlatformVersion::latest();
805 if platform_version.dpp.contract_versions.config.max_version >= 1 {
806 let result = config.config_valid_for_platform_version(platform_version);
807 assert_eq!(result.version(), 1);
808 }
809 }
810 }
811
812 mod get_contract_configuration_properties_v0_consensus_lock {
820 use super::*;
821 use crate::data_contract::config::property::{
822 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY, REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY,
823 };
824 use platform_value::Value;
825 use std::collections::BTreeMap;
826
827 #[test]
831 fn encryption_property_populates_both_fields() {
832 let mut map: BTreeMap<String, Value> = BTreeMap::new();
833 map.insert(
834 REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY.to_string(),
835 Value::U8(StorageKeyRequirements::Unique as u8),
836 );
837
838 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
839 .expect("should parse V0 config");
840
841 assert_eq!(
842 config.requires_identity_encryption_bounded_key,
843 Some(StorageKeyRequirements::Unique)
844 );
845 assert_eq!(
846 config.requires_identity_decryption_bounded_key,
847 Some(StorageKeyRequirements::Unique),
848 "V0 consensus quirk: decryption field is read from the ENCRYPTION key"
849 );
850 }
851
852 #[test]
856 fn decryption_property_is_ignored_by_v0() {
857 let mut map: BTreeMap<String, Value> = BTreeMap::new();
858 map.insert(
859 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY.to_string(),
860 Value::U8(StorageKeyRequirements::MultipleReferenceToLatest as u8),
861 );
862
863 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
864 .expect("should parse V0 config");
865
866 assert!(
867 config.requires_identity_encryption_bounded_key.is_none(),
868 "V0 does not read the DECRYPTION property at all"
869 );
870 assert!(
871 config.requires_identity_decryption_bounded_key.is_none(),
872 "V0 consensus quirk: decryption field is NOT sourced from the DECRYPTION key"
873 );
874 }
875
876 #[test]
879 fn encryption_wins_when_both_properties_set() {
880 let mut map: BTreeMap<String, Value> = BTreeMap::new();
881 map.insert(
882 REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY.to_string(),
883 Value::U8(StorageKeyRequirements::Unique as u8),
884 );
885 map.insert(
886 REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY.to_string(),
887 Value::U8(StorageKeyRequirements::Multiple as u8),
888 );
889
890 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
891 .expect("should parse V0 config");
892
893 assert_eq!(
894 config.requires_identity_encryption_bounded_key,
895 Some(StorageKeyRequirements::Unique)
896 );
897 assert_eq!(
898 config.requires_identity_decryption_bounded_key,
899 Some(StorageKeyRequirements::Unique),
900 "V0 consensus quirk: the DECRYPTION property is ignored"
901 );
902 }
903
904 #[test]
906 fn neither_property_set_leaves_both_none() {
907 let map: BTreeMap<String, Value> = BTreeMap::new();
908 let config = DataContractConfigV0::get_contract_configuration_properties_v0(&map)
909 .expect("should parse V0 config with defaults");
910 assert!(config.requires_identity_encryption_bounded_key.is_none());
911 assert!(config.requires_identity_decryption_bounded_key.is_none());
912 }
913 }
914
915 mod bincode_roundtrip {
916 use super::*;
917 use bincode::config;
918
919 #[test]
920 fn v0_bincode_roundtrip_preserves_fields() {
921 let cfg = config::standard();
922 let original = DataContractConfig::V0(DataContractConfigV0 {
923 can_be_deleted: true,
924 readonly: true,
925 keeps_history: true,
926 documents_keep_history_contract_default: true,
927 documents_mutable_contract_default: false,
928 documents_can_be_deleted_contract_default: false,
929 requires_identity_encryption_bounded_key: Some(StorageKeyRequirements::Unique),
930 requires_identity_decryption_bounded_key: None,
931 });
932 let bytes = bincode::encode_to_vec(&original, cfg).expect("encode");
933 let (decoded, _): (DataContractConfig, _) =
934 bincode::decode_from_slice(&bytes, cfg).expect("decode");
935 assert_eq!(decoded, original);
936 }
937
938 #[test]
939 fn v1_bincode_roundtrip_preserves_sized_integer_types() {
940 let cfg = config::standard();
941 let original = DataContractConfig::V1(DataContractConfigV1 {
942 can_be_deleted: false,
943 readonly: false,
944 keeps_history: false,
945 documents_keep_history_contract_default: false,
946 documents_mutable_contract_default: true,
947 documents_can_be_deleted_contract_default: true,
948 requires_identity_encryption_bounded_key: None,
949 requires_identity_decryption_bounded_key: None,
950 sized_integer_types: false,
951 });
952 let bytes = bincode::encode_to_vec(&original, cfg).expect("encode");
953 let (decoded, _): (DataContractConfig, _) =
954 bincode::decode_from_slice(&bytes, cfg).expect("decode");
955 assert_eq!(decoded, original);
956 assert!(!decoded.sized_integer_types());
958 }
959 }
960
961 mod from_value_tests {
962 use super::*;
963 use platform_value::platform_value;
964
965 #[test]
966 fn from_value_yields_default_for_empty_object() {
967 let value = platform_value!({});
969 let platform_version = PlatformVersion::latest();
970 let cfg = DataContractConfig::from_value(value, platform_version)
971 .expect("empty object should deserialize to defaults");
972 assert_eq!(cfg.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
974 }
975 }
976
977 mod get_contract_configuration_properties_tests {
978 use super::*;
979 use platform_value::Value;
980 use std::collections::BTreeMap;
981
982 fn make_contract_map(can_be_deleted: bool, readonly: bool) -> BTreeMap<String, Value> {
983 let mut m = BTreeMap::new();
984 m.insert(
985 property::CAN_BE_DELETED.to_string(),
986 Value::Bool(can_be_deleted),
987 );
988 m.insert(property::READONLY.to_string(), Value::Bool(readonly));
989 m
990 }
991
992 #[test]
993 fn reads_booleans_from_map() {
994 let platform_version = PlatformVersion::latest();
995 for (can_be_deleted, readonly) in [(true, false), (false, true)] {
998 let contract = make_contract_map(can_be_deleted, readonly);
999 let cfg = DataContractConfig::get_contract_configuration_properties(
1000 &contract,
1001 platform_version,
1002 )
1003 .expect("should parse config from map");
1004 assert_eq!(cfg.can_be_deleted(), can_be_deleted);
1005 assert_eq!(cfg.readonly(), readonly);
1006 }
1007 }
1008
1009 #[test]
1010 fn missing_keys_fall_back_to_defaults() {
1011 let platform_version = PlatformVersion::latest();
1012 let empty: BTreeMap<String, Value> = BTreeMap::new();
1013 let cfg =
1014 DataContractConfig::get_contract_configuration_properties(&empty, platform_version)
1015 .expect("should parse empty contract map");
1016 assert_eq!(cfg.can_be_deleted(), DEFAULT_CONTRACT_CAN_BE_DELETED);
1018 assert_eq!(cfg.keeps_history(), DEFAULT_CONTRACT_KEEPS_HISTORY);
1019 }
1020
1021 #[test]
1022 fn non_bool_value_errors() {
1023 let platform_version = PlatformVersion::latest();
1024 let mut m: BTreeMap<String, Value> = BTreeMap::new();
1025 m.insert(
1026 property::CAN_BE_DELETED.to_string(),
1027 Value::Text("not-a-bool".to_string()),
1028 );
1029 let result =
1030 DataContractConfig::get_contract_configuration_properties(&m, platform_version);
1031 assert!(result.is_err());
1032 }
1033 }
1034}
1035
1036#[cfg(all(
1037 test,
1038 feature = "json-conversion",
1039 feature = "value-conversion",
1040 feature = "serde-conversion"
1041))]
1042mod json_convertible_tests {
1043 use super::*;
1044 use crate::data_contract::config::v0::DataContractConfigV0;
1045 use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
1046 use platform_value::platform_value;
1047 use serde_json::json;
1048
1049 fn fixture() -> DataContractConfig {
1052 DataContractConfig::V0(DataContractConfigV0 {
1053 can_be_deleted: true,
1054 readonly: true,
1055 keeps_history: true,
1056 documents_keep_history_contract_default: true,
1057 documents_mutable_contract_default: false,
1058 documents_can_be_deleted_contract_default: false,
1059 requires_identity_encryption_bounded_key: Some(StorageKeyRequirements::Unique),
1060 requires_identity_decryption_bounded_key: Some(StorageKeyRequirements::Multiple),
1061 })
1062 }
1063
1064 #[test]
1065 fn json_round_trip_with_full_wire_shape() {
1066 use crate::serialization::JsonConvertible;
1067 let original = fixture();
1068 let json = original.to_json().expect("to_json");
1069 assert_eq!(
1075 json,
1076 json!({
1077 "$formatVersion": "0",
1078 "canBeDeleted": true,
1079 "readonly": true,
1080 "keepsHistory": true,
1081 "documentsKeepHistoryContractDefault": true,
1082 "documentsMutableContractDefault": false,
1083 "documentsCanBeDeletedContractDefault": false,
1084 "requiresIdentityEncryptionBoundedKey": 0,
1085 "requiresIdentityDecryptionBoundedKey": 1,
1086 })
1087 );
1088 let recovered = DataContractConfig::from_json(json).expect("from_json");
1089 assert_eq!(original, recovered);
1090 }
1091
1092 #[test]
1093 fn value_round_trip_with_full_wire_shape() {
1094 use crate::serialization::ValueConvertible;
1095 let original = fixture();
1096 let value = original.to_object().expect("to_object");
1097 assert_eq!(
1100 value,
1101 platform_value!({
1102 "$formatVersion": "0",
1103 "canBeDeleted": true,
1104 "readonly": true,
1105 "keepsHistory": true,
1106 "documentsKeepHistoryContractDefault": true,
1107 "documentsMutableContractDefault": false,
1108 "documentsCanBeDeletedContractDefault": false,
1109 "requiresIdentityEncryptionBoundedKey": 0u8,
1110 "requiresIdentityDecryptionBoundedKey": 1u8,
1111 })
1112 );
1113 let recovered = DataContractConfig::from_object(value).expect("from_object");
1114 assert_eq!(original, recovered);
1115 }
1116}