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