pallet_permission0/
permission.rs

1use codec::{Decode, Encode, MaxEncodedLen};
2use pallet_torus0_api::NamespacePath;
3use polkadot_sdk::{
4    frame_support::{
5        CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
6        dispatch::DispatchResult, ensure,
7    },
8    frame_system::{self, RawOrigin, ensure_signed_or_root},
9    polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
10    sp_core::{H256, U256},
11    sp_runtime::{
12        BoundedBTreeMap, BoundedVec, DispatchError, Percent,
13        traits::{BlakeTwo256, Hash},
14    },
15    sp_std::vec::Vec,
16    sp_tracing::{error, info, trace},
17};
18use scale_info::TypeInfo;
19
20use crate::*;
21
22pub use curator::{CuratorPermissions, CuratorScope};
23pub use emission::{DistributionControl, EmissionAllocation, EmissionScope};
24
25pub mod curator;
26pub mod emission;
27
28/// Type for permission ID
29pub type PermissionId = H256;
30
31/// Generate a unique permission ID by hashing a concat of
32/// `recipient | scope | block number`
33pub fn generate_permission_id<T: Config>(
34    delegator: &T::AccountId,
35    recipient: &T::AccountId,
36    scope: &PermissionScope<T>,
37) -> Result<PermissionId, DispatchError> {
38    let mut data = delegator.encode();
39    data.extend(recipient.encode());
40    data.extend(scope.encode());
41
42    data.extend(<frame_system::Pallet<T>>::block_number().encode());
43
44    if let Some(extrinsic_index) = <frame_system::Pallet<T>>::extrinsic_index() {
45        data.extend(extrinsic_index.encode());
46    }
47
48    let id = BlakeTwo256::hash(&data);
49    ensure!(
50        !Permissions::<T>::contains_key(id),
51        Error::<T>::DuplicatePermissionInBlock
52    );
53
54    Ok(id)
55}
56
57#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
58#[scale_info(skip_type_params(T))]
59pub struct PermissionContract<T: Config> {
60    pub delegator: T::AccountId,
61    pub recipient: T::AccountId,
62    pub scope: PermissionScope<T>,
63    pub duration: PermissionDuration<T>,
64    pub revocation: RevocationTerms<T>,
65    /// Enforcement authority that can toggle the permission
66    pub enforcement: EnforcementAuthority<T>,
67    /// Last execution block
68    last_execution: Option<BlockNumberFor<T>>,
69    /// Number of times the permission was executed
70    execution_count: u32,
71    /// Maximum number of instances of this permission
72    pub max_instances: u32,
73    /// Children permissions
74    pub children: BoundedBTreeSet<H256, T::MaxChildrenPerPermission>,
75    pub created_at: BlockNumberFor<T>,
76}
77
78impl<T: Config> PermissionContract<T> {
79    pub(crate) fn new(
80        delegator: T::AccountId,
81        recipient: T::AccountId,
82        scope: PermissionScope<T>,
83        duration: PermissionDuration<T>,
84        revocation: RevocationTerms<T>,
85        enforcement: EnforcementAuthority<T>,
86        max_instances: u32,
87    ) -> Self {
88        Self {
89            delegator,
90            recipient,
91            scope,
92            duration,
93            revocation,
94            enforcement,
95            max_instances,
96
97            last_execution: None,
98            execution_count: 0,
99            children: BoundedBTreeSet::new(),
100            created_at: frame_system::Pallet::<T>::block_number(),
101        }
102    }
103
104    #[deprecated]
105    pub(crate) fn set_execution_info(
106        &mut self,
107        block: Option<BlockNumberFor<T>>,
108        execution_count: u32,
109    ) {
110        self.last_execution = block;
111        self.execution_count = execution_count;
112    }
113}
114
115impl<T: Config> PermissionContract<T> {
116    pub fn is_expired(&self, current_block: BlockNumberFor<T>) -> bool {
117        match self.duration {
118            PermissionDuration::UntilBlock(block) => current_block > block,
119            PermissionDuration::Indefinite => false,
120        }
121    }
122
123    /// Returns the last execution block of this permission.
124    pub fn last_execution(&self) -> Option<BlockNumberFor<T>> {
125        self.last_execution
126    }
127
128    /// Returns the number of times this permission was executed.
129    pub fn execution_count(&self) -> u32 {
130        self.execution_count
131    }
132
133    /// Returns the number of available instances of this permission.
134    pub fn available_instances(&self) -> u32 {
135        let mut available = self.max_instances;
136        for child in &self.children {
137            available = available.saturating_sub(
138                Permissions::<T>::get(child).map_or(0, |child| child.max_instances),
139            );
140        }
141        available
142    }
143
144    pub fn tick_execution(&mut self, block: BlockNumberFor<T>) -> DispatchResult {
145        if self.available_instances() == 0 {
146            return Err(Error::<T>::NotEnoughInstances.into());
147        }
148
149        self.last_execution = Some(block);
150        self.execution_count = self.execution_count.saturating_add(1);
151
152        Ok(())
153    }
154
155    pub fn revoke(self, origin: OriginFor<T>, permission_id: H256) -> DispatchResult {
156        // The recipient is also always allowed to revoke the permission.
157        let who = ensure_signed_or_root(origin)?.filter(|who| who != &self.recipient);
158
159        let delegator = self.delegator.clone();
160        let recipient = self.recipient.clone();
161
162        // `who` will not be present if the origin is a root key
163        if let Some(who) = &who {
164            match &self.revocation {
165                RevocationTerms::RevocableByDelegator => {
166                    ensure!(who == &delegator, Error::<T>::NotAuthorizedToRevoke)
167                }
168                RevocationTerms::RevocableByArbiters {
169                    accounts,
170                    required_votes,
171                } if accounts.contains(who) => {
172                    let votes = RevocationTracking::<T>::get(permission_id)
173                        .into_iter()
174                        .filter(|id| id != who)
175                        .filter(|id| accounts.contains(id))
176                        .count();
177                    if votes.saturating_add(1) < *required_votes as usize {
178                        return RevocationTracking::<T>::mutate(permission_id, |votes| {
179                            votes
180                                .try_insert(who.clone())
181                                .map_err(|_| Error::<T>::TooManyRevokers)?;
182                            Ok(())
183                        });
184                    }
185                }
186                RevocationTerms::RevocableByArbiters { .. } => {
187                    return Err(Error::<T>::NotAuthorizedToRevoke.into());
188                }
189                RevocationTerms::RevocableAfter(block) if who == &delegator => ensure!(
190                    <frame_system::Pallet<T>>::block_number() >= *block,
191                    Error::<T>::NotAuthorizedToRevoke
192                ),
193                RevocationTerms::RevocableAfter(_) => {
194                    return Err(Error::<T>::NotAuthorizedToRevoke.into());
195                }
196                RevocationTerms::Irrevocable => {
197                    return Err(Error::<T>::NotAuthorizedToRevoke.into());
198                }
199            };
200        }
201
202        for child_id in &self.children {
203            let Some(child) = Permissions::<T>::get(child_id) else {
204                continue;
205            };
206
207            let revoker = if who.is_none() {
208                RawOrigin::Root
209            } else {
210                RawOrigin::Signed(self.recipient.clone())
211            };
212
213            child.revoke(revoker.into(), *child_id)?;
214        }
215
216        self.cleanup(permission_id)?;
217
218        <Pallet<T>>::deposit_event(Event::PermissionRevoked {
219            delegator,
220            recipient,
221            revoked_by: who,
222            permission_id,
223        });
224
225        Ok(())
226    }
227
228    /// Updates the enforcement authority for this permission.
229    ///
230    /// When the enforcement authority changes, all ongoing enforcement
231    /// referendums for this permission are wiped.
232    pub fn update_enforcement(
233        mut self,
234        permission_id: H256,
235        enforcement: EnforcementAuthority<T>,
236    ) -> DispatchResult {
237        let (controllers, required_votes) = match enforcement {
238            EnforcementAuthority::None => {
239                self.enforcement = EnforcementAuthority::None;
240                Permissions::<T>::insert(permission_id, self);
241
242                let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
243
244                Pallet::<T>::deposit_event(Event::EnforcementAuthoritySet {
245                    permission_id,
246                    controllers_count: 0,
247                    required_votes: 0,
248                });
249
250                return Ok(());
251            }
252            EnforcementAuthority::ControlledBy {
253                controllers,
254                required_votes,
255            } => (controllers, required_votes),
256        };
257
258        ensure!(
259            !controllers.is_empty(),
260            Error::<T>::InvalidNumberOfControllers
261        );
262        ensure!(required_votes > 0, Error::<T>::InvalidNumberOfControllers);
263        ensure!(
264            required_votes as usize <= controllers.len(),
265            Error::<T>::InvalidNumberOfControllers
266        );
267
268        let event = Event::EnforcementAuthoritySet {
269            permission_id,
270            controllers_count: controllers.len() as u32,
271            required_votes,
272        };
273
274        self.enforcement = EnforcementAuthority::ControlledBy {
275            controllers,
276            required_votes,
277        };
278        Permissions::<T>::insert(permission_id, self);
279
280        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
281
282        <Pallet<T>>::deposit_event(event);
283
284        Ok(())
285    }
286
287    fn cleanup(self, permission_id: H256) -> DispatchResult {
288        crate::remove_permission_from_indices::<T>(&self.delegator, &self.recipient, permission_id);
289
290        Permissions::<T>::remove(permission_id);
291        RevocationTracking::<T>::remove(permission_id);
292        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
293
294        match self.scope {
295            PermissionScope::Emission(emission) => {
296                emission.cleanup(permission_id, &self.last_execution, &self.delegator);
297            }
298            PermissionScope::Curator(curator) => {
299                curator.cleanup(permission_id, &self.last_execution, &self.delegator);
300            }
301            PermissionScope::Namespace(namespace) => {
302                namespace.cleanup(permission_id, &self.last_execution, &self.delegator);
303            }
304        }
305
306        Ok(())
307    }
308
309    pub fn is_updatable(&self) -> bool {
310        let current_block = frame_system::Pallet::<T>::block_number();
311
312        match &self.revocation {
313            RevocationTerms::RevocableByDelegator => true,
314            RevocationTerms::RevocableAfter(block) => &current_block > block,
315            _ => false,
316        }
317    }
318}
319
320/// Defines what the permission applies to
321#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
322#[scale_info(skip_type_params(T))]
323pub enum PermissionScope<T: Config> {
324    Emission(EmissionScope<T>),
325    Curator(CuratorScope<T>),
326    Namespace(NamespaceScope<T>),
327}
328
329/// Scope for namespace permissions
330#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
331#[scale_info(skip_type_params(T))]
332pub struct NamespaceScope<T: Config> {
333    /// Set of namespace paths this permission delegates access to
334    pub paths: BoundedBTreeMap<
335        Option<PermissionId>,
336        BoundedBTreeSet<NamespacePath, T::MaxNamespacesPerPermission>,
337        T::MaxNamespacesPerPermission,
338    >,
339}
340
341impl<T: Config> NamespaceScope<T> {
342    /// Cleanup operations when permission is revoked or expired
343    fn cleanup(
344        &self,
345        permission_id: polkadot_sdk::sp_core::H256,
346        _last_execution: &Option<crate::BlockNumberFor<T>>,
347        _delegator: &T::AccountId,
348    ) {
349        for pid in self.paths.keys().cloned().flatten() {
350            Permissions::<T>::mutate_extant(pid, |parent| {
351                parent.children.remove(&permission_id);
352            });
353        }
354    }
355}
356
357#[derive(
358    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
359)]
360#[scale_info(skip_type_params(T))]
361pub enum PermissionDuration<T: Config> {
362    /// Permission lasts until a specific block
363    UntilBlock(BlockNumberFor<T>),
364    /// Permission lasts indefinitely
365    Indefinite,
366}
367
368#[derive(
369    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
370)]
371#[scale_info(skip_type_params(T))]
372pub enum RevocationTerms<T: Config> {
373    /// Cannot be revoked
374    Irrevocable,
375    /// Can be revoked by the delegator at any time
376    RevocableByDelegator,
377    /// Can be revoked by third party arbiters
378    RevocableByArbiters {
379        accounts: BoundedVec<T::AccountId, T::MaxRevokersPerPermission>,
380        required_votes: u32,
381    },
382    /// Time-based revocation
383    RevocableAfter(BlockNumberFor<T>),
384}
385
386impl<T: Config> RevocationTerms<T> {
387    /// Checks if the child revocation terms are weaker than the parent.
388    pub fn is_weaker(parent: &Self, child: &Self) -> bool {
389        match (parent, child) {
390            (_, RevocationTerms::RevocableByDelegator) => true,
391
392            (RevocationTerms::RevocableAfter(a), RevocationTerms::RevocableAfter(b)) if a >= b => {
393                true
394            }
395
396            (RevocationTerms::Irrevocable, RevocationTerms::RevocableAfter(_)) => true,
397
398            (RevocationTerms::Irrevocable, RevocationTerms::Irrevocable) => true,
399
400            _ => false,
401        }
402    }
403}
404
405/// Types of enforcement actions that can be voted on
406#[derive(
407    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
408)]
409#[scale_info(skip_type_params(T))]
410pub enum EnforcementReferendum {
411    /// Toggle emission accumulation state
412    EmissionAccumulation(bool),
413    /// Execute the permission
414    Execution,
415}
416
417/// Defines how a permission's enforcement is controlled
418#[derive(
419    Encode,
420    Decode,
421    CloneNoBound,
422    PartialEqNoBound,
423    EqNoBound,
424    TypeInfo,
425    MaxEncodedLen,
426    DebugNoBound,
427    DefaultNoBound,
428)]
429#[scale_info(skip_type_params(T))]
430pub enum EnforcementAuthority<T: Config> {
431    /// No special enforcement (standard permission execution)
432    #[default]
433    None,
434    /// Permission can be toggled active/inactive by controllers
435    ControlledBy {
436        controllers: BoundedVec<T::AccountId, T::MaxControllersPerPermission>,
437        required_votes: u32,
438    },
439}
440
441/// Process all auto-distributions and time-based distributions
442pub(crate) fn do_auto_permission_execution<T: Config>(current_block: BlockNumberFor<T>) {
443    // Only check every 10 blocks to reduce computational overhead
444    if <BlockNumberFor<T> as Into<U256>>::into(current_block)
445        .checked_rem(10.into())
446        .unwrap_or_default()
447        != U256::zero()
448    {
449        return;
450    }
451
452    let permissions: Vec<_> = Permissions::<T>::iter().collect();
453    let mut expired = Vec::with_capacity(permissions.len());
454
455    info!(
456        target: "auto_permission_execution",
457        "executing auto permission execution for {} permissions",
458        permissions.len()
459    );
460
461    for (permission_id, contract) in Permissions::<T>::iter() {
462        #[allow(clippy::single_match)]
463        match &contract.scope {
464            PermissionScope::Emission(emission_scope) => {
465                trace!(target: "auto_permission_execution", "executing auto permission execution for permission {permission_id:?}");
466                if let Err(err) = emission::do_auto_distribution(
467                    emission_scope,
468                    permission_id,
469                    current_block,
470                    &contract,
471                ) {
472                    error!(
473                        "failed to auto distribute emissions for permission {permission_id:?}: {err:?}"
474                    );
475                }
476            }
477            _ => (),
478        }
479
480        if contract.is_expired(current_block) {
481            expired.push((permission_id, contract));
482        }
483    }
484
485    for (permission_id, contract) in expired {
486        let delegator = contract.delegator.clone();
487        let recipient = contract.recipient.clone();
488
489        if let Err(err) = contract.cleanup(permission_id) {
490            error!("failed to cleanup expired permission {permission_id:?}: {err:?}");
491        }
492
493        <Pallet<T>>::deposit_event(Event::PermissionExpired {
494            delegator,
495            recipient,
496            permission_id,
497        });
498    }
499}