1use codec::{Decode, Encode, MaxEncodedLen};
2use pallet_torus0_api::NamespacePath;
3use polkadot_sdk::{
4 frame_support::{
5 CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
6 dispatch::DispatchResult, ensure,
7 },
8 frame_system::{self, RawOrigin, ensure_signed_or_root},
9 polkadot_sdk_frame::prelude::{BlockNumberFor, OriginFor},
10 sp_core::{H256, U256},
11 sp_runtime::{
12 BoundedBTreeMap, BoundedVec, DispatchError, Percent,
13 traits::{BlakeTwo256, Hash},
14 },
15 sp_std::vec::Vec,
16 sp_tracing::{error, info, trace},
17};
18use scale_info::TypeInfo;
19
20use crate::*;
21
22pub use curator::{CuratorPermissions, CuratorScope};
23pub use emission::{DistributionControl, EmissionAllocation, EmissionScope};
24
25pub mod curator;
26pub mod emission;
27
28pub type PermissionId = H256;
30
31pub fn generate_permission_id<T: Config>(
34 delegator: &T::AccountId,
35 recipient: &T::AccountId,
36 scope: &PermissionScope<T>,
37) -> Result<PermissionId, DispatchError> {
38 let mut data = delegator.encode();
39 data.extend(recipient.encode());
40 data.extend(scope.encode());
41
42 data.extend(<frame_system::Pallet<T>>::block_number().encode());
43
44 if let Some(extrinsic_index) = <frame_system::Pallet<T>>::extrinsic_index() {
45 data.extend(extrinsic_index.encode());
46 }
47
48 let id = BlakeTwo256::hash(&data);
49 ensure!(
50 !Permissions::<T>::contains_key(id),
51 Error::<T>::DuplicatePermissionInBlock
52 );
53
54 Ok(id)
55}
56
57#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
58#[scale_info(skip_type_params(T))]
59pub struct PermissionContract<T: Config> {
60 pub delegator: T::AccountId,
61 pub recipient: T::AccountId,
62 pub scope: PermissionScope<T>,
63 pub duration: PermissionDuration<T>,
64 pub revocation: RevocationTerms<T>,
65 pub enforcement: EnforcementAuthority<T>,
67 last_execution: Option<BlockNumberFor<T>>,
69 execution_count: u32,
71 pub max_instances: u32,
73 pub children: BoundedBTreeSet<H256, T::MaxChildrenPerPermission>,
75 pub created_at: BlockNumberFor<T>,
76}
77
78impl<T: Config> PermissionContract<T> {
79 pub(crate) fn new(
80 delegator: T::AccountId,
81 recipient: T::AccountId,
82 scope: PermissionScope<T>,
83 duration: PermissionDuration<T>,
84 revocation: RevocationTerms<T>,
85 enforcement: EnforcementAuthority<T>,
86 max_instances: u32,
87 ) -> Self {
88 Self {
89 delegator,
90 recipient,
91 scope,
92 duration,
93 revocation,
94 enforcement,
95 max_instances,
96
97 last_execution: None,
98 execution_count: 0,
99 children: BoundedBTreeSet::new(),
100 created_at: frame_system::Pallet::<T>::block_number(),
101 }
102 }
103
104 #[deprecated]
105 pub(crate) fn set_execution_info(
106 &mut self,
107 block: Option<BlockNumberFor<T>>,
108 execution_count: u32,
109 ) {
110 self.last_execution = block;
111 self.execution_count = execution_count;
112 }
113}
114
115impl<T: Config> PermissionContract<T> {
116 pub fn is_expired(&self, current_block: BlockNumberFor<T>) -> bool {
117 match self.duration {
118 PermissionDuration::UntilBlock(block) => current_block > block,
119 PermissionDuration::Indefinite => false,
120 }
121 }
122
123 pub fn last_execution(&self) -> Option<BlockNumberFor<T>> {
125 self.last_execution
126 }
127
128 pub fn execution_count(&self) -> u32 {
130 self.execution_count
131 }
132
133 pub fn available_instances(&self) -> u32 {
135 let mut available = self.max_instances;
136 for child in &self.children {
137 available = available.saturating_sub(
138 Permissions::<T>::get(child).map_or(0, |child| child.max_instances),
139 );
140 }
141 available
142 }
143
144 pub fn tick_execution(&mut self, block: BlockNumberFor<T>) -> DispatchResult {
145 if self.available_instances() == 0 {
146 return Err(Error::<T>::NotEnoughInstances.into());
147 }
148
149 self.last_execution = Some(block);
150 self.execution_count = self.execution_count.saturating_add(1);
151
152 Ok(())
153 }
154
155 pub fn revoke(self, origin: OriginFor<T>, permission_id: H256) -> DispatchResult {
156 let who = ensure_signed_or_root(origin)?.filter(|who| who != &self.recipient);
158
159 let delegator = self.delegator.clone();
160 let recipient = self.recipient.clone();
161
162 if let Some(who) = &who {
164 match &self.revocation {
165 RevocationTerms::RevocableByDelegator => {
166 ensure!(who == &delegator, Error::<T>::NotAuthorizedToRevoke)
167 }
168 RevocationTerms::RevocableByArbiters {
169 accounts,
170 required_votes,
171 } if accounts.contains(who) => {
172 let votes = RevocationTracking::<T>::get(permission_id)
173 .into_iter()
174 .filter(|id| id != who)
175 .filter(|id| accounts.contains(id))
176 .count();
177 if votes.saturating_add(1) < *required_votes as usize {
178 return RevocationTracking::<T>::mutate(permission_id, |votes| {
179 votes
180 .try_insert(who.clone())
181 .map_err(|_| Error::<T>::TooManyRevokers)?;
182 Ok(())
183 });
184 }
185 }
186 RevocationTerms::RevocableByArbiters { .. } => {
187 return Err(Error::<T>::NotAuthorizedToRevoke.into());
188 }
189 RevocationTerms::RevocableAfter(block) if who == &delegator => ensure!(
190 <frame_system::Pallet<T>>::block_number() >= *block,
191 Error::<T>::NotAuthorizedToRevoke
192 ),
193 RevocationTerms::RevocableAfter(_) => {
194 return Err(Error::<T>::NotAuthorizedToRevoke.into());
195 }
196 RevocationTerms::Irrevocable => {
197 return Err(Error::<T>::NotAuthorizedToRevoke.into());
198 }
199 };
200 }
201
202 for child_id in &self.children {
203 let Some(child) = Permissions::<T>::get(child_id) else {
204 continue;
205 };
206
207 let revoker = if who.is_none() {
208 RawOrigin::Root
209 } else {
210 RawOrigin::Signed(self.recipient.clone())
211 };
212
213 child.revoke(revoker.into(), *child_id)?;
214 }
215
216 self.cleanup(permission_id)?;
217
218 <Pallet<T>>::deposit_event(Event::PermissionRevoked {
219 delegator,
220 recipient,
221 revoked_by: who,
222 permission_id,
223 });
224
225 Ok(())
226 }
227
228 pub fn update_enforcement(
233 mut self,
234 permission_id: H256,
235 enforcement: EnforcementAuthority<T>,
236 ) -> DispatchResult {
237 let (controllers, required_votes) = match enforcement {
238 EnforcementAuthority::None => {
239 self.enforcement = EnforcementAuthority::None;
240 Permissions::<T>::insert(permission_id, self);
241
242 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
243
244 Pallet::<T>::deposit_event(Event::EnforcementAuthoritySet {
245 permission_id,
246 controllers_count: 0,
247 required_votes: 0,
248 });
249
250 return Ok(());
251 }
252 EnforcementAuthority::ControlledBy {
253 controllers,
254 required_votes,
255 } => (controllers, required_votes),
256 };
257
258 ensure!(
259 !controllers.is_empty(),
260 Error::<T>::InvalidNumberOfControllers
261 );
262 ensure!(required_votes > 0, Error::<T>::InvalidNumberOfControllers);
263 ensure!(
264 required_votes as usize <= controllers.len(),
265 Error::<T>::InvalidNumberOfControllers
266 );
267
268 let event = Event::EnforcementAuthoritySet {
269 permission_id,
270 controllers_count: controllers.len() as u32,
271 required_votes,
272 };
273
274 self.enforcement = EnforcementAuthority::ControlledBy {
275 controllers,
276 required_votes,
277 };
278 Permissions::<T>::insert(permission_id, self);
279
280 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
281
282 <Pallet<T>>::deposit_event(event);
283
284 Ok(())
285 }
286
287 fn cleanup(self, permission_id: H256) -> DispatchResult {
288 crate::remove_permission_from_indices::<T>(&self.delegator, &self.recipient, permission_id);
289
290 Permissions::<T>::remove(permission_id);
291 RevocationTracking::<T>::remove(permission_id);
292 let _ = EnforcementTracking::<T>::clear_prefix(permission_id, u32::MAX, None);
293
294 match self.scope {
295 PermissionScope::Emission(emission) => {
296 emission.cleanup(permission_id, &self.last_execution, &self.delegator);
297 }
298 PermissionScope::Curator(curator) => {
299 curator.cleanup(permission_id, &self.last_execution, &self.delegator);
300 }
301 PermissionScope::Namespace(namespace) => {
302 namespace.cleanup(permission_id, &self.last_execution, &self.delegator);
303 }
304 }
305
306 Ok(())
307 }
308
309 pub fn is_updatable(&self) -> bool {
310 let current_block = frame_system::Pallet::<T>::block_number();
311
312 match &self.revocation {
313 RevocationTerms::RevocableByDelegator => true,
314 RevocationTerms::RevocableAfter(block) => ¤t_block > block,
315 _ => false,
316 }
317 }
318}
319
320#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
322#[scale_info(skip_type_params(T))]
323pub enum PermissionScope<T: Config> {
324 Emission(EmissionScope<T>),
325 Curator(CuratorScope<T>),
326 Namespace(NamespaceScope<T>),
327}
328
329#[derive(Encode, Decode, CloneNoBound, TypeInfo, MaxEncodedLen, DebugNoBound)]
331#[scale_info(skip_type_params(T))]
332pub struct NamespaceScope<T: Config> {
333 pub paths: BoundedBTreeMap<
335 Option<PermissionId>,
336 BoundedBTreeSet<NamespacePath, T::MaxNamespacesPerPermission>,
337 T::MaxNamespacesPerPermission,
338 >,
339}
340
341impl<T: Config> NamespaceScope<T> {
342 fn cleanup(
344 &self,
345 permission_id: polkadot_sdk::sp_core::H256,
346 _last_execution: &Option<crate::BlockNumberFor<T>>,
347 _delegator: &T::AccountId,
348 ) {
349 for pid in self.paths.keys().cloned().flatten() {
350 Permissions::<T>::mutate_extant(pid, |parent| {
351 parent.children.remove(&permission_id);
352 });
353 }
354 }
355}
356
357#[derive(
358 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
359)]
360#[scale_info(skip_type_params(T))]
361pub enum PermissionDuration<T: Config> {
362 UntilBlock(BlockNumberFor<T>),
364 Indefinite,
366}
367
368#[derive(
369 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
370)]
371#[scale_info(skip_type_params(T))]
372pub enum RevocationTerms<T: Config> {
373 Irrevocable,
375 RevocableByDelegator,
377 RevocableByArbiters {
379 accounts: BoundedVec<T::AccountId, T::MaxRevokersPerPermission>,
380 required_votes: u32,
381 },
382 RevocableAfter(BlockNumberFor<T>),
384}
385
386impl<T: Config> RevocationTerms<T> {
387 pub fn is_weaker(parent: &Self, child: &Self) -> bool {
389 match (parent, child) {
390 (_, RevocationTerms::RevocableByDelegator) => true,
391
392 (RevocationTerms::RevocableAfter(a), RevocationTerms::RevocableAfter(b)) if a >= b => {
393 true
394 }
395
396 (RevocationTerms::Irrevocable, RevocationTerms::RevocableAfter(_)) => true,
397
398 (RevocationTerms::Irrevocable, RevocationTerms::Irrevocable) => true,
399
400 _ => false,
401 }
402 }
403}
404
405#[derive(
407 Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, TypeInfo, MaxEncodedLen, DebugNoBound,
408)]
409#[scale_info(skip_type_params(T))]
410pub enum EnforcementReferendum {
411 EmissionAccumulation(bool),
413 Execution,
415}
416
417#[derive(
419 Encode,
420 Decode,
421 CloneNoBound,
422 PartialEqNoBound,
423 EqNoBound,
424 TypeInfo,
425 MaxEncodedLen,
426 DebugNoBound,
427 DefaultNoBound,
428)]
429#[scale_info(skip_type_params(T))]
430pub enum EnforcementAuthority<T: Config> {
431 #[default]
433 None,
434 ControlledBy {
436 controllers: BoundedVec<T::AccountId, T::MaxControllersPerPermission>,
437 required_votes: u32,
438 },
439}
440
441pub(crate) fn do_auto_permission_execution<T: Config>(current_block: BlockNumberFor<T>) {
443 if <BlockNumberFor<T> as Into<U256>>::into(current_block)
445 .checked_rem(10.into())
446 .unwrap_or_default()
447 != U256::zero()
448 {
449 return;
450 }
451
452 let permissions: Vec<_> = Permissions::<T>::iter().collect();
453 let mut expired = Vec::with_capacity(permissions.len());
454
455 info!(
456 target: "auto_permission_execution",
457 "executing auto permission execution for {} permissions",
458 permissions.len()
459 );
460
461 for (permission_id, contract) in Permissions::<T>::iter() {
462 #[allow(clippy::single_match)]
463 match &contract.scope {
464 PermissionScope::Emission(emission_scope) => {
465 trace!(target: "auto_permission_execution", "executing auto permission execution for permission {permission_id:?}");
466 if let Err(err) = emission::do_auto_distribution(
467 emission_scope,
468 permission_id,
469 current_block,
470 &contract,
471 ) {
472 error!(
473 "failed to auto distribute emissions for permission {permission_id:?}: {err:?}"
474 );
475 }
476 }
477 _ => (),
478 }
479
480 if contract.is_expired(current_block) {
481 expired.push((permission_id, contract));
482 }
483 }
484
485 for (permission_id, contract) in expired {
486 let delegator = contract.delegator.clone();
487 let recipient = contract.recipient.clone();
488
489 if let Err(err) = contract.cleanup(permission_id) {
490 error!("failed to cleanup expired permission {permission_id:?}: {err:?}");
491 }
492
493 <Pallet<T>>::deposit_event(Event::PermissionExpired {
494 delegator,
495 recipient,
496 permission_id,
497 });
498 }
499}