pallet_permission0/ext/
curator_impl.rs

1use crate::{
2    generate_permission_id, pallet, update_permission_indices, Config, CuratorPermissions,
3    CuratorScope, Error, Event, Pallet, PermissionContract, PermissionDuration, PermissionScope,
4    Permissions, PermissionsByGrantee, RevocationTerms,
5};
6
7use pallet_permission0_api::{
8    CuratorPermissions as ApiCuratorPermissions, Permission0CuratorApi,
9    PermissionDuration as ApiPermissionDuration, PermissionId,
10    RevocationTerms as ApiRevocationTerms,
11};
12use polkadot_sdk::frame_system::{ensure_root, ensure_signed_or_root};
13use polkadot_sdk::sp_core::Get;
14use polkadot_sdk::sp_runtime::traits::{AccountIdConversion, Saturating};
15use polkadot_sdk::{
16    frame_support::ensure,
17    frame_system,
18    polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
19    sp_runtime::{DispatchError, DispatchResult},
20};
21
22impl<T: Config> Permission0CuratorApi<T::AccountId, OriginFor<T>, BlockNumberFor<T>>
23    for pallet::Pallet<T>
24{
25    fn grant_curator_permission(
26        grantor: OriginFor<T>,
27        grantee: T::AccountId,
28        flags: ApiCuratorPermissions,
29        cooldown: Option<BlockNumberFor<T>>,
30        duration: ApiPermissionDuration<BlockNumberFor<T>>,
31        revocation: ApiRevocationTerms<T::AccountId, BlockNumberFor<T>>,
32    ) -> Result<PermissionId, DispatchError> {
33        let duration = super::translate_duration::<T>(duration)?;
34        let revocation = super::translate_revocation_terms::<T>(revocation)?;
35
36        let flags = CuratorPermissions::from_bits_truncate(flags.bits());
37        grant_curator_permission_impl(grantor, grantee, flags, cooldown, duration, revocation)
38    }
39
40    fn ensure_curator_permission(
41        grantee: OriginFor<T>,
42        flags: ApiCuratorPermissions,
43    ) -> Result<T::AccountId, DispatchError> {
44        let Some(grantee) = ensure_signed_or_root(grantee)? else {
45            return Ok(T::PalletId::get().into_account_truncating());
46        };
47
48        let permissions = PermissionsByGrantee::<T>::get(&grantee);
49        let Some((_, contract)) = permissions.into_iter().find_map(|permission_id| {
50            let contract = Permissions::<T>::get(permission_id)?;
51
52            if matches!(&contract.scope, PermissionScope::Curator(_)) {
53                Some((permission_id, contract))
54            } else {
55                None
56            }
57        }) else {
58            return Err(Error::<T>::PermissionNotFound.into());
59        };
60
61        let PermissionScope::Curator(scope) = &contract.scope else {
62            return Err(Error::<T>::PermissionNotFound.into());
63        };
64
65        let flags = CuratorPermissions::from_bits_truncate(flags.bits());
66        if !scope.has_permission(flags) {
67            return Err(Error::<T>::PermissionNotFound.into());
68        }
69
70        if let Some(cooldown) = scope.cooldown {
71            let now = <frame_system::Pallet<T>>::block_number();
72
73            if contract
74                .last_execution
75                .is_some_and(|last_execution| last_execution.saturating_add(cooldown) > now)
76            {
77                return Err(Error::<T>::PermissionInCooldown.into());
78            }
79        }
80
81        Ok(grantee)
82    }
83
84    fn get_curator_permission(grantee: &T::AccountId) -> Option<PermissionId> {
85        PermissionsByGrantee::<T>::get(grantee)
86            .into_iter()
87            .find_map(|permission_id| {
88                let contract = Permissions::<T>::get(permission_id)?;
89
90                if matches!(&contract.scope, PermissionScope::Curator(_)) {
91                    Some(permission_id)
92                } else {
93                    None
94                }
95            })
96    }
97}
98
99pub fn grant_curator_permission_impl<T: Config>(
100    grantor: OriginFor<T>,
101    grantee: T::AccountId,
102    mut flags: CuratorPermissions,
103    cooldown: Option<BlockNumberFor<T>>,
104    duration: PermissionDuration<T>,
105    revocation: RevocationTerms<T>,
106) -> Result<PermissionId, DispatchError> {
107    ensure_root(grantor)?;
108
109    // Root permission is not grantable
110    flags.remove(CuratorPermissions::ROOT);
111
112    ensure!(!flags.is_empty(), Error::<T>::InvalidCuratorPermissions);
113
114    let grantor = <T as Config>::PalletId::get().into_account_truncating();
115
116    // We do not check for the ROOT curator permission at the moment.
117    // This is mainly due to our use of a SUDO key at the moment.
118    // Once we move away from centralized chain management, a ROOT curator
119    // will be appointed by the system.
120
121    for perm in PermissionsByGrantee::<T>::get(&grantee) {
122        let Some(contract) = Permissions::<T>::get(perm) else {
123            continue;
124        };
125
126        if matches!(&contract.scope, PermissionScope::Curator(_)) {
127            return Err(Error::<T>::DuplicatePermission.into());
128        }
129    }
130
131    let scope = PermissionScope::Curator(CuratorScope { flags, cooldown });
132    let permission_id = generate_permission_id::<T>(&grantor, &grantee, &scope)?;
133
134    let contract = PermissionContract {
135        grantor,
136        grantee,
137        scope,
138        duration,
139        revocation,
140        enforcement: crate::EnforcementAuthority::None,
141        last_execution: None,
142        execution_count: 0,
143        // Will change once we have a ROOT curator.
144        parent: None,
145        created_at: <frame_system::Pallet<T>>::block_number(),
146    };
147
148    Permissions::<T>::insert(permission_id, &contract);
149    update_permission_indices::<T>(&contract.grantor, &contract.grantee, permission_id)?;
150
151    <Pallet<T>>::deposit_event(Event::PermissionGranted {
152        grantor: contract.grantor,
153        grantee: contract.grantee,
154        permission_id,
155    });
156
157    Ok(permission_id)
158}
159
160pub fn execute_permission_impl<T: Config>(permission_id: &PermissionId) -> DispatchResult {
161    Permissions::<T>::mutate(permission_id, |maybe_contract| {
162        if let Some(c) = maybe_contract {
163            c.last_execution = Some(<frame_system::Pallet<T>>::block_number());
164            c.execution_count = c.execution_count.saturating_add(1);
165        }
166    });
167
168    Ok(())
169}