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