Coverage Report

Created: 2025-09-12 14:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/torus-substrate/torus-substrate/pallets/permission0/src/lib.rs
Line
Count
Source
1
#![cfg_attr(not(feature = "std"), no_std)]
2
#![allow(clippy::too_many_arguments)]
3
4
pub mod benchmarking;
5
pub mod weights;
6
pub use weights::*;
7
8
pub mod ext;
9
pub mod migrations;
10
pub mod permission;
11
12
pub use pallet::*;
13
14
pub use permission::{
15
    CuratorPermissions, CuratorScope, DistributionControl, EnforcementAuthority,
16
    EnforcementReferendum, PermissionContract, PermissionDuration, PermissionId, PermissionScope,
17
    RevocationTerms, StreamAllocation, StreamScope, generate_permission_id,
18
};
19
20
pub use pallet_permission0_api::{StreamId, generate_root_stream_id};
21
22
use polkadot_sdk::{
23
    frame_support::{
24
        dispatch::DispatchResult,
25
        pallet_prelude::*,
26
        traits::{Currency, Get, ReservableCurrency},
27
    },
28
    frame_system::{self, pallet_prelude::*},
29
    polkadot_sdk_frame as frame,
30
    sp_runtime::{Percent, traits::Saturating},
31
    sp_std::prelude::*,
32
};
33
34
#[frame::pallet]
35
pub mod pallet {
36
    use pallet_torus0_api::NamespacePathInner;
37
    use permission::wallet::WalletStake;
38
    use polkadot_sdk::{frame_support::PalletId, sp_core::TryCollect};
39
40
    use super::*;
41
42
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(7);
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 recipients per permission.
69
        #[pallet::constant]
70
        type MaxRecipientsPerPermission: 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
        /// Maximum number of curator subpermissions a single permission can delegate.
85
        #[pallet::constant]
86
        type MaxCuratorSubpermissionsPerPermission: Get<u32>;
87
88
        /// Maximum number of children a single permission can have.
89
        #[pallet::constant]
90
        type MaxChildrenPerPermission: Get<u32>;
91
92
        /// Max operations a bulk extrinsic can perform per extrinsic call.
93
        #[pallet::constant]
94
        type MaxBulkOperationsPerCall: Get<u32>;
95
    }
96
97
    pub type BalanceOf<T> =
98
        <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
99
100
    pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
101
102
    pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
103
        <T as frame_system::Config>::AccountId,
104
    >>::NegativeImbalance;
105
106
    #[pallet::pallet]
107
    #[pallet::storage_version(STORAGE_VERSION)]
108
    pub struct Pallet<T>(_);
109
110
    /// Active permission contracts - stored by permission ID
111
    #[pallet::storage]
112
    pub type Permissions<T: Config> = StorageMap<_, Identity, PermissionId, PermissionContract<T>>;
113
114
    /// Mapping from (delegator, recipient) to permission IDs
115
    #[pallet::storage]
116
    pub type PermissionsByParticipants<T: Config> = StorageMap<
117
        _,
118
        Identity,
119
        (T::AccountId, T::AccountId),
120
        BoundedBTreeSet<PermissionId, T::MaxRecipientsPerPermission>,
121
        ValueQuery,
122
    >;
123
124
    /// Permissions delegated by a specific account
125
    #[pallet::storage]
126
    pub type PermissionsByDelegator<T: Config> = StorageMap<
127
        _,
128
        Identity,
129
        T::AccountId,
130
        BoundedBTreeSet<PermissionId, T::MaxRecipientsPerPermission>,
131
        ValueQuery,
132
    >;
133
134
    /// Permissions received by a specific account
135
    #[pallet::storage]
136
    pub type PermissionsByRecipient<T: Config> = StorageMap<
137
        _,
138
        Identity,
139
        T::AccountId,
140
        BoundedBTreeSet<PermissionId, T::MaxRecipientsPerPermission>,
141
        ValueQuery,
142
    >;
143
144
    /// Revocations in progress and the voters
145
    #[pallet::storage]
146
    pub type RevocationTracking<T: Config> = StorageMap<
147
        _,
148
        Identity,
149
        PermissionId,
150
        BoundedBTreeSet<T::AccountId, T::MaxRevokersPerPermission>,
151
        ValueQuery,
152
    >;
153
154
    /// Enforcement votes in progress and the voters
155
    #[pallet::storage]
156
    pub type EnforcementTracking<T: Config> = StorageDoubleMap<
157
        _,
158
        Identity,
159
        PermissionId,
160
        Identity,
161
        EnforcementReferendum,
162
        BoundedBTreeSet<T::AccountId, T::MaxControllersPerPermission>,
163
        ValueQuery,
164
    >;
165
166
    /// Accumulated amounts for each stream
167
    #[pallet::storage]
168
    pub type AccumulatedStreamAmounts<T: Config> = StorageNMap<
169
        _,
170
        (
171
            NMapKey<Identity, T::AccountId>,
172
            NMapKey<Identity, StreamId>,
173
            NMapKey<Identity, PermissionId>,
174
        ),
175
        BalanceOf<T>,
176
    >;
177
178
    #[pallet::event]
179
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
180
    pub enum Event<T: Config> {
181
        /// Permission delegated from delegator to recipient with ID
182
        PermissionDelegated {
183
            delegator: T::AccountId,
184
            permission_id: PermissionId,
185
        },
186
        /// Permission revoked with ID
187
        PermissionRevoked {
188
            delegator: T::AccountId,
189
            revoked_by: Option<T::AccountId>,
190
            permission_id: PermissionId,
191
        },
192
        /// Permission expired with ID
193
        PermissionExpired {
194
            delegator: T::AccountId,
195
            permission_id: PermissionId,
196
        },
197
        /// Permission accumulation state toggled
198
        PermissionAccumulationToggled {
199
            permission_id: PermissionId,
200
            accumulating: bool,
201
            toggled_by: Option<T::AccountId>,
202
        },
203
        /// Permission was executed by enforcement authority
204
        PermissionEnforcementExecuted {
205
            permission_id: PermissionId,
206
            executed_by: Option<T::AccountId>,
207
        },
208
        /// Vote for enforcement action
209
        EnforcementVoteCast {
210
            permission_id: PermissionId,
211
            voter: T::AccountId,
212
            referendum: EnforcementReferendum,
213
        },
214
        /// Enforcement authority set for permission
215
        EnforcementAuthoritySet {
216
            permission_id: PermissionId,
217
            controllers_count: u32,
218
            required_votes: u32,
219
        },
220
        /// An stream distribution happened
221
        StreamDistribution {
222
            permission_id: PermissionId,
223
            stream_id: Option<StreamId>,
224
            recipient: T::AccountId,
225
            amount: BalanceOf<T>,
226
            reason: permission::stream::DistributionReason,
227
        },
228
        /// Accumulated emission for stream
229
        AccumulatedEmission {
230
            permission_id: PermissionId,
231
            stream_id: StreamId,
232
            amount: BalanceOf<T>,
233
        },
234
    }
235
236
    #[pallet::error]
237
    pub enum Error<T> {
238
        /// The agent is not registered
239
        NotRegisteredAgent,
240
        /// Permissions can only be created through extrinsics
241
        PermissionCreationOutsideExtrinsic,
242
        /// A permission with the same exact parameters was
243
        /// already created in the current block
244
        DuplicatePermissionInBlock,
245
        /// Permission not found
246
        PermissionNotFound,
247
        /// Self-permission is not allowed
248
        SelfPermissionNotAllowed,
249
        /// Invalid percentage (out of range)
250
        InvalidPercentage,
251
        /// Invalid stream weight set to recipient
252
        InvalidRecipientWeight,
253
        /// No recipients specified
254
        NoRecipientsSpecified,
255
        /// Invalid threshold
256
        InvalidThreshold,
257
        /// No accumulated amount
258
        NoAccumulatedAmount,
259
        /// Not authorized to revoke
260
        NotAuthorizedToRevoke,
261
        /// Total allocation exceeded 100%
262
        TotalAllocationExceeded,
263
        /// Not the recipient of the permission
264
        NotPermissionRecipient,
265
        /// Not the delegator of the permission
266
        NotPermissionDelegator,
267
        /// Too many streams
268
        TooManyStreams,
269
        /// Too many recipients
270
        TooManyRecipients,
271
        /// Too many revokers
272
        TooManyRevokers,
273
        /// Failed to insert into storage
274
        StorageError,
275
        /// Invalid amount
276
        InvalidAmount,
277
        /// Insufficient balance for operation
278
        InsufficientBalance,
279
        /// Invalid distribution interval
280
        InvalidInterval,
281
        /// Parent permission not found
282
        ParentPermissionNotFound,
283
        /// Invalid distribution method
284
        InvalidDistributionMethod,
285
        /// Revokers and required voters must be at least one, and required voters must
286
        /// be less than the number of revokers
287
        InvalidNumberOfRevokers,
288
        /// Fixed amount streams can only be triggered once, manually or at a block
289
        FixedAmountCanOnlyBeTriggeredOnce,
290
        /// Unsupported permission type
291
        UnsupportedPermissionType,
292
        /// Not authorized to toggle permission state
293
        NotAuthorizedToToggle,
294
        /// Too many controllers
295
        TooManyControllers,
296
        /// Invalid number of controllers or required votes
297
        InvalidNumberOfControllers,
298
        /// Permission is a duplicate, revoke the previous one
299
        DuplicatePermission,
300
        /// Permission is in cooldown, wait a bit.
301
        PermissionInCooldown,
302
        /// Curator flags provided are invalid.
303
        InvalidCuratorPermissions,
304
        /// Tried delegating unknown namespace.
305
        NamespaceDoesNotExist,
306
        /// Namespace path provided contains illegal character or is malformatted.
307
        NamespacePathIsInvalid,
308
        /// Exceeded amount of total namespaces allowed in a single permission.
309
        TooManyNamespaces,
310
        /// Not authorized to edit a permission.
311
        NotAuthorizedToEdit,
312
        /// Permission is not editable
313
        NotEditable,
314
        /// Namespace creation was disabled by a curator.
315
        NamespaceCreationDisabled,
316
        /// Deriving a permission from multiple parents is still forbidden.
317
        MultiParentForbidden,
318
        /// Not enough instances available to delegate/execute a permission.
319
        /// This might mean the execution requires more than the available instances
320
        /// or that all instances are locked behind redelegations.
321
        NotEnoughInstances,
322
        /// Too many children for a permission.
323
        TooManyChildren,
324
        /// Stream managers must have up to two entries and always contain the delegator,
325
        InvalidStreamManagers,
326
        /// Revocation terms are too strong for a permission re-delegation.
327
        RevocationTermsTooStrong,
328
        /// Too many curator permissions being delegated in a single permission.
329
        TooManyCuratorPermissions,
330
        /// Namespace delegation depth exceeded the maximum allowed limit.
331
        DelegationDepthExceeded,
332
    }
333
334
    #[pallet::hooks]
335
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
336
477k
        fn on_finalize(current_block: BlockNumberFor<T>) {
337
477k
            permission::do_auto_permission_execution::<T>(current_block);
338
477k
        }
339
    }
340
341
    #[pallet::call]
342
    impl<T: Config> Pallet<T> {
343
        /// Delegate a permission for stream delegation
344
        #[pallet::call_index(0)]
345
        #[pallet::weight(T::WeightInfo::delegate_stream_permission())]
346
        pub fn delegate_stream_permission(
347
            origin: OriginFor<T>,
348
            recipients: BoundedBTreeMap<T::AccountId, u16, T::MaxRecipientsPerPermission>,
349
            allocation: StreamAllocation<T>,
350
            distribution: DistributionControl<T>,
351
            duration: PermissionDuration<T>,
352
            revocation: RevocationTerms<T>,
353
            enforcement: EnforcementAuthority<T>,
354
            recipient_manager: Option<T::AccountId>,
355
            weight_setter: Option<T::AccountId>,
356
16
        ) -> DispatchResult {
357
16
            let delegator = ensure_signed(origin)
?0
;
358
359
16
            ext::stream_impl::delegate_stream_permission_impl::<T>(
360
16
                delegator,
361
16
                recipients,
362
16
                allocation,
363
16
                distribution,
364
16
                duration,
365
16
                revocation,
366
16
                enforcement,
367
16
                recipient_manager,
368
16
                weight_setter,
369
4
            )?;
370
371
12
            Ok(())
372
        }
373
374
        /// Revoke a permission. The caller must meet revocation constraints or be a root key.
375
        #[pallet::call_index(1)]
376
        #[pallet::weight(T::WeightInfo::revoke_permission())]
377
        pub fn revoke_permission(
378
            origin: OriginFor<T>,
379
            permission_id: PermissionId,
380
40
        ) -> DispatchResult {
381
40
            ext::revoke_permission_impl::<T>(origin, &permission_id)
382
        }
383
384
        /// Execute a manual distribution based on permission
385
        #[pallet::call_index(2)]
386
        #[pallet::weight(T::WeightInfo::execute_permission())]
387
        pub fn execute_permission(
388
            origin: OriginFor<T>,
389
            permission_id: PermissionId,
390
16
        ) -> DispatchResult {
391
16
            ext::execute_permission_impl::<T>(origin, &permission_id)
392
        }
393
394
        /// Toggle a permission's accumulation state (enabled/disabled)
395
        /// The caller must be authorized as a controller or be the root key
396
        #[pallet::call_index(3)]
397
        #[pallet::weight(T::WeightInfo::toggle_permission_accumulation())]
398
        pub fn toggle_permission_accumulation(
399
            origin: OriginFor<T>,
400
            permission_id: PermissionId,
401
            accumulating: bool,
402
16
        ) -> DispatchResult {
403
16
            ext::stream_impl::toggle_permission_accumulation_impl::<T>(
404
16
                origin,
405
16
                permission_id,
406
16
                accumulating,
407
            )
408
        }
409
410
        /// Execute a permission through enforcement authority
411
        /// The caller must be authorized as a controller or be the root key
412
        #[pallet::call_index(4)]
413
        #[pallet::weight(T::WeightInfo::enforcement_execute_permission())]
414
        pub fn enforcement_execute_permission(
415
            origin: OriginFor<T>,
416
            permission_id: PermissionId,
417
12
        ) -> DispatchResult {
418
12
            ext::enforcement_execute_permission_impl::<T>(origin, permission_id)
419
        }
420
421
        /// Set enforcement authority for a permission
422
        /// Only the delegator or root can set enforcement authority
423
        #[pallet::call_index(5)]
424
        #[pallet::weight(T::WeightInfo::set_enforcement_authority())]
425
        pub fn set_enforcement_authority(
426
            origin: OriginFor<T>,
427
            permission_id: PermissionId,
428
            enforcement: EnforcementAuthority<T>,
429
6
        ) -> DispatchResult {
430
6
            let who = ensure_signed_or_root(origin)
?0
;
431
432
6
            let contract =
433
6
                Permissions::<T>::get(permission_id).ok_or(Error::<T>::PermissionNotFound)
?0
;
434
435
6
            if let Some(who) = &who {
436
6
                ensure!(
437
6
                    who == &contract.delegator,
438
4
                    Error::<T>::NotPermissionDelegator
439
                );
440
0
            }
441
442
2
            contract.update_enforcement(permission_id, enforcement)
443
        }
444
445
        /// Delegate a permission for curator delegation
446
        #[pallet::call_index(6)]
447
        #[pallet::weight(T::WeightInfo::delegate_curator_permission())]
448
        pub fn delegate_curator_permission(
449
            origin: OriginFor<T>,
450
            recipient: T::AccountId,
451
            flags: BoundedBTreeMap<
452
                Option<PermissionId>,
453
                u32,
454
                T::MaxCuratorSubpermissionsPerPermission,
455
            >,
456
            cooldown: Option<BlockNumberFor<T>>,
457
            duration: PermissionDuration<T>,
458
            revocation: RevocationTerms<T>,
459
            instances: u32,
460
38
        ) -> DispatchResult {
461
38
            let flags = flags
462
38
                .into_iter()
463
38
                .map(|(pid, flags)| (pid, CuratorPermissions::from_bits_truncate(flags)))
464
38
                .try_collect()
?0
;
465
466
38
            ext::curator_impl::delegate_curator_permission_impl::<T>(
467
38
                origin, recipient, flags, cooldown, duration, revocation, instances,
468
6
            )?;
469
470
32
            Ok(())
471
        }
472
473
        /// Delegate a permission over namespaces
474
        #[pallet::call_index(7)]
475
        #[pallet::weight(T::WeightInfo::delegate_namespace_permission())]
476
        pub fn delegate_namespace_permission(
477
            origin: OriginFor<T>,
478
            recipient: T::AccountId,
479
            paths: BoundedBTreeMap<
480
                Option<PermissionId>,
481
                BoundedBTreeSet<NamespacePathInner, T::MaxNamespacesPerPermission>,
482
                T::MaxNamespacesPerPermission,
483
            >,
484
            duration: PermissionDuration<T>,
485
            revocation: RevocationTerms<T>,
486
            instances: u32,
487
130
        ) -> DispatchResult {
488
130
            ext::namespace_impl::delegate_namespace_permission_impl::<T>(
489
130
                origin, recipient, paths, duration, revocation, instances,
490
32
            )?;
491
492
98
            Ok(())
493
        }
494
495
        /// Delegate a permission over namespaces
496
        #[pallet::call_index(11)]
497
        #[pallet::weight(T::WeightInfo::delegate_namespace_permission())]
498
        pub fn delegate_wallet_stake_permission(
499
            origin: OriginFor<T>,
500
            recipient: T::AccountId,
501
            stake_details: WalletStake,
502
            duration: PermissionDuration<T>,
503
            revocation: RevocationTerms<T>,
504
32
        ) -> DispatchResult {
505
32
            ext::wallet_impl::delegate_wallet_stake_permission::<T>(
506
32
                origin,
507
32
                recipient,
508
32
                stake_details,
509
32
                duration,
510
32
                revocation,
511
6
            )?;
512
513
26
            Ok(())
514
        }
515
516
        /// Delegate a permission over namespaces to multiple recipients.
517
        /// Note: this extrinsic creates _multiple_ permissions with the same
518
        /// properties.
519
        #[pallet::call_index(10)]
520
        #[pallet::weight({
521
            T::WeightInfo::delegate_namespace_permission()
522
                .saturating_mul(recipients.len() as u64)
523
        })]
524
        pub fn bulk_delegate_namespace_permission(
525
            origin: OriginFor<T>,
526
            recipients: BoundedBTreeSet<T::AccountId, T::MaxBulkOperationsPerCall>,
527
            paths: BoundedBTreeMap<
528
                Option<PermissionId>,
529
                BoundedBTreeSet<NamespacePathInner, T::MaxNamespacesPerPermission>,
530
                T::MaxNamespacesPerPermission,
531
            >,
532
            duration: PermissionDuration<T>,
533
            revocation: RevocationTerms<T>,
534
            instances: u32,
535
4
        ) -> DispatchResult {
536
10
            for 
recipient8
in recipients {
537
8
                ext::namespace_impl::delegate_namespace_permission_impl::<T>(
538
8
                    origin.clone(),
539
8
                    recipient,
540
8
                    paths.clone(),
541
8
                    duration.clone(),
542
8
                    revocation.clone(),
543
8
                    instances,
544
2
                )?;
545
            }
546
547
2
            Ok(())
548
        }
549
550
        /// Allows Delegator/Recipient to edit stream permission
551
        #[pallet::call_index(8)]
552
        #[pallet::weight(T::WeightInfo::delegate_curator_permission())]
553
        pub fn update_stream_permission(
554
            origin: OriginFor<T>,
555
            permission_id: PermissionId,
556
            new_recipients: Option<
557
                BoundedBTreeMap<T::AccountId, u16, T::MaxRecipientsPerPermission>,
558
            >,
559
            new_streams: Option<BoundedBTreeMap<StreamId, Percent, T::MaxStreamsPerPermission>>,
560
            new_distribution_control: Option<DistributionControl<T>>,
561
            new_recipient_manager: Option<Option<T::AccountId>>,
562
            new_weight_setter: Option<Option<T::AccountId>>,
563
44
        ) -> DispatchResult {
564
44
            ext::stream_impl::update_stream_permission(
565
44
                origin,
566
44
                permission_id,
567
44
                new_recipients,
568
44
                new_streams,
569
44
                new_distribution_control,
570
44
                new_recipient_manager,
571
44
                new_weight_setter,
572
24
            )?;
573
574
20
            Ok(())
575
        }
576
577
        /// Allows a delegator to update the number of instances of a permission
578
        #[pallet::call_index(9)]
579
        #[pallet::weight(T::WeightInfo::update_namespace_permission())]
580
        pub fn update_namespace_permission(
581
            origin: OriginFor<T>,
582
            permission_id: PermissionId,
583
            max_instances: u32,
584
22
        ) -> DispatchResult {
585
22
            ext::namespace_impl::update_namespace_permission::<T>(
586
22
                origin,
587
22
                permission_id,
588
22
                max_instances,
589
12
            )?;
590
591
10
            Ok(())
592
        }
593
594
        #[pallet::call_index(12)]
595
        #[pallet::weight(T::WeightInfo::update_namespace_permission())]
596
        pub fn execute_wallet_stake_permission(
597
            caller: OriginFor<T>,
598
            permission_id: PermissionId,
599
            op: ext::wallet_impl::WalletStakeOperation<T>,
600
16
        ) -> DispatchResult {
601
16
            ext::wallet_impl::execute_wallet_stake_permission(caller, permission_id, op)
602
        }
603
    }
604
}
605
606
/// Get total allocated percentage for a delegator
607
50
fn get_total_allocated_percentage<T: Config>(
608
50
    delegator: &T::AccountId,
609
50
    stream: &StreamId,
610
50
) -> Percent {
611
50
    AccumulatedStreamAmounts::<T>::iter_key_prefix((delegator, stream))
612
50
        .filter_map(Permissions::<T>::get)
613
50
        .map(|contract| match 
contract.scope2
{
614
            PermissionScope::Stream(StreamScope {
615
2
                allocation: StreamAllocation::Streams(streams),
616
                ..
617
2
            }) => streams.get(stream).copied().unwrap_or_default(),
618
0
            _ => Percent::zero(),
619
2
        })
620
50
        .fold(Percent::zero(), |acc, percentage| 
{2
621
2
            acc.saturating_add(percentage)
622
2
        })
623
50
}