|
| 1 | +# Permission0: Recursive Emission Delegation |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Permission0 implements a permission-based delegation system for emission distribution in the Torus network. This pallet enables agents to delegate a portion of their emissions to other agents through a structured permission contract framework. Agents can delegate either a percentage of incoming emissions or a fixed amount of tokens. |
| 6 | + |
| 7 | +The concept draws inspiration from multi-level competency networks that self-organize for efficient resource allocation. When an agent delegates emissions to another agent, they create economic pathways for token flow, effectively allowing the network to discover and reinforce valuable contributions across different domains. |
| 8 | + |
| 9 | +## Permission Contracts |
| 10 | + |
| 11 | +A permission contract forms the foundation of the delegation relationship. Each contract defines the relationship between a grantor (who delegates emissions) and a grantee (who receives the delegation authority). The contract specifies allocation parameters, distribution controls, duration, and revocation terms. |
| 12 | + |
| 13 | +Permission contracts are identified by a unique `PermissionId` generated deterministically from the grantor, grantee, scope, and creation block. This ensures contracts can be consistently referenced and avoids collision issues when multiple contracts exist between the same parties. |
| 14 | + |
| 15 | +```rust |
| 16 | +pub struct PermissionContract<T: Config> { |
| 17 | + pub grantor: T::AccountId, |
| 18 | + pub grantee: T::AccountId, |
| 19 | + pub scope: PermissionScope<T>, |
| 20 | + pub duration: PermissionDuration<T>, |
| 21 | + pub revocation: RevocationTerms<T>, |
| 22 | + pub last_execution: Option<BlockNumberFor<T>>, |
| 23 | + pub execution_count: u32, |
| 24 | + pub parent: Option<PermissionId>, |
| 25 | + pub created_at: BlockNumberFor<T>, |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +The `parent` field enables recursive delegation chains, where a permission can be derived from a higher-level permission. This creates hierarchical delegation trees where permissions can cascade through multiple levels of delegation. |
| 30 | + |
| 31 | +## Emission Allocation |
| 32 | + |
| 33 | +The permission contract's scope defines how emissions are allocated. The current implementation supports only emission-type permissions through the `EmissionScope` structure: |
| 34 | + |
| 35 | +```rust |
| 36 | +pub struct EmissionScope<T: Config> { |
| 37 | + pub allocation: EmissionAllocation<T>, |
| 38 | + pub distribution: DistributionControl<T>, |
| 39 | + pub targets: BoundedBTreeMap<T::AccountId, u16, T::MaxTargetsPerPermission>, |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +The `allocation` field determines how tokens are allocated: |
| 44 | + |
| 45 | +```rust |
| 46 | +pub enum EmissionAllocation<T: Config> { |
| 47 | + Streams(BoundedBTreeMap<StreamId, Percent, T::MaxTargetsPerPermission>), |
| 48 | + FixedAmount(BalanceOf<T>), |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +With `Streams` allocation, portions of the grantor's incoming emissions from specific streams are diverted according to the percentages specified (0-100%). Each stream ID represents a distinct emission source, allowing for fine-grained control over different emission types. For `FixedAmount` allocation, a specific number of tokens is reserved from the grantor's account at contract creation. |
| 53 | + |
| 54 | +The `targets` field identifies recipients with associated weights, determining how tokens are distributed among multiple targets. For example, with targets A (weight 1) and B (weight 2), target B receives twice the tokens of target A. |
| 55 | + |
| 56 | +## Distribution Control |
| 57 | + |
| 58 | +The `distribution` field determines how and when accumulated emissions are distributed: |
| 59 | + |
| 60 | +```rust |
| 61 | +pub enum DistributionControl<T: Config> { |
| 62 | + Manual, |
| 63 | + Automatic(BalanceOf<T>), |
| 64 | + AtBlock(BlockNumberFor<T>), |
| 65 | + Interval(BlockNumberFor<T>), |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +The distribution control mechanism provides flexibility in how emissions flow through the network: |
| 70 | + |
| 71 | +- `Manual`: The grantee must explicitly call `execute_permission` to trigger distribution |
| 72 | +- `Automatic`: Distribution occurs when accumulated amount reaches the specified threshold |
| 73 | +- `AtBlock`: Distribution triggers at a specific block number |
| 74 | +- `Interval`: Distribution occurs periodically at the specified block interval |
| 75 | + |
| 76 | +## Permission Duration and Revocation |
| 77 | + |
| 78 | +Permissions can have different durations: |
| 79 | + |
| 80 | +```rust |
| 81 | +pub enum PermissionDuration<T: Config> { |
| 82 | + Blocks(BlockNumberFor<T>), |
| 83 | + UntilBlock(BlockNumberFor<T>), |
| 84 | + Indefinite, |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +This allows for temporary delegations (`Blocks`, `UntilBlock`) or permanent ones (`Indefinite`). Expired permissions are automatically removed during the regular block processing. |
| 89 | + |
| 90 | +Revocation terms define how a permission can be revoked before its normal expiration: |
| 91 | + |
| 92 | +```rust |
| 93 | +pub enum RevocationTerms<T: Config> { |
| 94 | + Irrevocable, |
| 95 | + RevocableByGrantor, |
| 96 | + RevocableByArbiters(BoundedVec<T::AccountId, T::MaxArbitersPerPermission>), |
| 97 | + AfterBlock(BlockNumberFor<T>), |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +These terms create different security guarantees for the grantee, ranging from complete assurance (`Irrevocable`) to flexible arrangements (`RevocableByGrantor`). The grantee can ALWAYS revoke a permission as it is the one being benefitted. |
| 102 | + |
| 103 | +## Permission creation |
| 104 | + |
| 105 | +A new permission is created through the `grant_permission` extrinsic. |
| 106 | + |
| 107 | +```mermaid |
| 108 | +flowchart TD |
| 109 | + A["Agent/Grantor"] -- grant_permission --> B["Create Permission"] |
| 110 | + B -- validate --> C["Check agent registration"] --> E["Check targets"] --> P{"Check Allocation Type"} |
| 111 | + P -- Fixed Amount --> K["Reserve Tokens"] |
| 112 | + P -- Streams --> D["Check stream percentages <= 100%"] |
| 113 | + B -- Generate ID --> F["Permission Contract"] |
| 114 | + F -- store --> G["Permissions Storage"] |
| 115 | + F -- index --> J["PermissionsByParticipants"] |
| 116 | + F -- index --> I["PermissionsByGrantee"] |
| 117 | + F -- index --> H["PermissionsByGrantor"] |
| 118 | + B -- emit event --> M["PermissionGranted Event"] |
| 119 | + D -- accumulate --> L["AccumulatedStreamAmounts"] |
| 120 | +``` |
| 121 | + |
| 122 | +## Emission Accumulation and Distribution Process |
| 123 | + |
| 124 | +```mermaid |
| 125 | +graph TD |
| 126 | + Root[Root stake emissions / network rewards] -- root stream ID -->A |
| 127 | + Delegation[Delegated emissions] -- parent stream ID -->A |
| 128 | + A[Agent Receives Emissions] -->|do_accumulate_emissions| B[Check Active Permissions] |
| 129 | + B -->|For each permission| C{Permission Type?} |
| 130 | + C -->|Streams| D[Check streams map for matching stream] |
| 131 | + C -->|Fixed Amount| E[Already reserved at creation] |
| 132 | + D -->|Extract from imbalance| F[Store in AccumulatedStreamAmounts] |
| 133 | + F -->|Wait for trigger| G{Distribution Trigger?} |
| 134 | + G -->|Manual| H[Execute Permission called] |
| 135 | + G -->|Automatic| I[Threshold reached] |
| 136 | + G -->|AtBlock| J[Specific block reached] |
| 137 | + G -->|Interval| K[Interval blocks passed] |
| 138 | + H --> L[do_distribute_emission] |
| 139 | + I --> L |
| 140 | + J --> L |
| 141 | + K --> L |
| 142 | + L -->|Retrieve accumulated amounts for each stream| M[Calculate target amounts] |
| 143 | + M -->|for each target| N[Recursive Accumulation] |
| 144 | + N --> O[Update last execution] |
| 145 | + O --> P[Emit events] |
| 146 | +``` |
| 147 | + |
| 148 | +When an agent receives emissions, the pallet intercepts a portion based on active permission contracts through the `do_accumulate_emissions` function. The accumulated amounts are stored in the `AccumulatedStreamAmounts` storage map until distribution conditions are met. |
| 149 | + |
| 150 | +The function is designed to be highly efficient, with a storage structure optimized for quick lookup of all permissions associated with a specific (agent, stream) pair: |
| 151 | + |
| 152 | +```rust |
| 153 | +fn do_accumulate_emissions<T: Config>( |
| 154 | + agent: &T::AccountId, |
| 155 | + stream: &StreamId, |
| 156 | + imbalance: &mut <T::Currency as Currency<T::AccountId>>::NegativeImbalance, |
| 157 | +) { |
| 158 | + // Get all permissions for this agent and stream |
| 159 | + let streams = AccumulatedStreamAmounts::<T>::iter_prefix((agent, stream)); |
| 160 | + |
| 161 | + // Process each permission |
| 162 | + for (permission_id, balance) in streams { |
| 163 | + // Calculate and accumulate based on stream percentage |
| 164 | + // ... |
| 165 | + } |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +During distribution (`do_distribute_emission`), the accumulated amount for each stream is divided among targets according to their weights. The distribution uses the `Currency` trait to handle token movement between accounts. |
| 170 | + |
| 171 | +Importantly, the recursive accumulation does not happen in the same block to prevent unbounded recursion and excessive computation. Instead, when a target receives their portion, it becomes a regular imbalance that will trigger the standard accumulation process in the next applicable block. |
| 172 | + |
| 173 | +## Storage Design |
| 174 | + |
| 175 | +The Permission0 pallet uses several storage maps to track permissions and accumulated amounts: |
| 176 | + |
| 177 | +```rust |
| 178 | +pub type Permissions<T: Config> = StorageMap<_, Identity, PermissionId, PermissionContract<T>>; |
| 179 | +pub type PermissionsByParticipants<T: Config> = StorageMap<_, Identity, (T::AccountId, T::AccountId), BoundedVec<PermissionId, T::MaxTargetsPerPermission>>; |
| 180 | +pub type PermissionsByGrantor<T: Config> = StorageMap<_, Identity, T::AccountId, BoundedVec<PermissionId, T::MaxTargetsPerPermission>>; |
| 181 | +pub type PermissionsByGrantee<T: Config> = StorageMap<_, Identity, T::AccountId, BoundedVec<PermissionId, T::MaxTargetsPerPermission>>; |
| 182 | +pub type AccumulatedStreamAmounts<T: Config> = StorageNMap< |
| 183 | + _, |
| 184 | + ( |
| 185 | + NMapKey<Identity, T::AccountId>, |
| 186 | + NMapKey<Identity, StreamId>, |
| 187 | + NMapKey<Identity, PermissionId>, |
| 188 | + ), |
| 189 | + BalanceOf<T>, |
| 190 | +>; |
| 191 | +``` |
| 192 | + |
| 193 | +This storage design allows efficient lookups for: |
| 194 | + |
| 195 | +- Finding all permissions between specific parties |
| 196 | +- Retrieving all permissions granted by an account |
| 197 | +- Retrieving all permissions received by an account |
| 198 | +- Tracking accumulated tokens for each permission by stream |
| 199 | + |
| 200 | +The `AccumulatedStreamAmounts` uses a StorageNMap with a triple key of (AccountId, StreamId, PermissionId). This structure was chosen for performance reasons, as the `do_accumulate_emissions` function is called frequently and needs to quickly find all permissions associated with a specific account and stream combination. The order of the keys prioritizes searching by account and stream first, which is the most common access pattern. |
| 201 | + |
| 202 | +## Stream-based Emission Model |
| 203 | + |
| 204 | +The permission system uses a stream-based approach for tracking different sources of emissions: |
| 205 | + |
| 206 | +```rust |
| 207 | +pub type StreamId = H256; |
| 208 | +``` |
| 209 | + |
| 210 | +Each stream is identified by a unique `StreamId`, which represents a specific source of emissions. The system distinguishes between: |
| 211 | + |
| 212 | +1. **Root Streams**: Generated for each agent using a deterministic function. These are the primary emission streams that agents receive directly. |
| 213 | + |
| 214 | +```rust |
| 215 | +pub fn generate_root_stream_id<AccountId: Encode>(agent_id: &AccountId) -> StreamId { |
| 216 | + let mut data = ROOT_STREAM_PREFIX.to_vec(); |
| 217 | + data.extend(agent_id.encode()); |
| 218 | + blake2_256(&data).into() |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +2. **Derived Streams**: Generated when emissions flow through permissions, allowing for tracking of emission pathways through the network. |
| 223 | + |
| 224 | +When streams are redelegated through the permission system, their IDs are preserved rather than generating new ones. This crucial design choice allows for easy tracking of the lineage and flow of emissions throughout the network, making it possible to trace the complete path of tokens from their source to final recipients across multiple delegation hops. |
| 225 | + |
| 226 | +This stream-based model allows for much more granular control over emission delegation, enabling agents to specify different delegation percentages for different types of emission streams they receive. |
| 227 | + |
| 228 | +## Integration with Emission Distribution |
| 229 | + |
| 230 | +Permission0 integrates with the `Emission0` pallet by intercepting the emission distribution process. When the linear rewards mechanism distributes tokens, the `do_accumulate_emissions` function is called to divert portions according to active permissions. |
| 231 | + |
| 232 | +This integration preserves the existing emission calculation logic while adding the delegation layer on top. The approach ensures delegations only affect how already-calculated emissions are distributed, rather than altering the emission calculations themselves. |
| 233 | + |
| 234 | +## Automatic Processing |
| 235 | + |
| 236 | +The pallet implements the `on_finalize` hook to handle periodic tasks: |
| 237 | + |
| 238 | +```rust |
| 239 | +fn on_finalize(block_number: BlockNumberFor<T>) { |
| 240 | + // Process automatic distributions every 10 blocks |
| 241 | + if (block_number % 10).is_zero() { |
| 242 | + Self::do_auto_permission_execution(block_number); |
| 243 | + } |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +The `do_auto_permission_execution` function processes all permissions to: |
| 248 | + |
| 249 | +1. Check for and execute automatic distributions |
| 250 | +2. Check for and execute interval-based distributions |
| 251 | +3. Check for and execute at-block distributions |
| 252 | +4. Remove expired permissions |
| 253 | + |
| 254 | +This mechanism ensures automatic processes happen regularly without requiring manual intervention. |
| 255 | + |
| 256 | +## Configuration |
| 257 | + |
| 258 | +The pallet is customizable through several configuration parameters: |
| 259 | + |
| 260 | +```rust |
| 261 | +type MaxTargetsPerPermission: Get<u32>; |
| 262 | +type MaxArbitersPerPermission: Get<u32>; |
| 263 | +``` |
| 264 | + |
| 265 | +These parameters control storage limits and processing intervals, allowing the network to balance functionality against resource usage. |
| 266 | + |
| 267 | +## Historical Context |
| 268 | + |
| 269 | +Permission0 emerged from the need to create more sophisticated economic relationships in the Torus network. The initial concept was described as: |
| 270 | + |
| 271 | +_"A distributed multi-level search process for new competencies, methods and organizational forms that can serve the respective higher level competitively, such that it can serve the level above better too, cascading upwards each delegation tree."_ |
| 272 | + |
| 273 | +The implementation allows economic signals (in the form of emissions) to flow through the network according to agent decisions, creating a dynamic feedback mechanism that rewards valuable contributions at all levels. |
| 274 | + |
| 275 | +A crucial insight was that rational agents will redelegate emissions when doing so increases their own emissions by more than they delegate. This positive-sum logic creates natural incentives for delegation trees to form and adapt over time. |
| 276 | + |
| 277 | +## Practical Applications |
| 278 | + |
| 279 | +The permission-based delegation system enables several practical scenarios: |
| 280 | + |
| 281 | +1. Validators can delegate a percentage of emissions to miners who provide specialized services |
| 282 | +2. Module operators can share emissions with agents who contribute to their module |
| 283 | +3. Teams can create token distribution trees that align with organizational structures |
| 284 | +4. Specialized agents can emerge to discover and connect valuable contributors |
| 285 | + |
| 286 | +The flexible distribution controls accommodate different time horizons and trust relationships, from immediate rewards to long-term alignments. |
| 287 | + |
| 288 | +Crucially, the recursive nature of permissions means delegation trees can extend to arbitrary depth, allowing for complex specialization hierarchies to emerge organically. |
| 289 | + |
| 290 | +## Safety Mechanisms |
| 291 | + |
| 292 | +By using the `Currency` trait, we are able to use reserves for fixed amount emissions, and negative imbalances to avoid emitting duplicate tokens. This is a core part of safety. |
| 293 | + |
| 294 | +Additionally, recursive accumulation is designed to prevent infinite loops by deferring lower-level accumulation to subsequent blocks. |
| 295 | + |
| 296 | +## Future Development |
| 297 | + |
| 298 | +While the current implementation focuses on emission delegation, the permission framework could extend to other domains like governance rights, data access, or identity verification. The modular design allows new permission scopes to be added without disrupting existing functionality. |
0 commit comments