scrypto/resource/
proof.rs

1use crate::resource::*;
2use crate::runtime::Runtime;
3use crate::*;
4use radix_common::prelude::*;
5use radix_common::traits::NonFungibleData;
6use radix_engine_interface::blueprints::resource::*;
7use runtime::LocalAuthZone;
8use scrypto::engine::scrypto_env::ScryptoVmV1Api;
9
10// Different from the native SDK, in Scrypto we use `CheckedProof`, `CheckedFungibleProof`
11// and `CheckedNonFungibleProof` (instead of `Proof`/`FungibleProof`/`NonFungibleProof`)
12// to prevent developers from reading proof states (and having business logic relying on them)
13// without checking the resource address.
14
15//========
16// Traits
17//========
18
19pub trait ScryptoUncheckedProof {
20    type CheckedProofType;
21    type ResourceManagerType;
22
23    /// Checks the resource address of this proof and panics if it's unexpected.
24    fn check(self, expected_resource_address: ResourceAddress) -> Self::CheckedProofType;
25
26    /// Checks the resource address of this proof and panics with custom error message if it's unexpected.
27    fn check_with_message<S: ToString>(
28        self,
29        expected_resource_address: ResourceAddress,
30        custom_error_message: S,
31    ) -> Self::CheckedProofType;
32
33    /// Skips checking and converts this proof into a "checked" proof.
34    ///
35    /// # Warning!
36    /// Be sure to validate the resource address before reading data from the proof
37    /// in your custom validation logic!
38    fn skip_checking(self) -> Self::CheckedProofType;
39
40    fn resource_address(&self) -> ResourceAddress;
41
42    fn resource_manager(&self) -> Self::ResourceManagerType;
43
44    fn drop(self);
45
46    fn clone(&self) -> Self;
47
48    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O;
49}
50
51pub trait ScryptoProof {
52    type ResourceManagerType;
53
54    fn contains_amount(&self, amount: Decimal) -> bool;
55
56    fn amount(&self) -> Decimal;
57
58    fn resource_address(&self) -> ResourceAddress;
59
60    fn resource_manager(&self) -> Self::ResourceManagerType;
61
62    fn drop(self);
63
64    fn clone(&self) -> Self;
65
66    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O;
67}
68
69pub trait ScryptoGenericProof {
70    fn as_fungible(&self) -> CheckedFungibleProof;
71
72    fn as_non_fungible(&self) -> CheckedNonFungibleProof;
73}
74
75pub trait ScryptoFungibleProof {}
76
77pub trait ScryptoNonFungibleProof {
78    fn contains_non_fungible(&self, id: &NonFungibleLocalId) -> bool;
79
80    fn contains_non_fungibles(&self, ids: &IndexSet<NonFungibleLocalId>) -> bool;
81
82    fn non_fungible_local_ids(&self) -> IndexSet<NonFungibleLocalId>;
83
84    fn non_fungible_local_id(&self) -> NonFungibleLocalId;
85
86    fn non_fungible_global_id(&self) -> NonFungibleGlobalId;
87
88    fn non_fungible<T: NonFungibleData>(&self) -> NonFungible<T>;
89
90    fn non_fungibles<T: NonFungibleData>(&self) -> Vec<NonFungible<T>>;
91}
92
93//=====================
94// Checked proof types
95//=====================
96
97/// Represents an address-checked proof
98///
99/// This may become unnecessary when `Proof<X>` is supported.
100///
101// TODO: cache resource address in `CheckedProof`!
102#[derive(Debug, PartialEq, Eq, Hash, ScryptoSbor)]
103#[sbor(transparent)]
104pub struct CheckedProof(pub Proof);
105
106#[derive(Debug, PartialEq, Eq, Hash, ScryptoSbor)]
107#[sbor(transparent)]
108pub struct CheckedFungibleProof(pub CheckedProof);
109
110#[derive(Debug, PartialEq, Eq, Hash, ScryptoSbor)]
111#[sbor(transparent)]
112pub struct CheckedNonFungibleProof(pub CheckedProof);
113
114impl From<CheckedFungibleProof> for CheckedProof {
115    fn from(value: CheckedFungibleProof) -> Self {
116        value.0
117    }
118}
119
120impl From<CheckedNonFungibleProof> for CheckedProof {
121    fn from(value: CheckedNonFungibleProof) -> Self {
122        value.0
123    }
124}
125
126//=================
127// Unchecked proof
128//=================
129
130impl ScryptoUncheckedProof for Proof {
131    type CheckedProofType = CheckedProof;
132    type ResourceManagerType = ResourceManager;
133
134    fn check(self, expected_resource_address: ResourceAddress) -> CheckedProof {
135        let actual_resource_address = self.resource_address();
136
137        if actual_resource_address != expected_resource_address {
138            Runtime::panic(format!(
139                "Invalid proof: Expected {:?}, but got {:?}",
140                expected_resource_address, actual_resource_address
141            ))
142        }
143
144        CheckedProof(self)
145    }
146
147    fn check_with_message<S: ToString>(
148        self,
149        expected_resource_address: ResourceAddress,
150        custom_error_message: S,
151    ) -> CheckedProof {
152        let actual_resource_address = self.resource_address();
153
154        if actual_resource_address != expected_resource_address {
155            Runtime::panic(custom_error_message.to_string())
156        }
157
158        CheckedProof(self)
159    }
160
161    fn skip_checking(self) -> CheckedProof {
162        CheckedProof(self)
163    }
164
165    fn resource_address(&self) -> ResourceAddress {
166        let rtn = ScryptoVmV1Api::object_call(
167            self.0.as_node_id(),
168            PROOF_GET_RESOURCE_ADDRESS_IDENT,
169            scrypto_encode(&ProofGetResourceAddressInput {}).unwrap(),
170        );
171        scrypto_decode(&rtn).unwrap()
172    }
173
174    fn resource_manager(&self) -> Self::ResourceManagerType {
175        self.resource_address().into()
176    }
177
178    fn drop(self) {
179        ScryptoVmV1Api::blueprint_call(
180            RESOURCE_PACKAGE,
181            if ScryptoVmV1Api::object_instance_of(
182                self.0.as_node_id(),
183                &BlueprintId {
184                    package_address: RESOURCE_PACKAGE,
185                    blueprint_name: FUNGIBLE_PROOF_BLUEPRINT.to_owned(),
186                },
187            ) {
188                FUNGIBLE_PROOF_BLUEPRINT
189            } else {
190                NON_FUNGIBLE_PROOF_BLUEPRINT
191            },
192            PROOF_DROP_IDENT,
193            scrypto_encode(&ProofDropInput {
194                proof: Proof(self.0),
195            })
196            .unwrap(),
197        );
198    }
199
200    fn clone(&self) -> Self {
201        let rtn = ScryptoVmV1Api::object_call(
202            self.0.as_node_id(),
203            PROOF_CLONE_IDENT,
204            scrypto_encode(&ProofCloneInput {}).unwrap(),
205        );
206        scrypto_decode(&rtn).unwrap()
207    }
208
209    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
210        LocalAuthZone::push(self.clone());
211        let output = f();
212        LocalAuthZone::pop()
213            .expect("Authorized closure changed auth zone proof stack")
214            .drop();
215        output
216    }
217}
218
219impl ScryptoUncheckedProof for FungibleProof {
220    type CheckedProofType = CheckedFungibleProof;
221    type ResourceManagerType = FungibleResourceManager;
222
223    fn check(self, expected_resource_address: ResourceAddress) -> Self::CheckedProofType {
224        CheckedFungibleProof(Proof::check(self.0, expected_resource_address))
225    }
226
227    fn check_with_message<S: ToString>(
228        self,
229        expected_resource_address: ResourceAddress,
230        custom_error_message: S,
231    ) -> Self::CheckedProofType {
232        CheckedFungibleProof(Proof::check_with_message(
233            self.0,
234            expected_resource_address,
235            custom_error_message,
236        ))
237    }
238
239    fn skip_checking(self) -> Self::CheckedProofType {
240        CheckedFungibleProof(Proof::skip_checking(self.0))
241    }
242
243    fn resource_address(&self) -> ResourceAddress {
244        self.0.resource_address()
245    }
246
247    fn resource_manager(&self) -> Self::ResourceManagerType {
248        self.resource_address().into()
249    }
250
251    fn drop(self) {
252        self.0.drop()
253    }
254
255    fn clone(&self) -> Self {
256        FungibleProof(self.0.clone())
257    }
258
259    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
260        self.0.authorize(f)
261    }
262}
263
264impl ScryptoUncheckedProof for NonFungibleProof {
265    type CheckedProofType = CheckedNonFungibleProof;
266    type ResourceManagerType = NonFungibleResourceManager;
267
268    fn check(self, expected_resource_address: ResourceAddress) -> Self::CheckedProofType {
269        CheckedNonFungibleProof(Proof::check(self.0, expected_resource_address))
270    }
271
272    fn check_with_message<S: ToString>(
273        self,
274        expected_resource_address: ResourceAddress,
275        custom_error_message: S,
276    ) -> Self::CheckedProofType {
277        CheckedNonFungibleProof(Proof::check_with_message(
278            self.0,
279            expected_resource_address,
280            custom_error_message,
281        ))
282    }
283
284    fn skip_checking(self) -> Self::CheckedProofType {
285        CheckedNonFungibleProof(Proof::skip_checking(self.0))
286    }
287
288    fn resource_address(&self) -> ResourceAddress {
289        self.0.resource_address()
290    }
291
292    fn resource_manager(&self) -> Self::ResourceManagerType {
293        self.resource_address().into()
294    }
295
296    fn drop(self) {
297        self.0.drop()
298    }
299
300    fn clone(&self) -> Self {
301        NonFungibleProof(self.0.clone())
302    }
303
304    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
305        self.0.authorize(f)
306    }
307}
308
309//===================
310// Any checked Proof
311//===================
312
313impl ScryptoProof for CheckedProof {
314    type ResourceManagerType = ResourceManager;
315
316    fn contains_amount(&self, amount: Decimal) -> bool {
317        self.amount() >= amount
318    }
319
320    fn amount(&self) -> Decimal {
321        let rtn = ScryptoVmV1Api::object_call(
322            self.0 .0.as_node_id(),
323            PROOF_GET_AMOUNT_IDENT,
324            scrypto_encode(&ProofGetAmountInput {}).unwrap(),
325        );
326        scrypto_decode(&rtn).unwrap()
327    }
328
329    fn resource_address(&self) -> ResourceAddress {
330        self.0.resource_address()
331    }
332
333    fn resource_manager(&self) -> Self::ResourceManagerType {
334        self.resource_address().into()
335    }
336
337    fn drop(self) {
338        self.0.drop()
339    }
340
341    fn clone(&self) -> Self {
342        Self(self.0.clone())
343    }
344
345    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
346        self.0.authorize(f)
347    }
348}
349
350impl ScryptoGenericProof for CheckedProof {
351    fn as_fungible(&self) -> CheckedFungibleProof {
352        assert!(
353            self.resource_address()
354                .as_node_id()
355                .is_global_fungible_resource_manager(),
356            "Not a fungible proof"
357        );
358        CheckedFungibleProof(Self(Proof(self.0 .0)))
359    }
360
361    fn as_non_fungible(&self) -> CheckedNonFungibleProof {
362        assert!(
363            self.resource_address()
364                .as_node_id()
365                .is_global_non_fungible_resource_manager(),
366            "Not a non-fungible proof"
367        );
368        CheckedNonFungibleProof(Self(Proof(self.0 .0)))
369    }
370}
371
372//========================
373// Checked fungible proof
374//========================
375
376impl ScryptoProof for CheckedFungibleProof {
377    type ResourceManagerType = FungibleResourceManager;
378
379    fn contains_amount(&self, amount: Decimal) -> bool {
380        self.0.contains_amount(amount)
381    }
382
383    fn amount(&self) -> Decimal {
384        self.0.amount()
385    }
386
387    fn resource_manager(&self) -> Self::ResourceManagerType {
388        self.resource_address().into()
389    }
390
391    fn resource_address(&self) -> ResourceAddress {
392        self.0.resource_address()
393    }
394
395    fn drop(self) {
396        self.0.drop()
397    }
398
399    fn clone(&self) -> Self {
400        Self(self.0.clone())
401    }
402
403    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
404        self.0.authorize(f)
405    }
406}
407
408impl ScryptoFungibleProof for CheckedFungibleProof {}
409
410//============================
411// Checked non-fungible proof
412//============================
413
414impl ScryptoProof for CheckedNonFungibleProof {
415    type ResourceManagerType = NonFungibleResourceManager;
416
417    fn contains_amount(&self, amount: Decimal) -> bool {
418        self.0.contains_amount(amount)
419    }
420
421    fn amount(&self) -> Decimal {
422        self.0.amount()
423    }
424
425    fn resource_manager(&self) -> Self::ResourceManagerType {
426        self.resource_address().into()
427    }
428
429    fn resource_address(&self) -> ResourceAddress {
430        self.0.resource_address()
431    }
432
433    fn drop(self) {
434        self.0.drop()
435    }
436
437    fn clone(&self) -> Self {
438        Self(self.0.clone())
439    }
440
441    fn authorize<F: FnOnce() -> O, O>(&self, f: F) -> O {
442        self.0.authorize(f)
443    }
444}
445
446impl ScryptoNonFungibleProof for CheckedNonFungibleProof {
447    fn contains_non_fungible(&self, id: &NonFungibleLocalId) -> bool {
448        self.non_fungible_local_ids().contains(id)
449    }
450
451    fn contains_non_fungibles(&self, ids: &IndexSet<NonFungibleLocalId>) -> bool {
452        self.non_fungible_local_ids().is_superset(ids)
453    }
454
455    /// Returns all the non-fungible units contained.
456    ///
457    /// # Panics
458    /// Panics if this is not a non-fungible proof.
459    fn non_fungibles<T: NonFungibleData>(&self) -> Vec<NonFungible<T>> {
460        let resource_address = self.0.resource_address();
461        self.non_fungible_local_ids()
462            .iter()
463            .map(|id| NonFungible::from(NonFungibleGlobalId::new(resource_address, id.clone())))
464            .collect()
465    }
466
467    /// Returns the non-fungible local id if this is a singleton non-fungible proof.
468    ///
469    /// # Panics
470    /// Panics if this is not a singleton proof
471    fn non_fungible_local_id(&self) -> NonFungibleLocalId {
472        let non_fungible_local_ids = self.non_fungible_local_ids();
473        if non_fungible_local_ids.len() != 1 {
474            panic!("Expecting singleton NFT vault");
475        }
476        self.non_fungible_local_ids().into_iter().next().unwrap()
477    }
478
479    /// Returns the non-fungible global id if this is a singleton non-fungible proof.
480    ///
481    /// # Panics
482    /// Panics if this is not a singleton proof
483    fn non_fungible_global_id(&self) -> NonFungibleGlobalId {
484        NonFungibleGlobalId::new(self.resource_address(), self.non_fungible_local_id())
485    }
486
487    /// Returns a singleton non-fungible.
488    ///
489    /// # Panics
490    /// Panics if this is not a singleton proof
491    fn non_fungible<T: NonFungibleData>(&self) -> NonFungible<T> {
492        let non_fungibles = self.non_fungibles();
493        if non_fungibles.len() != 1 {
494            panic!("Expecting singleton NFT proof");
495        }
496        non_fungibles.into_iter().next().unwrap()
497    }
498
499    fn non_fungible_local_ids(&self) -> IndexSet<NonFungibleLocalId> {
500        let rtn = ScryptoVmV1Api::object_call(
501            self.0 .0 .0.as_node_id(),
502            NON_FUNGIBLE_PROOF_GET_LOCAL_IDS_IDENT,
503            scrypto_encode(&NonFungibleProofGetLocalIdsInput {}).unwrap(),
504        );
505        scrypto_decode(&rtn).unwrap()
506    }
507}