radix_engine/blueprints/access_controller/v1/
blueprint.rs

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        // Allocating the address of the access controller - this will be needed for the metadata
64        // and access rules of the recovery badge
65        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        // Creating a new vault and putting in it the controlled asset
78        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        // Creating a new recovery badge resource
88        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        // Creating a global component address for the access controller RENode
169        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        // Update the access rules
418        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
597//=========
598// Helpers
599//=========
600
601fn 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}