pallet_permission0/
permission.rs

1use codec::{Decode, Encode, MaxEncodedLen};
2use polkadot_sdk::{
3    frame_support::{
4        CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
5        dispatch::DispatchResult, ensure,
6    },
7    frame_system::{self, RawOrigin, ensure_signed_or_root},
8    polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
9    sp_core::{H256, U256},
10    sp_runtime::{
11        BoundedBTreeMap, BoundedVec, DispatchError, Percent,
12        traits::{BlakeTwo256, Hash},
13    },
14    sp_std::{vec, vec::Vec},
15    sp_tracing::{error, info, trace},
16};
17use scale_info::TypeInfo;
18use wallet::WalletScope;
19
20use crate::*;
21
22pub use curator::{CuratorPermissions, CuratorScope};
23pub use namespace::NamespaceScope;
24pub use stream::{DistributionControl, StreamAllocation, StreamScope};
25
26pub mod curator;
27pub mod namespace;
28pub mod stream;
29pub mod wallet;
30
31/// Type for permission ID
32pub type PermissionId = H256;
33
34/// Generate a unique permission ID by hashing a concat of
35/// `recipient | scope | block number`
36pub fn generate_permission_id<T: Config>(
37    delegator: &T::AccountId,
38    scope: &PermissionScope<T>,
39) -> Result<PermissionId, DispatchError> {
40    let mut data = delegator.encode();
41    data.extend(scope.encode());
42
43    data.extend(<frame_system::Pallet<T>>::block_number().encode());
44
45    if let Some(extrinsic_index) = <frame_system::Pallet<T>>::extrinsic_index() {
46        data.extend(extrinsic_index.encode());
47    }
48
49    let id = BlakeTwo256::hash(&data);
50    ensure!(
51        !Permissions::<T>::contains_key(id),
52        Error::<T>::DuplicatePermissionInBlock
53    );
54
55    Ok(id)
56}
57
58#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
59#[scale_info(skip_type_params(T))]
60pub struct PermissionContract<T: Config> {
61    pub delegator: 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 update block
68    pub last_update: BlockNumberFor<T>,
69    /// Last execution block
70    #[doc(hidden)]
71    pub last_execution: Option<BlockNumberFor<T>>,
72    /// Number of times the permission was executed
73    #[doc(hidden)]
74    pub execution_count: u32,
75    pub created_at: BlockNumberFor<T>,
76}
77
78impl<T: Config> PermissionContract<T> {
79    pub(crate) fn new(
80        delegator: T::AccountId,
81        scope: PermissionScope<T>,
82        duration: PermissionDuration<T>,
83        revocation: RevocationTerms<T>,
84        enforcement: EnforcementAuthority<T>,
85    ) -> Self {
86        let now = frame_system::Pallet::<T>::block_number();
87        Self {
88            delegator,
89            scope,
90            duration,
91            revocation,
92            enforcement,
93
94            last_update: now,
95            last_execution: None,
96            execution_count: 0,
97            created_at: now,
98        }
99    }
100}
101
102impl<T: Config> PermissionContract<T> {
103    pub fn is_expired(&self, current_block: BlockNumberFor<T>) -> bool {
104        match self.duration {
105            PermissionDuration::UntilBlock(block) => current_block > block,
106            PermissionDuration::Indefinite => false,
107        }
108    }
109
110    /// Returns the last execution block of this permission.
111    pub fn last_execution(&self) -> Option<BlockNumberFor<T>> {
112        self.last_execution
113    }
114
115    /// Returns the number of times this permission was executed.
116    pub fn execution_count(&self) -> u32 {
117        self.execution_count
118    }
119
120    /// Returns the number of used instances of this permission.
121    pub fn used_instances(&self) -> u32 {
122        match &self.scope {
123            PermissionScope::Curator(CuratorScope { children, .. })
124            | PermissionScope::Namespace(NamespaceScope { children, .. }) => {
125                let mut used = 0;
126                for child in children {
127                    used = used.saturating_add(
128                        Permissions::<T>::get(child)
129                            .map_or(0, |child| child.max_instances().unwrap_or_default()),
130                    );
131                }
132                used
133            }
134            _ => 0,
135        }
136    }
137
138    /// Returns the number of max instances attached to this permission, if present.
139    pub fn max_instances(&self) -> Option<u32> {
140        match &self.scope {
141            PermissionScope::Curator(CuratorScope { max_instances, .. })
142            | PermissionScope::Namespace(NamespaceScope { max_instances, .. }) => {
143                Some(*max_instances)
144            }
145            _ => None,
146        }
147    }
148
149    /// Returns the number of available instances of this permission.
150    pub fn available_instances(&self) -> Option<u32> {
151        Some(self.max_instances()?.saturating_sub(self.used_instances()))
152    }
153
154    /// Returns a mutable reference to the permission children, if the scope allows for it.
155    pub fn children_mut(
156        &mut self,
157    ) -> Option<&mut BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>> {
158        match &mut self.scope {
159            PermissionScope::Curator(CuratorScope { children, .. })
160            | PermissionScope::Namespace(NamespaceScope { children, .. }) => Some(children),
161            _ => None,
162        }
163    }
164
165    pub fn children(&self) -> Option<&BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>> {
166        match &self.scope {
167            PermissionScope::Curator(CuratorScope { children, .. })
168            | PermissionScope::Namespace(NamespaceScope { children, .. }) => Some(children),
169            _ => None,
170        }
171    }
172
173    pub fn tick_execution(&mut self, block: BlockNumberFor<T>) -> DispatchResult {
174        if let Some(available_instances) = self.available_instances()
175            && available_instances == 0
176        {
177            return Err(Error::<T>::NotEnoughInstances.into());
178        }
179
180        self.last_execution = Some(block);
181        self.execution_count = self.execution_count.saturating_add(1);
182
183        Ok(())
184    }
185
186    /// Updates the enforcement authority for this permission.
187    ///
188    /// When the enforcement authority changes, all ongoing enforcement
189    /// referendums for this permission are wiped.
190    pub fn update_enforcement(
191        mut self,
192        permission_id: H256,
193        enforcement: EnforcementAuthority<T>,
194    ) -> DispatchResult {
195        let (controllers, required_votes) = match enforcement {
196            EnforcementAuthority::None => {
197                self.enforcement = EnforcementAuthority::None;
198                Permissions::<T>::insert(permission_id, self);
199
200                let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
201
202                Pallet::<T>::deposit_event(Event::EnforcementAuthoritySet {
203                    permission_id,
204                    controllers_count: 0,
205                    required_votes: 0,
206                });
207
208                return Ok(());
209            }
210            EnforcementAuthority::ControlledBy {
211                controllers,
212                required_votes,
213            } => (controllers, required_votes),
214        };
215
216        ensure!(
217            !controllers.is_empty(),
218            Error::<T>::InvalidNumberOfControllers
219        );
220        ensure!(required_votes > 0, Error::<T>::InvalidNumberOfControllers);
221        ensure!(
222            required_votes as usize <= controllers.len(),
223            Error::<T>::InvalidNumberOfControllers
224        );
225
226        let event = Event::EnforcementAuthoritySet {
227            permission_id,
228            controllers_count: controllers.len() as u32,
229            required_votes,
230        };
231
232        self.enforcement = EnforcementAuthority::ControlledBy {
233            controllers,
234            required_votes,
235        };
236        Permissions::<T>::insert(permission_id, self);
237
238        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
239
240        <Pallet<T>>::deposit_event(event);
241
242        Ok(())
243    }
244
245    pub fn revoke(self, origin: OriginFor<T>, permission_id: H256) -> DispatchResult {
246        let caller = ensure_signed_or_root(origin)?;
247
248        let delegator = self.delegator.clone();
249        let recipients = match &self.scope {
250            PermissionScope::Curator(CuratorScope { recipient, .. })
251            | PermissionScope::Namespace(NamespaceScope { recipient, .. })
252            | PermissionScope::Wallet(WalletScope { recipient, .. }) => {
253                vec![recipient.clone()]
254            }
255            PermissionScope::Stream(StreamScope { recipients, .. }) => {
256                recipients.keys().cloned().collect()
257            }
258        };
259
260        // `who` will not be present if the origin is a root key
261        if let Some(caller) = &caller {
262            match &self.revocation {
263                RevocationTerms::RevocableByDelegator if caller == &delegator => {
264                    // Allowed to revoke
265                }
266                RevocationTerms::RevocableByArbiters {
267                    accounts,
268                    required_votes,
269                } if accounts.contains(caller) => {
270                    let votes = RevocationTracking::<T>::get(permission_id)
271                        .into_iter()
272                        .filter(|id| id != caller)
273                        .filter(|id| accounts.contains(id))
274                        .count();
275                    if votes.saturating_add(1) < *required_votes as usize {
276                        return RevocationTracking::<T>::mutate(permission_id, |votes| {
277                            votes
278                                .try_insert(caller.clone())
279                                .map_err(|_| Error::<T>::TooManyRevokers)?;
280                            Ok(())
281                        });
282                    }
283
284                    // Allowed to revoke
285                }
286                RevocationTerms::RevocableAfter(block)
287                    if caller == &delegator
288                        && <frame_system::Pallet<T>>::block_number() >= *block =>
289                {
290                    // Allowed to revoke
291                }
292                _ => {
293                    ensure!(
294                        recipients.contains(caller),
295                        Error::<T>::NotAuthorizedToRevoke
296                    );
297
298                    if recipients.len() > 1usize {
299                        remove_recipient_from_indices::<T>(&delegator, caller, permission_id);
300
301                        Permissions::<T>::mutate(permission_id, |permission| {
302                            if let Some(permission) = permission {
303                                #[allow(clippy::single_match)]
304                                match &mut permission.scope {
305                                    PermissionScope::Stream(StreamScope { recipients, .. }) => {
306                                        recipients.remove(caller);
307                                    }
308                                    _ => {}
309                                }
310                            }
311                        });
312
313                        <Pallet<T>>::deposit_event(Event::PermissionRevoked {
314                            delegator,
315                            revoked_by: Some(caller.clone()),
316                            permission_id,
317                        });
318
319                        return Ok(());
320                    }
321                }
322            };
323        }
324
325        for child_id in self.children().into_iter().flat_map(|c| c.iter()) {
326            let Some(child) = Permissions::<T>::get(child_id) else {
327                continue;
328            };
329
330            let revoker = if caller.is_none() {
331                RawOrigin::Root
332            } else {
333                RawOrigin::Signed(child.delegator.clone())
334            };
335
336            child.revoke(revoker.into(), *child_id)?;
337        }
338
339        self.cleanup(permission_id)?;
340
341        <Pallet<T>>::deposit_event(Event::PermissionRevoked {
342            delegator,
343            revoked_by: caller,
344            permission_id,
345        });
346
347        Ok(())
348    }
349
350    fn cleanup(self, permission_id: H256) -> DispatchResult {
351        match &self.scope {
352            PermissionScope::Curator(CuratorScope { recipient, .. })
353            | PermissionScope::Namespace(NamespaceScope { recipient, .. })
354            | PermissionScope::Wallet(WalletScope { recipient, .. }) => {
355                remove_permission_from_indices::<T>(
356                    &self.delegator,
357                    core::iter::once(recipient),
358                    permission_id,
359                );
360            }
361            PermissionScope::Stream(StreamScope { recipients, .. }) => {
362                remove_permission_from_indices::<T>(
363                    &self.delegator,
364                    recipients.keys(),
365                    permission_id,
366                );
367            }
368        };
369
370        Permissions::<T>::remove(permission_id);
371        RevocationTracking::<T>::remove(permission_id);
372        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
373
374        match self.scope {
375            PermissionScope::Stream(stream) => {
376                stream.cleanup(permission_id, &self.last_execution, &self.delegator);
377            }
378            PermissionScope::Curator(curator) => {
379                curator.cleanup(permission_id, &self.last_execution, &self.delegator);
380            }
381            PermissionScope::Namespace(namespace) => {
382                namespace.cleanup(permission_id, &self.last_execution, &self.delegator);
383            }
384            PermissionScope::Wallet(wallet) => {
385                wallet.cleanup(permission_id, &self.last_execution, &self.delegator);
386            }
387        }
388
389        Ok(())
390    }
391
392    pub fn is_updatable(&self) -> bool {
393        self.revocation.is_revokable()
394    }
395}
396
397/// Defines what the permission applies to
398#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
399#[scale_info(skip_type_params(T))]
400pub enum PermissionScope<T: Config> {
401    Stream(StreamScope<T>),
402    Curator(CuratorScope<T>),
403    Namespace(NamespaceScope<T>),
404    Wallet(WalletScope<T>),
405}
406
407#[derive(
408    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
409)]
410#[scale_info(skip_type_params(T))]
411pub enum PermissionDuration<T: Config> {
412    /// Permission lasts until a specific block
413    UntilBlock(BlockNumberFor<T>),
414    /// Permission lasts indefinitely
415    Indefinite,
416}
417
418#[derive(
419    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
420)]
421#[scale_info(skip_type_params(T))]
422pub enum RevocationTerms<T: Config> {
423    /// Cannot be revoked
424    Irrevocable,
425    /// Can be revoked by the delegator at any time
426    RevocableByDelegator,
427    /// Can be revoked by third party arbiters
428    RevocableByArbiters {
429        accounts: BoundedVec<T::AccountId, T::MaxRevokersPerPermission>,
430        required_votes: u32,
431    },
432    /// Time-based revocation
433    RevocableAfter(BlockNumberFor<T>),
434}
435
436impl<T: Config> RevocationTerms<T> {
437    /// Returns true if this revocation term is revocable by the delegator
438    /// or the current block is past the one defined.
439    pub fn is_revokable(&self) -> bool {
440        let current_block = frame_system::Pallet::<T>::block_number();
441
442        match &self {
443            RevocationTerms::RevocableByDelegator => true,
444            RevocationTerms::RevocableAfter(block) => &current_block > block,
445            _ => false,
446        }
447    }
448
449    /// Checks if the child revocation terms are weaker than the parent.
450    pub fn is_weaker(parent: &Self, child: &Self) -> bool {
451        match (parent, child) {
452            (_, RevocationTerms::RevocableByDelegator) => true,
453
454            (RevocationTerms::RevocableAfter(a), RevocationTerms::RevocableAfter(b)) if a >= b => {
455                true
456            }
457
458            (RevocationTerms::Irrevocable, RevocationTerms::RevocableAfter(_)) => true,
459
460            (RevocationTerms::Irrevocable, RevocationTerms::Irrevocable) => true,
461
462            _ => false,
463        }
464    }
465}
466
467/// Types of enforcement actions that can be voted on
468#[derive(
469    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
470)]
471#[scale_info(skip_type_params(T))]
472pub enum EnforcementReferendum {
473    /// Toggle emission accumulation state
474    EmissionAccumulation(bool),
475    /// Execute the permission
476    Execution,
477}
478
479/// Defines how a permission's enforcement is controlled
480#[derive(
481    Encode,
482    Decode,
483    CloneNoBound,
484    PartialEqNoBound,
485    EqNoBound,
486    TypeInfo,
487    MaxEncodedLen,
488    DebugNoBound,
489    DefaultNoBound,
490)]
491#[scale_info(skip_type_params(T))]
492pub enum EnforcementAuthority<T: Config> {
493    /// No special enforcement (standard permission execution)
494    #[default]
495    None,
496    /// Permission can be toggled active/inactive by controllers
497    ControlledBy {
498        controllers: BoundedVec<T::AccountId, T::MaxControllersPerPermission>,
499        required_votes: u32,
500    },
501}
502
503/// Process all auto-distributions and time-based distributions
504pub(crate) fn do_auto_permission_execution<T: Config>(current_block: BlockNumberFor<T>) {
505    // Only check every 10 blocks to reduce computational overhead
506    if <BlockNumberFor<T> as Into<U256>>::into(current_block)
507        .checked_rem(10.into())
508        .unwrap_or_default()
509        != U256::zero()
510    {
511        return;
512    }
513
514    let permissions: Vec<_> = Permissions::<T>::iter().collect();
515    let mut expired = Vec::with_capacity(permissions.len());
516
517    info!(
518        target: "auto_permission_execution",
519        "executing auto permission execution for {} permissions",
520        permissions.len()
521    );
522
523    for (permission_id, contract) in Permissions::<T>::iter() {
524        #[allow(clippy::single_match)]
525        match &contract.scope {
526            PermissionScope::Stream(stream_scope) => {
527                trace!(target: "auto_permission_execution", "executing auto permission execution for permission {permission_id:?}");
528                if let Err(err) = stream::do_auto_distribution(
529                    stream_scope,
530                    permission_id,
531                    current_block,
532                    &contract,
533                ) {
534                    error!(
535                        "failed to auto distribute streams for permission {permission_id:?}: {err:?}"
536                    );
537                }
538            }
539            _ => (),
540        }
541
542        if contract.is_expired(current_block) {
543            expired.push((permission_id, contract));
544        }
545    }
546
547    for (permission_id, contract) in expired {
548        let delegator = contract.delegator.clone();
549
550        if let Err(err) = contract.cleanup(permission_id) {
551            error!("failed to cleanup expired permission {permission_id:?}: {err:?}");
552        }
553
554        <Pallet<T>>::deposit_event(Event::PermissionExpired {
555            delegator,
556            permission_id,
557        });
558    }
559}
560
561/// Update storage indices when creating a new permission
562pub(crate) fn add_permission_indices<'a, T: Config>(
563    delegator: &T::AccountId,
564    recipients: impl Iterator<Item = &'a T::AccountId>,
565    permission_id: PermissionId,
566) -> Result<(), DispatchError> {
567    for recipient in recipients {
568        // Update (delegator, recipient) -> [permission_id] mapping
569        PermissionsByParticipants::<T>::try_mutate(
570            (delegator.clone(), recipient.clone()),
571            |permissions| -> Result<(), DispatchError> {
572                permissions
573                    .try_insert(permission_id)
574                    .map_err(|_| Error::<T>::TooManyRecipients)?;
575                Ok(())
576            },
577        )?;
578
579        // Update recipient -> [permission_id] mapping
580        PermissionsByRecipient::<T>::try_mutate(
581            recipient.clone(),
582            |permissions| -> Result<(), DispatchError> {
583                permissions
584                    .try_insert(permission_id)
585                    .map_err(|_| Error::<T>::TooManyRecipients)?;
586                Ok(())
587            },
588        )?;
589    }
590
591    // Update delegator -> [permission_id] mapping
592    PermissionsByDelegator::<T>::try_mutate(
593        delegator.clone(),
594        |permissions| -> Result<(), DispatchError> {
595            permissions
596                .try_insert(permission_id)
597                .map_err(|_| Error::<T>::TooManyRecipients)?;
598            Ok(())
599        },
600    )?;
601
602    Ok(())
603}
604
605/// Remove a permission recipient from storage indices
606fn remove_recipient_from_indices<T: Config>(
607    delegator: &T::AccountId,
608    recipient: &T::AccountId,
609    permission_id: PermissionId,
610) {
611    PermissionsByParticipants::<T>::mutate_exists(
612        (delegator.clone(), recipient.clone()),
613        |permissions| {
614            if let Some(p) = permissions {
615                p.remove(&permission_id);
616                if p.is_empty() {
617                    *permissions = None;
618                }
619            }
620        },
621    );
622
623    PermissionsByRecipient::<T>::mutate_exists(recipient, |permissions| {
624        if let Some(p) = permissions {
625            p.remove(&permission_id);
626            if p.is_empty() {
627                *permissions = None;
628            }
629        }
630    });
631}
632
633/// Remove a permission from storage indices
634pub(crate) fn remove_permission_from_indices<'a, T: Config>(
635    delegator: &T::AccountId,
636    recipients: impl Iterator<Item = &'a T::AccountId>,
637    permission_id: PermissionId,
638) {
639    for recipient in recipients {
640        PermissionsByParticipants::<T>::mutate_exists(
641            (delegator.clone(), recipient.clone()),
642            |permissions| {
643                if let Some(p) = permissions {
644                    p.remove(&permission_id);
645                    if p.is_empty() {
646                        *permissions = None;
647                    }
648                }
649            },
650        );
651
652        PermissionsByRecipient::<T>::mutate_exists(recipient, |permissions| {
653            if let Some(p) = permissions {
654                p.remove(&permission_id);
655                if p.is_empty() {
656                    *permissions = None;
657                }
658            }
659        });
660    }
661
662    PermissionsByDelegator::<T>::mutate_exists(delegator, |permissions| {
663        if let Some(p) = permissions {
664            p.remove(&permission_id);
665            if p.is_empty() {
666                *permissions = None;
667            }
668        }
669    });
670}