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 BasicAssertionFailed,
29 InsufficientBalance,
30 AssertionFailed(ResourceConstraintsError),
31}
32
33pub struct WorktopBlueprint;
34
35impl 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 let input: WorktopDropInput = input
206 .as_typed()
207 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
208
209 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 for (_, bucket) in resources {
231 let bucket = Bucket(bucket);
232 bucket.drop_empty(api)?;
233 }
234
235 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 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 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 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}