1use super::internal_prelude::*;
2use crate::internal_prelude::*;
3use radix_engine_interface::api::field_api::*;
4use radix_engine_interface::api::object_api::*;
5use radix_engine_interface::api::*;
6use radix_engine_interface::blueprints::access_controller::*;
7use radix_engine_interface::blueprints::resource::*;
8use radix_engine_interface::object_modules::metadata::*;
9use radix_engine_interface::*;
10use radix_native_sdk::modules::metadata::*;
11use radix_native_sdk::modules::role_assignment::*;
12use radix_native_sdk::resource::*;
13use radix_native_sdk::runtime::*;
14use sbor::rust::prelude::*;
15
16pub struct AccessControllerV1Blueprint;
17
18impl AccessControllerV1Blueprint {
19 pub fn invoke_export<Y: SystemApi<RuntimeError>>(
20 export_name: &str,
21 input: &IndexedScryptoValue,
22 api: &mut Y,
23 ) -> Result<IndexedScryptoValue, RuntimeError> {
24 dispatch! {
25 IDENT,
26 export_name,
27 input,
28 api,
29 AccessController,
30 [
31 create,
32 create_proof,
33 initiate_recovery_as_primary,
34 initiate_recovery_as_recovery,
35 initiate_badge_withdraw_attempt_as_primary,
36 initiate_badge_withdraw_attempt_as_recovery,
37 quick_confirm_primary_role_recovery_proposal,
38 quick_confirm_recovery_role_recovery_proposal,
39 quick_confirm_primary_role_badge_withdraw_attempt,
40 quick_confirm_recovery_role_badge_withdraw_attempt,
41 timed_confirm_recovery,
42 cancel_primary_role_recovery_proposal,
43 cancel_recovery_role_recovery_proposal,
44 cancel_primary_role_badge_withdraw_attempt,
45 cancel_recovery_role_badge_withdraw_attempt,
46 lock_primary_role,
47 unlock_primary_role,
48 stop_timed_recovery,
49 mint_recovery_badges,
50 ]
51 }
52 }
53
54 pub fn create<Y: SystemApi<RuntimeError>>(
55 AccessControllerCreateInput {
56 controlled_asset,
57 rule_set,
58 timed_recovery_delay_in_minutes,
59 address_reservation,
60 }: AccessControllerCreateInput,
61 api: &mut Y,
62 ) -> Result<AccessControllerCreateOutput, RuntimeError> {
63 let (address_reservation, address) = {
66 if let Some(address_reservation) = address_reservation {
67 let address = api.get_reservation_address(address_reservation.0.as_node_id())?;
68 (address_reservation, address)
69 } else {
70 api.allocate_global_address(BlueprintId {
71 package_address: ACCESS_CONTROLLER_PACKAGE,
72 blueprint_name: ACCESS_CONTROLLER_BLUEPRINT.to_string(),
73 })?
74 }
75 };
76
77 let vault = {
79 let mut vault = controlled_asset
80 .resource_address(api)
81 .and_then(|resource_address| Vault::create(resource_address, api))?;
82 vault.put(controlled_asset, api)?;
83
84 vault
85 };
86
87 let recovery_badge_resource = {
89 let global_component_caller_badge =
90 NonFungibleGlobalId::global_caller_badge(GlobalCaller::GlobalObject(address));
91
92 let resource_address = {
93 let non_fungible_schema =
94 NonFungibleDataSchema::new_local_without_self_package_replacement::<()>();
95
96 let result = api.call_function(
97 RESOURCE_PACKAGE,
98 NON_FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT,
99 NON_FUNGIBLE_RESOURCE_MANAGER_CREATE_IDENT,
100 scrypto_encode(&NonFungibleResourceManagerCreateInput {
101 owner_role: OwnerRole::Fixed(rule!(require(global_component_caller_badge.clone()))),
102 id_type: NonFungibleIdType::Integer,
103 track_total_supply: true,
104 non_fungible_schema,
105 resource_roles: NonFungibleResourceRoles {
106 mint_roles: mint_roles! {
107 minter => rule!(require(global_component_caller_badge.clone()));
108 minter_updater => rule!(deny_all);
109 },
110 burn_roles: burn_roles! {
111 burner => rule!(allow_all);
112 burner_updater => rule!(allow_all);
113 },
114 withdraw_roles: withdraw_roles! {
115 withdrawer => rule!(deny_all);
116 withdrawer_updater => rule!(deny_all);
117 },
118 ..Default::default()
119 },
120 metadata: metadata! {
121 roles {
122 metadata_setter => AccessRule::DenyAll;
123 metadata_setter_updater => AccessRule::DenyAll;
124 metadata_locker => AccessRule::DenyAll;
125 metadata_locker_updater => AccessRule::DenyAll;
126 },
127 init {
128 "name" => "Recovery Badge".to_owned(), locked;
129 "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-recovery_badge.png"), locked;
130 "access_controller" => address, locked;
131 }
132 },
133 address_reservation: None,
134 })
135 .unwrap(),
136 )?;
137 scrypto_decode::<ResourceAddress>(result.as_slice()).unwrap()
138 };
139
140 resource_address
141 };
142
143 let substate = AccessControllerV1Substate::new(
144 vault,
145 timed_recovery_delay_in_minutes,
146 recovery_badge_resource,
147 );
148 let object_id = api.new_simple_object(
149 ACCESS_CONTROLLER_BLUEPRINT,
150 indexmap! {
151 AccessControllerField::State.field_index() => FieldValue::new(
152 AccessControllerStateFieldPayload::from_content_source(substate)
153 ),
154 },
155 )?;
156
157 let roles = init_roles_from_rule_set(rule_set);
158 let roles = indexmap!(ModuleId::Main => roles);
159 let role_assignment = RoleAssignment::create(OwnerRole::None, roles, api)?.0;
160
161 let metadata = Metadata::create_with_data(
162 metadata_init! {
163 "recovery_badge" => GlobalAddress::from(recovery_badge_resource), locked;
164 },
165 api,
166 )?;
167
168 api.globalize(
170 object_id,
171 indexmap!(
172 AttachedModuleId::RoleAssignment => role_assignment.0,
173 AttachedModuleId::Metadata => metadata.0,
174 ),
175 Some(address_reservation),
176 )?;
177
178 Ok(Global::new(ComponentAddress::try_from(address).unwrap()))
179 }
180
181 pub fn create_proof<Y: SystemApi<RuntimeError>>(
182 _: AccessControllerCreateProofInput,
183 api: &mut Y,
184 ) -> Result<AccessControllerCreateProofOutput, RuntimeError> {
185 transition(api, AccessControllerCreateProofStateMachineInput)
186 }
187
188 pub fn initiate_recovery_as_primary<Y: SystemApi<RuntimeError>>(
189 AccessControllerInitiateRecoveryAsPrimaryInput {
190 rule_set,
191 timed_recovery_delay_in_minutes,
192 }: AccessControllerInitiateRecoveryAsPrimaryInput,
193 api: &mut Y,
194 ) -> Result<AccessControllerInitiateRecoveryAsPrimaryOutput, RuntimeError> {
195 let proposal = RecoveryProposal {
196 rule_set,
197 timed_recovery_delay_in_minutes,
198 };
199
200 transition_mut(
201 api,
202 AccessControllerInitiateRecoveryAsPrimaryStateMachineInput {
203 proposal: proposal.clone(),
204 },
205 )?;
206
207 Runtime::emit_event(
208 api,
209 InitiateRecoveryEvent {
210 proposal,
211 proposer: Proposer::Primary,
212 },
213 )?;
214
215 Ok(())
216 }
217
218 pub fn initiate_recovery_as_recovery<Y: SystemApi<RuntimeError>>(
219 AccessControllerInitiateRecoveryAsRecoveryInput {
220 rule_set,
221 timed_recovery_delay_in_minutes,
222 }: AccessControllerInitiateRecoveryAsRecoveryInput,
223 api: &mut Y,
224 ) -> Result<AccessControllerInitiateRecoveryAsRecoveryOutput, RuntimeError> {
225 let proposal = RecoveryProposal {
226 rule_set,
227 timed_recovery_delay_in_minutes,
228 };
229
230 transition_mut(
231 api,
232 AccessControllerInitiateRecoveryAsRecoveryStateMachineInput {
233 proposal: proposal.clone(),
234 },
235 )?;
236
237 Runtime::emit_event(
238 api,
239 InitiateRecoveryEvent {
240 proposal,
241 proposer: Proposer::Recovery,
242 },
243 )?;
244
245 Ok(())
246 }
247
248 pub fn initiate_badge_withdraw_attempt_as_primary<Y: SystemApi<RuntimeError>>(
249 AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryInput { .. }: AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryInput,
250 api: &mut Y,
251 ) -> Result<AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryOutput, RuntimeError> {
252 transition_mut(
253 api,
254 AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryStateMachineInput,
255 )?;
256
257 Runtime::emit_event(
258 api,
259 InitiateBadgeWithdrawAttemptEvent {
260 proposer: Proposer::Primary,
261 },
262 )?;
263
264 Ok(())
265 }
266
267 pub fn initiate_badge_withdraw_attempt_as_recovery<Y: SystemApi<RuntimeError>>(
268 _: AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryInput,
269 api: &mut Y,
270 ) -> Result<AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryOutput, RuntimeError> {
271 transition_mut(
272 api,
273 AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryStateMachineInput,
274 )?;
275
276 Runtime::emit_event(
277 api,
278 InitiateBadgeWithdrawAttemptEvent {
279 proposer: Proposer::Recovery,
280 },
281 )?;
282
283 Ok(())
284 }
285
286 pub fn quick_confirm_primary_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
287 AccessControllerQuickConfirmPrimaryRoleRecoveryProposalInput {
288 rule_set,
289 timed_recovery_delay_in_minutes,
290 }: AccessControllerQuickConfirmPrimaryRoleRecoveryProposalInput,
291 api: &mut Y,
292 ) -> Result<AccessControllerQuickConfirmPrimaryRoleRecoveryProposalOutput, RuntimeError> {
293 let proposal = RecoveryProposal {
294 rule_set,
295 timed_recovery_delay_in_minutes,
296 };
297
298 let recovery_proposal = transition_mut(
299 api,
300 AccessControllerQuickConfirmPrimaryRoleRecoveryProposalStateMachineInput {
301 proposal_to_confirm: proposal.clone(),
302 },
303 )?;
304
305 let receiver = Runtime::get_node_id(api)?;
306 update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
307
308 Runtime::emit_event(
309 api,
310 RuleSetUpdateEvent {
311 proposal,
312 proposer: Proposer::Primary,
313 },
314 )?;
315
316 Ok(())
317 }
318
319 pub fn quick_confirm_recovery_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
320 AccessControllerQuickConfirmRecoveryRoleRecoveryProposalInput {
321 rule_set,
322 timed_recovery_delay_in_minutes,
323 }: AccessControllerQuickConfirmRecoveryRoleRecoveryProposalInput,
324 api: &mut Y,
325 ) -> Result<AccessControllerQuickConfirmRecoveryRoleRecoveryProposalOutput, RuntimeError> {
326 let proposal = RecoveryProposal {
327 rule_set,
328 timed_recovery_delay_in_minutes,
329 };
330
331 let recovery_proposal = transition_mut(
332 api,
333 AccessControllerQuickConfirmRecoveryRoleRecoveryProposalStateMachineInput {
334 proposal_to_confirm: proposal.clone(),
335 },
336 )?;
337
338 let receiver = Runtime::get_node_id(api)?;
339 update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
340
341 Runtime::emit_event(
342 api,
343 RuleSetUpdateEvent {
344 proposal,
345 proposer: Proposer::Recovery,
346 },
347 )?;
348
349 Ok(())
350 }
351
352 pub fn quick_confirm_primary_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
353 _: AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptInput,
354 api: &mut Y,
355 ) -> Result<AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptOutput, RuntimeError>
356 {
357 let bucket = transition_mut(
358 api,
359 AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptStateMachineInput,
360 )?;
361
362 let receiver = Runtime::get_node_id(api)?;
363 update_role_assignment(api, &receiver, locked_role_assignment())?;
364
365 Runtime::emit_event(
366 api,
367 BadgeWithdrawEvent {
368 proposer: Proposer::Primary,
369 },
370 )?;
371
372 Ok(bucket)
373 }
374
375 pub fn quick_confirm_recovery_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
376 _: AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptInput,
377 api: &mut Y,
378 ) -> Result<AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptOutput, RuntimeError>
379 {
380 let bucket = transition_mut(
381 api,
382 AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptStateMachineInput,
383 )?;
384
385 let receiver = Runtime::get_node_id(api)?;
386 update_role_assignment(api, &receiver, locked_role_assignment())?;
387
388 Runtime::emit_event(
389 api,
390 BadgeWithdrawEvent {
391 proposer: Proposer::Recovery,
392 },
393 )?;
394
395 Ok(bucket)
396 }
397
398 pub fn timed_confirm_recovery<Y: SystemApi<RuntimeError>>(
399 AccessControllerTimedConfirmRecoveryInput {
400 rule_set,
401 timed_recovery_delay_in_minutes,
402 }: AccessControllerTimedConfirmRecoveryInput,
403 api: &mut Y,
404 ) -> Result<AccessControllerTimedConfirmRecoveryOutput, RuntimeError> {
405 let proposal = RecoveryProposal {
406 rule_set,
407 timed_recovery_delay_in_minutes,
408 };
409
410 let recovery_proposal = transition_mut(
411 api,
412 AccessControllerTimedConfirmRecoveryStateMachineInput {
413 proposal_to_confirm: proposal.clone(),
414 },
415 )?;
416
417 let receiver = Runtime::get_node_id(api)?;
419 update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
420
421 Runtime::emit_event(
422 api,
423 RuleSetUpdateEvent {
424 proposal,
425 proposer: Proposer::Recovery,
426 },
427 )?;
428
429 Ok(())
430 }
431
432 pub fn cancel_primary_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
433 AccessControllerCancelPrimaryRoleRecoveryProposalInput { .. }: AccessControllerCancelPrimaryRoleRecoveryProposalInput,
434 api: &mut Y,
435 ) -> Result<AccessControllerCancelPrimaryRoleRecoveryProposalOutput, RuntimeError> {
436 transition_mut(
437 api,
438 AccessControllerCancelPrimaryRoleRecoveryProposalStateMachineInput,
439 )?;
440
441 Runtime::emit_event(
442 api,
443 CancelRecoveryProposalEvent {
444 proposer: Proposer::Primary,
445 },
446 )?;
447
448 Ok(())
449 }
450
451 pub fn cancel_recovery_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
452 AccessControllerCancelRecoveryRoleRecoveryProposalInput { .. }: AccessControllerCancelRecoveryRoleRecoveryProposalInput,
453 api: &mut Y,
454 ) -> Result<AccessControllerCancelRecoveryRoleRecoveryProposalOutput, RuntimeError> {
455 transition_mut(
456 api,
457 AccessControllerCancelRecoveryRoleRecoveryProposalStateMachineInput,
458 )?;
459
460 Runtime::emit_event(
461 api,
462 CancelRecoveryProposalEvent {
463 proposer: Proposer::Recovery,
464 },
465 )?;
466
467 Ok(())
468 }
469
470 pub fn cancel_primary_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
471 AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptInput { .. }: AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptInput,
472 api: &mut Y,
473 ) -> Result<AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptOutput, RuntimeError> {
474 transition_mut(
475 api,
476 AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptStateMachineInput,
477 )?;
478
479 Runtime::emit_event(
480 api,
481 CancelBadgeWithdrawAttemptEvent {
482 proposer: Proposer::Primary,
483 },
484 )?;
485
486 Ok(())
487 }
488
489 pub fn cancel_recovery_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
490 AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptInput { .. }: AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptInput,
491 api: &mut Y,
492 ) -> Result<AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptOutput, RuntimeError> {
493 transition_mut(
494 api,
495 AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptStateMachineInput,
496 )?;
497
498 Runtime::emit_event(
499 api,
500 CancelBadgeWithdrawAttemptEvent {
501 proposer: Proposer::Recovery,
502 },
503 )?;
504
505 Ok(())
506 }
507
508 pub fn lock_primary_role<Y: SystemApi<RuntimeError>>(
509 AccessControllerLockPrimaryRoleInput { .. }: AccessControllerLockPrimaryRoleInput,
510 api: &mut Y,
511 ) -> Result<AccessControllerLockPrimaryRoleOutput, RuntimeError> {
512 transition_mut(api, AccessControllerLockPrimaryRoleStateMachineInput)?;
513 Runtime::emit_event(api, LockPrimaryRoleEvent {})?;
514
515 Ok(())
516 }
517
518 pub fn unlock_primary_role<Y: SystemApi<RuntimeError>>(
519 _: AccessControllerUnlockPrimaryRoleInput,
520 api: &mut Y,
521 ) -> Result<AccessControllerUnlockPrimaryRoleOutput, RuntimeError> {
522 transition_mut(api, AccessControllerUnlockPrimaryRoleStateMachineInput)?;
523 Runtime::emit_event(api, UnlockPrimaryRoleEvent {})?;
524
525 Ok(())
526 }
527
528 pub fn stop_timed_recovery<Y: SystemApi<RuntimeError>>(
529 AccessControllerStopTimedRecoveryInput {
530 rule_set,
531 timed_recovery_delay_in_minutes,
532 }: AccessControllerStopTimedRecoveryInput,
533 api: &mut Y,
534 ) -> Result<AccessControllerStopTimedRecoveryOutput, RuntimeError> {
535 transition_mut(
536 api,
537 AccessControllerStopTimedRecoveryStateMachineInput {
538 proposal: RecoveryProposal {
539 rule_set,
540 timed_recovery_delay_in_minutes,
541 },
542 },
543 )?;
544 Runtime::emit_event(api, StopTimedRecoveryEvent)?;
545
546 Ok(())
547 }
548
549 pub fn mint_recovery_badges<Y: SystemApi<RuntimeError>>(
550 AccessControllerMintRecoveryBadgesInput {
551 non_fungible_local_ids,
552 }: AccessControllerMintRecoveryBadgesInput,
553 api: &mut Y,
554 ) -> Result<AccessControllerMintRecoveryBadgesOutput, RuntimeError> {
555 let resource_address = {
556 let handle = api.actor_open_field(
557 ACTOR_STATE_SELF,
558 AccessControllerField::State.field_index(),
559 LockFlags::read_only(),
560 )?;
561
562 let access_controller = {
563 let access_controller: AccessControllerStateFieldPayload =
564 api.field_read_typed(handle)?;
565 access_controller.fully_update_and_into_latest_version()
566 };
567 access_controller.recovery_badge
568 };
569
570 let non_fungibles: IndexMap<NonFungibleLocalId, (ScryptoValue,)> = non_fungible_local_ids
571 .into_iter()
572 .map(|local_id| {
573 (
574 local_id,
575 (scrypto_decode(&scrypto_encode(&()).unwrap()).unwrap(),),
576 )
577 })
578 .collect();
579
580 let bucket = api
581 .call_method(
582 resource_address.as_node_id(),
583 NON_FUNGIBLE_RESOURCE_MANAGER_MINT_IDENT,
584 scrypto_encode(&NonFungibleResourceManagerMintInput {
585 entries: non_fungibles,
586 })
587 .unwrap(),
588 )
589 .map(|buffer| {
590 scrypto_decode::<NonFungibleResourceManagerMintOutput>(&buffer).unwrap()
591 })?;
592
593 Ok(bucket)
594 }
595}
596
597fn locked_role_assignment() -> RuleSet {
602 RuleSet {
603 primary_role: AccessRule::DenyAll,
604 recovery_role: AccessRule::DenyAll,
605 confirmation_role: AccessRule::DenyAll,
606 }
607}
608
609fn init_roles_from_rule_set(rule_set: RuleSet) -> RoleAssignmentInit {
610 roles2! {
611 "primary" => rule_set.primary_role, updatable;
612 "recovery" => rule_set.recovery_role, updatable;
613 "confirmation" => rule_set.confirmation_role, updatable;
614 }
615}
616
617fn transition<Y: SystemApi<RuntimeError>, I>(
618 api: &mut Y,
619 input: I,
620) -> Result<<AccessControllerV1Substate as Transition<I>>::Output, RuntimeError>
621where
622 AccessControllerV1Substate: Transition<I>,
623{
624 let handle = api.actor_open_field(
625 ACTOR_STATE_SELF,
626 AccessControllerField::State.field_index(),
627 LockFlags::read_only(),
628 )?;
629
630 let access_controller = {
631 let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?;
632 access_controller.fully_update_and_into_latest_version()
633 };
634
635 let rtn = access_controller.transition(api, input)?;
636
637 api.field_close(handle)?;
638
639 Ok(rtn)
640}
641
642fn transition_mut<Y: SystemApi<RuntimeError>, I>(
643 api: &mut Y,
644 input: I,
645) -> Result<<AccessControllerV1Substate as TransitionMut<I>>::Output, RuntimeError>
646where
647 AccessControllerV1Substate: TransitionMut<I>,
648{
649 let handle = api.actor_open_field(
650 ACTOR_STATE_SELF,
651 AccessControllerField::State.field_index(),
652 LockFlags::MUTABLE,
653 )?;
654
655 let mut access_controller = {
656 let access_controller: AccessControllerStateFieldPayload = api.field_read_typed(handle)?;
657 access_controller.fully_update_and_into_latest_version()
658 };
659
660 let rtn = access_controller.transition_mut(api, input)?;
661
662 {
663 api.field_write_typed(
664 handle,
665 &AccessControllerStateFieldPayload::from_content_source(access_controller),
666 )?;
667 }
668
669 api.field_close(handle)?;
670
671 Ok(rtn)
672}
673
674fn update_role_assignment<Y: SystemApi<RuntimeError>>(
675 api: &mut Y,
676 receiver: &NodeId,
677 rule_set: RuleSet,
678) -> Result<(), RuntimeError> {
679 let attached = AttachedRoleAssignment(*receiver);
680 attached.set_role(
681 ModuleId::Main,
682 RoleKey::new("primary"),
683 rule_set.primary_role.clone(),
684 api,
685 )?;
686 attached.set_role(
687 ModuleId::Main,
688 RoleKey::new("recovery"),
689 rule_set.recovery_role.clone(),
690 api,
691 )?;
692 attached.set_role(
693 ModuleId::Main,
694 RoleKey::new("confirmation"),
695 rule_set.confirmation_role.clone(),
696 api,
697 )?;
698
699 Ok(())
700}