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 #[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
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 #[pallet::storage]
100 pub type Permissions<T: Config> = StorageMap<_, Identity, PermissionId, PermissionContract<T>>;
101
102 #[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 #[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 #[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 #[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 #[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 #[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 PermissionGranted {
171 grantor: T::AccountId,
172 grantee: T::AccountId,
173 permission_id: PermissionId,
174 },
175 PermissionRevoked {
177 grantor: T::AccountId,
178 grantee: T::AccountId,
179 revoked_by: Option<T::AccountId>,
180 permission_id: PermissionId,
181 },
182 PermissionExecuted {
184 grantor: T::AccountId,
185 grantee: T::AccountId,
186 permission_id: PermissionId,
187 stream_id: Option<StreamId>,
188 amount: BalanceOf<T>,
189 },
190 AutoDistributionExecuted {
192 grantor: T::AccountId,
193 grantee: T::AccountId,
194 permission_id: PermissionId,
195 stream_id: Option<StreamId>,
196 amount: BalanceOf<T>,
197 },
198 PermissionExpired {
200 grantor: T::AccountId,
201 grantee: T::AccountId,
202 permission_id: PermissionId,
203 },
204 PermissionAccumulationToggled {
206 permission_id: PermissionId,
207 accumulating: bool,
208 toggled_by: Option<T::AccountId>,
209 },
210 PermissionEnforcementExecuted {
212 permission_id: PermissionId,
213 executed_by: Option<T::AccountId>,
214 },
215 EnforcementVoteCast {
217 permission_id: PermissionId,
218 voter: T::AccountId,
219 referendum: EnforcementReferendum,
220 },
221 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 NotRegisteredAgent,
233 PermissionCreationOutsideExtrinsic,
235 DuplicatePermissionInBlock,
238 PermissionNotFound,
240 SelfPermissionNotAllowed,
242 InvalidPercentage,
244 InvalidTargetWeight,
246 NoTargetsSpecified,
248 InvalidThreshold,
250 NoAccumulatedAmount,
252 NotAuthorizedToRevoke,
254 TotalAllocationExceeded,
256 NotPermissionGrantee,
258 NotPermissionGrantor,
260 TooManyStreams,
262 TooManyTargets,
264 TooManyRevokers,
266 StorageError,
268 InvalidAmount,
270 InsufficientBalance,
272 InvalidInterval,
274 ParentPermissionNotFound,
276 InvalidDistributionMethod,
278 InvalidNumberOfRevokers,
281 FixedAmountCanOnlyBeTriggeredOnce,
283 UnsupportedPermissionType,
285 NotAuthorizedToToggle,
287 TooManyControllers,
289 InvalidNumberOfControllers,
291 DuplicatePermission,
293 PermissionInCooldown,
295 InvalidCuratorPermissions,
297 NamespaceDoesNotExist,
299 NamespacePathIsInvalid,
301 TooManyNamespaces,
303 NotAuthorizedToEdit,
305 NotEditable,
307 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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
481fn 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
497fn update_permission_indices<T: Config>(
499 grantor: &T::AccountId,
500 grantee: &T::AccountId,
501 permission_id: PermissionId,
502) -> Result<(), DispatchError> {
503 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 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 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
539fn 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}