radix_engine/blueprints/resource/
worktop.rs

1use crate::internal_prelude::*;
2use crate::kernel::kernel_api::KernelSubstateApi;
3use crate::system::system_callback::SystemLockData;
4use crate::system::system_substates::FieldSubstate;
5use radix_engine_interface::api::field_api::LockFlags;
6use radix_engine_interface::api::{SystemApi, ACTOR_STATE_SELF};
7use radix_engine_interface::blueprints::resource::*;
8use radix_native_sdk::resource::*;
9
10#[derive(Debug, ScryptoSbor)]
11pub struct WorktopSubstate {
12    pub resources: IndexMap<ResourceAddress, Own>,
13}
14
15impl WorktopSubstate {
16    pub fn new() -> Self {
17        Self {
18            resources: index_map_new(),
19        }
20    }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
24pub enum WorktopError {
25    /// This is now unused, but kept in for backwards compatibility
26    /// of error models (at least whilst we need to do that for the node,
27    /// so that legacy errors can be serialized to string)
28    BasicAssertionFailed,
29    InsufficientBalance,
30    AssertionFailed(ResourceConstraintsError),
31}
32
33pub struct WorktopBlueprint;
34
35//==============================================
36// Invariant: no empty buckets in the worktop!
37//==============================================
38
39impl WorktopBlueprint {
40    pub fn get_definition() -> BlueprintDefinitionInit {
41        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
42
43        let mut fields = vec![];
44        fields.push(FieldSchema::static_field(
45            aggregator.add_child_type_and_descendents::<WorktopSubstate>(),
46        ));
47
48        let mut functions = index_map_new();
49        functions.insert(
50            WORKTOP_DROP_IDENT.to_string(),
51            FunctionSchemaInit {
52                receiver: None,
53                input: TypeRef::Static(
54                    aggregator.add_child_type_and_descendents::<WorktopDropInput>(),
55                ),
56                output: TypeRef::Static(
57                    aggregator.add_child_type_and_descendents::<WorktopDropOutput>(),
58                ),
59                export: WORKTOP_DROP_IDENT.to_string(),
60            },
61        );
62        functions.insert(
63            WORKTOP_PUT_IDENT.to_string(),
64            FunctionSchemaInit {
65                receiver: Some(ReceiverInfo::normal_ref_mut()),
66                input: TypeRef::Static(
67                    aggregator.add_child_type_and_descendents::<WorktopPutInput>(),
68                ),
69                output: TypeRef::Static(
70                    aggregator.add_child_type_and_descendents::<WorktopPutOutput>(),
71                ),
72                export: WORKTOP_PUT_IDENT.to_string(),
73            },
74        );
75        functions.insert(
76            WORKTOP_TAKE_IDENT.to_string(),
77            FunctionSchemaInit {
78                receiver: Some(ReceiverInfo::normal_ref_mut()),
79                input: TypeRef::Static(
80                    aggregator.add_child_type_and_descendents::<WorktopTakeInput>(),
81                ),
82                output: TypeRef::Static(
83                    aggregator.add_child_type_and_descendents::<WorktopTakeOutput>(),
84                ),
85                export: WORKTOP_TAKE_IDENT.to_string(),
86            },
87        );
88        functions.insert(
89            WORKTOP_TAKE_NON_FUNGIBLES_IDENT.to_string(),
90            FunctionSchemaInit {
91                receiver: Some(ReceiverInfo::normal_ref_mut()),
92                input: TypeRef::Static(
93                    aggregator.add_child_type_and_descendents::<WorktopTakeNonFungiblesInput>(),
94                ),
95                output: TypeRef::Static(
96                    aggregator.add_child_type_and_descendents::<WorktopTakeNonFungiblesOutput>(),
97                ),
98                export: WORKTOP_TAKE_NON_FUNGIBLES_IDENT.to_string(),
99            },
100        );
101        functions.insert(
102            WORKTOP_TAKE_ALL_IDENT.to_string(),
103            FunctionSchemaInit {
104                receiver: Some(ReceiverInfo::normal_ref_mut()),
105                input: TypeRef::Static(
106                    aggregator.add_child_type_and_descendents::<WorktopTakeAllInput>(),
107                ),
108                output: TypeRef::Static(
109                    aggregator.add_child_type_and_descendents::<WorktopTakeAllOutput>(),
110                ),
111                export: WORKTOP_TAKE_ALL_IDENT.to_string(),
112            },
113        );
114        functions.insert(
115            WORKTOP_ASSERT_CONTAINS_IDENT.to_string(),
116            FunctionSchemaInit {
117                receiver: Some(ReceiverInfo::normal_ref_mut()),
118                input: TypeRef::Static(
119                    aggregator.add_child_type_and_descendents::<WorktopAssertContainsInput>(),
120                ),
121                output: TypeRef::Static(
122                    aggregator.add_child_type_and_descendents::<WorktopAssertContainsOutput>(),
123                ),
124                export: WORKTOP_ASSERT_CONTAINS_IDENT.to_string(),
125            },
126        );
127        functions.insert(
128            WORKTOP_ASSERT_CONTAINS_AMOUNT_IDENT.to_string(),
129            FunctionSchemaInit {
130                receiver: Some(ReceiverInfo::normal_ref_mut()),
131                input: TypeRef::Static(
132                    aggregator.add_child_type_and_descendents::<WorktopAssertContainsAmountInput>(),
133                ),
134                output: TypeRef::Static(
135                    aggregator
136                        .add_child_type_and_descendents::<WorktopAssertContainsAmountOutput>(),
137                ),
138                export: WORKTOP_ASSERT_CONTAINS_AMOUNT_IDENT.to_string(),
139            },
140        );
141        functions.insert(
142            WORKTOP_ASSERT_CONTAINS_NON_FUNGIBLES_IDENT.to_string(),
143            FunctionSchemaInit {
144                receiver: Some(ReceiverInfo::normal_ref_mut()),
145                input: TypeRef::Static(
146                    aggregator
147                        .add_child_type_and_descendents::<WorktopAssertContainsNonFungiblesInput>(),
148                ),
149                output: TypeRef::Static(
150                    aggregator
151                        .add_child_type_and_descendents::<WorktopAssertContainsNonFungiblesOutput>(
152                        ),
153                ),
154                export: WORKTOP_ASSERT_CONTAINS_NON_FUNGIBLES_IDENT.to_string(),
155            },
156        );
157        functions.insert(
158            WORKTOP_DRAIN_IDENT.to_string(),
159            FunctionSchemaInit {
160                receiver: Some(ReceiverInfo::normal_ref_mut()),
161                input: TypeRef::Static(
162                    aggregator.add_child_type_and_descendents::<WorktopDrainInput>(),
163                ),
164                output: TypeRef::Static(
165                    aggregator.add_child_type_and_descendents::<WorktopDrainOutput>(),
166                ),
167                export: WORKTOP_DRAIN_IDENT.to_string(),
168            },
169        );
170        let schema = generate_full_schema(aggregator);
171
172        BlueprintDefinitionInit {
173            blueprint_type: BlueprintType::default(),
174            is_transient: true,
175            dependencies: indexset!(),
176            feature_set: indexset!(),
177
178            schema: BlueprintSchemaInit {
179                generics: vec![],
180                schema,
181                state: BlueprintStateSchemaInit {
182                    fields,
183                    collections: vec![],
184                },
185                events: BlueprintEventSchemaInit::default(),
186                types: BlueprintTypeSchemaInit::default(),
187                functions: BlueprintFunctionsSchemaInit { functions },
188                hooks: BlueprintHooksInit::default(),
189            },
190
191            royalty_config: PackageRoyaltyConfig::default(),
192            auth_config: AuthConfig {
193                function_auth: FunctionAuth::AllowAll,
194                method_auth: MethodAuthTemplate::AllowAll,
195            },
196        }
197    }
198
199    pub(crate) fn drop<Y: SystemApi<RuntimeError> + KernelSubstateApi<SystemLockData>>(
200        input: &IndexedScryptoValue,
201        api: &mut Y,
202    ) -> Result<IndexedScryptoValue, RuntimeError> {
203        // TODO: add `drop` callback for drop atomicity, which will remove the necessity of kernel api.
204
205        let input: WorktopDropInput = input
206            .as_typed()
207            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
208
209        // Detach buckets from worktop
210        let handle = api.kernel_open_substate(
211            input.worktop.0.as_node_id(),
212            MAIN_BASE_PARTITION,
213            &WorktopField::Worktop.into(),
214            LockFlags::MUTABLE,
215            SystemLockData::Default,
216        )?;
217        let mut worktop = api
218            .kernel_read_substate(handle)?
219            .as_typed::<FieldSubstate<WorktopSubstate>>()
220            .unwrap()
221            .into_payload();
222        let resources = core::mem::replace(&mut worktop.resources, index_map_new());
223        api.kernel_write_substate(
224            handle,
225            IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(worktop)),
226        )?;
227        api.kernel_close_substate(handle)?;
228
229        // Recursively drop buckets
230        for (_, bucket) in resources {
231            let bucket = Bucket(bucket);
232            bucket.drop_empty(api)?;
233        }
234
235        // Destroy self
236        api.drop_object(input.worktop.0.as_node_id())?;
237
238        Ok(IndexedScryptoValue::from_typed(&()))
239    }
240
241    pub(crate) fn put<Y: SystemApi<RuntimeError>>(
242        input: &IndexedScryptoValue,
243        api: &mut Y,
244    ) -> Result<IndexedScryptoValue, RuntimeError> {
245        let input: WorktopPutInput = input
246            .as_typed()
247            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
248
249        let resource_address = input.bucket.resource_address(api)?;
250        let amount = input.bucket.amount(api)?;
251
252        if amount.is_zero() {
253            input.bucket.drop_empty(api)?;
254            Ok(IndexedScryptoValue::from_typed(&()))
255        } else {
256            let worktop_handle = api.actor_open_field(
257                ACTOR_STATE_SELF,
258                WorktopField::Worktop.into(),
259                LockFlags::MUTABLE,
260            )?;
261            let mut worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
262            if let Some(own) = worktop.resources.get(&resource_address).cloned() {
263                Bucket(own).put(input.bucket, api)?;
264            } else {
265                worktop.resources.insert(resource_address, input.bucket.0);
266                api.field_write_typed(worktop_handle, &worktop)?;
267            }
268            api.field_close(worktop_handle)?;
269            Ok(IndexedScryptoValue::from_typed(&()))
270        }
271    }
272
273    pub(crate) fn take<Y: SystemApi<RuntimeError>>(
274        input: &IndexedScryptoValue,
275        api: &mut Y,
276    ) -> Result<IndexedScryptoValue, RuntimeError> {
277        let input: WorktopTakeInput = input
278            .as_typed()
279            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
280
281        let resource_address = input.resource_address;
282        let amount = input.amount;
283
284        if amount.is_zero() {
285            let bucket = ResourceManager(resource_address).new_empty_bucket(api)?;
286            Ok(IndexedScryptoValue::from_typed(&bucket))
287        } else {
288            let worktop_handle = api.actor_open_field(
289                ACTOR_STATE_SELF,
290                WorktopField::Worktop.into(),
291                LockFlags::MUTABLE,
292            )?;
293            let mut worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
294            let existing_bucket = Bucket(worktop.resources.get(&resource_address).cloned().ok_or(
295                RuntimeError::ApplicationError(ApplicationError::WorktopError(
296                    WorktopError::InsufficientBalance,
297                )),
298            )?);
299            let existing_amount = existing_bucket.amount(api)?;
300
301            if existing_amount < amount {
302                Err(RuntimeError::ApplicationError(
303                    ApplicationError::WorktopError(WorktopError::InsufficientBalance),
304                ))
305            } else if existing_amount == amount {
306                // Move
307                worktop.resources.swap_remove(&resource_address);
308                api.field_write_typed(worktop_handle, &worktop)?;
309                api.field_close(worktop_handle)?;
310                Ok(IndexedScryptoValue::from_typed(&existing_bucket))
311            } else {
312                let bucket = existing_bucket.take(amount, api)?;
313                api.field_close(worktop_handle)?;
314                Ok(IndexedScryptoValue::from_typed(&bucket))
315            }
316        }
317    }
318
319    pub(crate) fn take_non_fungibles<Y: SystemApi<RuntimeError>>(
320        input: &IndexedScryptoValue,
321        api: &mut Y,
322    ) -> Result<IndexedScryptoValue, RuntimeError> {
323        let input: WorktopTakeNonFungiblesInput = input
324            .as_typed()
325            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
326
327        let resource_address = input.resource_address;
328        let ids = input.ids;
329
330        if ids.is_empty() {
331            let bucket = ResourceManager(resource_address).new_empty_bucket(api)?;
332            Ok(IndexedScryptoValue::from_typed(&bucket))
333        } else {
334            let worktop_handle = api.actor_open_field(
335                ACTOR_STATE_SELF,
336                WorktopField::Worktop.into(),
337                LockFlags::MUTABLE,
338            )?;
339            let mut worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
340            let existing_bucket = Bucket(worktop.resources.get(&resource_address).cloned().ok_or(
341                RuntimeError::ApplicationError(ApplicationError::WorktopError(
342                    WorktopError::InsufficientBalance,
343                )),
344            )?);
345            let existing_non_fungibles = existing_bucket.non_fungible_local_ids(api)?;
346
347            if !existing_non_fungibles.is_superset(&ids) {
348                Err(RuntimeError::ApplicationError(
349                    ApplicationError::WorktopError(WorktopError::InsufficientBalance),
350                ))
351            } else if existing_non_fungibles.len() == ids.len() {
352                // Move
353                worktop = api.field_read_typed(worktop_handle)?;
354                worktop.resources.swap_remove(&resource_address);
355                api.field_write_typed(worktop_handle, &worktop)?;
356                api.field_close(worktop_handle)?;
357                Ok(IndexedScryptoValue::from_typed(&existing_bucket))
358            } else {
359                let bucket = existing_bucket.take_non_fungibles(ids, api)?;
360                api.field_close(worktop_handle)?;
361                Ok(IndexedScryptoValue::from_typed(&bucket))
362            }
363        }
364    }
365
366    pub(crate) fn take_all<Y: SystemApi<RuntimeError>>(
367        input: &IndexedScryptoValue,
368        api: &mut Y,
369    ) -> Result<IndexedScryptoValue, RuntimeError> {
370        let input: WorktopTakeAllInput = input
371            .as_typed()
372            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
373
374        let worktop_handle = api.actor_open_field(
375            ACTOR_STATE_SELF,
376            WorktopField::Worktop.into(),
377            LockFlags::MUTABLE,
378        )?;
379        let mut worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
380        if let Some(bucket) = worktop.resources.swap_remove(&input.resource_address) {
381            // Move
382            api.field_write_typed(worktop_handle, &worktop)?;
383            api.field_close(worktop_handle)?;
384            Ok(IndexedScryptoValue::from_typed(&bucket))
385        } else {
386            api.field_close(worktop_handle)?;
387            let bucket = ResourceManager(input.resource_address).new_empty_bucket(api)?;
388            Ok(IndexedScryptoValue::from_typed(&bucket))
389        }
390    }
391
392    pub(crate) fn assert_contains<Y: SystemApi<RuntimeError>>(
393        input: &IndexedScryptoValue,
394        api: &mut Y,
395    ) -> Result<IndexedScryptoValue, RuntimeError> {
396        let input: WorktopAssertContainsInput = input
397            .as_typed()
398            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
399
400        let worktop_handle = api.actor_open_field(
401            ACTOR_STATE_SELF,
402            WorktopField::Worktop.into(),
403            LockFlags::read_only(),
404        )?;
405        let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
406        let amount = if let Some(bucket) = worktop.resources.get(&input.resource_address).cloned() {
407            Bucket(bucket).amount(api)?
408        } else {
409            Decimal::zero()
410        };
411        if amount.is_zero() {
412            let worktop_error =
413                WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed {
414                    resource_address: input.resource_address,
415                    error: ResourceConstraintError::ExpectedNonZeroAmount,
416                });
417            return Err(RuntimeError::ApplicationError(
418                ApplicationError::WorktopError(worktop_error),
419            ));
420        }
421        api.field_close(worktop_handle)?;
422        Ok(IndexedScryptoValue::from_typed(&()))
423    }
424
425    pub(crate) fn assert_contains_amount<Y: SystemApi<RuntimeError>>(
426        input: &IndexedScryptoValue,
427        api: &mut Y,
428    ) -> Result<IndexedScryptoValue, RuntimeError> {
429        let input: WorktopAssertContainsAmountInput = input
430            .as_typed()
431            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
432
433        let worktop_handle = api.actor_open_field(
434            ACTOR_STATE_SELF,
435            WorktopField::Worktop.into(),
436            LockFlags::read_only(),
437        )?;
438        let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
439        let amount = if let Some(bucket) = worktop.resources.get(&input.resource_address).cloned() {
440            Bucket(bucket).amount(api)?
441        } else {
442            Decimal::zero()
443        };
444        if amount < input.amount {
445            let worktop_error =
446                WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed {
447                    resource_address: input.resource_address,
448                    error: ResourceConstraintError::ExpectedAtLeastAmount {
449                        expected_at_least_amount: input.amount,
450                        actual_amount: amount,
451                    },
452                });
453            return Err(RuntimeError::ApplicationError(
454                ApplicationError::WorktopError(worktop_error),
455            ));
456        }
457        api.field_close(worktop_handle)?;
458        Ok(IndexedScryptoValue::from_typed(&()))
459    }
460
461    pub(crate) fn assert_contains_non_fungibles<Y: SystemApi<RuntimeError>>(
462        input: &IndexedScryptoValue,
463        api: &mut Y,
464    ) -> Result<IndexedScryptoValue, RuntimeError> {
465        let input: WorktopAssertContainsNonFungiblesInput = input
466            .as_typed()
467            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
468
469        let worktop_handle = api.actor_open_field(
470            ACTOR_STATE_SELF,
471            WorktopField::Worktop.into(),
472            LockFlags::read_only(),
473        )?;
474        let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
475        let bucket_ids = if let Some(bucket) = worktop.resources.get(&input.resource_address) {
476            let bucket = Bucket(bucket.clone());
477            bucket.non_fungible_local_ids(api)?
478        } else {
479            index_set_new()
480        };
481        if let Some(missing_id) = input.ids.difference(&bucket_ids).next() {
482            let worktop_error =
483                WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed {
484                    resource_address: input.resource_address,
485                    error: ResourceConstraintError::NonFungibleMissing {
486                        missing_id: missing_id.clone(),
487                    },
488                });
489            return Err(RuntimeError::ApplicationError(
490                ApplicationError::WorktopError(worktop_error),
491            ));
492        }
493        api.field_close(worktop_handle)?;
494        Ok(IndexedScryptoValue::from_typed(&()))
495    }
496
497    pub(crate) fn drain<Y: SystemApi<RuntimeError>>(
498        input: &IndexedScryptoValue,
499        api: &mut Y,
500    ) -> Result<IndexedScryptoValue, RuntimeError> {
501        let _input: WorktopDrainInput = input
502            .as_typed()
503            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
504
505        let worktop_handle = api.actor_open_field(
506            ACTOR_STATE_SELF,
507            WorktopField::Worktop.into(),
508            LockFlags::MUTABLE,
509        )?;
510        let mut worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
511        let buckets: Vec<Own> = worktop.resources.values().cloned().collect();
512        worktop.resources.clear();
513        api.field_write_typed(worktop_handle, &worktop)?;
514        api.field_close(worktop_handle)?;
515        Ok(IndexedScryptoValue::from_typed(&buckets))
516    }
517}
518
519pub struct WorktopBlueprintCuttlefishExtension;
520
521impl WorktopBlueprintCuttlefishExtension {
522    pub fn added_functions_schema() -> (
523        IndexMap<String, FunctionSchemaInit>,
524        VersionedSchema<ScryptoCustomSchema>,
525    ) {
526        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
527        let mut functions = index_map_new();
528        functions.insert(
529            WORKTOP_ASSERT_RESOURCES_INCLUDE_IDENT.to_string(),
530            FunctionSchemaInit {
531                receiver: Some(ReceiverInfo::normal_ref()),
532                input: TypeRef::Static(
533                    aggregator
534                        .add_child_type_and_descendents::<WorktopAssertResourcesIncludeInput>(),
535                ),
536                output: TypeRef::Static(
537                    aggregator
538                        .add_child_type_and_descendents::<WorktopAssertResourcesIncludeOutput>(),
539                ),
540                export: WORKTOP_ASSERT_RESOURCES_INCLUDE_IDENT.to_string(),
541            },
542        );
543
544        functions.insert(
545            WORKTOP_ASSERT_RESOURCES_ONLY_IDENT.to_string(),
546            FunctionSchemaInit {
547                receiver: Some(ReceiverInfo::normal_ref()),
548                input: TypeRef::Static(
549                    aggregator.add_child_type_and_descendents::<WorktopAssertResourcesOnlyInput>(),
550                ),
551                output: TypeRef::Static(
552                    aggregator.add_child_type_and_descendents::<WorktopAssertResourcesOnlyOutput>(),
553                ),
554                export: WORKTOP_ASSERT_RESOURCES_ONLY_IDENT.to_string(),
555            },
556        );
557
558        let schema = generate_full_schema(aggregator);
559        (functions, schema)
560    }
561
562    pub(crate) fn assert_resources_includes<Y: SystemApi<RuntimeError>>(
563        input: &IndexedScryptoValue,
564        api: &mut Y,
565    ) -> Result<IndexedScryptoValue, RuntimeError> {
566        let input: WorktopAssertResourcesIncludeInput = input
567            .as_typed()
568            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
569
570        Self::aggregate_resources(api)?
571            .validate_includes(input.constraints)
572            .map_err(|e| {
573                RuntimeError::ApplicationError(ApplicationError::WorktopError(
574                    WorktopError::AssertionFailed(e),
575                ))
576            })?;
577
578        Ok(IndexedScryptoValue::from_typed(&()))
579    }
580
581    pub(crate) fn assert_resources_only<Y: SystemApi<RuntimeError>>(
582        input: &IndexedScryptoValue,
583        api: &mut Y,
584    ) -> Result<IndexedScryptoValue, RuntimeError> {
585        let input: WorktopAssertResourcesIncludeInput = input
586            .as_typed()
587            .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
588
589        Self::aggregate_resources(api)?
590            .validate_only(input.constraints)
591            .map_err(|e| {
592                RuntimeError::ApplicationError(ApplicationError::WorktopError(
593                    WorktopError::AssertionFailed(e),
594                ))
595            })?;
596
597        Ok(IndexedScryptoValue::from_typed(&()))
598    }
599
600    fn aggregate_resources(
601        api: &mut impl SystemApi<RuntimeError>,
602    ) -> Result<AggregateResourceBalances, RuntimeError> {
603        let worktop_handle = api.actor_open_field(
604            ACTOR_STATE_SELF,
605            WorktopField::Worktop.into(),
606            LockFlags::read_only(),
607        )?;
608        let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?;
609
610        let mut aggregated_balances = AggregateResourceBalances::new();
611
612        for (resource, bucket) in worktop.resources {
613            let bucket = Bucket(bucket.clone());
614            if resource.is_fungible() {
615                let amount = bucket.amount(api)?;
616                aggregated_balances.add_fungible(resource, amount);
617            } else {
618                let ids = bucket.non_fungible_local_ids(api)?;
619                aggregated_balances.add_non_fungible(resource, ids);
620            }
621        }
622
623        Ok(aggregated_balances)
624    }
625
626    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
627        export_name: &str,
628        input: &IndexedScryptoValue,
629        api: &mut Y,
630    ) -> Result<IndexedScryptoValue, RuntimeError> {
631        match export_name {
632            WORKTOP_ASSERT_RESOURCES_INCLUDE_IDENT => {
633                WorktopBlueprintCuttlefishExtension::assert_resources_includes(input, api)
634            }
635            WORKTOP_ASSERT_RESOURCES_ONLY_IDENT => {
636                WorktopBlueprintCuttlefishExtension::assert_resources_only(input, api)
637            }
638            _ => Err(RuntimeError::ApplicationError(
639                ApplicationError::ExportDoesNotExist(export_name.to_string()),
640            )),
641        }
642    }
643}