1#![allow(clippy::too_many_arguments)]
2
3use super::*;
4use crate::internal_prelude::*;
5use radix_engine_interface::blueprints::account::*;
6use radix_engine_interface::blueprints::locker::*;
7use radix_native_sdk::modules::metadata::*;
8use radix_native_sdk::modules::role_assignment::*;
9use radix_native_sdk::resource::*;
10use radix_native_sdk::runtime::*;
11
12pub const STORER_ROLE: &str = "storer";
13pub const STORER_UPDATER_ROLE: &str = "storer_updater";
14pub const RECOVERER_ROLE: &str = "recoverer";
15pub const RECOVERER_UPDATER_ROLE: &str = "recoverer_updater";
16
17pub struct AccountLockerBlueprint;
18
19#[allow(unused_variables)]
20impl AccountLockerBlueprint {
21 pub fn definition() -> BlueprintDefinitionInit {
22 let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
23
24 let feature_set = AccountLockerFeatureSet::all_features();
25 let state = AccountLockerStateSchemaInit::create_schema_init(&mut aggregator);
26
27 let functions = function_schema! {
28 aggregator,
29 AccountLocker {
30 instantiate: None,
31 instantiate_simple: None,
32 store: Some(ReceiverInfo::normal_ref_mut()),
33 airdrop: Some(ReceiverInfo::normal_ref_mut()),
34 recover: Some(ReceiverInfo::normal_ref_mut()),
35 recover_non_fungibles: Some(ReceiverInfo::normal_ref_mut()),
36 claim: Some(ReceiverInfo::normal_ref_mut()),
37 claim_non_fungibles: Some(ReceiverInfo::normal_ref_mut()),
38 get_amount: Some(ReceiverInfo::normal_ref()),
39 get_non_fungible_local_ids: Some(ReceiverInfo::normal_ref()),
40 }
41 };
42
43 let events = event_schema! {
44 aggregator,
45 [
46 StoreEvent,
47 RecoverEvent,
48 ClaimEvent,
49 ]
50 };
51
52 let schema = generate_full_schema(aggregator);
53
54 BlueprintDefinitionInit {
55 blueprint_type: BlueprintType::default(),
56 is_transient: false,
57 feature_set,
58 dependencies: indexset!(),
59 schema: BlueprintSchemaInit {
60 generics: vec![],
61 schema,
62 state,
63 events,
64 types: BlueprintTypeSchemaInit::default(),
65 functions: BlueprintFunctionsSchemaInit { functions },
66 hooks: BlueprintHooksInit::default(),
67 },
68
69 royalty_config: PackageRoyaltyConfig::default(),
70 auth_config: AuthConfig {
71 function_auth: FunctionAuth::AllowAll,
72 method_auth: MethodAuthTemplate::StaticRoleDefinition(roles_template!(
73 roles {
74 STORER_ROLE => updaters: [STORER_UPDATER_ROLE];
75 STORER_UPDATER_ROLE => updaters: [STORER_UPDATER_ROLE];
76 RECOVERER_ROLE => updaters: [RECOVERER_UPDATER_ROLE];
77 RECOVERER_UPDATER_ROLE => updaters: [RECOVERER_UPDATER_ROLE];
78 },
79 methods {
80 ACCOUNT_LOCKER_STORE_IDENT => [STORER_ROLE];
81 ACCOUNT_LOCKER_AIRDROP_IDENT => [STORER_ROLE];
82
83 ACCOUNT_LOCKER_RECOVER_IDENT => [RECOVERER_ROLE];
84 ACCOUNT_LOCKER_RECOVER_NON_FUNGIBLES_IDENT => [RECOVERER_ROLE];
85
86 ACCOUNT_LOCKER_CLAIM_IDENT => MethodAccessibility::Public;
87 ACCOUNT_LOCKER_CLAIM_NON_FUNGIBLES_IDENT => MethodAccessibility::Public;
88 ACCOUNT_LOCKER_GET_AMOUNT_IDENT => MethodAccessibility::Public;
89 ACCOUNT_LOCKER_GET_NON_FUNGIBLE_LOCAL_IDS_IDENT => MethodAccessibility::Public;
90 }
91 )),
92 },
93 }
94 }
95
96 pub fn invoke_export<Y: SystemApi<RuntimeError>>(
97 export_name: &str,
98 input: &IndexedScryptoValue,
99 api: &mut Y,
100 ) -> Result<IndexedScryptoValue, RuntimeError> {
101 dispatch! {
102 EXPORT_NAME,
103 export_name,
104 input,
105 api,
106 AccountLocker,
107 [
108 instantiate,
109 instantiate_simple,
110 store,
111 airdrop,
112 recover,
113 recover_non_fungibles,
114 claim,
115 claim_non_fungibles,
116 get_amount,
117 get_non_fungible_local_ids,
118 ]
119 }
120 }
121
122 fn instantiate<Y: SystemApi<RuntimeError>>(
123 AccountLockerInstantiateInput {
124 owner_role,
125 storer_role,
126 storer_updater_role,
127 recoverer_role,
128 recoverer_updater_role,
129 address_reservation,
130 }: AccountLockerInstantiateInput,
131 api: &mut Y,
132 ) -> Result<AccountLockerInstantiateOutput, RuntimeError> {
133 Self::instantiate_internal(
134 owner_role,
135 storer_role,
136 storer_updater_role,
137 recoverer_role,
138 recoverer_updater_role,
139 metadata_init! {
140 "admin_badge" => EMPTY, locked;
141 },
142 address_reservation,
143 api,
144 )
145 }
146
147 fn instantiate_simple<Y: SystemApi<RuntimeError>>(
148 AccountLockerInstantiateSimpleInput { allow_recover }: AccountLockerInstantiateSimpleInput,
149 api: &mut Y,
150 ) -> Result<AccountLockerInstantiateSimpleOutput, RuntimeError> {
151 let (locker_reservation, locker_address) = api.allocate_global_address(BlueprintId {
154 package_address: LOCKER_PACKAGE,
155 blueprint_name: ACCOUNT_LOCKER_BLUEPRINT.into(),
156 })?;
157 let (badge_reservation, badge_address) = api.allocate_global_address(BlueprintId {
158 package_address: RESOURCE_PACKAGE,
159 blueprint_name: FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT.into(),
160 })?;
161 let badge_address = ResourceAddress::new_or_panic(badge_address.as_node_id().0);
162
163 let (badge_address, badge) = api
164 .call_function(
165 RESOURCE_PACKAGE,
166 FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT,
167 FUNGIBLE_RESOURCE_MANAGER_CREATE_WITH_INITIAL_SUPPLY_IDENT,
168 scrypto_encode(&FungibleResourceManagerCreateWithInitialSupplyInput {
169 owner_role: OwnerRole::Updatable(rule!(require(badge_address))),
170 track_total_supply: true,
171 divisibility: 0,
172 resource_roles: Default::default(),
173 metadata: metadata! {
174 init {
175 "account_locker" => locker_address, locked;
176 }
177 },
178 address_reservation: Some(badge_reservation),
179 initial_supply: dec!(1),
180 })
181 .unwrap(),
182 )
183 .map(|rtn| {
184 scrypto_decode::<FungibleResourceManagerCreateWithInitialSupplyOutput>(&rtn)
185 .unwrap()
186 })?;
187
188 let rule = rule!(require(badge_address));
190 let recoverer_rule = match allow_recover {
191 true => rule.clone(),
192 false => rule!(deny_all),
193 };
194
195 Self::instantiate_internal(
196 OwnerRole::Updatable(rule.clone()),
197 rule.clone(),
198 rule.clone(),
199 recoverer_rule.clone(),
200 recoverer_rule,
201 metadata_init! {
202 "admin_badge" => badge_address, locked;
203 },
204 Some(locker_reservation),
205 api,
206 )
207 .map(|rtn| (rtn, badge))
208 }
209
210 fn instantiate_internal<Y: SystemApi<RuntimeError>>(
211 owner_role: OwnerRole,
212 storer_role: AccessRule,
213 storer_updater_role: AccessRule,
214 recoverer_role: AccessRule,
215 recoverer_updater_role: AccessRule,
216 metadata_init: MetadataInit,
217 address_reservation: Option<GlobalAddressReservation>,
218 api: &mut Y,
219 ) -> Result<Global<AccountLockerMarker>, RuntimeError> {
220 let object_id = api.new_simple_object(ACCOUNT_LOCKER_BLUEPRINT, indexmap! {})?;
222
223 let roles = indexmap! {
225 ModuleId::Main => roles2! {
226 STORER_ROLE => storer_role, updatable;
227 STORER_UPDATER_ROLE => storer_updater_role, updatable;
228 RECOVERER_ROLE => recoverer_role, updatable;
229 RECOVERER_UPDATER_ROLE => recoverer_updater_role, updatable;
230 }
231 };
232 let role_assignment = RoleAssignment::create(owner_role, roles, api)?.0;
233
234 let metadata = Metadata::create_with_data(metadata_init, api)?;
236
237 let address = api.globalize(
239 object_id,
240 indexmap!(
241 AttachedModuleId::RoleAssignment => role_assignment.0,
242 AttachedModuleId::Metadata => metadata.0,
243 ),
244 address_reservation,
245 )?;
246 let component_address = ComponentAddress::new_or_panic(address.as_node_id().0);
247
248 Ok(Global::new(component_address))
249 }
250
251 fn store<Y: SystemApi<RuntimeError>>(
252 AccountLockerStoreInput {
253 claimant,
254 bucket,
255 try_direct_send,
256 }: AccountLockerStoreInput,
257 api: &mut Y,
258 ) -> Result<AccountLockerStoreOutput, RuntimeError> {
259 let bucket = if try_direct_send {
261 let actor_node_id = api.actor_get_node_id(ACTOR_STATE_SELF)?;
264 let global_caller_non_fungible_global_id =
265 global_caller(GlobalAddress::new_or_panic(actor_node_id.0));
266
267 let bucket = api
268 .call_method(
269 claimant.0.as_node_id(),
270 ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT,
271 scrypto_encode(&AccountTryDepositOrRefundInput {
272 bucket,
273 authorized_depositor_badge: Some(global_caller_non_fungible_global_id),
274 })
275 .unwrap(),
276 )
277 .map(|rtn| scrypto_decode::<AccountTryDepositOrRefundOutput>(&rtn).unwrap())?;
278 match bucket {
279 Some(bucket) => bucket,
280 None => return Ok(()),
281 }
282 } else {
283 bucket
284 };
285
286 let (resource_address, resource_specifier) = bucket_to_resource_specifier(&bucket, api)?;
291
292 Self::with_vault_create_on_traversal(
294 claimant.0,
295 resource_address,
296 api,
297 |mut vault, api| vault.put(bucket, api),
298 )?;
299
300 Runtime::emit_event(
302 api,
303 StoreEvent {
304 claimant,
305 resource_address,
306 resources: resource_specifier,
307 },
308 )?;
309
310 Ok(())
311 }
312
313 fn airdrop<Y: SystemApi<RuntimeError>>(
314 AccountLockerAirdropInput {
315 claimants,
316 bucket,
317 try_direct_send,
318 }: AccountLockerAirdropInput,
319 api: &mut Y,
320 ) -> Result<AccountLockerAirdropOutput, RuntimeError> {
321 let resource_address = bucket.resource_address(api)?;
323 for (account_address, specifier) in claimants.iter() {
324 let claim_bucket = match specifier {
325 ResourceSpecifier::Fungible(amount) => bucket.take(*amount, api)?,
326 ResourceSpecifier::NonFungible(ids) => {
327 bucket.take_non_fungibles(ids.clone(), api)?.into()
328 }
329 };
330
331 Self::store(
332 AccountLockerStoreInput {
333 claimant: *account_address,
334 bucket: claim_bucket,
335 try_direct_send,
336 },
337 api,
338 )?;
339 }
340
341 if bucket.is_empty(api)? {
342 bucket.drop_empty(api)?;
343 Ok(None)
344 } else {
345 Ok(Some(bucket))
346 }
347 }
348
349 fn recover<Y: SystemApi<RuntimeError>>(
350 AccountLockerRecoverInput {
351 claimant,
352 resource_address,
353 amount,
354 }: AccountLockerRecoverInput,
355 api: &mut Y,
356 ) -> Result<AccountLockerRecoverOutput, RuntimeError> {
357 let bucket = Self::with_vault_create_on_traversal(
359 claimant.0,
360 resource_address,
361 api,
362 |mut vault, api| vault.take(amount, api),
363 )?;
364
365 let (resource_address, resource_specifier) = bucket_to_resource_specifier(&bucket, api)?;
367 Runtime::emit_event(
368 api,
369 RecoverEvent {
370 claimant,
371 resource_address,
372 resources: resource_specifier,
373 },
374 )?;
375
376 Ok(bucket)
378 }
379
380 fn recover_non_fungibles<Y: SystemApi<RuntimeError>>(
381 AccountLockerRecoverNonFungiblesInput {
382 claimant,
383 resource_address,
384 ids,
385 }: AccountLockerRecoverNonFungiblesInput,
386 api: &mut Y,
387 ) -> Result<AccountLockerRecoverNonFungiblesOutput, RuntimeError> {
388 let bucket = Self::with_vault_create_on_traversal(
390 claimant.0,
391 resource_address,
392 api,
393 |mut vault, api| vault.take_non_fungibles(ids, api),
394 )?;
395
396 let (resource_address, resource_specifier) = bucket_to_resource_specifier(&bucket, api)?;
398 Runtime::emit_event(
399 api,
400 RecoverEvent {
401 claimant,
402 resource_address,
403 resources: resource_specifier,
404 },
405 )?;
406
407 Ok(bucket)
409 }
410
411 fn claim<Y: SystemApi<RuntimeError>>(
412 AccountLockerClaimInput {
413 claimant,
414 resource_address,
415 amount,
416 }: AccountLockerClaimInput,
417 api: &mut Y,
418 ) -> Result<AccountLockerClaimOutput, RuntimeError> {
419 let claimant_owner_role = api
421 .call_module_method(
422 claimant.0.as_node_id(),
423 AttachedModuleId::RoleAssignment,
424 ROLE_ASSIGNMENT_GET_OWNER_ROLE_IDENT,
425 scrypto_encode(&RoleAssignmentGetOwnerRoleInput).unwrap(),
426 )
427 .map(|rtn| scrypto_decode::<RoleAssignmentGetOwnerRoleOutput>(&rtn).unwrap())?;
428 Runtime::assert_access_rule(claimant_owner_role.rule, api)?;
429
430 let bucket = Self::with_vault_create_on_traversal(
432 claimant.0,
433 resource_address,
434 api,
435 |mut vault, api| vault.take(amount, api),
436 )?;
437
438 let (resource_address, resource_specifier) = bucket_to_resource_specifier(&bucket, api)?;
440 Runtime::emit_event(
441 api,
442 ClaimEvent {
443 claimant,
444 resource_address,
445 resources: resource_specifier,
446 },
447 )?;
448
449 Ok(bucket)
451 }
452
453 fn claim_non_fungibles<Y: SystemApi<RuntimeError>>(
454 AccountLockerClaimNonFungiblesInput {
455 claimant,
456 resource_address,
457 ids,
458 }: AccountLockerClaimNonFungiblesInput,
459 api: &mut Y,
460 ) -> Result<AccountLockerClaimNonFungiblesOutput, RuntimeError> {
461 let claimant_owner_role = api
463 .call_module_method(
464 claimant.0.as_node_id(),
465 AttachedModuleId::RoleAssignment,
466 ROLE_ASSIGNMENT_GET_OWNER_ROLE_IDENT,
467 scrypto_encode(&RoleAssignmentGetOwnerRoleInput).unwrap(),
468 )
469 .map(|rtn| scrypto_decode::<RoleAssignmentGetOwnerRoleOutput>(&rtn).unwrap())?;
470 Runtime::assert_access_rule(claimant_owner_role.rule, api)?;
471
472 let bucket = Self::with_vault_create_on_traversal(
474 claimant.0,
475 resource_address,
476 api,
477 |mut vault, api| vault.take_non_fungibles(ids, api),
478 )?;
479
480 let (resource_address, resource_specifier) = bucket_to_resource_specifier(&bucket, api)?;
482 Runtime::emit_event(
483 api,
484 ClaimEvent {
485 claimant,
486 resource_address,
487 resources: resource_specifier,
488 },
489 )?;
490
491 Ok(bucket)
493 }
494
495 fn get_amount<Y: SystemApi<RuntimeError>>(
496 AccountLockerGetAmountInput {
497 claimant,
498 resource_address,
499 }: AccountLockerGetAmountInput,
500 api: &mut Y,
501 ) -> Result<AccountLockerGetAmountOutput, RuntimeError> {
502 Self::with_vault(claimant.0, resource_address, api, |vault, api| {
503 vault
504 .map(|vault| vault.amount(api))
505 .unwrap_or(Ok(Decimal::ZERO))
506 })
507 }
508
509 fn get_non_fungible_local_ids<Y: SystemApi<RuntimeError>>(
510 AccountLockerGetNonFungibleLocalIdsInput {
511 claimant,
512 resource_address,
513 limit,
514 }: AccountLockerGetNonFungibleLocalIdsInput,
515 api: &mut Y,
516 ) -> Result<AccountLockerGetNonFungibleLocalIdsOutput, RuntimeError> {
517 Self::with_vault(claimant.0, resource_address, api, |vault, api| {
518 vault
519 .map(|vault| vault.non_fungible_local_ids(limit, api))
520 .unwrap_or(Ok(indexset! {}))
521 })
522 }
523
524 fn with_vault_create_on_traversal<Y: SystemApi<RuntimeError>, O>(
525 account_address: ComponentAddress,
526 resource_address: ResourceAddress,
527 api: &mut Y,
528 handler: impl FnOnce(Vault, &mut Y) -> Result<O, RuntimeError>,
529 ) -> Result<O, RuntimeError> {
530 let account_claims_handle = api.actor_open_key_value_entry(
533 ACTOR_STATE_SELF,
534 AccountLockerCollection::AccountClaimsKeyValue.collection_index(),
535 &scrypto_encode(&account_address).unwrap(),
536 LockFlags::MUTABLE,
537 )?;
538 let account_claims = api
539 .key_value_entry_get_typed::<VersionedAccountLockerAccountClaims>(
540 account_claims_handle,
541 )?
542 .map(|entry| entry.fully_update_and_into_latest_version());
543
544 let account_claims_kv_store = match account_claims {
545 Some(account_claims_kv_store) => account_claims_kv_store,
546 None => {
547 let key_value_store = api
549 .key_value_store_new(
550 KeyValueStoreDataSchema::new_local_without_self_package_replacement::<
551 ResourceAddress,
552 Vault,
553 >(true),
554 )
555 .map(Own)?;
556 api.key_value_entry_set_typed(
558 account_claims_handle,
559 AccountLockerAccountClaimsVersions::V1(key_value_store).into_versioned(),
560 )?;
561 key_value_store
563 }
564 };
565
566 let vault_entry_handle = api.key_value_store_open_entry(
568 account_claims_kv_store.as_node_id(),
569 &scrypto_encode(&resource_address).unwrap(),
570 LockFlags::MUTABLE,
571 )?;
572
573 let vault_entry = api.key_value_entry_get_typed::<Vault>(vault_entry_handle)?;
574 let vault = match vault_entry {
575 Some(vault) => vault,
576 None => {
577 let vault = Vault::create(resource_address, api)?;
579 api.key_value_entry_set_typed(vault_entry_handle, Vault(vault.0))?;
581 vault
583 }
584 };
585
586 let rtn = handler(vault, api)?;
590
591 api.key_value_entry_close(vault_entry_handle)?;
593 api.key_value_entry_close(account_claims_handle)?;
594
595 Ok(rtn)
597 }
598
599 fn with_vault<Y: SystemApi<RuntimeError>, O>(
600 account_address: ComponentAddress,
601 resource_address: ResourceAddress,
602 api: &mut Y,
603 handler: impl FnOnce(Option<Vault>, &mut Y) -> Result<O, RuntimeError>,
604 ) -> Result<O, RuntimeError> {
605 let account_claims_handle = api.actor_open_key_value_entry(
608 ACTOR_STATE_SELF,
609 AccountLockerCollection::AccountClaimsKeyValue.collection_index(),
610 &scrypto_encode(&account_address).unwrap(),
611 LockFlags::read_only(),
612 )?;
613 let account_claims = api
614 .key_value_entry_get_typed::<VersionedAccountLockerAccountClaims>(
615 account_claims_handle,
616 )?
617 .map(|entry| entry.fully_update_and_into_latest_version());
618
619 let account_claims_kv_store = match account_claims {
620 Some(account_claims_kv_store) => account_claims_kv_store,
621 None => {
622 let rtn = handler(None, api)?;
624 api.key_value_entry_close(account_claims_handle)?;
626 return Ok(rtn);
628 }
629 };
630
631 let vault_entry_handle = api.key_value_store_open_entry(
634 account_claims_kv_store.as_node_id(),
635 &scrypto_encode(&resource_address).unwrap(),
636 LockFlags::read_only(),
637 )?;
638
639 let vault_entry = api.key_value_entry_get_typed::<Vault>(vault_entry_handle)?;
640
641 let rtn = handler(vault_entry, api)?;
645
646 api.key_value_entry_close(vault_entry_handle)?;
648 api.key_value_entry_close(account_claims_handle)?;
649
650 Ok(rtn)
652 }
653}
654
655fn bucket_to_resource_specifier<Y: SystemApi<RuntimeError>>(
656 bucket: &Bucket,
657 api: &mut Y,
658) -> Result<(ResourceAddress, ResourceSpecifier), RuntimeError> {
659 let resource_address = bucket.resource_address(api)?;
660 if resource_address.is_fungible() {
661 let amount = bucket.amount(api)?;
662 Ok((resource_address, ResourceSpecifier::Fungible(amount)))
663 } else {
664 let ids = bucket.non_fungible_local_ids(api)?;
665 Ok((resource_address, ResourceSpecifier::NonFungible(ids)))
666 }
667}