radix_engine_interface/blueprints/resource/
proof_rule.rs

1use crate::blueprints::resource::CompositeRequirement::{AllOf, AnyOf};
2use crate::internal_prelude::*;
3
4use radix_common::define_untyped_manifest_type_wrapper;
5
6#[cfg_attr(
7    feature = "fuzzing",
8    derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
9)]
10#[derive(
11    Debug,
12    Clone,
13    PartialEq,
14    Eq,
15    Hash,
16    Ord,
17    PartialOrd,
18    ManifestSbor,
19    ScryptoCategorize,
20    ScryptoEncode,
21    ScryptoDecode,
22)]
23pub enum ResourceOrNonFungible {
24    NonFungible(NonFungibleGlobalId),
25    Resource(ResourceAddress),
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, ManifestSbor, ScryptoDescribe)]
29pub enum ManifestResourceOrNonFungible {
30    NonFungible(NonFungibleGlobalId),
31    Resource(ManifestResourceAddress),
32}
33
34impl From<ResourceOrNonFungible> for ManifestResourceOrNonFungible {
35    fn from(value: ResourceOrNonFungible) -> Self {
36        match value {
37            ResourceOrNonFungible::NonFungible(non_fungible_global_id) => {
38                Self::NonFungible(non_fungible_global_id)
39            }
40            ResourceOrNonFungible::Resource(resource_address) => {
41                Self::Resource(ManifestResourceAddress::Static(resource_address))
42            }
43        }
44    }
45}
46
47impl Describe<ScryptoCustomTypeKind> for ResourceOrNonFungible {
48    const TYPE_ID: RustTypeId =
49        RustTypeId::WellKnown(well_known_scrypto_custom_types::RESOURCE_OR_NON_FUNGIBLE_TYPE);
50
51    fn type_data() -> ScryptoTypeData<RustTypeId> {
52        well_known_scrypto_custom_types::resource_or_non_fungible_type_data()
53    }
54}
55
56impl From<NonFungibleGlobalId> for ResourceOrNonFungible {
57    fn from(non_fungible_global_id: NonFungibleGlobalId) -> Self {
58        ResourceOrNonFungible::NonFungible(non_fungible_global_id)
59    }
60}
61
62impl From<ResourceAddress> for ResourceOrNonFungible {
63    fn from(resource_address: ResourceAddress) -> Self {
64        ResourceOrNonFungible::Resource(resource_address)
65    }
66}
67
68pub struct ResourceOrNonFungibleList {
69    list: Vec<ResourceOrNonFungible>,
70}
71
72impl<T> From<Vec<T>> for ResourceOrNonFungibleList
73where
74    T: Into<ResourceOrNonFungible>,
75{
76    fn from(addresses: Vec<T>) -> Self {
77        ResourceOrNonFungibleList {
78            list: addresses.into_iter().map(|a| a.into()).collect(),
79        }
80    }
81}
82
83/// Resource Proof Rules
84#[cfg_attr(
85    feature = "fuzzing",
86    derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
87)]
88#[derive(
89    Debug,
90    Clone,
91    PartialEq,
92    Eq,
93    Hash,
94    Ord,
95    PartialOrd,
96    ManifestSbor,
97    ScryptoCategorize,
98    ScryptoEncode,
99    ScryptoDecode,
100)]
101pub enum BasicRequirement {
102    Require(ResourceOrNonFungible),
103    AmountOf(Decimal, ResourceAddress),
104    CountOf(u8, Vec<ResourceOrNonFungible>),
105    AllOf(Vec<ResourceOrNonFungible>),
106    AnyOf(Vec<ResourceOrNonFungible>),
107}
108
109impl Describe<ScryptoCustomTypeKind> for BasicRequirement {
110    const TYPE_ID: RustTypeId =
111        RustTypeId::WellKnown(well_known_scrypto_custom_types::BASIC_REQUIREMENT_TYPE);
112
113    fn type_data() -> ScryptoTypeData<RustTypeId> {
114        well_known_scrypto_custom_types::basic_requirement_type_data()
115    }
116}
117
118impl From<ResourceAddress> for CompositeRequirement {
119    fn from(resource_address: ResourceAddress) -> Self {
120        CompositeRequirement::BasicRequirement(BasicRequirement::Require(resource_address.into()))
121    }
122}
123
124impl From<NonFungibleGlobalId> for CompositeRequirement {
125    fn from(id: NonFungibleGlobalId) -> Self {
126        CompositeRequirement::BasicRequirement(BasicRequirement::Require(id.into()))
127    }
128}
129
130impl From<ResourceOrNonFungible> for CompositeRequirement {
131    fn from(resource_or_non_fungible: ResourceOrNonFungible) -> Self {
132        CompositeRequirement::BasicRequirement(BasicRequirement::Require(resource_or_non_fungible))
133    }
134}
135
136define_untyped_manifest_type_wrapper!(
137    BasicRequirement => ManifestBasicRequirement(EnumVariantValue<ManifestCustomValueKind, ManifestCustomValue>)
138);
139
140#[cfg_attr(
141    feature = "fuzzing",
142    derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
143)]
144#[derive(
145    Debug,
146    Clone,
147    PartialEq,
148    Eq,
149    Hash,
150    Ord,
151    PartialOrd,
152    ManifestSbor,
153    ScryptoCategorize,
154    ScryptoEncode,
155    ScryptoDecode,
156)]
157pub enum CompositeRequirement {
158    BasicRequirement(BasicRequirement),
159    AnyOf(Vec<CompositeRequirement>),
160    AllOf(Vec<CompositeRequirement>),
161}
162
163impl Describe<ScryptoCustomTypeKind> for CompositeRequirement {
164    const TYPE_ID: RustTypeId =
165        RustTypeId::WellKnown(well_known_scrypto_custom_types::COMPOSITE_REQUIREMENT_TYPE);
166
167    fn type_data() -> ScryptoTypeData<RustTypeId> {
168        well_known_scrypto_custom_types::composite_requirement_type_data()
169    }
170}
171
172impl CompositeRequirement {
173    pub fn or(self, other: CompositeRequirement) -> Self {
174        match self {
175            CompositeRequirement::AnyOf(mut rules) => {
176                rules.push(other);
177                AnyOf(rules)
178            }
179            _ => AnyOf(vec![self, other]),
180        }
181    }
182
183    pub fn and(self, other: CompositeRequirement) -> Self {
184        match self {
185            CompositeRequirement::AllOf(mut rules) => {
186                rules.push(other);
187                AllOf(rules)
188            }
189            _ => AllOf(vec![self, other]),
190        }
191    }
192}
193
194define_untyped_manifest_type_wrapper!(
195    CompositeRequirement => ManifestCompositeRequirement(EnumVariantValue<ManifestCustomValueKind, ManifestCustomValue>)
196);
197
198/// A requirement for the immediate caller's package to equal the given package.
199pub fn package_of_direct_caller(package: PackageAddress) -> ResourceOrNonFungible {
200    ResourceOrNonFungible::NonFungible(NonFungibleGlobalId::package_of_direct_caller_badge(package))
201}
202
203/// A requirement for the global ancestor of the actor who made the latest global call to either be:
204/// * The main module of the given global component (pass a `ComponentAddress` or `GlobalAddress`)
205/// * A package function on the given blueprint (pass `(PackageAddress, String)` or `Blueprint`)
206pub fn global_caller(global_caller: impl Into<GlobalCaller>) -> ResourceOrNonFungible {
207    ResourceOrNonFungible::NonFungible(NonFungibleGlobalId::global_caller_badge(global_caller))
208}
209
210/// A requirement for the transaction to be signed using a specific key.
211pub fn signature(public_key: impl HasPublicKeyHash) -> ResourceOrNonFungible {
212    ResourceOrNonFungible::NonFungible(NonFungibleGlobalId::from_public_key(public_key))
213}
214
215/// A requirement for the transaction to be a system transaction.
216pub fn system_execution(transaction_type: SystemExecution) -> NonFungibleGlobalId {
217    transaction_type.into()
218}
219
220pub fn require<T>(required: T) -> CompositeRequirement
221where
222    T: Into<CompositeRequirement>,
223{
224    required.into()
225}
226
227pub fn require_any_of<T>(resources: T) -> CompositeRequirement
228where
229    T: Into<ResourceOrNonFungibleList>,
230{
231    let list: ResourceOrNonFungibleList = resources.into();
232    CompositeRequirement::BasicRequirement(BasicRequirement::AnyOf(list.list))
233}
234
235pub fn require_all_of<T>(resources: T) -> CompositeRequirement
236where
237    T: Into<ResourceOrNonFungibleList>,
238{
239    let list: ResourceOrNonFungibleList = resources.into();
240    CompositeRequirement::BasicRequirement(BasicRequirement::AllOf(list.list))
241}
242
243pub fn require_n_of<C, T>(count: C, resources: T) -> CompositeRequirement
244where
245    C: Into<u8>,
246    T: Into<ResourceOrNonFungibleList>,
247{
248    let list: ResourceOrNonFungibleList = resources.into();
249    CompositeRequirement::BasicRequirement(BasicRequirement::CountOf(count.into(), list.list))
250}
251
252pub fn require_amount<D, T>(amount: D, resource: T) -> CompositeRequirement
253where
254    D: Into<Decimal>,
255    T: Into<ResourceAddress>,
256{
257    CompositeRequirement::BasicRequirement(BasicRequirement::AmountOf(
258        amount.into(),
259        resource.into(),
260    ))
261}
262
263#[cfg_attr(
264    feature = "fuzzing",
265    derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
266)]
267#[derive(
268    Debug,
269    Clone,
270    PartialEq,
271    Eq,
272    Hash,
273    Ord,
274    PartialOrd,
275    ManifestSbor,
276    ScryptoCategorize,
277    ScryptoEncode,
278    ScryptoDecode,
279)]
280pub enum AccessRule {
281    AllowAll,
282    DenyAll,
283    Protected(CompositeRequirement),
284}
285
286impl Describe<ScryptoCustomTypeKind> for AccessRule {
287    const TYPE_ID: RustTypeId =
288        RustTypeId::WellKnown(well_known_scrypto_custom_types::ACCESS_RULE_TYPE);
289
290    fn type_data() -> ScryptoTypeData<RustTypeId> {
291        well_known_scrypto_custom_types::access_rule_type_data()
292    }
293}
294
295impl From<CompositeRequirement> for AccessRule {
296    fn from(value: CompositeRequirement) -> Self {
297        AccessRule::Protected(value)
298    }
299}
300
301define_untyped_manifest_type_wrapper!(
302    AccessRule => ManifestAccessRule(EnumVariantValue<ManifestCustomValueKind, ManifestCustomValue>)
303);
304
305pub trait AccessRuleVisitor {
306    type Error;
307    fn visit(&mut self, node: &CompositeRequirement, depth: usize) -> Result<(), Self::Error>;
308}
309
310impl AccessRule {
311    pub fn dfs_traverse_nodes<V: AccessRuleVisitor>(
312        &self,
313        visitor: &mut V,
314    ) -> Result<(), V::Error> {
315        match self {
316            AccessRule::Protected(node) => node.dfs_traverse_recursive(visitor, 0),
317            _ => Ok(()),
318        }
319    }
320}
321
322impl CompositeRequirement {
323    fn dfs_traverse_recursive<V: AccessRuleVisitor>(
324        &self,
325        visitor: &mut V,
326        depth: usize,
327    ) -> Result<(), V::Error> {
328        visitor.visit(self, depth)?;
329
330        match self {
331            CompositeRequirement::BasicRequirement(..) => {}
332            CompositeRequirement::AnyOf(nodes) | CompositeRequirement::AllOf(nodes) => {
333                for node in nodes {
334                    node.dfs_traverse_recursive(visitor, depth + 1)?;
335                }
336            }
337        }
338
339        Ok(())
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use radix_common::prelude::*;
347
348    #[test]
349    fn require_signature_secp256k1() {
350        let private_key = Secp256k1PrivateKey::from_u64(1).unwrap();
351        let public_key = private_key.public_key();
352
353        let r1 = rule!(require(NonFungibleGlobalId::from_public_key(public_key)));
354        let r2 = rule!(require(signature(public_key)));
355
356        assert_eq!(r1, r2);
357    }
358
359    #[test]
360    fn require_signature_ed25519() {
361        let private_key = Ed25519PrivateKey::from_u64(1).unwrap();
362        let public_key = private_key.public_key();
363
364        let r1 = rule!(require(NonFungibleGlobalId::from_public_key(public_key)));
365        let r2 = rule!(require(signature(public_key)));
366
367        assert_eq!(r1, r2);
368    }
369
370    #[test]
371    fn access_rule_can_be_converted_to_manifest_access_rule() {
372        let _ = ManifestAccessRule::from(rule!(require(XRD) && require(SYSTEM_EXECUTION_RESOURCE)));
373    }
374
375    #[test]
376    fn sbor_encoding_of_access_rule_and_manifest_access_rule_are_the_same() {
377        // Arrange
378        let rule = rule!(require(XRD) && require(SYSTEM_EXECUTION_RESOURCE));
379
380        // Act
381        let encoded_access_rule = scrypto_encode(&rule).unwrap();
382        let encoded_manifest_access_rule =
383            manifest_encode(&ManifestAccessRule::from(rule)).unwrap();
384
385        // Assert
386        let (local_type_id, versioned_schema) =
387            generate_full_schema_from_single_type::<AccessRule, ScryptoCustomSchema>();
388        validate_payload_against_schema::<ScryptoCustomExtension, _>(
389            &encoded_access_rule,
390            versioned_schema.v1(),
391            local_type_id,
392            &(),
393            SCRYPTO_SBOR_V1_MAX_DEPTH,
394        )
395        .expect("Scrypto access rule payload should match AccessRule schema");
396        validate_payload_against_schema::<ManifestCustomExtension, _>(
397            &encoded_manifest_access_rule,
398            versioned_schema.v1(),
399            local_type_id,
400            &(),
401            MANIFEST_SBOR_V1_MAX_DEPTH,
402        )
403        .expect("Manifest access rule payload should match AccessRule schema");
404
405        let (local_type_id, versioned_schema) =
406            generate_full_schema_from_single_type::<ManifestAccessRule, ScryptoCustomSchema>();
407        validate_payload_against_schema::<ScryptoCustomExtension, _>(
408            &encoded_access_rule,
409            versioned_schema.v1(),
410            local_type_id,
411            &(),
412            SCRYPTO_SBOR_V1_MAX_DEPTH,
413        )
414        .expect("Scrypto access rule payload should match Manifest schema");
415        validate_payload_against_schema::<ManifestCustomExtension, _>(
416            &encoded_manifest_access_rule,
417            versioned_schema.v1(),
418            local_type_id,
419            &(),
420            MANIFEST_SBOR_V1_MAX_DEPTH,
421        )
422        .expect("Manifest access rule payload should match Manifest schema");
423    }
424
425    #[test]
426    fn non_enums_cant_be_decoded_as_a_manifest_access_rule() {
427        // Arrange
428        let enum_value = ManifestValue::U8 { value: 1 };
429        let encoded = manifest_encode(&enum_value).unwrap();
430
431        // Act
432        let manifest_access_rule = manifest_decode::<ManifestAccessRule>(&encoded);
433
434        // Assert
435        manifest_access_rule.expect_err("Expected decoding to fail");
436    }
437}