radix_common/data/manifest/
custom_validation.rs1use super::model::*;
2use crate::data::scrypto::{
3 ReferenceValidation, ScryptoCustomTypeKind, ScryptoCustomTypeValidation,
4};
5use crate::internal_prelude::*;
6
7impl<'a> ValidatableCustomExtension<()> for ManifestCustomExtension {
8 fn apply_validation_for_custom_value<'de>(
9 schema: &Schema<Self::CustomSchema>,
10 custom_value: &<Self::CustomTraversal as traversal::CustomTraversal>::CustomTerminalValueRef<'de>,
11 type_id: LocalTypeId,
12 _: &(),
13 ) -> Result<(), PayloadValidationError<Self>> {
14 let ManifestCustomTerminalValueRef(custom_value) = custom_value;
15 match custom_value {
18 ManifestCustomValue::Expression(ManifestExpression::EntireWorktop) => {
19 let element_type = match schema
20 .resolve_type_kind(type_id)
21 .ok_or(PayloadValidationError::SchemaInconsistency)?
22 {
23 TypeKind::Any => return Ok(()), TypeKind::Array { element_type } => element_type,
25 _ => return Err(PayloadValidationError::SchemaInconsistency),
26 };
27 let element_type_kind = schema
28 .resolve_type_kind(*element_type)
29 .ok_or(PayloadValidationError::SchemaInconsistency)?;
30 match element_type_kind {
31 TypeKind::Any |
32 TypeKind::Custom(ScryptoCustomTypeKind::Own) => {
33 let element_type_validation = schema.resolve_type_validation(*element_type).ok_or(PayloadValidationError::SchemaInconsistency)?;
34 match element_type_validation {
35 TypeValidation::None => {},
36 TypeValidation::Custom(ScryptoCustomTypeValidation::Own(own_validation)) => {
37 if !own_validation.could_match_manifest_bucket() {
38 return Err(PayloadValidationError::ValidationError(ValidationError::CustomError(format!("ENTIRE_WORKTOP gives an array of buckets, but an array of Own<{:?}> was expected", own_validation))))
39 }
40 },
41 _ => return Err(PayloadValidationError::SchemaInconsistency),
42 }
43 },
44 _ => {
45 return Err(PayloadValidationError::ValidationError(ValidationError::CustomError(format!("ENTIRE_WORKTOP gives an array of buckets, but an array of {:?} was expected", element_type_kind))))
46 }
47 };
48 }
49 ManifestCustomValue::Expression(ManifestExpression::EntireAuthZone) => {
50 let element_type = match schema
51 .resolve_type_kind(type_id)
52 .ok_or(PayloadValidationError::SchemaInconsistency)?
53 {
54 TypeKind::Any => return Ok(()), TypeKind::Array { element_type } => element_type,
56 _ => return Err(PayloadValidationError::SchemaInconsistency),
57 };
58 let element_type_kind = schema
59 .resolve_type_kind(*element_type)
60 .ok_or(PayloadValidationError::SchemaInconsistency)?;
61 match element_type_kind {
62 TypeKind::Any |
63 TypeKind::Custom(ScryptoCustomTypeKind::Own) => {
64 let element_type_validation = schema.resolve_type_validation(*element_type).ok_or(PayloadValidationError::SchemaInconsistency)?;
65 match element_type_validation {
66 TypeValidation::None => {},
67 TypeValidation::Custom(ScryptoCustomTypeValidation::Own(own_validation)) => {
68 if !own_validation.could_match_manifest_proof() {
69 return Err(PayloadValidationError::ValidationError(ValidationError::CustomError(format!("ENTIRE_AUTH_ZONE gives an array of proofs, but an array of Own<{:?}> was expected", own_validation))))
70 }
71 },
72 _ => return Err(PayloadValidationError::SchemaInconsistency),
73 }
74 },
75 _ => {
76 return Err(PayloadValidationError::ValidationError(ValidationError::CustomError(format!("ENTIRE_AUTH_ZONE gives an array of proofs, but an array of {:?} was expected", element_type_kind))))
77 }
78 };
79 }
80 ManifestCustomValue::Blob(_) => {
81 let element_type = match schema
82 .resolve_type_kind(type_id)
83 .ok_or(PayloadValidationError::SchemaInconsistency)?
84 {
85 TypeKind::Any => return Ok(()), TypeKind::Array { element_type } => element_type,
87 _ => return Err(PayloadValidationError::SchemaInconsistency),
88 };
89 let element_type_kind = schema
90 .resolve_type_kind(*element_type)
91 .ok_or(PayloadValidationError::SchemaInconsistency)?;
92 let is_valid = match element_type_kind {
93 TypeKind::Any => true,
94 TypeKind::U8 => true,
95 _ => false,
96 };
97 if !is_valid {
98 return Err(PayloadValidationError::ValidationError(
99 ValidationError::CustomError(format!(
100 "Blob provides a U8 array, but an array of {:?} was expected",
101 element_type_kind
102 )),
103 ));
104 }
105 }
106 ManifestCustomValue::Address(address) => {
107 let validation = schema
109 .resolve_type_validation(type_id)
110 .ok_or(PayloadValidationError::SchemaInconsistency)?;
111 match validation {
112 TypeValidation::None => {}
113 TypeValidation::Custom(ScryptoCustomTypeValidation::Reference(
114 reference_validation,
115 )) => {
116 let is_valid = match address {
117 ManifestAddress::Static(node_id) => match reference_validation {
118 ReferenceValidation::IsGlobal => node_id.is_global(),
119 ReferenceValidation::IsGlobalPackage => node_id.is_global_package(),
120 ReferenceValidation::IsGlobalComponent => {
121 node_id.is_global_component()
122 }
123 ReferenceValidation::IsGlobalResourceManager => {
124 node_id.is_global_resource_manager()
125 }
126 ReferenceValidation::IsGlobalTyped(_, _) => node_id.is_global(), ReferenceValidation::IsInternal => node_id.is_internal(),
128 ReferenceValidation::IsInternalTyped(_, _) => node_id.is_internal(), },
130 ManifestAddress::Named(_) => {
131 reference_validation.could_match_manifest_address()
132 }
133 };
134 if !is_valid {
135 return Err(PayloadValidationError::ValidationError(
136 ValidationError::CustomError(format!(
137 "Expected Reference<{:?}>",
138 reference_validation
139 )),
140 ));
141 }
142 }
143 _ => return Err(PayloadValidationError::SchemaInconsistency),
144 };
145 }
146 ManifestCustomValue::Bucket(_) => {
147 let validation = schema
149 .resolve_type_validation(type_id)
150 .ok_or(PayloadValidationError::SchemaInconsistency)?;
151 match validation {
152 TypeValidation::None => {}
153 TypeValidation::Custom(ScryptoCustomTypeValidation::Own(own_validation)) => {
154 if !own_validation.could_match_manifest_bucket() {
155 return Err(PayloadValidationError::ValidationError(
156 ValidationError::CustomError(format!(
157 "Expected Own<{:?}>, but found manifest bucket",
158 own_validation
159 )),
160 ));
161 }
162 }
163 _ => return Err(PayloadValidationError::SchemaInconsistency),
164 };
165 }
166 ManifestCustomValue::Proof(_) => {
167 let validation = schema
169 .resolve_type_validation(type_id)
170 .ok_or(PayloadValidationError::SchemaInconsistency)?;
171 match validation {
172 TypeValidation::None => {}
173 TypeValidation::Custom(ScryptoCustomTypeValidation::Own(own_validation)) => {
174 if !own_validation.could_match_manifest_proof() {
175 return Err(PayloadValidationError::ValidationError(
176 ValidationError::CustomError(format!(
177 "Expected Own<{:?}>, but found manifest proof",
178 own_validation
179 )),
180 ));
181 }
182 }
183 _ => return Err(PayloadValidationError::SchemaInconsistency),
184 };
185 }
186 ManifestCustomValue::AddressReservation(_) => {
187 let validation = schema
189 .resolve_type_validation(type_id)
190 .ok_or(PayloadValidationError::SchemaInconsistency)?;
191 match validation {
192 TypeValidation::None => {}
193 TypeValidation::Custom(ScryptoCustomTypeValidation::Own(own_validation)) => {
194 if !own_validation.could_match_manifest_address_reservation() {
195 return Err(PayloadValidationError::ValidationError(
196 ValidationError::CustomError(format!(
197 "Expected Own<{:?}>, but found manifest address reservation",
198 own_validation
199 )),
200 ));
201 }
202 }
203 _ => return Err(PayloadValidationError::SchemaInconsistency),
204 };
205 }
206 ManifestCustomValue::Decimal(_) => {}
208 ManifestCustomValue::PreciseDecimal(_) => {}
209 ManifestCustomValue::NonFungibleLocalId(_) => {}
210 };
211 Ok(())
212 }
213
214 fn apply_custom_type_validation_for_non_custom_value<'de>(
215 _: &Schema<Self::CustomSchema>,
216 _: &<Self::CustomSchema as CustomSchema>::CustomTypeValidation,
217 _: &TerminalValueRef<'de, Self::CustomTraversal>,
218 _: &(),
219 ) -> Result<(), PayloadValidationError<Self>> {
220 Err(PayloadValidationError::SchemaInconsistency)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 use crate::constants::*;
231 use crate::data::scrypto::model::NonFungibleLocalId;
232 use crate::data::scrypto::{well_known_scrypto_custom_types, ScryptoValue};
233 use crate::data::scrypto::{ScryptoCustomSchema, ScryptoDescribe};
234 use crate::math::{Decimal, PreciseDecimal};
235 use crate::types::{PackageAddress, ResourceAddress};
236
237 pub struct Bucket;
238
239 impl Describe<ScryptoCustomTypeKind> for Bucket {
240 const TYPE_ID: RustTypeId =
241 RustTypeId::WellKnown(well_known_scrypto_custom_types::OWN_BUCKET_TYPE);
242
243 fn type_data() -> TypeData<ScryptoCustomTypeKind, RustTypeId> {
244 well_known_scrypto_custom_types::own_bucket_type_data()
245 }
246 }
247
248 pub struct Proof;
249
250 impl Describe<ScryptoCustomTypeKind> for Proof {
251 const TYPE_ID: RustTypeId =
252 RustTypeId::WellKnown(well_known_scrypto_custom_types::OWN_PROOF_TYPE);
253
254 fn type_data() -> TypeData<ScryptoCustomTypeKind, RustTypeId> {
255 well_known_scrypto_custom_types::own_proof_type_data()
256 }
257 }
258
259 type MyScryptoTuple = (
260 ResourceAddress,
261 Vec<u8>,
262 Bucket,
263 Proof,
264 Decimal,
265 PreciseDecimal,
266 NonFungibleLocalId,
267 Vec<Proof>,
268 Vec<Bucket>,
269 );
270
271 type Any = ScryptoValue;
272
273 #[test]
274 fn valid_manifest_composite_value_passes_validation_against_radix_blueprint_schema_init() {
275 let payload = manifest_encode(&(
276 ManifestValue::Custom {
277 value: ManifestCustomValue::Address(ManifestAddress::Static(
278 XRD.as_node_id().clone(),
279 )),
280 },
281 ManifestValue::Custom {
282 value: ManifestCustomValue::Blob(ManifestBlobRef([0; 32])),
283 },
284 ManifestValue::Custom {
285 value: ManifestCustomValue::Bucket(ManifestBucket(0)),
286 },
287 ManifestValue::Custom {
288 value: ManifestCustomValue::Proof(ManifestProof(0)),
289 },
290 ManifestValue::Custom {
291 value: ManifestCustomValue::Decimal(ManifestDecimal([0; DECIMAL_SIZE])),
292 },
293 ManifestValue::Custom {
294 value: ManifestCustomValue::PreciseDecimal(ManifestPreciseDecimal(
295 [0; PRECISE_DECIMAL_SIZE],
296 )),
297 },
298 ManifestValue::Custom {
299 value: ManifestCustomValue::NonFungibleLocalId(ManifestNonFungibleLocalId::String(
300 "hello".to_string(),
301 )),
302 },
303 ManifestValue::Custom {
304 value: ManifestCustomValue::Expression(ManifestExpression::EntireAuthZone),
305 },
306 ManifestValue::Custom {
307 value: ManifestCustomValue::Expression(ManifestExpression::EntireWorktop),
308 },
309 ))
310 .unwrap();
311
312 let (type_id, schema) =
313 generate_full_schema_from_single_type::<MyScryptoTuple, ScryptoCustomSchema>();
314
315 let result = validate_payload_against_schema::<ManifestCustomExtension, _>(
316 &payload,
317 schema.v1(),
318 type_id,
319 &(),
320 MANIFEST_SBOR_V1_MAX_DEPTH,
321 );
322
323 result.expect("Validation check failed");
324 }
325
326 #[test]
327 fn manifest_address_fails_validation_against_mismatching_radix_blueprint_schema_init() {
328 let payload = manifest_encode(&ManifestValue::Custom {
329 value: ManifestCustomValue::Address(ManifestAddress::Static(XRD.as_node_id().clone())),
330 })
331 .unwrap();
332
333 expect_matches::<ResourceAddress>(&payload);
334 expect_matches::<Any>(&payload);
335 expect_does_not_match::<PackageAddress>(&payload);
336 expect_does_not_match::<Bucket>(&payload);
337 expect_does_not_match::<u8>(&payload);
338 }
339
340 #[test]
341 fn manifest_blob_fails_validation_against_mismatching_radix_blueprint_schema_init() {
342 let payload = manifest_encode(&ManifestValue::Custom {
343 value: ManifestCustomValue::Blob(ManifestBlobRef([0; 32])),
344 })
345 .unwrap();
346
347 expect_matches::<Vec<u8>>(&payload);
348 expect_matches::<Any>(&payload);
349 expect_does_not_match::<Vec<Bucket>>(&payload);
350 expect_does_not_match::<Vec<Proof>>(&payload);
351 expect_does_not_match::<Proof>(&payload);
352 expect_does_not_match::<u8>(&payload);
353 }
354
355 #[test]
356 fn manifest_entire_worktop_expression_fails_validation_against_mismatching_radix_blueprint_schema_init(
357 ) {
358 let payload = manifest_encode(&ManifestValue::Custom {
359 value: ManifestCustomValue::Expression(ManifestExpression::EntireWorktop),
360 })
361 .unwrap();
362
363 expect_matches::<Vec<Bucket>>(&payload);
364 expect_matches::<Any>(&payload);
365 expect_does_not_match::<Vec<Proof>>(&payload);
366 expect_does_not_match::<Proof>(&payload);
367 expect_does_not_match::<Bucket>(&payload);
368 expect_does_not_match::<u8>(&payload);
369 }
370
371 #[test]
372 fn manifest_entire_auth_zone_expression_fails_validation_against_mismatching_radix_blueprint_schema_init(
373 ) {
374 let payload = manifest_encode(&ManifestValue::Custom {
375 value: ManifestCustomValue::Expression(ManifestExpression::EntireAuthZone),
376 })
377 .unwrap();
378
379 expect_matches::<Vec<Proof>>(&payload);
380 expect_matches::<Any>(&payload);
381 expect_does_not_match::<Vec<Bucket>>(&payload);
382 expect_does_not_match::<Proof>(&payload);
383 expect_does_not_match::<Bucket>(&payload);
384 expect_does_not_match::<u8>(&payload);
385 }
386
387 fn expect_matches<T: ScryptoDescribe>(payload: &[u8]) {
388 let (type_id, schema) = generate_full_schema_from_single_type::<T, ScryptoCustomSchema>();
389
390 let result = validate_payload_against_schema::<ManifestCustomExtension, _>(
391 &payload,
392 schema.v1(),
393 type_id,
394 &(),
395 MANIFEST_SBOR_V1_MAX_DEPTH,
396 );
397
398 result.expect("Expected validation to succeed");
399 }
400
401 fn expect_does_not_match<T: ScryptoDescribe>(payload: &[u8]) {
402 let (type_id, schema) = generate_full_schema_from_single_type::<T, ScryptoCustomSchema>();
403
404 let result = validate_payload_against_schema::<ManifestCustomExtension, _>(
405 &payload,
406 schema.v1(),
407 type_id,
408 &(),
409 MANIFEST_SBOR_V1_MAX_DEPTH,
410 );
411
412 matches!(
413 result,
414 Err(LocatedValidationError {
415 error: PayloadValidationError::ValidationError(_),
416 ..
417 })
418 );
419 }
420}