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
92static 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#[derive(Debug, Clone, PartialEq, From, PlatformVersioned)]
121pub enum DataContract {
122 V0(DataContractV0),
123 V1(DataContractV1),
124}
125
126impl 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 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 #[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 #[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 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 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
295fn 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
307fn 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
321fn 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 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 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 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 #[cfg(test)]
531 pub fn into_latest(self) -> Option<DataContractV1> {
532 self.into_v1()
533 }
534
535 #[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 #[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 #[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 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 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}