pallet_permission0/
permission.rs

1use codec::{Decode, Encode, MaxEncodedLen};
2use pallet_torus0_api::NamespacePath;
3use polkadot_sdk::{
4    frame_support::{
5        dispatch::DispatchResult, ensure, CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound,
6        PartialEqNoBound,
7    },
8    frame_system::{self, ensure_signed_or_root},
9    polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
10    sp_core::{H256, U256},
11    sp_runtime::{
12        traits::{BlakeTwo256, Hash},
13        BoundedBTreeMap, BoundedVec, DispatchError, Percent,
14    },
15    sp_std::vec::Vec,
16};
17use scale_info::TypeInfo;
18
19use crate::*;
20
21pub use curator::{CuratorPermissions, CuratorScope};
22pub use emission::{DistributionControl, EmissionAllocation, EmissionScope};
23
24pub mod curator;
25pub mod emission;
26
27/// Type for permission ID
28pub type PermissionId = H256;
29
30/// Generate a unique permission ID by hashing a concat of
31/// `grantee | scope | block number`
32pub fn generate_permission_id<T: Config>(
33    grantor: &T::AccountId,
34    grantee: &T::AccountId,
35    scope: &PermissionScope<T>,
36) -> Result<PermissionId, DispatchError> {
37    let mut data = grantor.encode();
38    data.extend(grantee.encode());
39    data.extend(scope.encode());
40
41    data.extend(<frame_system::Pallet<T>>::block_number().encode());
42
43    if let Some(extrinsic_index) = <frame_system::Pallet<T>>::extrinsic_index() {
44        data.extend(extrinsic_index.encode());
45    }
46
47    let id = BlakeTwo256::hash(&data);
48    ensure!(
49        !Permissions::<T>::contains_key(id),
50        Error::<T>::DuplicatePermissionInBlock
51    );
52
53    Ok(id)
54}
55
56#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
57#[scale_info(skip_type_params(T))]
58pub struct PermissionContract<T: Config> {
59    pub grantor: T::AccountId,
60    pub grantee: T::AccountId,
61    pub scope: PermissionScope<T>,
62    pub duration: PermissionDuration<T>,
63    pub revocation: RevocationTerms<T>,
64    /// Enforcement authority that can toggle the permission
65    pub enforcement: EnforcementAuthority<T>,
66    /// Last execution block
67    pub last_execution: Option<BlockNumberFor<T>>,
68    /// Number of times the permission was executed
69    pub execution_count: u32,
70    /// Parent permission ID (None for root permissions)
71    pub parent: Option<PermissionId>,
72    pub created_at: BlockNumberFor<T>,
73}
74
75impl<T: Config> PermissionContract<T> {
76    pub fn is_expired(&self, current_block: BlockNumberFor<T>) -> bool {
77        match self.duration {
78            PermissionDuration::UntilBlock(block) => current_block > block,
79            PermissionDuration::Indefinite => false,
80        }
81    }
82
83    pub fn revoke(self, origin: OriginFor<T>, permission_id: H256) -> DispatchResult {
84        // The grantee is also always allowed to revoke the permission.
85        let who = ensure_signed_or_root(origin)?.filter(|who| who != &self.grantee);
86
87        let grantor = self.grantor.clone();
88        let grantee = self.grantee.clone();
89
90        // `who` will not be present if the origin is a root key
91        if let Some(who) = &who {
92            match &self.revocation {
93                RevocationTerms::RevocableByGrantor => {
94                    ensure!(who == &grantor, Error::<T>::NotAuthorizedToRevoke)
95                }
96                RevocationTerms::RevocableByArbiters {
97                    accounts,
98                    required_votes,
99                } if accounts.contains(who) => {
100                    let votes = RevocationTracking::<T>::get(permission_id)
101                        .into_iter()
102                        .filter(|id| id != who)
103                        .filter(|id| accounts.contains(id))
104                        .count();
105                    if votes.saturating_add(1) < *required_votes as usize {
106                        return RevocationTracking::<T>::mutate(permission_id, |votes| {
107                            votes
108                                .try_insert(who.clone())
109                                .map_err(|_| Error::<T>::TooManyRevokers)?;
110                            Ok(())
111                        });
112                    }
113                }
114                RevocationTerms::RevocableByArbiters { .. } => {
115                    return Err(Error::<T>::NotAuthorizedToRevoke.into())
116                }
117                RevocationTerms::RevocableAfter(block) if who == &grantor => ensure!(
118                    <frame_system::Pallet<T>>::block_number() >= *block,
119                    Error::<T>::NotAuthorizedToRevoke
120                ),
121                RevocationTerms::RevocableAfter(_) => {
122                    return Err(Error::<T>::NotAuthorizedToRevoke.into())
123                }
124                RevocationTerms::Irrevocable => {
125                    return Err(Error::<T>::NotAuthorizedToRevoke.into())
126                }
127            };
128        }
129
130        self.cleanup(permission_id);
131
132        <Pallet<T>>::deposit_event(Event::PermissionRevoked {
133            grantor,
134            grantee,
135            revoked_by: who,
136            permission_id,
137        });
138
139        Ok(())
140    }
141
142    /// Updates the enforcement authority for this permission.
143    ///
144    /// When the enforcement authority changes, all ongoing enforcement
145    /// referendums for this permission are wiped.
146    pub fn update_enforcement(
147        mut self,
148        permission_id: H256,
149        enforcement: EnforcementAuthority<T>,
150    ) -> DispatchResult {
151        let (controllers, required_votes) = match enforcement {
152            EnforcementAuthority::None => {
153                self.enforcement = EnforcementAuthority::None;
154                Permissions::<T>::insert(permission_id, self);
155
156                let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
157
158                Pallet::<T>::deposit_event(Event::EnforcementAuthoritySet {
159                    permission_id,
160                    controllers_count: 0,
161                    required_votes: 0,
162                });
163
164                return Ok(());
165            }
166            EnforcementAuthority::ControlledBy {
167                controllers,
168                required_votes,
169            } => (controllers, required_votes),
170        };
171
172        ensure!(
173            !controllers.is_empty(),
174            Error::<T>::InvalidNumberOfControllers
175        );
176        ensure!(required_votes > 0, Error::<T>::InvalidNumberOfControllers);
177        ensure!(
178            required_votes as usize <= controllers.len(),
179            Error::<T>::InvalidNumberOfControllers
180        );
181
182        let event = Event::EnforcementAuthoritySet {
183            permission_id,
184            controllers_count: controllers.len() as u32,
185            required_votes,
186        };
187
188        self.enforcement = EnforcementAuthority::ControlledBy {
189            controllers,
190            required_votes,
191        };
192        Permissions::<T>::insert(permission_id, self);
193
194        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
195
196        <Pallet<T>>::deposit_event(event);
197
198        Ok(())
199    }
200
201    fn cleanup(self, permission_id: H256) {
202        crate::remove_permission_from_indices::<T>(&self.grantor, &self.grantee, permission_id);
203
204        Permissions::<T>::remove(permission_id);
205        RevocationTracking::<T>::remove(permission_id);
206        let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
207
208        match self.scope {
209            PermissionScope::Emission(emission) => {
210                emission.cleanup(permission_id, &self.last_execution, &self.grantor)
211            }
212            PermissionScope::Curator(curator) => {
213                curator.cleanup(permission_id, &self.last_execution, &self.grantor)
214            }
215            PermissionScope::Namespace(_) => {
216                // No cleanup needed for namespace permissions
217            }
218        }
219    }
220
221    pub fn is_updatable(&self) -> bool {
222        let current_block = frame_system::Pallet::<T>::block_number();
223
224        match &self.revocation {
225            RevocationTerms::RevocableByGrantor => true,
226            RevocationTerms::RevocableAfter(block) => &current_block > block,
227            _ => false,
228        }
229    }
230}
231
232/// Defines what the permission applies to
233#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
234#[scale_info(skip_type_params(T))]
235pub enum PermissionScope<T: Config> {
236    Emission(EmissionScope<T>),
237    Curator(CuratorScope<T>),
238    Namespace(NamespaceScope<T>),
239}
240
241/// Scope for namespace permissions
242#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
243#[scale_info(skip_type_params(T))]
244pub struct NamespaceScope<T: Config> {
245    /// Set of namespace paths this permission grants access to
246    pub paths: BoundedBTreeSet<NamespacePath, T::MaxNamespacesPerPermission>,
247}
248
249#[derive(
250    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
251)]
252#[scale_info(skip_type_params(T))]
253pub enum PermissionDuration<T: Config> {
254    /// Permission lasts until a specific block
255    UntilBlock(BlockNumberFor<T>),
256    /// Permission lasts indefinitely
257    Indefinite,
258}
259
260#[derive(
261    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
262)]
263#[scale_info(skip_type_params(T))]
264pub enum RevocationTerms<T: Config> {
265    /// Cannot be revoked
266    Irrevocable,
267    /// Can be revoked by the grantor at any time
268    RevocableByGrantor,
269    /// Can be revoked by third party arbiters
270    RevocableByArbiters {
271        accounts: BoundedVec<T::AccountId, T::MaxRevokersPerPermission>,
272        required_votes: u32,
273    },
274    /// Time-based revocation
275    RevocableAfter(BlockNumberFor<T>),
276}
277
278/// Types of enforcement actions that can be voted on
279#[derive(
280    Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
281)]
282#[scale_info(skip_type_params(T))]
283pub enum EnforcementReferendum {
284    /// Toggle emission accumulation state
285    EmissionAccumulation(bool),
286    /// Execute the permission
287    Execution,
288}
289
290/// Defines how a permission's enforcement is controlled
291#[derive(
292    Encode,
293    Decode,
294    CloneNoBound,
295    PartialEqNoBound,
296    EqNoBound,
297    TypeInfo,
298    MaxEncodedLen,
299    DebugNoBound,
300    DefaultNoBound,
301)]
302#[scale_info(skip_type_params(T))]
303pub enum EnforcementAuthority<T: Config> {
304    /// No special enforcement (standard permission execution)
305    #[default]
306    None,
307    /// Permission can be toggled active/inactive by controllers
308    ControlledBy {
309        controllers: BoundedVec<T::AccountId, T::MaxControllersPerPermission>,
310        required_votes: u32,
311    },
312}
313
314/// Process all auto-distributions and time-based distributions
315pub(crate) fn do_auto_permission_execution<T: Config>(current_block: BlockNumberFor<T>) {
316    // Only check every 10 blocks to reduce computational overhead
317    if <BlockNumberFor<T> as Into<U256>>::into(current_block)
318        .checked_rem(10.into())
319        .unwrap_or_default()
320        != U256::zero()
321    {
322        return;
323    }
324
325    let permissions: Vec<_> = Permissions::<T>::iter().collect();
326    let mut expired = Vec::with_capacity(permissions.len());
327
328    for (permission_id, contract) in Permissions::<T>::iter() {
329        #[allow(clippy::single_match)]
330        match &contract.scope {
331            PermissionScope::Emission(emission_scope) => {
332                emission::do_auto_distribution(
333                    emission_scope,
334                    permission_id,
335                    current_block,
336                    &contract,
337                );
338            }
339            _ => (),
340        }
341
342        if contract.is_expired(current_block) {
343            expired.push((permission_id, contract));
344        }
345    }
346
347    for (permission_id, contract) in expired {
348        let grantor = contract.grantor.clone();
349        let grantee = contract.grantee.clone();
350
351        contract.cleanup(permission_id);
352
353        <Pallet<T>>::deposit_event(Event::PermissionExpired {
354            grantor,
355            grantee,
356            permission_id,
357        });
358    }
359}