radix_common/data/manifest/
custom_validation.rs

1use 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        // Because of the mis-match between Manifest values and Scrypto TypeKinds/TypeValidations, it's easier to match over values first,
16        // and then check over which validations apply.
17        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(()), // Can't do any validation on an any
24                    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(()), // Can't do any validation on an any
55                    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(()), // Can't do any validation on an any
86                    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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Reference
104                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(), // Assume yes
123                                ReferenceValidation::IsInternal => node_id.is_internal(),
124                                ReferenceValidation::IsInternalTyped(_, _) => node_id.is_internal(), // Assume yes
125                            },
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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
144                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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
164                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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
184                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            // No custom validations apply (yet) to Decimal/PreciseDecimal/NonFungibleLocalId
203            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        // Non-custom values must have non-custom type kinds...
217        // But custom type validations aren't allowed to combine with non-custom type kinds
218        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}