pallet_permission0/ext/
curator_impl.rs

1use crate::{
2    Config, CuratorPermissions, CuratorScope, Error, Event, Pallet, PermissionContract,
3    PermissionDuration, PermissionScope, Permissions, PermissionsByRecipient, RevocationTerms,
4    generate_permission_id, pallet, permission::add_permission_indices,
5};
6
7use pallet_permission0_api::{
8    CuratorPermissions as ApiCuratorPermissions, Permission0CuratorApi, PermissionId,
9};
10use polkadot_sdk::frame_system::ensure_signed_or_root;
11use polkadot_sdk::sp_core::Get;
12use polkadot_sdk::sp_runtime::BoundedBTreeMap;
13use polkadot_sdk::sp_runtime::traits::{AccountIdConversion, Saturating};
14use polkadot_sdk::{
15    frame_support::ensure,
16    frame_system,
17    polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
18    sp_runtime::{DispatchError, DispatchResult},
19};
20
21impl<T: Config> Permission0CuratorApi<T::AccountId, OriginFor<T>, BlockNumberFor<T>>
22    for pallet::Pallet<T>
23{
24    fn ensure_curator_permission(
25        recipient: OriginFor<T>,
26        flags: ApiCuratorPermissions,
27    ) -> Result<T::AccountId, DispatchError> {
28        let Some(recipient) = ensure_signed_or_root(recipient)? else {
29            return Ok(T::PalletId::get().into_account_truncating());
30        };
31
32        let flags = CuratorPermissions::from_bits_truncate(flags.bits());
33        let permissions = PermissionsByRecipient::<T>::get(&recipient);
34        let now = <frame_system::Pallet<T>>::block_number();
35
36        let mut cur_error = Error::<T>::PermissionNotFound;
37        for permission_id in permissions {
38            let Some(contract) = Permissions::<T>::get(permission_id) else {
39                continue;
40            };
41
42            let PermissionScope::Curator(scope) = &contract.scope else {
43                continue;
44            };
45
46            if !scope.has_permission(flags) {
47                continue;
48            }
49
50            if contract.available_instances().unwrap_or_default() < 1 {
51                if !matches!(cur_error, Error::<T>::PermissionInCooldown) {
52                    cur_error = Error::<T>::NotEnoughInstances;
53                }
54
55                continue;
56            }
57
58            if let Some(cooldown) = scope.cooldown
59                && let Some(last_execution) = contract.last_execution()
60                && last_execution.saturating_add(cooldown) > now
61            {
62                cur_error = Error::<T>::PermissionInCooldown;
63                continue;
64            }
65
66            return Ok(recipient);
67        }
68
69        Err(cur_error.into())
70    }
71
72    fn get_curator_permission(recipient: &T::AccountId) -> Option<PermissionId> {
73        PermissionsByRecipient::<T>::get(recipient)
74            .into_iter()
75            .find_map(|permission_id| {
76                let contract = Permissions::<T>::get(permission_id)?;
77
78                if matches!(&contract.scope, PermissionScope::Curator(_)) {
79                    Some(permission_id)
80                } else {
81                    None
82                }
83            })
84    }
85
86    #[cfg(feature = "runtime-benchmarks")]
87    fn force_curator(recipient: &T::AccountId, flags: ApiCuratorPermissions) {
88        use polkadot_sdk::frame_system::RawOrigin;
89
90        let mut map: BoundedBTreeMap<
91            Option<PermissionId>,
92            CuratorPermissions,
93            T::MaxCuratorSubpermissionsPerPermission,
94        > = BoundedBTreeMap::new();
95        map.try_insert(None, CuratorPermissions::from_bits_truncate(flags.bits()))
96            .unwrap();
97
98        delegate_curator_permission_impl(
99            RawOrigin::Root.into(),
100            recipient.clone(),
101            map,
102            None,
103            PermissionDuration::<T>::Indefinite,
104            RevocationTerms::<T>::Irrevocable,
105            1,
106        )
107        .unwrap();
108    }
109}
110
111pub fn delegate_curator_permission_impl<T: Config>(
112    delegator: OriginFor<T>,
113    recipient: T::AccountId,
114    mut flags: BoundedBTreeMap<
115        Option<PermissionId>,
116        CuratorPermissions,
117        T::MaxCuratorSubpermissionsPerPermission,
118    >,
119    cooldown: Option<BlockNumberFor<T>>,
120    duration: PermissionDuration<T>,
121    revocation: RevocationTerms<T>,
122    instances: u32,
123) -> Result<PermissionId, DispatchError> {
124    let signer = ensure_signed_or_root(delegator)?;
125
126    let is_root = signer.is_none();
127    let delegator =
128        signer.unwrap_or_else(|| <T as Config>::PalletId::get().into_account_truncating());
129
130    let mut parents = polkadot_sdk::sp_std::vec::Vec::with_capacity(flags.len());
131
132    for (parent_pid, flags) in &mut flags {
133        // Root permission is not delegateable
134        flags.remove(CuratorPermissions::ROOT);
135
136        ensure!(!flags.is_empty(), Error::<T>::InvalidCuratorPermissions);
137
138        if let Some(parent_pid) = parent_pid {
139            let Some(parent) = Permissions::<T>::get(parent_pid) else {
140                return Err(Error::<T>::ParentPermissionNotFound.into());
141            };
142
143            let PermissionScope::Curator(scope) = &parent.scope else {
144                return Err(Error::<T>::UnsupportedPermissionType.into());
145            };
146
147            ensure!(
148                scope.recipient == delegator,
149                Error::<T>::NotPermissionRecipient
150            );
151
152            ensure!(
153                scope.has_permission(*flags),
154                Error::<T>::InvalidCuratorPermissions
155            );
156
157            ensure!(
158                instances <= parent.available_instances().unwrap_or_default(),
159                Error::<T>::NotEnoughInstances
160            );
161
162            ensure!(
163                RevocationTerms::<T>::is_weaker(&parent.revocation, &revocation),
164                Error::<T>::RevocationTermsTooStrong
165            );
166
167            parents.push(*parent_pid);
168        } else {
169            // We do not check for the ROOT curator permission at the moment.
170            // This is mainly due to our use of a SUDO key at the moment.
171            // Once we move away from centralized chain management, a ROOT curator
172            // will be appointed by the system.
173
174            ensure!(is_root, Error::<T>::NotPermissionRecipient);
175        }
176    }
177
178    let scope = PermissionScope::Curator(CuratorScope {
179        recipient: recipient.clone(),
180        flags,
181        cooldown,
182        children: Default::default(),
183        max_instances: 1,
184    });
185    let permission_id = generate_permission_id::<T>(&delegator, &scope)?;
186
187    for parent in parents {
188        Permissions::<T>::mutate_extant(parent, |parent| {
189            if let Some(children) = parent.children_mut() {
190                children.try_insert(permission_id).ok()
191            } else {
192                Some(false)
193            }
194        })
195        .ok_or(Error::<T>::TooManyChildren)?;
196    }
197
198    let contract = PermissionContract::<T>::new(
199        delegator,
200        scope,
201        duration,
202        revocation,
203        crate::EnforcementAuthority::None,
204    );
205
206    Permissions::<T>::insert(permission_id, &contract);
207
208    add_permission_indices::<T>(
209        &contract.delegator,
210        core::iter::once(&recipient),
211        permission_id,
212    )?;
213
214    <Pallet<T>>::deposit_event(Event::PermissionDelegated {
215        delegator: contract.delegator,
216        permission_id,
217    });
218
219    Ok(permission_id)
220}
221
222pub fn execute_permission_impl<T: Config>(permission_id: &PermissionId) -> DispatchResult {
223    if let Some(mut contract) = Permissions::<T>::get(permission_id) {
224        contract.tick_execution(<frame_system::Pallet<T>>::block_number())?;
225        Permissions::<T>::insert(permission_id, &contract);
226    }
227
228    Ok(())
229}