pallet_permission0/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![allow(clippy::too_many_arguments)]
3
4pub mod benchmarking;
5pub mod weights;
6pub use weights::*;
7
8pub mod ext;
9pub mod migrations;
10pub mod permission;
11
12pub use pallet::*;
13
14pub use permission::{
15    CuratorPermissions, CuratorScope, DistributionControl, EmissionAllocation, EmissionScope,
16    EnforcementAuthority, EnforcementReferendum, PermissionContract, PermissionDuration,
17    PermissionId, PermissionScope, RevocationTerms, generate_permission_id,
18};
19
20pub use pallet_permission0_api::{StreamId, generate_root_stream_id};
21
22use polkadot_sdk::{
23    frame_support::{
24        BoundedVec,
25        dispatch::DispatchResult,
26        pallet_prelude::*,
27        traits::{Currency, Get, ReservableCurrency},
28    },
29    frame_system::{self, pallet_prelude::*},
30    polkadot_sdk_frame as frame,
31    sp_runtime::{Percent, traits::Saturating},
32    sp_std::prelude::*,
33};
34
35#[frame::pallet]
36pub mod pallet {
37    use pallet_torus0_api::NamespacePathInner;
38    use polkadot_sdk::{frame_support::PalletId, sp_core::TryCollect};
39
40    use super::*;
41
42    const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
43
44    /// Configure the pallet by specifying the parameters and types on which it depends.
45    #[pallet::config]
46    pub trait Config: polkadot_sdk::frame_system::Config {
47        type RuntimeEvent: From<Event<Self>>
48            + IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
49
50        /// Permission0 pallet ID
51        #[pallet::constant]
52        type PalletId: Get<PalletId>;
53
54        type WeightInfo: WeightInfo;
55
56        type Currency: ReservableCurrency<Self::AccountId>;
57
58        type Torus: pallet_torus0_api::Torus0Api<Self::AccountId, BalanceOf<Self>>;
59
60        /// Maximum number of controllers per permission.
61        #[pallet::constant]
62        type MaxControllersPerPermission: Get<u32>;
63
64        /// Maximum number of revokers.
65        #[pallet::constant]
66        type MaxRevokersPerPermission: Get<u32>;
67
68        /// Maximum number of targets per permission.
69        #[pallet::constant]
70        type MaxTargetsPerPermission: Get<u32>;
71
72        /// Maximum number of delegated streams per permission.
73        #[pallet::constant]
74        type MaxStreamsPerPermission: Get<u32>;
75
76        /// Minimum threshold for auto-distribution
77        #[pallet::constant]
78        type MinAutoDistributionThreshold: Get<BalanceOf<Self>>;
79
80        /// Maximum number of namespaces a single permission can delegate.
81        #[pallet::constant]
82        type MaxNamespacesPerPermission: Get<u32>;
83
84        /// Maximum number of curator subpermissions a single permission can delegate.
85        #[pallet::constant]
86        type MaxCuratorSubpermissionsPerPermission: Get<u32>;
87
88        /// Maximum number of children a single permission can have.
89        #[pallet::constant]
90        type MaxChildrenPerPermission: Get<u32>;
91    }
92
93    pub type BalanceOf<T> =
94        <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
95
96    pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
97
98    pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
99        <T as frame_system::Config>::AccountId,
100    >>::NegativeImbalance;
101
102    #[pallet::pallet]
103    #[pallet::storage_version(STORAGE_VERSION)]
104    pub struct Pallet<T>(_);
105
106    /// Active permission contracts - stored by permission ID
107    #[pallet::storage]
108    pub type Permissions<T: Config> = StorageMap<_, Identity, PermissionId, PermissionContract<T>>;
109
110    /// Mapping from (delegator, recipient) to permission IDs
111    #[pallet::storage]
112    pub type PermissionsByParticipants<T: Config> = StorageMap<
113        _,
114        Identity,
115        (T::AccountId, T::AccountId),
116        BoundedVec<PermissionId, T::MaxTargetsPerPermission>,
117        ValueQuery,
118    >;
119
120    /// Permissions delegated by a specific account
121    #[pallet::storage]
122    pub type PermissionsByDelegator<T: Config> = StorageMap<
123        _,
124        Identity,
125        T::AccountId,
126        BoundedVec<PermissionId, T::MaxTargetsPerPermission>,
127        ValueQuery,
128    >;
129
130    /// Permissions received by a specific account
131    #[pallet::storage]
132    pub type PermissionsByRecipient<T: Config> = StorageMap<
133        _,
134        Identity,
135        T::AccountId,
136        BoundedVec<PermissionId, T::MaxTargetsPerPermission>,
137        ValueQuery,
138    >;
139
140    /// Revocations in progress and the voters
141    #[pallet::storage]
142    pub type RevocationTracking<T: Config> = StorageMap<
143        _,
144        Identity,
145        PermissionId,
146        BoundedBTreeSet<T::AccountId, T::MaxRevokersPerPermission>,
147        ValueQuery,
148    >;
149
150    /// Enforcement votes in progress and the voters
151    #[pallet::storage]
152    pub type EnforcementTracking<T: Config> = StorageDoubleMap<
153        _,
154        Identity,
155        PermissionId,
156        Identity,
157        EnforcementReferendum,
158        BoundedBTreeSet<T::AccountId, T::MaxControllersPerPermission>,
159        ValueQuery,
160    >;
161
162    /// Accumulated amounts for each stream
163    #[pallet::storage]
164    pub type AccumulatedStreamAmounts<T: Config> = StorageNMap<
165        _,
166        (
167            NMapKey<Identity, T::AccountId>,
168            NMapKey<Identity, StreamId>,
169            NMapKey<Identity, PermissionId>,
170        ),
171        BalanceOf<T>,
172    >;
173
174    #[pallet::event]
175    #[pallet::generate_deposit(pub(super) fn deposit_event)]
176    pub enum Event<T: Config> {
177        /// Permission delegated from delegator to recipient with ID
178        PermissionDelegated {
179            delegator: T::AccountId,
180            recipient: T::AccountId,
181            permission_id: PermissionId,
182        },
183        /// Permission revoked with ID
184        PermissionRevoked {
185            delegator: T::AccountId,
186            recipient: T::AccountId,
187            revoked_by: Option<T::AccountId>,
188            permission_id: PermissionId,
189        },
190        /// Permission expired with ID
191        PermissionExpired {
192            delegator: T::AccountId,
193            recipient: T::AccountId,
194            permission_id: PermissionId,
195        },
196        /// Permission accumulation state toggled
197        PermissionAccumulationToggled {
198            permission_id: PermissionId,
199            accumulating: bool,
200            toggled_by: Option<T::AccountId>,
201        },
202        /// Permission was executed by enforcement authority
203        PermissionEnforcementExecuted {
204            permission_id: PermissionId,
205            executed_by: Option<T::AccountId>,
206        },
207        /// Vote for enforcement action
208        EnforcementVoteCast {
209            permission_id: PermissionId,
210            voter: T::AccountId,
211            referendum: EnforcementReferendum,
212        },
213        /// Enforcement authority set for permission
214        EnforcementAuthoritySet {
215            permission_id: PermissionId,
216            controllers_count: u32,
217            required_votes: u32,
218        },
219        /// An emission distribution happened
220        EmissionDistribution {
221            permission_id: PermissionId,
222            stream_id: Option<StreamId>,
223            target: T::AccountId,
224            amount: BalanceOf<T>,
225            reason: permission::emission::DistributionReason,
226        },
227        /// Accumulated emission for stream
228        AccumulatedEmission {
229            permission_id: PermissionId,
230            stream_id: StreamId,
231            amount: BalanceOf<T>,
232        },
233    }
234
235    #[pallet::error]
236    pub enum Error<T> {
237        /// The agent is not registered
238        NotRegisteredAgent,
239        /// Permissions can only be created through extrinsics
240        PermissionCreationOutsideExtrinsic,
241        /// A permission with the same exact parameters was
242        /// already created in the current block
243        DuplicatePermissionInBlock,
244        /// Permission not found
245        PermissionNotFound,
246        /// Self-permission is not allowed
247        SelfPermissionNotAllowed,
248        /// Invalid percentage (out of range)
249        InvalidPercentage,
250        /// Invalid emission weight set to target
251        InvalidTargetWeight,
252        /// No targets specified
253        NoTargetsSpecified,
254        /// Invalid threshold
255        InvalidThreshold,
256        /// No accumulated amount
257        NoAccumulatedAmount,
258        /// Not authorized to revoke
259        NotAuthorizedToRevoke,
260        /// Total allocation exceeded 100%
261        TotalAllocationExceeded,
262        /// Not the recipient of the permission
263        NotPermissionRecipient,
264        /// Not the delegator of the permission
265        NotPermissionDelegator,
266        /// Too many streams
267        TooManyStreams,
268        /// Too many targets
269        TooManyTargets,
270        /// Too many revokers
271        TooManyRevokers,
272        /// Failed to insert into storage
273        StorageError,
274        /// Invalid amount
275        InvalidAmount,
276        /// Insufficient balance for operation
277        InsufficientBalance,
278        /// Invalid distribution interval
279        InvalidInterval,
280        /// Parent permission not found
281        ParentPermissionNotFound,
282        /// Invalid distribution method
283        InvalidDistributionMethod,
284        /// Revokers and required voters must be at least one, and required voters must
285        /// be less than the number of revokers
286        InvalidNumberOfRevokers,
287        /// Fixed amount emissions can only be triggered once, manually or at a block
288        FixedAmountCanOnlyBeTriggeredOnce,
289        /// Unsupported permission type
290        UnsupportedPermissionType,
291        /// Not authorized to toggle permission state
292        NotAuthorizedToToggle,
293        /// Too many controllers
294        TooManyControllers,
295        /// Invalid number of controllers or required votes
296        InvalidNumberOfControllers,
297        /// Permission is a duplicate, revoke the previous one
298        DuplicatePermission,
299        /// Permission is in cooldown, wait a bit.
300        PermissionInCooldown,
301        /// Curator flags provided are invalid.
302        InvalidCuratorPermissions,
303        /// Tried delegating unknown namespace.
304        NamespaceDoesNotExist,
305        /// Namespace path provided contains illegal character or is malformatted.
306        NamespacePathIsInvalid,
307        /// Exceeded amount of total namespaces allowed in a single permission.
308        TooManyNamespaces,
309        /// Not authorized to edit a stream emission permission.
310        NotAuthorizedToEdit,
311        /// Stream emission permission is not editable
312        NotEditable,
313        /// Namespace creation was disabled by a curator.
314        NamespaceCreationDisabled,
315        /// Deriving a permission from multiple parents is still forbidden.
316        MultiParentForbidden,
317        /// Not enough instances available to delegate/execute a permission.
318        /// This might mean the execution requires more than the available instances
319        /// or that all instances are locked behind redelegations.
320        NotEnoughInstances,
321        /// Too many children for a permission.
322        TooManyChildren,
323        /// Revocation terms are too strong for a permission re-delegation.
324        RevocationTermsTooStrong,
325        /// Too many curator permissions being delegated in a single permission.
326        TooManyCuratorPermissions,
327        /// Namespace delegation depth exceeded the maximum allowed limit.
328        DelegationDepthExceeded,
329    }
330
331    #[pallet::hooks]
332    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
333        fn on_finalize(current_block: BlockNumberFor<T>) {
334            permission::do_auto_permission_execution::<T>(current_block);
335        }
336    }
337
338    #[pallet::call]
339    impl<T: Config> Pallet<T> {
340        /// Delegate a permission for emission delegation
341        #[pallet::call_index(0)]
342        #[pallet::weight(T::WeightInfo::delegate_emission_permission())]
343        pub fn delegate_emission_permission(
344            origin: OriginFor<T>,
345            recipient: T::AccountId,
346            allocation: EmissionAllocation<T>,
347            targets: BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>,
348            distribution: DistributionControl<T>,
349            duration: PermissionDuration<T>,
350            revocation: RevocationTerms<T>,
351            enforcement: EnforcementAuthority<T>,
352        ) -> DispatchResult {
353            let delegator = ensure_signed(origin)?;
354
355            ext::emission_impl::delegate_emission_permission_impl::<T>(
356                delegator,
357                recipient,
358                allocation,
359                targets,
360                distribution,
361                duration,
362                revocation,
363                enforcement,
364                None,
365            )?;
366
367            Ok(())
368        }
369
370        /// Revoke a permission. The caller must met revocation constraints or be a root key.
371        #[pallet::call_index(1)]
372        #[pallet::weight(T::WeightInfo::revoke_permission())]
373        pub fn revoke_permission(
374            origin: OriginFor<T>,
375            permission_id: PermissionId,
376        ) -> DispatchResult {
377            ext::revoke_permission_impl::<T>(origin, &permission_id)
378        }
379
380        /// Execute a manual distribution based on permission
381        #[pallet::call_index(2)]
382        #[pallet::weight(T::WeightInfo::execute_permission())]
383        pub fn execute_permission(
384            origin: OriginFor<T>,
385            permission_id: PermissionId,
386        ) -> DispatchResult {
387            ext::execute_permission_impl::<T>(origin, &permission_id)
388        }
389
390        /// Toggle a permission's accumulation state (enabled/disabled)
391        /// The caller must be authorized as a controller or be the root key
392        #[pallet::call_index(3)]
393        #[pallet::weight(T::WeightInfo::toggle_permission_accumulation())]
394        pub fn toggle_permission_accumulation(
395            origin: OriginFor<T>,
396            permission_id: PermissionId,
397            accumulating: bool,
398        ) -> DispatchResult {
399            ext::emission_impl::toggle_permission_accumulation_impl::<T>(
400                origin,
401                permission_id,
402                accumulating,
403            )
404        }
405
406        /// Execute a permission through enforcement authority
407        /// The caller must be authorized as a controller or be the root key
408        #[pallet::call_index(4)]
409        #[pallet::weight(T::WeightInfo::enforcement_execute_permission())]
410        pub fn enforcement_execute_permission(
411            origin: OriginFor<T>,
412            permission_id: PermissionId,
413        ) -> DispatchResult {
414            ext::enforcement_execute_permission_impl::<T>(origin, permission_id)
415        }
416
417        /// Set enforcement authority for a permission
418        /// Only the delegator or root can set enforcement authority
419        #[pallet::call_index(5)]
420        #[pallet::weight(T::WeightInfo::set_enforcement_authority())]
421        pub fn set_enforcement_authority(
422            origin: OriginFor<T>,
423            permission_id: PermissionId,
424            enforcement: EnforcementAuthority<T>,
425        ) -> DispatchResult {
426            let who = ensure_signed_or_root(origin)?;
427
428            let contract =
429                Permissions::<T>::get(permission_id).ok_or(Error::<T>::PermissionNotFound)?;
430
431            if let Some(who) = &who {
432                ensure!(
433                    who == &contract.delegator,
434                    Error::<T>::NotPermissionDelegator
435                );
436            }
437
438            contract.update_enforcement(permission_id, enforcement)
439        }
440
441        /// Delegate a permission for curator delegation
442        #[pallet::call_index(6)]
443        #[pallet::weight(T::WeightInfo::delegate_curator_permission())]
444        pub fn delegate_curator_permission(
445            origin: OriginFor<T>,
446            recipient: T::AccountId,
447            flags: BoundedBTreeMap<
448                Option<PermissionId>,
449                u32,
450                T::MaxCuratorSubpermissionsPerPermission,
451            >,
452            cooldown: Option<BlockNumberFor<T>>,
453            duration: PermissionDuration<T>,
454            revocation: RevocationTerms<T>,
455            instances: u32,
456        ) -> DispatchResult {
457            let flags = flags
458                .into_iter()
459                .map(|(pid, flags)| (pid, CuratorPermissions::from_bits_truncate(flags)))
460                .try_collect()?;
461
462            ext::curator_impl::delegate_curator_permission_impl::<T>(
463                origin, recipient, flags, cooldown, duration, revocation, instances,
464            )?;
465
466            Ok(())
467        }
468
469        /// Delegate a permission over namespaces
470        #[pallet::call_index(7)]
471        #[pallet::weight(T::WeightInfo::delegate_curator_permission())]
472        pub fn delegate_namespace_permission(
473            origin: OriginFor<T>,
474            recipient: T::AccountId,
475            paths: BoundedBTreeMap<
476                Option<PermissionId>,
477                BoundedBTreeSet<NamespacePathInner, T::MaxNamespacesPerPermission>,
478                T::MaxNamespacesPerPermission,
479            >,
480            duration: PermissionDuration<T>,
481            revocation: RevocationTerms<T>,
482            instances: u32,
483        ) -> DispatchResult {
484            ext::namespace_impl::delegate_namespace_permission_impl::<T>(
485                origin, recipient, paths, duration, revocation, instances,
486            )?;
487
488            Ok(())
489        }
490
491        /// Allows Delegator/Recipient to edit stream emission permission
492        #[pallet::call_index(8)]
493        #[pallet::weight(T::WeightInfo::delegate_curator_permission())]
494        pub fn update_emission_permission(
495            origin: OriginFor<T>,
496            permission_id: PermissionId,
497            new_targets: BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>,
498            new_streams: Option<BoundedBTreeMap<StreamId, Percent, T::MaxStreamsPerPermission>>,
499            new_distribution_control: Option<DistributionControl<T>>,
500        ) -> DispatchResult {
501            ext::emission_impl::update_emission_permission(
502                origin,
503                permission_id,
504                new_targets,
505                new_streams,
506                new_distribution_control,
507            )?;
508
509            Ok(())
510        }
511    }
512}
513
514/// Get total allocated percentage for a delegator
515fn get_total_allocated_percentage<T: Config>(
516    delegator: &T::AccountId,
517    stream: &StreamId,
518) -> Percent {
519    AccumulatedStreamAmounts::<T>::iter_key_prefix((delegator, stream))
520        .filter_map(Permissions::<T>::get)
521        .map(|contract| match contract.scope {
522            PermissionScope::Emission(EmissionScope {
523                allocation: EmissionAllocation::Streams(streams),
524                ..
525            }) => streams.get(stream).copied().unwrap_or_default(),
526            _ => Percent::zero(),
527        })
528        .fold(Percent::zero(), |acc, percentage| {
529            acc.saturating_add(percentage)
530        })
531}
532
533/// Update storage indices when creating a new permission
534fn update_permission_indices<T: Config>(
535    delegator: &T::AccountId,
536    recipient: &T::AccountId,
537    permission_id: PermissionId,
538) -> Result<(), DispatchError> {
539    // Update (delegator, recipient) -> [permission_id] mapping
540    PermissionsByParticipants::<T>::try_mutate(
541        (delegator.clone(), recipient.clone()),
542        |permissions| -> Result<(), DispatchError> {
543            permissions
544                .try_push(permission_id)
545                .map_err(|_| Error::<T>::TooManyTargets)?;
546            Ok(())
547        },
548    )?;
549
550    // Update delegator -> [permission_id] mapping
551    PermissionsByDelegator::<T>::try_mutate(
552        delegator.clone(),
553        |permissions| -> Result<(), DispatchError> {
554            permissions
555                .try_push(permission_id)
556                .map_err(|_| Error::<T>::TooManyTargets)?;
557            Ok(())
558        },
559    )?;
560
561    // Update recipient -> [permission_id] mapping
562    PermissionsByRecipient::<T>::try_mutate(
563        recipient.clone(),
564        |permissions| -> Result<(), DispatchError> {
565            permissions
566                .try_push(permission_id)
567                .map_err(|_| Error::<T>::TooManyTargets)?;
568            Ok(())
569        },
570    )?;
571
572    Ok(())
573}
574
575/// Remove a permission from storage indices
576fn remove_permission_from_indices<T: Config>(
577    delegator: &T::AccountId,
578    recipient: &T::AccountId,
579    permission_id: PermissionId,
580) {
581    PermissionsByParticipants::<T>::mutate((delegator.clone(), recipient.clone()), |permissions| {
582        permissions.retain(|id| *id != permission_id);
583    });
584
585    PermissionsByDelegator::<T>::mutate(delegator, |permissions| {
586        permissions.retain(|id| *id != permission_id);
587    });
588
589    PermissionsByRecipient::<T>::mutate(recipient, |permissions| {
590        permissions.retain(|id| *id != permission_id);
591    });
592}