radix_engine/blueprints/access_controller/v2/
blueprint.rs

1use super::internal_prelude::*;
2use crate::errors::*;
3use crate::internal_prelude::*;
4use radix_engine_interface::api::field_api::*;
5use radix_engine_interface::api::object_api::*;
6use radix_engine_interface::api::*;
7use radix_engine_interface::blueprints::access_controller::*;
8use radix_engine_interface::blueprints::resource::*;
9use radix_engine_interface::object_modules::metadata::*;
10use radix_engine_interface::*;
11use radix_native_sdk::modules::metadata::*;
12use radix_native_sdk::modules::role_assignment::*;
13use radix_native_sdk::resource::*;
14use radix_native_sdk::runtime::*;
15use sbor::rust::prelude::*;
16
17pub struct AccessControllerV2Blueprint;
18
19impl AccessControllerV2Blueprint {
20    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
21        export_name: &str,
22        input: &IndexedScryptoValue,
23        api: &mut Y,
24    ) -> Result<IndexedScryptoValue, RuntimeError> {
25        dispatch! {
26            IDENT,
27            export_name,
28            input,
29            api,
30            AccessController,
31            [
32                // Original Methods
33                create,
34                create_proof,
35                initiate_recovery_as_primary,
36                initiate_recovery_as_recovery,
37                initiate_badge_withdraw_attempt_as_primary,
38                initiate_badge_withdraw_attempt_as_recovery,
39                quick_confirm_primary_role_recovery_proposal,
40                quick_confirm_recovery_role_recovery_proposal,
41                quick_confirm_primary_role_badge_withdraw_attempt,
42                quick_confirm_recovery_role_badge_withdraw_attempt,
43                timed_confirm_recovery,
44                cancel_primary_role_recovery_proposal,
45                cancel_recovery_role_recovery_proposal,
46                cancel_primary_role_badge_withdraw_attempt,
47                cancel_recovery_role_badge_withdraw_attempt,
48                lock_primary_role,
49                unlock_primary_role,
50                stop_timed_recovery,
51                mint_recovery_badges,
52                // Bottlenose Extension
53                lock_recovery_fee,
54                withdraw_recovery_fee,
55                contribute_recovery_fee,
56            ]
57        }
58    }
59
60    pub fn create<Y: SystemApi<RuntimeError>>(
61        AccessControllerCreateInput {
62            controlled_asset,
63            rule_set,
64            timed_recovery_delay_in_minutes,
65            address_reservation,
66        }: AccessControllerCreateInput,
67        api: &mut Y,
68    ) -> Result<AccessControllerCreateOutput, RuntimeError> {
69        // Allocating the address of the access controller - this will be needed for the metadata
70        // and access rules of the recovery badge
71        let (address_reservation, address) = {
72            if let Some(address_reservation) = address_reservation {
73                let address = api.get_reservation_address(address_reservation.0.as_node_id())?;
74                (address_reservation, address)
75            } else {
76                api.allocate_global_address(BlueprintId {
77                    package_address: ACCESS_CONTROLLER_PACKAGE,
78                    blueprint_name: ACCESS_CONTROLLER_BLUEPRINT.to_string(),
79                })?
80            }
81        };
82
83        // Creating a new vault and putting in it the controlled asset
84        let vault = {
85            let mut vault = controlled_asset
86                .resource_address(api)
87                .and_then(|resource_address| Vault::create(resource_address, api))?;
88            vault.put(controlled_asset, api)?;
89
90            vault
91        };
92
93        // Creating a new recovery badge resource
94        let recovery_badge_resource = {
95            let global_component_caller_badge =
96                NonFungibleGlobalId::global_caller_badge(GlobalCaller::GlobalObject(address));
97
98            let resource_address = {
99                let non_fungible_schema =
100                    NonFungibleDataSchema::new_local_without_self_package_replacement::<()>();
101
102                let result = api.call_function(
103                    RESOURCE_PACKAGE,
104                    NON_FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT,
105                    NON_FUNGIBLE_RESOURCE_MANAGER_CREATE_IDENT,
106                    scrypto_encode(&NonFungibleResourceManagerCreateInput {
107                        owner_role: OwnerRole::Fixed(rule!(require(global_component_caller_badge.clone()))),
108                        id_type: NonFungibleIdType::Integer,
109                        track_total_supply: true,
110                        non_fungible_schema,
111                        resource_roles: NonFungibleResourceRoles {
112                            mint_roles: mint_roles! {
113                                minter => rule!(require(global_component_caller_badge.clone()));
114                                minter_updater => rule!(deny_all);
115                            },
116                            burn_roles: burn_roles! {
117                                burner => rule!(allow_all);
118                                burner_updater => rule!(allow_all);
119                            },
120                            withdraw_roles: withdraw_roles! {
121                                withdrawer => rule!(deny_all);
122                                withdrawer_updater => rule!(deny_all);
123                            },
124                            ..Default::default()
125                        },
126                        metadata: metadata! {
127                            roles {
128                                metadata_setter => AccessRule::DenyAll;
129                                metadata_setter_updater => AccessRule::DenyAll;
130                                metadata_locker => AccessRule::DenyAll;
131                                metadata_locker_updater => AccessRule::DenyAll;
132                            },
133                            init {
134                                "name" => "Recovery Badge".to_owned(), locked;
135                                "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-recovery_badge.png"), locked;
136                                "access_controller" => address, locked;
137                            }
138                        },
139                        address_reservation: None,
140                    })
141                        .unwrap(),
142                )?;
143                scrypto_decode::<ResourceAddress>(result.as_slice()).unwrap()
144            };
145
146            resource_address
147        };
148
149        let substate = AccessControllerV2Substate::new(
150            vault,
151            None,
152            timed_recovery_delay_in_minutes,
153            recovery_badge_resource,
154        );
155
156        let object_id = api.new_simple_object(
157            ACCESS_CONTROLLER_BLUEPRINT,
158            indexmap! {
159                AccessControllerV2Field::State.field_index() => FieldValue::new(
160                    AccessControllerV2StateFieldPayload::from_content_source(substate)
161                ),
162            },
163        )?;
164
165        let roles = init_roles_from_rule_set(rule_set);
166        let roles = indexmap!(ModuleId::Main => roles);
167        let role_assignment = RoleAssignment::create(OwnerRole::None, roles, api)?.0;
168
169        let metadata = Metadata::create_with_data(
170            metadata_init! {
171                "recovery_badge" => GlobalAddress::from(recovery_badge_resource), locked;
172            },
173            api,
174        )?;
175
176        // Creating a global component address for the access controller RENode
177        api.globalize(
178            object_id,
179            indexmap!(
180                AttachedModuleId::RoleAssignment => role_assignment.0,
181                AttachedModuleId::Metadata => metadata.0,
182            ),
183            Some(address_reservation),
184        )?;
185
186        Ok(Global::new(ComponentAddress::try_from(address).unwrap()))
187    }
188
189    pub fn create_proof<Y: SystemApi<RuntimeError>>(
190        _: AccessControllerCreateProofInput,
191        api: &mut Y,
192    ) -> Result<AccessControllerCreateProofOutput, RuntimeError> {
193        transition(api, AccessControllerCreateProofStateMachineInput)
194    }
195
196    pub fn initiate_recovery_as_primary<Y: SystemApi<RuntimeError>>(
197        AccessControllerInitiateRecoveryAsPrimaryInput {
198            rule_set,
199            timed_recovery_delay_in_minutes,
200        }: AccessControllerInitiateRecoveryAsPrimaryInput,
201        api: &mut Y,
202    ) -> Result<AccessControllerInitiateRecoveryAsPrimaryOutput, RuntimeError> {
203        let proposal = RecoveryProposal {
204            rule_set,
205            timed_recovery_delay_in_minutes,
206        };
207
208        transition_mut(
209            api,
210            AccessControllerInitiateRecoveryAsPrimaryStateMachineInput {
211                proposal: proposal.clone(),
212            },
213        )?;
214
215        Runtime::emit_event(
216            api,
217            InitiateRecoveryEvent {
218                proposal,
219                proposer: Proposer::Primary,
220            },
221        )?;
222
223        Ok(())
224    }
225
226    pub fn initiate_recovery_as_recovery<Y: SystemApi<RuntimeError>>(
227        AccessControllerInitiateRecoveryAsRecoveryInput {
228            rule_set,
229            timed_recovery_delay_in_minutes,
230        }: AccessControllerInitiateRecoveryAsRecoveryInput,
231        api: &mut Y,
232    ) -> Result<AccessControllerInitiateRecoveryAsRecoveryOutput, RuntimeError> {
233        let proposal = RecoveryProposal {
234            rule_set,
235            timed_recovery_delay_in_minutes,
236        };
237
238        transition_mut(
239            api,
240            AccessControllerInitiateRecoveryAsRecoveryStateMachineInput {
241                proposal: proposal.clone(),
242            },
243        )?;
244
245        Runtime::emit_event(
246            api,
247            InitiateRecoveryEvent {
248                proposal,
249                proposer: Proposer::Recovery,
250            },
251        )?;
252
253        Ok(())
254    }
255
256    pub fn initiate_badge_withdraw_attempt_as_primary<Y: SystemApi<RuntimeError>>(
257        AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryInput { .. }: AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryInput,
258        api: &mut Y,
259    ) -> Result<AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryOutput, RuntimeError> {
260        transition_mut(
261            api,
262            AccessControllerInitiateBadgeWithdrawAttemptAsPrimaryStateMachineInput,
263        )?;
264
265        Runtime::emit_event(
266            api,
267            InitiateBadgeWithdrawAttemptEvent {
268                proposer: Proposer::Primary,
269            },
270        )?;
271
272        Ok(())
273    }
274
275    pub fn initiate_badge_withdraw_attempt_as_recovery<Y: SystemApi<RuntimeError>>(
276        _: AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryInput,
277        api: &mut Y,
278    ) -> Result<AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryOutput, RuntimeError> {
279        transition_mut(
280            api,
281            AccessControllerInitiateBadgeWithdrawAttemptAsRecoveryStateMachineInput,
282        )?;
283
284        Runtime::emit_event(
285            api,
286            InitiateBadgeWithdrawAttemptEvent {
287                proposer: Proposer::Recovery,
288            },
289        )?;
290
291        Ok(())
292    }
293
294    pub fn quick_confirm_primary_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
295        AccessControllerQuickConfirmPrimaryRoleRecoveryProposalInput {
296            rule_set,
297            timed_recovery_delay_in_minutes,
298        }: AccessControllerQuickConfirmPrimaryRoleRecoveryProposalInput,
299        api: &mut Y,
300    ) -> Result<AccessControllerQuickConfirmPrimaryRoleRecoveryProposalOutput, RuntimeError> {
301        let proposal = RecoveryProposal {
302            rule_set,
303            timed_recovery_delay_in_minutes,
304        };
305
306        let recovery_proposal = transition_mut(
307            api,
308            AccessControllerQuickConfirmPrimaryRoleRecoveryProposalStateMachineInput {
309                proposal_to_confirm: proposal.clone(),
310            },
311        )?;
312
313        let receiver = Runtime::get_node_id(api)?;
314        update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
315
316        Runtime::emit_event(
317            api,
318            RuleSetUpdateEvent {
319                proposal,
320                proposer: Proposer::Primary,
321            },
322        )?;
323
324        Ok(())
325    }
326
327    pub fn quick_confirm_recovery_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
328        AccessControllerQuickConfirmRecoveryRoleRecoveryProposalInput {
329            rule_set,
330            timed_recovery_delay_in_minutes,
331        }: AccessControllerQuickConfirmRecoveryRoleRecoveryProposalInput,
332        api: &mut Y,
333    ) -> Result<AccessControllerQuickConfirmRecoveryRoleRecoveryProposalOutput, RuntimeError> {
334        let proposal = RecoveryProposal {
335            rule_set,
336            timed_recovery_delay_in_minutes,
337        };
338
339        let recovery_proposal = transition_mut(
340            api,
341            AccessControllerQuickConfirmRecoveryRoleRecoveryProposalStateMachineInput {
342                proposal_to_confirm: proposal.clone(),
343            },
344        )?;
345
346        let receiver = Runtime::get_node_id(api)?;
347        update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
348
349        Runtime::emit_event(
350            api,
351            RuleSetUpdateEvent {
352                proposal,
353                proposer: Proposer::Recovery,
354            },
355        )?;
356
357        Ok(())
358    }
359
360    pub fn quick_confirm_primary_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
361        _: AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptInput,
362        api: &mut Y,
363    ) -> Result<AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptOutput, RuntimeError>
364    {
365        let bucket = transition_mut(
366            api,
367            AccessControllerQuickConfirmPrimaryRoleBadgeWithdrawAttemptStateMachineInput,
368        )?;
369
370        let receiver = Runtime::get_node_id(api)?;
371        update_role_assignment(api, &receiver, locked_role_assignment())?;
372
373        Runtime::emit_event(
374            api,
375            BadgeWithdrawEvent {
376                proposer: Proposer::Primary,
377            },
378        )?;
379
380        Ok(bucket)
381    }
382
383    pub fn quick_confirm_recovery_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
384        _: AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptInput,
385        api: &mut Y,
386    ) -> Result<AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptOutput, RuntimeError>
387    {
388        let bucket = transition_mut(
389            api,
390            AccessControllerQuickConfirmRecoveryRoleBadgeWithdrawAttemptStateMachineInput,
391        )?;
392
393        let receiver = Runtime::get_node_id(api)?;
394        update_role_assignment(api, &receiver, locked_role_assignment())?;
395
396        Runtime::emit_event(
397            api,
398            BadgeWithdrawEvent {
399                proposer: Proposer::Recovery,
400            },
401        )?;
402
403        Ok(bucket)
404    }
405
406    pub fn timed_confirm_recovery<Y: SystemApi<RuntimeError>>(
407        AccessControllerTimedConfirmRecoveryInput {
408            rule_set,
409            timed_recovery_delay_in_minutes,
410        }: AccessControllerTimedConfirmRecoveryInput,
411        api: &mut Y,
412    ) -> Result<AccessControllerTimedConfirmRecoveryOutput, RuntimeError> {
413        let proposal = RecoveryProposal {
414            rule_set,
415            timed_recovery_delay_in_minutes,
416        };
417
418        let recovery_proposal = transition_mut(
419            api,
420            AccessControllerTimedConfirmRecoveryStateMachineInput {
421                proposal_to_confirm: proposal.clone(),
422            },
423        )?;
424
425        // Update the access rules
426        let receiver = Runtime::get_node_id(api)?;
427        update_role_assignment(api, &receiver, recovery_proposal.rule_set)?;
428
429        Runtime::emit_event(
430            api,
431            RuleSetUpdateEvent {
432                proposal,
433                proposer: Proposer::Recovery,
434            },
435        )?;
436
437        Ok(())
438    }
439
440    pub fn cancel_primary_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
441        AccessControllerCancelPrimaryRoleRecoveryProposalInput { .. }: AccessControllerCancelPrimaryRoleRecoveryProposalInput,
442        api: &mut Y,
443    ) -> Result<AccessControllerCancelPrimaryRoleRecoveryProposalOutput, RuntimeError> {
444        transition_mut(
445            api,
446            AccessControllerCancelPrimaryRoleRecoveryProposalStateMachineInput,
447        )?;
448
449        Runtime::emit_event(
450            api,
451            CancelRecoveryProposalEvent {
452                proposer: Proposer::Primary,
453            },
454        )?;
455
456        Ok(())
457    }
458
459    pub fn cancel_recovery_role_recovery_proposal<Y: SystemApi<RuntimeError>>(
460        AccessControllerCancelRecoveryRoleRecoveryProposalInput { .. }: AccessControllerCancelRecoveryRoleRecoveryProposalInput,
461        api: &mut Y,
462    ) -> Result<AccessControllerCancelRecoveryRoleRecoveryProposalOutput, RuntimeError> {
463        transition_mut(
464            api,
465            AccessControllerCancelRecoveryRoleRecoveryProposalStateMachineInput,
466        )?;
467
468        Runtime::emit_event(
469            api,
470            CancelRecoveryProposalEvent {
471                proposer: Proposer::Recovery,
472            },
473        )?;
474
475        Ok(())
476    }
477
478    pub fn cancel_primary_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
479        AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptInput { .. }: AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptInput,
480        api: &mut Y,
481    ) -> Result<AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptOutput, RuntimeError> {
482        transition_mut(
483            api,
484            AccessControllerCancelPrimaryRoleBadgeWithdrawAttemptStateMachineInput,
485        )?;
486
487        Runtime::emit_event(
488            api,
489            CancelBadgeWithdrawAttemptEvent {
490                proposer: Proposer::Primary,
491            },
492        )?;
493
494        Ok(())
495    }
496
497    pub fn cancel_recovery_role_badge_withdraw_attempt<Y: SystemApi<RuntimeError>>(
498        AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptInput { .. }: AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptInput,
499        api: &mut Y,
500    ) -> Result<AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptOutput, RuntimeError> {
501        transition_mut(
502            api,
503            AccessControllerCancelRecoveryRoleBadgeWithdrawAttemptStateMachineInput,
504        )?;
505
506        Runtime::emit_event(
507            api,
508            CancelBadgeWithdrawAttemptEvent {
509                proposer: Proposer::Recovery,
510            },
511        )?;
512
513        Ok(())
514    }
515
516    pub fn lock_primary_role<Y: SystemApi<RuntimeError>>(
517        AccessControllerLockPrimaryRoleInput { .. }: AccessControllerLockPrimaryRoleInput,
518        api: &mut Y,
519    ) -> Result<AccessControllerLockPrimaryRoleOutput, RuntimeError> {
520        transition_mut(api, AccessControllerLockPrimaryRoleStateMachineInput)?;
521        Runtime::emit_event(api, LockPrimaryRoleEvent {})?;
522
523        Ok(())
524    }
525
526    pub fn unlock_primary_role<Y: SystemApi<RuntimeError>>(
527        _: AccessControllerUnlockPrimaryRoleInput,
528        api: &mut Y,
529    ) -> Result<AccessControllerUnlockPrimaryRoleOutput, RuntimeError> {
530        transition_mut(api, AccessControllerUnlockPrimaryRoleStateMachineInput)?;
531        Runtime::emit_event(api, UnlockPrimaryRoleEvent {})?;
532
533        Ok(())
534    }
535
536    pub fn stop_timed_recovery<Y: SystemApi<RuntimeError>>(
537        AccessControllerStopTimedRecoveryInput {
538            rule_set,
539            timed_recovery_delay_in_minutes,
540        }: AccessControllerStopTimedRecoveryInput,
541        api: &mut Y,
542    ) -> Result<AccessControllerStopTimedRecoveryOutput, RuntimeError> {
543        transition_mut(
544            api,
545            AccessControllerStopTimedRecoveryStateMachineInput {
546                proposal: RecoveryProposal {
547                    rule_set,
548                    timed_recovery_delay_in_minutes,
549                },
550            },
551        )?;
552        Runtime::emit_event(api, StopTimedRecoveryEvent)?;
553
554        Ok(())
555    }
556
557    pub fn mint_recovery_badges<Y: SystemApi<RuntimeError>>(
558        AccessControllerMintRecoveryBadgesInput {
559            non_fungible_local_ids,
560        }: AccessControllerMintRecoveryBadgesInput,
561        api: &mut Y,
562    ) -> Result<AccessControllerMintRecoveryBadgesOutput, RuntimeError> {
563        Self::with_state(api, |state, api| {
564            api.call_method(
565                state.recovery_badge.as_node_id(),
566                NON_FUNGIBLE_RESOURCE_MANAGER_MINT_IDENT,
567                scrypto_encode(&NonFungibleResourceManagerMintInput {
568                    entries: non_fungible_local_ids
569                        .into_iter()
570                        .map(|local_id| {
571                            (
572                                local_id,
573                                (scrypto_decode(&scrypto_encode(&()).unwrap()).unwrap(),),
574                            )
575                        })
576                        .collect(),
577                })
578                .unwrap(),
579            )
580            .map(|buffer| scrypto_decode::<NonFungibleResourceManagerMintOutput>(&buffer).unwrap())
581        })
582    }
583
584    pub fn lock_recovery_fee<Y: SystemApi<RuntimeError>>(
585        AccessControllerLockRecoveryFeeInput { amount }: AccessControllerLockRecoveryFeeInput,
586        api: &mut Y,
587    ) -> Result<AccessControllerLockRecoveryFeeOutput, RuntimeError> {
588        Self::with_state_mut(api, |state, api| {
589            let vault = state
590                .xrd_fee_vault
591                .as_mut()
592                .ok_or(AccessControllerError::NoXrdFeeVault)?;
593            vault.lock_fee(api, amount)
594        })
595    }
596
597    pub fn withdraw_recovery_fee<Y: SystemApi<RuntimeError>>(
598        AccessControllerWithdrawRecoveryFeeInput { amount }: AccessControllerWithdrawRecoveryFeeInput,
599        api: &mut Y,
600    ) -> Result<AccessControllerWithdrawRecoveryFeeOutput, RuntimeError> {
601        Runtime::emit_event(api, WithdrawRecoveryXrdEvent { amount })?;
602
603        Self::with_state_mut(api, |state, api| {
604            let vault = state
605                .xrd_fee_vault
606                .as_mut()
607                .ok_or(AccessControllerError::NoXrdFeeVault)?;
608            vault.take(amount, api)
609        })
610    }
611
612    pub fn contribute_recovery_fee<Y: SystemApi<RuntimeError>>(
613        AccessControllerContributeRecoveryFeeInput { bucket }: AccessControllerContributeRecoveryFeeInput,
614        api: &mut Y,
615    ) -> Result<AccessControllerContributeRecoveryFeeOutput, RuntimeError> {
616        bucket
617            .amount(api)
618            .and_then(|amount| Runtime::emit_event(api, DepositRecoveryXrdEvent { amount }))?;
619
620        Self::with_state_mut(api, |state, api| {
621            let vault = match state.xrd_fee_vault {
622                Some(ref mut vault) => vault,
623                None => {
624                    state.xrd_fee_vault = Some(Vault::create(XRD, api)?);
625                    state.xrd_fee_vault.as_mut().unwrap()
626                }
627            };
628            vault.put(bucket, api)
629        })
630    }
631
632    /// This method is used to read the access controller state and perform any lazy updating
633    /// required.
634    fn with_state<Y: SystemApi<RuntimeError>, O>(
635        api: &mut Y,
636        callback: impl FnOnce(&mut AccessControllerV2Substate, &mut Y) -> Result<O, RuntimeError>,
637    ) -> Result<O, RuntimeError> {
638        // Get a read lock over the access-controller field.
639        let handle = api.actor_open_field(
640            ACTOR_STATE_SELF,
641            AccessControllerV2Field::State.field_index(),
642            LockFlags::read_only(),
643        )?;
644
645        // Read the access controller state.
646        let access_controller_state = api
647            .field_read_typed::<AccessControllerV2StateFieldPayload>(handle)?
648            .into_content();
649
650        // Determine if updating the state is required or not. If a state update is required then
651        // perform it and write it to the state. To do this we do the following:
652        // 1. We have a readonly handle to the substate so we need a new write handle. We close and
653        //    reopen the field for write.
654        // 2. Perform the update to the state and write it to the field.
655        // 3. Return the state and the handle that should be closed later on.
656        let (mut access_controller_state, handle) = if !access_controller_state.is_fully_updated() {
657            // Update the state to the latest version.
658            let access_controller_fully_updated_state = access_controller_state.fully_update();
659
660            // Close the reopen the field with a write lock.
661            api.field_close(handle)?;
662            let handle = api.actor_open_field(
663                ACTOR_STATE_SELF,
664                AccessControllerV2Field::State.field_index(),
665                LockFlags::MUTABLE,
666            )?;
667
668            // Write to the field.
669            api.field_write_typed(handle, &access_controller_fully_updated_state)?;
670
671            // Return the state and the handle
672            (
673                access_controller_fully_updated_state.fully_update_and_into_latest_version(),
674                handle,
675            )
676        }
677        // Already fully updated - just return the state and the handle we already have.
678        else {
679            (
680                access_controller_state.fully_update_and_into_latest_version(),
681                handle,
682            )
683        };
684
685        // Call the callback with the state.
686        let rtn = callback(&mut access_controller_state, api)?;
687
688        // Close the field.
689        api.field_close(handle)?;
690
691        // Return the callback's return
692        Ok(rtn)
693    }
694
695    /// This method is used to read the access controller state and perform any lazy updating
696    /// required.
697    fn with_state_mut<Y: SystemApi<RuntimeError>, O>(
698        api: &mut Y,
699        callback: impl FnOnce(&mut AccessControllerV2Substate, &mut Y) -> Result<O, RuntimeError>,
700    ) -> Result<O, RuntimeError> {
701        // Get a write lock over the access-controller field.
702        let handle = api.actor_open_field(
703            ACTOR_STATE_SELF,
704            AccessControllerV2Field::State.field_index(),
705            LockFlags::MUTABLE,
706        )?;
707
708        // Read the access controller state.
709        let access_controller_state = api
710            .field_read_typed::<AccessControllerV2StateFieldPayload>(handle)?
711            .into_content();
712
713        // Determine if updating the state is required or not. If a state update is required then
714        // perform it and write it to the state. To do this we do the following:
715        // 1. Perform the update to the state and write it to the field.
716        // 2. Return the state and the handle that should be closed later on.
717        let mut access_controller_state = if !access_controller_state.is_fully_updated() {
718            // Update the state to the latest version.
719            let access_controller_fully_updated_state = access_controller_state.fully_update();
720
721            // Write to the field.
722            api.field_write_typed(handle, &access_controller_fully_updated_state)?;
723
724            // Return the state and the handle
725            access_controller_fully_updated_state.fully_update_and_into_latest_version()
726        }
727        // Already fully updated - just return the state and the handle we already have.
728        else {
729            access_controller_state.fully_update_and_into_latest_version()
730        };
731
732        // Call the callback with the state.
733        let rtn = callback(&mut access_controller_state, api)?;
734
735        // The callback is allowed to mutate the state of the access controller. Write the changes
736        // to the substate store.
737        api.field_write_typed(
738            handle,
739            &VersionedAccessControllerV2State::from(AccessControllerV2StateVersions::from(
740                access_controller_state,
741            )),
742        )?;
743
744        // Close the field.
745        api.field_close(handle)?;
746
747        // Return the callback's return
748        Ok(rtn)
749    }
750}
751
752//=========
753// Helpers
754//=========
755
756fn locked_role_assignment() -> RuleSet {
757    RuleSet {
758        primary_role: AccessRule::DenyAll,
759        recovery_role: AccessRule::DenyAll,
760        confirmation_role: AccessRule::DenyAll,
761    }
762}
763
764fn init_roles_from_rule_set(rule_set: RuleSet) -> RoleAssignmentInit {
765    roles2! {
766        "primary" => rule_set.primary_role, updatable;
767        "recovery" => rule_set.recovery_role, updatable;
768        "confirmation" => rule_set.confirmation_role, updatable;
769    }
770}
771
772fn transition<Y: SystemApi<RuntimeError>, I>(
773    api: &mut Y,
774    input: I,
775) -> Result<<AccessControllerV2Substate as Transition<I>>::Output, RuntimeError>
776where
777    AccessControllerV2Substate: Transition<I>,
778{
779    AccessControllerV2Blueprint::with_state(api, |state, api| state.transition(api, input))
780}
781
782fn transition_mut<Y: SystemApi<RuntimeError>, I>(
783    api: &mut Y,
784    input: I,
785) -> Result<<AccessControllerV2Substate as TransitionMut<I>>::Output, RuntimeError>
786where
787    AccessControllerV2Substate: TransitionMut<I>,
788{
789    AccessControllerV2Blueprint::with_state_mut(api, |state, api| state.transition_mut(api, input))
790}
791
792fn update_role_assignment<Y: SystemApi<RuntimeError>>(
793    api: &mut Y,
794    receiver: &NodeId,
795    rule_set: RuleSet,
796) -> Result<(), RuntimeError> {
797    let attached = AttachedRoleAssignment(*receiver);
798    attached.set_role(
799        ModuleId::Main,
800        RoleKey::new("primary"),
801        rule_set.primary_role.clone(),
802        api,
803    )?;
804    attached.set_role(
805        ModuleId::Main,
806        RoleKey::new("recovery"),
807        rule_set.recovery_role.clone(),
808        api,
809    )?;
810    attached.set_role(
811        ModuleId::Main,
812        RoleKey::new("confirmation"),
813        rule_set.confirmation_role.clone(),
814        api,
815    )?;
816
817    Ok(())
818}