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