Skip to content

Commit e24e7e6

Browse files
feat(permission0): initial implementation (#87)
This is the first version for the permission0 pallet. Effectively implements emission recursion. --------- Co-authored-by: devwckd <[email protected]>
1 parent 88367b8 commit e24e7e6

File tree

23 files changed

+2472
-6
lines changed

23 files changed

+2472
-6
lines changed

Cargo.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pallet-emission0 = { path = "./pallets/emission0", default-features = false }
2424
pallet-emission0-api = { path = "./pallets/emission0/api", default-features = false }
2525
pallet-torus0 = { path = "./pallets/torus0", default-features = false }
2626
pallet-torus0-api = { path = "./pallets/torus0/api", default-features = false }
27+
pallet-permission0 = { path = "./pallets/permission0", default-features = false }
28+
pallet-permission0-api = { path = "./pallets/permission0/api", default-features = false }
2729
test-utils.path = "./test-utils"
2830

2931
# Substrate

docs/permission0.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
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.

pallets/emission0/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ std = [
1212
"codec/std",
1313
"polkadot-sdk/std",
1414
"scale-info/std",
15-
"pallet-torus0-api/std",
1615
"num-traits/std",
16+
"pallet-torus0-api/std",
17+
"pallet-governance-api/std",
18+
"pallet-permission0-api/std",
1719
]
1820
runtime-benchmarks = [
1921
"polkadot-sdk/runtime-benchmarks",
2022
"pallet-torus0-api/runtime-benchmarks",
23+
"pallet-governance-api/runtime-benchmarks",
24+
"pallet-permission0-api/runtime-benchmarks",
2125
]
2226
try-runtime = ["polkadot-sdk/try-runtime"]
2327

@@ -32,7 +36,7 @@ num-traits.workspace = true
3236
pallet-torus0-api.workspace = true
3337
pallet-emission0-api.workspace = true
3438
pallet-governance-api.workspace = true
35-
39+
pallet-permission0-api.workspace = true
3640

3741
[dev-dependencies]
3842
test-utils.workspace = true

0 commit comments

Comments
 (0)