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 #[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 #[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 #[pallet::constant]
62 type MaxControllersPerPermission: Get<u32>;
63
64 #[pallet::constant]
66 type MaxRevokersPerPermission: Get<u32>;
67
68 #[pallet::constant]
70 type MaxTargetsPerPermission: Get<u32>;
71
72 #[pallet::constant]
74 type MaxStreamsPerPermission: Get<u32>;
75
76 #[pallet::constant]
78 type MinAutoDistributionThreshold: Get<BalanceOf<Self>>;
79
80 #[pallet::constant]
82 type MaxNamespacesPerPermission: Get<u32>;
83
84 #[pallet::constant]
86 type MaxCuratorSubpermissionsPerPermission: Get<u32>;
87
88 #[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 #[pallet::storage]
108 pub type Permissions<T: Config> = StorageMap<_, Identity, PermissionId, PermissionContract<T>>;
109
110 #[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 #[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 #[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 #[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 #[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 #[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 PermissionDelegated {
179 delegator: T::AccountId,
180 recipient: T::AccountId,
181 permission_id: PermissionId,
182 },
183 PermissionRevoked {
185 delegator: T::AccountId,
186 recipient: T::AccountId,
187 revoked_by: Option<T::AccountId>,
188 permission_id: PermissionId,
189 },
190 PermissionExpired {
192 delegator: T::AccountId,
193 recipient: T::AccountId,
194 permission_id: PermissionId,
195 },
196 PermissionAccumulationToggled {
198 permission_id: PermissionId,
199 accumulating: bool,
200 toggled_by: Option<T::AccountId>,
201 },
202 PermissionEnforcementExecuted {
204 permission_id: PermissionId,
205 executed_by: Option<T::AccountId>,
206 },
207 EnforcementVoteCast {
209 permission_id: PermissionId,
210 voter: T::AccountId,
211 referendum: EnforcementReferendum,
212 },
213 EnforcementAuthoritySet {
215 permission_id: PermissionId,
216 controllers_count: u32,
217 required_votes: u32,
218 },
219 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 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 NotRegisteredAgent,
239 PermissionCreationOutsideExtrinsic,
241 DuplicatePermissionInBlock,
244 PermissionNotFound,
246 SelfPermissionNotAllowed,
248 InvalidPercentage,
250 InvalidTargetWeight,
252 NoTargetsSpecified,
254 InvalidThreshold,
256 NoAccumulatedAmount,
258 NotAuthorizedToRevoke,
260 TotalAllocationExceeded,
262 NotPermissionRecipient,
264 NotPermissionDelegator,
266 TooManyStreams,
268 TooManyTargets,
270 TooManyRevokers,
272 StorageError,
274 InvalidAmount,
276 InsufficientBalance,
278 InvalidInterval,
280 ParentPermissionNotFound,
282 InvalidDistributionMethod,
284 InvalidNumberOfRevokers,
287 FixedAmountCanOnlyBeTriggeredOnce,
289 UnsupportedPermissionType,
291 NotAuthorizedToToggle,
293 TooManyControllers,
295 InvalidNumberOfControllers,
297 DuplicatePermission,
299 PermissionInCooldown,
301 InvalidCuratorPermissions,
303 NamespaceDoesNotExist,
305 NamespacePathIsInvalid,
307 TooManyNamespaces,
309 NotAuthorizedToEdit,
311 NotEditable,
313 NamespaceCreationDisabled,
315 MultiParentForbidden,
317 NotEnoughInstances,
321 TooManyChildren,
323 RevocationTermsTooStrong,
325 TooManyCuratorPermissions,
327 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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
514fn 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
533fn update_permission_indices<T: Config>(
535 delegator: &T::AccountId,
536 recipient: &T::AccountId,
537 permission_id: PermissionId,
538) -> Result<(), DispatchError> {
539 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 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 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
575fn 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}