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 BasicAssertionFailed,
35 InsufficientBalance,
36 AssertionFailed(ResourceConstraintsError),
37}
38
39pub struct WorktopBlueprint;
40
41impl 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 let input: WorktopDropInput = input
211 .as_typed()
212 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?;
213
214 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 for (_, bucket) in resources {
236 let bucket = Bucket(bucket);
237 bucket.drop_empty(api)?;
238 }
239
240 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 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 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 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}