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<'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        // 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 = 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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Reference
108                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(), // Assume yes
127                                ReferenceValidation::IsInternal => node_id.is_internal(),
128                                ReferenceValidation::IsInternalTyped(_, _) => node_id.is_internal(), // Assume yes
129                            },
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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
148                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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
168                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                // We know from `custom_value_kind_matches_type_kind` that this has a ScryptoCustomTypeKind::Own
188                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            // No custom validations apply (yet) to Decimal/PreciseDecimal/NonFungibleLocalId
207            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        // Non-custom values must have non-custom type kinds...
221        // But custom type validations aren't allowed to combine with non-custom type kinds
222        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}