1use codec::{Decode, Encode, MaxEncodedLen};
2use polkadot_sdk::{
3 frame_support::{
4 CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
5 dispatch::DispatchResult, ensure,
6 },
7 frame_system::{self, RawOrigin, ensure_signed_or_root},
8 polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
9 sp_core::{H256, U256},
10 sp_runtime::{
11 BoundedBTreeMap, BoundedVec, DispatchError, Percent,
12 traits::{BlakeTwo256, Hash},
13 },
14 sp_std::{vec, vec::Vec},
15 sp_tracing::{error, info, trace},
16};
17use scale_info::TypeInfo;
18use wallet::WalletScope;
19
20use crate::*;
21
22pub use curator::{CuratorPermissions, CuratorScope};
23pub use namespace::NamespaceScope;
24pub use stream::{DistributionControl, StreamAllocation, StreamScope};
25
26pub mod curator;
27pub mod namespace;
28pub mod stream;
29pub mod wallet;
30
31pub type PermissionId = H256;
33
34pub fn generate_permission_id<T: Config>(
37 delegator: &T::AccountId,
38 scope: &PermissionScope<T>,
39) -> Result<PermissionId, DispatchError> {
40 let mut data = delegator.encode();
41 data.extend(scope.encode());
42
43 data.extend(<frame_system::Pallet<T>>::block_number().encode());
44
45 if let Some(extrinsic_index) = <frame_system::Pallet<T>>::extrinsic_index() {
46 data.extend(extrinsic_index.encode());
47 }
48
49 let id = BlakeTwo256::hash(&data);
50 ensure!(
51 !Permissions::<T>::contains_key(id),
52 Error::<T>::DuplicatePermissionInBlock
53 );
54
55 Ok(id)
56}
57
58#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
59#[scale_info(skip_type_params(T))]
60pub struct PermissionContract<T: Config> {
61 pub delegator: T::AccountId,
62 pub scope: PermissionScope<T>,
63 pub duration: PermissionDuration<T>,
64 pub revocation: RevocationTerms<T>,
65 pub enforcement: EnforcementAuthority<T>,
67 pub last_update: BlockNumberFor<T>,
69 #[doc(hidden)]
71 pub last_execution: Option<BlockNumberFor<T>>,
72 #[doc(hidden)]
74 pub execution_count: u32,
75 pub created_at: BlockNumberFor<T>,
76}
77
78impl<T: Config> PermissionContract<T> {
79 pub(crate) fn new(
80 delegator: T::AccountId,
81 scope: PermissionScope<T>,
82 duration: PermissionDuration<T>,
83 revocation: RevocationTerms<T>,
84 enforcement: EnforcementAuthority<T>,
85 ) -> Self {
86 let now = frame_system::Pallet::<T>::block_number();
87 Self {
88 delegator,
89 scope,
90 duration,
91 revocation,
92 enforcement,
93
94 last_update: now,
95 last_execution: None,
96 execution_count: 0,
97 created_at: now,
98 }
99 }
100}
101
102impl<T: Config> PermissionContract<T> {
103 pub fn is_expired(&self, current_block: BlockNumberFor<T>) -> bool {
104 match self.duration {
105 PermissionDuration::UntilBlock(block) => current_block > block,
106 PermissionDuration::Indefinite => false,
107 }
108 }
109
110 pub fn last_execution(&self) -> Option<BlockNumberFor<T>> {
112 self.last_execution
113 }
114
115 pub fn execution_count(&self) -> u32 {
117 self.execution_count
118 }
119
120 pub fn used_instances(&self) -> u32 {
122 match &self.scope {
123 PermissionScope::Curator(CuratorScope { children, .. })
124 | PermissionScope::Namespace(NamespaceScope { children, .. }) => {
125 let mut used = 0;
126 for child in children {
127 used = used.saturating_add(
128 Permissions::<T>::get(child)
129 .map_or(0, |child| child.max_instances().unwrap_or_default()),
130 );
131 }
132 used
133 }
134 _ => 0,
135 }
136 }
137
138 pub fn max_instances(&self) -> Option<u32> {
140 match &self.scope {
141 PermissionScope::Curator(CuratorScope { max_instances, .. })
142 | PermissionScope::Namespace(NamespaceScope { max_instances, .. }) => {
143 Some(*max_instances)
144 }
145 _ => None,
146 }
147 }
148
149 pub fn available_instances(&self) -> Option<u32> {
151 Some(self.max_instances()?.saturating_sub(self.used_instances()))
152 }
153
154 pub fn children_mut(
156 &mut self,
157 ) -> Option<&mut BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>> {
158 match &mut self.scope {
159 PermissionScope::Curator(CuratorScope { children, .. })
160 | PermissionScope::Namespace(NamespaceScope { children, .. }) => Some(children),
161 _ => None,
162 }
163 }
164
165 pub fn children(&self) -> Option<&BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>> {
166 match &self.scope {
167 PermissionScope::Curator(CuratorScope { children, .. })
168 | PermissionScope::Namespace(NamespaceScope { children, .. }) => Some(children),
169 _ => None,
170 }
171 }
172
173 pub fn tick_execution(&mut self, block: BlockNumberFor<T>) -> DispatchResult {
174 if let Some(available_instances) = self.available_instances()
175 && available_instances == 0
176 {
177 return Err(Error::<T>::NotEnoughInstances.into());
178 }
179
180 self.last_execution = Some(block);
181 self.execution_count = self.execution_count.saturating_add(1);
182
183 Ok(())
184 }
185
186 pub fn update_enforcement(
191 mut self,
192 permission_id: H256,
193 enforcement: EnforcementAuthority<T>,
194 ) -> DispatchResult {
195 let (controllers, required_votes) = match enforcement {
196 EnforcementAuthority::None => {
197 self.enforcement = EnforcementAuthority::None;
198 Permissions::<T>::insert(permission_id, self);
199
200 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
201
202 Pallet::<T>::deposit_event(Event::EnforcementAuthoritySet {
203 permission_id,
204 controllers_count: 0,
205 required_votes: 0,
206 });
207
208 return Ok(());
209 }
210 EnforcementAuthority::ControlledBy {
211 controllers,
212 required_votes,
213 } => (controllers, required_votes),
214 };
215
216 ensure!(
217 !controllers.is_empty(),
218 Error::<T>::InvalidNumberOfControllers
219 );
220 ensure!(required_votes > 0, Error::<T>::InvalidNumberOfControllers);
221 ensure!(
222 required_votes as usize <= controllers.len(),
223 Error::<T>::InvalidNumberOfControllers
224 );
225
226 let event = Event::EnforcementAuthoritySet {
227 permission_id,
228 controllers_count: controllers.len() as u32,
229 required_votes,
230 };
231
232 self.enforcement = EnforcementAuthority::ControlledBy {
233 controllers,
234 required_votes,
235 };
236 Permissions::<T>::insert(permission_id, self);
237
238 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
239
240 <Pallet<T>>::deposit_event(event);
241
242 Ok(())
243 }
244
245 pub fn revoke(self, origin: OriginFor<T>, permission_id: H256) -> DispatchResult {
246 let caller = ensure_signed_or_root(origin)?;
247
248 let delegator = self.delegator.clone();
249 let recipients = match &self.scope {
250 PermissionScope::Curator(CuratorScope { recipient, .. })
251 | PermissionScope::Namespace(NamespaceScope { recipient, .. })
252 | PermissionScope::Wallet(WalletScope { recipient, .. }) => {
253 vec![recipient.clone()]
254 }
255 PermissionScope::Stream(StreamScope { recipients, .. }) => {
256 recipients.keys().cloned().collect()
257 }
258 };
259
260 if let Some(caller) = &caller {
262 match &self.revocation {
263 RevocationTerms::RevocableByDelegator if caller == &delegator => {
264 }
266 RevocationTerms::RevocableByArbiters {
267 accounts,
268 required_votes,
269 } if accounts.contains(caller) => {
270 let votes = RevocationTracking::<T>::get(permission_id)
271 .into_iter()
272 .filter(|id| id != caller)
273 .filter(|id| accounts.contains(id))
274 .count();
275 if votes.saturating_add(1) < *required_votes as usize {
276 return RevocationTracking::<T>::mutate(permission_id, |votes| {
277 votes
278 .try_insert(caller.clone())
279 .map_err(|_| Error::<T>::TooManyRevokers)?;
280 Ok(())
281 });
282 }
283
284 }
286 RevocationTerms::RevocableAfter(block)
287 if caller == &delegator
288 && <frame_system::Pallet<T>>::block_number() >= *block =>
289 {
290 }
292 _ => {
293 ensure!(
294 recipients.contains(caller),
295 Error::<T>::NotAuthorizedToRevoke
296 );
297
298 if recipients.len() > 1usize {
299 remove_recipient_from_indices::<T>(&delegator, caller, permission_id);
300
301 Permissions::<T>::mutate(permission_id, |permission| {
302 if let Some(permission) = permission {
303 #[allow(clippy::single_match)]
304 match &mut permission.scope {
305 PermissionScope::Stream(StreamScope { recipients, .. }) => {
306 recipients.remove(caller);
307 }
308 _ => {}
309 }
310 }
311 });
312
313 <Pallet<T>>::deposit_event(Event::PermissionRevoked {
314 delegator,
315 revoked_by: Some(caller.clone()),
316 permission_id,
317 });
318
319 return Ok(());
320 }
321 }
322 };
323 }
324
325 for child_id in self.children().into_iter().flat_map(|c| c.iter()) {
326 let Some(child) = Permissions::<T>::get(child_id) else {
327 continue;
328 };
329
330 let revoker = if caller.is_none() {
331 RawOrigin::Root
332 } else {
333 RawOrigin::Signed(child.delegator.clone())
334 };
335
336 child.revoke(revoker.into(), *child_id)?;
337 }
338
339 self.cleanup(permission_id)?;
340
341 <Pallet<T>>::deposit_event(Event::PermissionRevoked {
342 delegator,
343 revoked_by: caller,
344 permission_id,
345 });
346
347 Ok(())
348 }
349
350 fn cleanup(self, permission_id: H256) -> DispatchResult {
351 match &self.scope {
352 PermissionScope::Curator(CuratorScope { recipient, .. })
353 | PermissionScope::Namespace(NamespaceScope { recipient, .. })
354 | PermissionScope::Wallet(WalletScope { recipient, .. }) => {
355 remove_permission_from_indices::<T>(
356 &self.delegator,
357 core::iter::once(recipient),
358 permission_id,
359 );
360 }
361 PermissionScope::Stream(StreamScope { recipients, .. }) => {
362 remove_permission_from_indices::<T>(
363 &self.delegator,
364 recipients.keys(),
365 permission_id,
366 );
367 }
368 };
369
370 Permissions::<T>::remove(permission_id);
371 RevocationTracking::<T>::remove(permission_id);
372 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
373
374 match self.scope {
375 PermissionScope::Stream(stream) => {
376 stream.cleanup(permission_id, &self.last_execution, &self.delegator);
377 }
378 PermissionScope::Curator(curator) => {
379 curator.cleanup(permission_id, &self.last_execution, &self.delegator);
380 }
381 PermissionScope::Namespace(namespace) => {
382 namespace.cleanup(permission_id, &self.last_execution, &self.delegator);
383 }
384 PermissionScope::Wallet(wallet) => {
385 wallet.cleanup(permission_id, &self.last_execution, &self.delegator);
386 }
387 }
388
389 Ok(())
390 }
391
392 pub fn is_updatable(&self) -> bool {
393 self.revocation.is_revokable()
394 }
395}
396
397#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
399#[scale_info(skip_type_params(T))]
400pub enum PermissionScope<T: Config> {
401 Stream(StreamScope<T>),
402 Curator(CuratorScope<T>),
403 Namespace(NamespaceScope<T>),
404 Wallet(WalletScope<T>),
405}
406
407#[derive(
408 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
409)]
410#[scale_info(skip_type_params(T))]
411pub enum PermissionDuration<T: Config> {
412 UntilBlock(BlockNumberFor<T>),
414 Indefinite,
416}
417
418#[derive(
419 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
420)]
421#[scale_info(skip_type_params(T))]
422pub enum RevocationTerms<T: Config> {
423 Irrevocable,
425 RevocableByDelegator,
427 RevocableByArbiters {
429 accounts: BoundedVec<T::AccountId, T::MaxRevokersPerPermission>,
430 required_votes: u32,
431 },
432 RevocableAfter(BlockNumberFor<T>),
434}
435
436impl<T: Config> RevocationTerms<T> {
437 pub fn is_revokable(&self) -> bool {
440 let current_block = frame_system::Pallet::<T>::block_number();
441
442 match &self {
443 RevocationTerms::RevocableByDelegator => true,
444 RevocationTerms::RevocableAfter(block) => ¤t_block > block,
445 _ => false,
446 }
447 }
448
449 pub fn is_weaker(parent: &Self, child: &Self) -> bool {
451 match (parent, child) {
452 (_, RevocationTerms::RevocableByDelegator) => true,
453
454 (RevocationTerms::RevocableAfter(a), RevocationTerms::RevocableAfter(b)) if a >= b => {
455 true
456 }
457
458 (RevocationTerms::Irrevocable, RevocationTerms::RevocableAfter(_)) => true,
459
460 (RevocationTerms::Irrevocable, RevocationTerms::Irrevocable) => true,
461
462 _ => false,
463 }
464 }
465}
466
467#[derive(
469 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
470)]
471#[scale_info(skip_type_params(T))]
472pub enum EnforcementReferendum {
473 EmissionAccumulation(bool),
475 Execution,
477}
478
479#[derive(
481 Encode,
482 Decode,
483 CloneNoBound,
484 PartialEqNoBound,
485 EqNoBound,
486 TypeInfo,
487 MaxEncodedLen,
488 DebugNoBound,
489 DefaultNoBound,
490)]
491#[scale_info(skip_type_params(T))]
492pub enum EnforcementAuthority<T: Config> {
493 #[default]
495 None,
496 ControlledBy {
498 controllers: BoundedVec<T::AccountId, T::MaxControllersPerPermission>,
499 required_votes: u32,
500 },
501}
502
503pub(crate) fn do_auto_permission_execution<T: Config>(current_block: BlockNumberFor<T>) {
505 if <BlockNumberFor<T> as Into<U256>>::into(current_block)
507 .checked_rem(10.into())
508 .unwrap_or_default()
509 != U256::zero()
510 {
511 return;
512 }
513
514 let permissions: Vec<_> = Permissions::<T>::iter().collect();
515 let mut expired = Vec::with_capacity(permissions.len());
516
517 info!(
518 target: "auto_permission_execution",
519 "executing auto permission execution for {} permissions",
520 permissions.len()
521 );
522
523 for (permission_id, contract) in Permissions::<T>::iter() {
524 #[allow(clippy::single_match)]
525 match &contract.scope {
526 PermissionScope::Stream(stream_scope) => {
527 trace!(target: "auto_permission_execution", "executing auto permission execution for permission {permission_id:?}");
528 if let Err(err) = stream::do_auto_distribution(
529 stream_scope,
530 permission_id,
531 current_block,
532 &contract,
533 ) {
534 error!(
535 "failed to auto distribute streams for permission {permission_id:?}: {err:?}"
536 );
537 }
538 }
539 _ => (),
540 }
541
542 if contract.is_expired(current_block) {
543 expired.push((permission_id, contract));
544 }
545 }
546
547 for (permission_id, contract) in expired {
548 let delegator = contract.delegator.clone();
549
550 if let Err(err) = contract.cleanup(permission_id) {
551 error!("failed to cleanup expired permission {permission_id:?}: {err:?}");
552 }
553
554 <Pallet<T>>::deposit_event(Event::PermissionExpired {
555 delegator,
556 permission_id,
557 });
558 }
559}
560
561pub(crate) fn add_permission_indices<'a, T: Config>(
563 delegator: &T::AccountId,
564 recipients: impl Iterator<Item = &'a T::AccountId>,
565 permission_id: PermissionId,
566) -> Result<(), DispatchError> {
567 for recipient in recipients {
568 PermissionsByParticipants::<T>::try_mutate(
570 (delegator.clone(), recipient.clone()),
571 |permissions| -> Result<(), DispatchError> {
572 permissions
573 .try_insert(permission_id)
574 .map_err(|_| Error::<T>::TooManyRecipients)?;
575 Ok(())
576 },
577 )?;
578
579 PermissionsByRecipient::<T>::try_mutate(
581 recipient.clone(),
582 |permissions| -> Result<(), DispatchError> {
583 permissions
584 .try_insert(permission_id)
585 .map_err(|_| Error::<T>::TooManyRecipients)?;
586 Ok(())
587 },
588 )?;
589 }
590
591 PermissionsByDelegator::<T>::try_mutate(
593 delegator.clone(),
594 |permissions| -> Result<(), DispatchError> {
595 permissions
596 .try_insert(permission_id)
597 .map_err(|_| Error::<T>::TooManyRecipients)?;
598 Ok(())
599 },
600 )?;
601
602 Ok(())
603}
604
605fn remove_recipient_from_indices<T: Config>(
607 delegator: &T::AccountId,
608 recipient: &T::AccountId,
609 permission_id: PermissionId,
610) {
611 PermissionsByParticipants::<T>::mutate_exists(
612 (delegator.clone(), recipient.clone()),
613 |permissions| {
614 if let Some(p) = permissions {
615 p.remove(&permission_id);
616 if p.is_empty() {
617 *permissions = None;
618 }
619 }
620 },
621 );
622
623 PermissionsByRecipient::<T>::mutate_exists(recipient, |permissions| {
624 if let Some(p) = permissions {
625 p.remove(&permission_id);
626 if p.is_empty() {
627 *permissions = None;
628 }
629 }
630 });
631}
632
633pub(crate) fn remove_permission_from_indices<'a, T: Config>(
635 delegator: &T::AccountId,
636 recipients: impl Iterator<Item = &'a T::AccountId>,
637 permission_id: PermissionId,
638) {
639 for recipient in recipients {
640 PermissionsByParticipants::<T>::mutate_exists(
641 (delegator.clone(), recipient.clone()),
642 |permissions| {
643 if let Some(p) = permissions {
644 p.remove(&permission_id);
645 if p.is_empty() {
646 *permissions = None;
647 }
648 }
649 },
650 );
651
652 PermissionsByRecipient::<T>::mutate_exists(recipient, |permissions| {
653 if let Some(p) = permissions {
654 p.remove(&permission_id);
655 if p.is_empty() {
656 *permissions = None;
657 }
658 }
659 });
660 }
661
662 PermissionsByDelegator::<T>::mutate_exists(delegator, |permissions| {
663 if let Some(p) = permissions {
664 p.remove(&permission_id);
665 if p.is_empty() {
666 *permissions = None;
667 }
668 }
669 });
670}