1use crate::block::block_info::BlockInfo;
2use crate::data_contract::DataContract;
3use crate::validation::SimpleConsensusValidationResult;
4use crate::ProtocolError;
5use platform_version::version::PlatformVersion;
6
7pub trait DataContractUpdateValidationMethodsV0 {
8 fn validate_update(
9 &self,
10 data_contract: &DataContract,
11 block_info: &BlockInfo,
12 platform_version: &PlatformVersion,
13 ) -> Result<SimpleConsensusValidationResult, ProtocolError>;
14}
15
16impl DataContract {
17 #[inline(always)]
18 pub(super) fn validate_update_v0(
19 &self,
20 new_data_contract: &DataContract,
21 block_info: &BlockInfo,
22 platform_version: &PlatformVersion,
23 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
24 let result = self.validate_update_ownership_and_version(new_data_contract);
25 if !result.is_valid() {
26 return Ok(result);
27 }
28
29 let result = self.validate_update_config(new_data_contract, platform_version)?;
30 if !result.is_valid() {
31 return Ok(result);
32 }
33
34 let result =
35 self.validate_update_existing_document_types(new_data_contract, platform_version)?;
36 if !result.is_valid() {
37 return Ok(result);
38 }
39
40 let result = self.validate_update_schema_defs(new_data_contract, platform_version)?;
41 if !result.is_valid() {
42 return Ok(result);
43 }
44
45 let result = self.validate_update_groups(new_data_contract);
46 if !result.is_valid() {
47 return Ok(result);
48 }
49
50 let result = self.validate_update_tokens(new_data_contract, block_info);
51 if !result.is_valid() {
52 return Ok(result);
53 }
54
55 let result = self.validate_update_keywords(new_data_contract);
56 if !result.is_valid() {
57 return Ok(result);
58 }
59
60 Ok(self.validate_update_description(new_data_contract))
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::consensus::basic::basic_error::BasicError;
68 use crate::consensus::state::state_error::StateError;
69 use crate::consensus::ConsensusError;
70 use crate::data_contract::accessors::v0::DataContractV0Getters;
71 use crate::data_contract::accessors::v1::DataContractV1Getters;
72 use crate::data_contract::config::v0::DataContractConfigSettersV0;
73 use crate::data_contract::methods::validate_update::DataContractUpdateValidationMethodsV0;
74 use crate::data_contract::schema::DataContractSchemaMethodsV0;
75 use crate::prelude::IdentityNonce;
76 use crate::tests::fixtures::get_data_contract_fixture;
77 use assert_matches::assert_matches;
78 use platform_value::platform_value;
79 use platform_value::Identifier;
80
81 mod validate_update {
82 use std::collections::BTreeMap;
83
84 use super::*;
85 use crate::data_contract::accessors::v0::DataContractV0Setters;
86 use crate::data_contract::accessors::v1::DataContractV1Setters;
87 use crate::data_contract::associated_token::token_configuration::accessors::v0::{
88 TokenConfigurationV0Getters, TokenConfigurationV0Setters,
89 };
90 use crate::data_contract::associated_token::token_configuration::accessors::v1::TokenConfigurationV1Setters;
91 use crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0;
92 use crate::data_contract::associated_token::token_configuration::v1::TokenConfigurationV1;
93 use crate::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0;
94 use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention;
95 use crate::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0;
96 use crate::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization;
97 use crate::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Setters;
98 use crate::data_contract::associated_token::token_pre_programmed_distribution::v0::TokenPreProgrammedDistributionV0;
99 use crate::data_contract::associated_token::token_pre_programmed_distribution::TokenPreProgrammedDistribution;
100 use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
101 use crate::data_contract::document_type::DocumentTypeMutRef;
102 use crate::data_contract::group::accessors::v0::{GroupV0Getters, GroupV0Setters};
103 use crate::data_contract::group::v0::GroupV0;
104 use crate::data_contract::group::Group;
105 use crate::data_contract::TokenConfiguration;
106 use crate::identity::accessors::IdentityGettersV0;
107 use crate::prelude::Identity;
108
109 #[test]
110 fn should_return_invalid_result_if_owner_id_is_not_the_same() {
111 let platform_version = PlatformVersion::latest();
112
113 let old_data_contract = get_data_contract_fixture(
114 None,
115 IdentityNonce::default(),
116 platform_version.protocol_version,
117 )
118 .data_contract_owned();
119
120 let mut new_data_contract = old_data_contract.clone();
121
122 new_data_contract.set_owner_id(Identifier::random());
123
124 let result = old_data_contract
125 .validate_update(&new_data_contract, &BlockInfo::default(), platform_version)
126 .expect("failed validate update");
127
128 assert_matches!(
129 result.errors.as_slice(),
130 [ConsensusError::StateError(
131 StateError::DataContractUpdatePermissionError(e)
132 )] if *e.data_contract_id() == old_data_contract.id() && *e.identity_id() == new_data_contract.owner_id()
133 );
134 }
135
136 #[test]
137 fn should_return_invalid_result_if_contract_version_is_not_greater_for_one() {
138 let platform_version = PlatformVersion::latest();
139
140 let old_data_contract = get_data_contract_fixture(
141 None,
142 IdentityNonce::default(),
143 platform_version.protocol_version,
144 )
145 .data_contract_owned();
146
147 let new_data_contract = old_data_contract.clone();
148
149 let result = old_data_contract
150 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
151 .expect("failed validate update");
152
153 assert_matches!(
154 result.errors.as_slice(),
155 [ConsensusError::BasicError(
156 BasicError::InvalidDataContractVersionError(e)
157 )] if e.expected_version() == old_data_contract.version() + 1 && e.version() == new_data_contract.version()
158 );
159 }
160
161 #[test]
162 fn should_return_invalid_result_if_config_was_updated() {
163 let platform_version = PlatformVersion::latest();
164
165 let old_data_contract = get_data_contract_fixture(
166 None,
167 IdentityNonce::default(),
168 platform_version.protocol_version,
169 )
170 .data_contract_owned();
171
172 let mut new_data_contract = old_data_contract.clone();
173
174 new_data_contract.set_version(old_data_contract.version() + 1);
175 new_data_contract.config_mut().set_readonly(true);
176
177 let result = old_data_contract
178 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
179 .expect("failed validate update");
180
181 assert_matches!(
182 result.errors.as_slice(),
183 [ConsensusError::StateError(
184 StateError::DataContractConfigUpdateError(e)
185 )] if e.additional_message() == "contract can not be changed to readonly"
186 );
187 }
188
189 #[test]
190 fn should_return_invalid_result_when_document_type_is_removed() {
191 let platform_version = PlatformVersion::latest();
192
193 let old_data_contract = get_data_contract_fixture(
194 None,
195 IdentityNonce::default(),
196 platform_version.protocol_version,
197 )
198 .data_contract_owned();
199
200 let mut new_data_contract = old_data_contract.clone();
201
202 new_data_contract.set_version(old_data_contract.version() + 1);
203 new_data_contract
204 .document_types_mut()
205 .remove("niceDocument");
206
207 let result = old_data_contract
208 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
209 .expect("failed validate update");
210
211 assert_matches!(
212 result.errors.as_slice(),
213 [ConsensusError::StateError(
214 StateError::DocumentTypeUpdateError(e)
215 )] if e.additional_message() == "document type can't be removed"
216 );
217 }
218
219 #[test]
220 fn should_return_invalid_result_when_document_type_has_incompatible_change() {
221 let platform_version = PlatformVersion::latest();
222
223 let old_data_contract = get_data_contract_fixture(
224 None,
225 IdentityNonce::default(),
226 platform_version.protocol_version,
227 )
228 .data_contract_owned();
229
230 let mut new_data_contract = old_data_contract.clone();
231
232 new_data_contract.set_version(old_data_contract.version() + 1);
233
234 match new_data_contract
235 .document_types_mut()
236 .get_mut("niceDocument")
237 .unwrap()
238 .as_mut_ref()
239 {
240 DocumentTypeMutRef::V0(dt) => dt.documents_mutable = false,
241 DocumentTypeMutRef::V1(dt) => dt.documents_mutable = false,
242 DocumentTypeMutRef::V2(dt) => dt.documents_mutable = false,
243 }
244
245 let result = old_data_contract
246 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
247 .expect("failed validate update");
248
249 assert_matches!(
250 result.errors.as_slice(),
251 [ConsensusError::StateError(
252 StateError::DocumentTypeUpdateError(e)
253 )] if e.additional_message() == "document type can not change whether its documents are mutable: changing from true to false"
254 );
255 }
256
257 #[test]
258 fn should_return_invalid_result_when_defs_is_removed() {
259 let platform_version = PlatformVersion::latest();
260
261 let mut old_data_contract = get_data_contract_fixture(
262 None,
263 IdentityNonce::default(),
264 platform_version.protocol_version,
265 )
266 .data_contract_owned();
267
268 old_data_contract
270 .document_types_mut()
271 .remove("prettyDocument");
272
273 let mut new_data_contract = old_data_contract.clone();
274
275 new_data_contract.set_version(old_data_contract.version() + 1);
276 new_data_contract
277 .set_schema_defs(None, false, &mut Vec::new(), platform_version)
278 .expect("failed to set schema defs");
279
280 let result = old_data_contract
281 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
282 .expect("failed validate update");
283
284 assert_matches!(
285 result.errors.as_slice(),
286 [ConsensusError::BasicError(
287 BasicError::IncompatibleDataContractSchemaError(e)
288 )] if e.operation() == "remove" && e.field_path() == "/$defs"
289 );
290 }
291
292 #[test]
293 fn should_return_invalid_result_when_updated_defs_is_incompatible() {
294 let platform_version = PlatformVersion::latest();
295
296 let old_data_contract = get_data_contract_fixture(
297 None,
298 IdentityNonce::default(),
299 platform_version.protocol_version,
300 )
301 .data_contract_owned();
302
303 let mut new_data_contract = old_data_contract.clone();
304
305 let incompatible_defs_value = platform_value!({
306 "lastName": {
307 "type": "number",
308 },
309 });
310 let incompatible_defs = incompatible_defs_value
311 .into_btree_string_map()
312 .expect("should convert to map");
313
314 new_data_contract.set_version(old_data_contract.version() + 1);
315 new_data_contract
316 .set_schema_defs(
317 Some(incompatible_defs),
318 false,
319 &mut Vec::new(),
320 platform_version,
321 )
322 .expect("failed to set schema defs");
323
324 let result = old_data_contract
325 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
326 .expect("failed validate update");
327
328 assert_matches!(
329 result.errors.as_slice(),
330 [ConsensusError::BasicError(
331 BasicError::IncompatibleDataContractSchemaError(e)
332 )] if e.operation() == "replace" && e.field_path() == "/$defs/lastName/type"
333 );
334 }
335
336 #[test]
337 fn should_pass_when_all_changes_are_compatible() {
338 let platform_version = PlatformVersion::latest();
339
340 let old_data_contract = get_data_contract_fixture(
341 None,
342 IdentityNonce::default(),
343 platform_version.protocol_version,
344 )
345 .data_contract_owned();
346
347 let mut new_data_contract = old_data_contract.clone();
348
349 new_data_contract.set_version(old_data_contract.version() + 1);
350
351 let result = old_data_contract
352 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
353 .expect("failed validate update");
354
355 assert!(result.is_valid());
356 }
357
358 #[test]
365 fn should_return_invalid_result_when_group_is_removed() {
366 let platform_version = PlatformVersion::latest();
367
368 let identity_1 = Identity::random_identity(3, Some(14), platform_version)
369 .expect("expected a platform identity");
370 let identity_1_id = identity_1.id();
371 let identity_2 = Identity::random_identity(3, Some(506), platform_version)
372 .expect("expected a platform identity");
373 let identity_2_id = identity_2.id();
374
375 let mut old_data_contract =
376 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
377 old_data_contract.set_groups(BTreeMap::from([(
378 0,
379 Group::V0(GroupV0 {
380 members: [(identity_1_id, 1), (identity_2_id, 1)].into(),
381 required_power: 2,
382 }),
383 )]));
384
385 let mut new_data_contract = old_data_contract.clone();
387 new_data_contract.set_version(old_data_contract.version() + 1);
388
389 let first_group_pos = *old_data_contract
391 .groups()
392 .keys()
393 .next()
394 .expect("fixture must have at least one group");
395 new_data_contract
396 .groups_mut()
397 .unwrap()
398 .remove(&first_group_pos);
399
400 let result = old_data_contract
401 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
402 .expect("failed validate update");
403
404 assert_matches!(
405 result.errors.as_slice(),
406 [ConsensusError::StateError(
407 StateError::DataContractUpdateActionNotAllowedError(e)
408 )] if e.action() == "remove group"
409 );
410 }
411
412 #[test]
413 fn should_return_invalid_result_when_group_is_changed() {
414 let platform_version = PlatformVersion::latest();
415
416 let identity_1 = Identity::random_identity(3, Some(14), platform_version)
417 .expect("expected a platform identity");
418 let identity_1_id = identity_1.id();
419 let identity_2 = Identity::random_identity(3, Some(506), platform_version)
420 .expect("expected a platform identity");
421 let identity_2_id = identity_2.id();
422
423 let mut old_data_contract =
424 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
425 old_data_contract.set_groups(BTreeMap::from([(
426 0,
427 Group::V0(GroupV0 {
428 members: [(identity_1_id, 1), (identity_2_id, 1)].into(),
429 required_power: 2,
430 }),
431 )]));
432
433 let mut new_data_contract = old_data_contract.clone();
435 new_data_contract.set_version(old_data_contract.version() + 1);
436
437 let first_group_pos = *new_data_contract
440 .groups()
441 .keys()
442 .next()
443 .expect("fixture must have at least one group");
444 let mut altered_group = new_data_contract
445 .groups()
446 .get(&first_group_pos)
447 .cloned()
448 .expect("group must exist");
449 altered_group.set_required_power(altered_group.required_power() + 1);
451 new_data_contract
452 .groups_mut()
453 .unwrap()
454 .insert(first_group_pos, altered_group);
455
456 let result = old_data_contract
457 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
458 .expect("failed validate update");
459
460 assert_matches!(
461 result.errors.as_slice(),
462 [ConsensusError::StateError(
463 StateError::DataContractUpdateActionNotAllowedError(e)
464 )] if e.action() == format!(
465 "change group at position {} is not allowed",
466 first_group_pos
467 )
468 );
469 }
470
471 #[test]
478 fn should_return_invalid_result_when_token_is_removed() {
479 let platform_version = PlatformVersion::latest();
480
481 let mut old_data_contract =
482 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
483 old_data_contract.set_tokens(BTreeMap::from([(
484 0,
485 TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()),
486 )]));
487
488 let mut new_data_contract = old_data_contract.clone();
489 new_data_contract.set_version(old_data_contract.version() + 1);
490
491 let first_token_pos = *old_data_contract
493 .tokens()
494 .keys()
495 .next()
496 .expect("fixture must have at least one token");
497 new_data_contract
498 .tokens_mut()
499 .unwrap()
500 .remove(&first_token_pos);
501
502 let result = old_data_contract
503 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
504 .expect("failed validate update");
505
506 assert_matches!(
507 result.errors.as_slice(),
508 [ConsensusError::StateError(
509 StateError::DataContractUpdateActionNotAllowedError(e)
510 )] if e.action() == format!("remove token at position {}", first_token_pos)
511 );
512 }
513
514 #[test]
515 fn should_return_invalid_result_when_token_is_updated() {
516 let platform_version = PlatformVersion::latest();
517
518 let mut old_data_contract =
519 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
520 old_data_contract.set_tokens(BTreeMap::from([(
521 0,
522 TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()),
523 )]));
524
525 let mut new_data_contract = old_data_contract.clone();
526 new_data_contract.set_version(old_data_contract.version() + 1);
527
528 let first_token_pos = *new_data_contract
530 .tokens()
531 .keys()
532 .next()
533 .expect("fixture must have at least one token");
534 let mut altered_token_cfg = new_data_contract
535 .tokens()
536 .get(&first_token_pos)
537 .cloned()
538 .expect("token must exist");
539 altered_token_cfg.set_base_supply(altered_token_cfg.base_supply() + 1);
541 new_data_contract
542 .tokens_mut()
543 .unwrap()
544 .insert(first_token_pos, altered_token_cfg);
545
546 let result = old_data_contract
547 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
548 .expect("failed validate update");
549
550 assert_matches!(
551 result.errors.as_slice(),
552 [ConsensusError::StateError(
553 StateError::DataContractUpdateActionNotAllowedError(e)
554 )] if e.action() == format!("update token at position {}", first_token_pos)
555 );
556 }
557
558 #[test]
564 fn should_return_invalid_result_when_any_token_configuration_v1_field_is_updated() {
565 let platform_version = PlatformVersion::latest();
566 let mut pooled =
567 TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive());
568 pooled.set_has_shielded_pool(true);
569 let TokenConfiguration::V1(original) = &pooled else {
570 panic!("a pooled configuration is V1");
571 };
572 let TokenConfigurationV1 {
573 base: _,
574 has_shielded_pool: _,
575 minimum_pool_notes_for_outgoing: _,
576 minimum_pool_notes_for_outgoing_change_rules: _,
577 } = original;
578
579 type Change = fn(&mut TokenConfigurationV1);
580 let changes: [(&str, Change); 3] = [
581 ("has_shielded_pool", |v1| v1.has_shielded_pool = false),
582 ("minimum_pool_notes_for_outgoing", |v1| {
583 v1.minimum_pool_notes_for_outgoing = Some(1)
584 }),
585 ("minimum_pool_notes_for_outgoing_change_rules", |v1| {
586 v1.minimum_pool_notes_for_outgoing_change_rules
587 .set_authorized_to_make_change_action_takers(
588 AuthorizedActionTakers::ContractOwner,
589 )
590 }),
591 ];
592 for (field, change) in changes {
593 let mut old_data_contract =
594 get_data_contract_fixture(None, IdentityNonce::default(), 9)
595 .data_contract_owned();
596 old_data_contract.set_tokens(BTreeMap::from([(0, pooled.clone())]));
597
598 let mut changed = original.clone();
599 change(&mut changed);
600 assert_ne!(&changed, original, "{field} must actually change");
601 let mut new_data_contract = old_data_contract.clone();
602 new_data_contract.set_version(old_data_contract.version() + 1);
603 new_data_contract
604 .set_tokens(BTreeMap::from([(0, TokenConfiguration::V1(changed))]));
605
606 let result = old_data_contract
607 .validate_update(&new_data_contract, &BlockInfo::default(), platform_version)
608 .expect("failed validate update");
609
610 assert_matches!(
611 result.errors.as_slice(),
612 [ConsensusError::StateError(
613 StateError::DataContractUpdateActionNotAllowedError(e)
614 )] if e.action() == "update token at position 0",
615 "a contract update changing {field} must be refused"
616 );
617 }
618 }
619
620 #[test]
621 fn should_return_invalid_result_when_token_is_added_with_past_timestamp() {
622 let platform_version = PlatformVersion::latest();
623
624 let mut old_data_contract =
625 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
626 let mut token_cfg =
627 TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive());
628 token_cfg.set_conventions(TokenConfigurationConvention::V0(
629 TokenConfigurationConventionV0 {
630 localizations: BTreeMap::from([(
631 "en".to_string(),
632 TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 {
633 should_capitalize: false,
634 singular_form: "test".to_string(),
635 plural_form: "tests".to_string(),
636 }),
637 )]),
638 decimals: 8,
639 },
640 ));
641 old_data_contract.set_tokens(BTreeMap::from([(0, token_cfg)]));
642
643 let mut new_data_contract = old_data_contract.clone();
644 new_data_contract.set_version(old_data_contract.version() + 1);
645
646 let existing_cfg = new_data_contract
648 .tokens()
649 .values()
650 .next()
651 .expect("fixture must have at least one token")
652 .clone();
653 let new_position = old_data_contract
654 .tokens()
655 .keys()
656 .max()
657 .expect("fixture must have at least one token")
658 + 1;
659 let mut new_token_cfg = existing_cfg.clone();
660 new_token_cfg
661 .distribution_rules_mut()
662 .set_pre_programmed_distribution(Some(TokenPreProgrammedDistribution::V0(
663 TokenPreProgrammedDistributionV0 {
664 distributions: BTreeMap::from([(
665 0,
666 BTreeMap::from([(new_data_contract.owner_id(), 100)]),
667 )]),
668 },
669 )));
670 new_data_contract
671 .tokens_mut()
672 .unwrap()
673 .insert(new_position, new_token_cfg);
674
675 let result = old_data_contract
676 .validate_update_v0(
677 &new_data_contract,
678 &BlockInfo::default_with_time(100000),
679 platform_version,
680 )
681 .expect("failed validate update");
682
683 assert_matches!(
684 result.errors.as_slice(),
685 [ConsensusError::StateError(
686 StateError::PreProgrammedDistributionTimestampInPastError(e)
687 )] if e.token_position() == new_position
688 );
689 }
690
691 #[test]
692 fn should_pass_when_a_well_formed_new_token_is_added() {
693 let platform_version = PlatformVersion::latest();
694
695 let old_data_contract =
696 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
697
698 let mut new_data_contract = old_data_contract.clone();
699 new_data_contract.set_version(old_data_contract.version() + 1);
700
701 let valid_token_cfg = {
703 let mut cfg =
704 TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive());
705
706 cfg.set_base_supply(1_000_000); cfg.set_conventions(TokenConfigurationConvention::V0(
709 TokenConfigurationConventionV0 {
710 localizations: BTreeMap::from([(
711 "en".to_string(),
712 TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 {
713 should_capitalize: true,
714 singular_form: "credit".to_string(),
715 plural_form: "credits".to_string(),
716 }),
717 )]),
718 decimals: 8,
719 },
720 ));
721
722 cfg
723 };
724
725 new_data_contract
727 .tokens_mut()
728 .unwrap()
729 .insert(0, valid_token_cfg);
730
731 let result = old_data_contract
732 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
733 .expect("failed validate update");
734
735 assert!(result.is_valid(), "well‑formed token should be accepted");
736 }
737
738 #[test]
745 fn should_pass_when_groups_and_tokens_unchanged() {
746 let platform_version = PlatformVersion::latest();
747
748 let old_data_contract =
749 get_data_contract_fixture(None, IdentityNonce::default(), 9).data_contract_owned();
750
751 let mut new_data_contract = old_data_contract.clone();
752 new_data_contract.set_version(old_data_contract.version() + 1);
753
754 let result = old_data_contract
755 .validate_update_v0(&new_data_contract, &BlockInfo::default(), platform_version)
756 .expect("failed validate update");
757
758 assert!(result.is_valid());
759 }
760 }
761}