Skip to content

Commit 42bcc81

Browse files
sync: merge spec 24 and 25 to main (#139)
This is a sync PR. Take a look at the changes at `docs/changes/spec-{24, 25}.md`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Stream-based permissions with multi-recipient support, plus recipient managers and weight setters. - Namespace enhancements: update existing permissions and bulk-delegate to multiple recipients. - Changes - Events simplified (recipient removed); storage/indexing updated for multi-recipient model. - Rebenchmarked weights across permission, governance, emission, and torus pallets. - Migration - Automatic runtime migration to v7; runtime version bumped to 25. - Documentation - Added spec-24 and spec-25 detailing streams model and per-scope instance management. - Chores/Tests - Switched to cargo-nextest and added pre-push test hook; examples and tests updated to new APIs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 0acc792 + 8ccfed1 commit 42bcc81

39 files changed

+4051
-1757
lines changed

.helix/languages.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[language-server.rust-analyzer.config]
2+
cargo.extraEnv = { SKIP_WASM_BUILD = "true" }
3+
cargo.features = ["runtime-benchmarks"]
4+

client/examples/calling_extrinsic.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
use subxt_signer::ecdsa::dev::{alice, bob};
1+
use subxt_signer::ecdsa::dev::alice;
22
use torus_client::client::TorusClient;
33

44
#[tokio::main]
55
pub async fn main() {
6-
let agent_key = alice().public_key().to_account_id(); //change it to your agent's account id
6+
let signer = alice(); // change it to your signer
77
let name = "alice agent".as_bytes().to_vec();
88
let url = "url".as_bytes().to_vec();
99
let metadata = "metadata".as_bytes().to_vec();
10-
let signer = bob(); // change it to your signer
1110

1211
let client = TorusClient::for_mainnet().await.unwrap();
1312

1413
if let Err(err) = client
1514
.torus0()
1615
.calls()
17-
.register_agent_wait(agent_key, name, url, metadata, signer)
16+
.register_agent_wait(name, url, metadata, signer)
1817
.await
1918
{
2019
print!("could not register agent: {err}");

docs/changes/spec-24.md

Lines changed: 364 additions & 0 deletions
Large diffs are not rendered by default.

docs/changes/spec-25.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Runtime Spec Version 25 Changes
2+
3+
This document outlines all interface changes between runtime spec version 24 and 25, focusing on structural refactoring of the permission system to move instance management into individual permission scopes.
4+
5+
## Extrinsics
6+
7+
No extrinsic signatures were changed in this version. The permission0 pallet maintains the same external interface with internal restructuring only.
8+
9+
## Events
10+
11+
No event changes were made in this version.
12+
13+
## Storage Items
14+
15+
No storage item changes were made in this version. A migration (v7) handles the internal restructuring of existing data.
16+
17+
## Structs & Enums
18+
19+
### `permission0::PermissionContract<T>`
20+
21+
```diff
22+
pub struct PermissionContract<T: Config> {
23+
pub delegator: T::AccountId,
24+
pub scope: PermissionScope<T>,
25+
pub duration: PermissionDuration<T>,
26+
pub revocation: RevocationTerms<T>,
27+
pub enforcement: EnforcementAuthority<T>,
28+
pub last_update: BlockNumberFor<T>,
29+
pub last_execution: Option<BlockNumberFor<T>>,
30+
pub execution_count: u32,
31+
- pub max_instances: u32,
32+
- pub children: BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>,
33+
pub created_at: BlockNumberFor<T>,
34+
}
35+
```
36+
37+
The `max_instances` and `children` fields have been removed from the main contract structure and moved into the individual permission scope types that require them (CuratorScope and NamespaceScope). This architectural change better encapsulates scope-specific functionality and eliminates unnecessary fields for stream permissions which don't use instance management. See [Instance Management Refactoring](#instance-management-refactoring).
38+
39+
### `permission0::CuratorScope<T>`
40+
41+
```diff
42+
pub struct CuratorScope<T: Config> {
43+
pub recipient: T::AccountId,
44+
pub flags: BoundedBTreeMap<
45+
Option<PermissionId>,
46+
CuratorPermissions,
47+
T::MaxCuratorSubpermissionsPerPermission,
48+
>,
49+
pub cooldown: Option<BlockNumberFor<T>>,
50+
+ pub max_instances: u32,
51+
+ pub children: BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>,
52+
}
53+
```
54+
55+
The curator scope now directly contains instance management fields, allowing curator permissions to manage their own hierarchical relationships and instance limits independently. See [Scope-Specific Instance Management](#scope-specific-instance-management).
56+
57+
### `permission0::NamespaceScope<T>`
58+
59+
```diff
60+
pub struct NamespaceScope<T: Config> {
61+
pub recipient: T::AccountId,
62+
pub paths: BoundedBTreeMap<
63+
Option<PermissionId>,
64+
BoundedBTreeSet<NamespacePath, T::MaxNamespacesPerPermission>,
65+
T::MaxNamespacesPerPermission,
66+
>,
67+
+ pub max_instances: u32,
68+
+ pub children: BoundedBTreeSet<PermissionId, T::MaxChildrenPerPermission>,
69+
}
70+
```
71+
72+
Similar to curator permissions, namespace permissions now maintain their own instance tracking and children collections, enabling proper encapsulation of hierarchical permission structures. See [Scope-Specific Instance Management](#scope-specific-instance-management).
73+
74+
### `permission0::PermissionContract<T>::new()` signature
75+
76+
```diff
77+
pub(crate) fn new(
78+
delegator: T::AccountId,
79+
scope: PermissionScope<T>,
80+
duration: PermissionDuration<T>,
81+
revocation: RevocationTerms<T>,
82+
enforcement: EnforcementAuthority<T>,
83+
- max_instances: u32,
84+
) -> Self
85+
```
86+
87+
The constructor no longer accepts `max_instances` as a parameter since this is now managed within the individual scope structures, simplifying permission creation for stream permissions which don't require instance management.
88+
89+
## Behavior Changes
90+
91+
### Instance Management Refactoring
92+
93+
**What changed**: Instance management (max_instances and children tracking) has been moved from the PermissionContract level into the specific permission scopes that require it (CuratorScope and NamespaceScope). The PermissionContract methods `max_instances()`, `available_instances()`, `used_instances()`, `children()`, and `children_mut()` now return `Option` types and delegate to the underlying scope implementations.
94+
95+
**Why it matters**: This refactoring improves the architecture by eliminating unnecessary fields for stream permissions, which don't use instance management. It creates better separation of concerns where each permission type manages only the data it needs, reducing storage overhead for stream permissions and making the codebase more maintainable.
96+
97+
**Migration needed**: The v7 migration automatically restructures existing permissions by moving the max_instances and children fields from the contract level into the appropriate scope structures. No manual intervention is required.
98+
99+
*Tests*: Migration validated through comprehensive tests in `pallets/permission0/src/migrations.rs::v7` ensuring all permission types are correctly transformed.
100+
101+
*Cross-pallet impact*: None. The external API remains unchanged, and other pallets interact with permissions through the same interface.
102+
103+
### Scope-Specific Instance Management
104+
105+
**What changed**: CuratorScope and NamespaceScope now directly manage their instance limits and children collections. The cleanup logic for removing children from parent permissions when a permission is revoked has been updated to use the new `children_mut()` method that safely accesses the children collection only when present in the scope.
106+
107+
**Why it matters**: This change creates a cleaner separation where only permission types that support hierarchical delegation (curator and namespace) carry the overhead of tracking instances and children. Stream permissions, which are flat structures, no longer carry unnecessary instance-related data.
108+
109+
**Migration needed**: Handled automatically by the v7 migration. Existing permissions are restructured to move instance data into the appropriate scope types.
110+
111+
*Tests*: Instance management behavior is validated through existing permission delegation and revocation tests.
112+
113+
*Cross-pallet impact*: None. The changes are internal to the permission0 pallet structure.
114+
115+
### Dynamic Instance Updates for Namespace Permissions
116+
117+
**What changed**: The `update_namespace_permission` function now correctly updates the max_instances field within the NamespaceScope rather than at the PermissionContract level. The validation logic checks parent permissions' available instances and ensures that reducing instances doesn't invalidate existing child permissions.
118+
119+
**Why it matters**: This ensures that instance management for namespace permissions works correctly with the new structure where instances are tracked at the scope level. The change maintains the same external behavior while operating on the restructured internal data.
120+
121+
**Migration needed**: None. This is an internal implementation change that maintains the same external behavior.
122+
123+
*Tests*: Instance update validation tested through namespace permission update tests ensuring proper parent-child instance tracking.
124+
125+
*Cross-pallet impact*: None. The external behavior of updating namespace permissions remains unchanged.
126+
127+
### Optional Instance Methods
128+
129+
**What changed**: The methods `max_instances()`, `available_instances()`, and `children()` on PermissionContract now return `Option` types instead of direct values. These methods return `Some(value)` for curator and namespace permissions that support instances, and `None` for stream permissions that don't.
130+
131+
**Why it matters**: This change makes the API more explicit about which permission types support instance management, preventing incorrect assumptions about stream permissions having instance limits. It provides compile-time safety for code that works with instance management.
132+
133+
**Migration needed**: None. Internal implementation detail.
134+
135+
*Tests*: Method behavior validated through permission contract unit tests.
136+
137+
*Cross-pallet impact*: None. These are internal methods not exposed through the pallet's external API.

flake.nix

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
pkgs.python310
5252
# Subxt CLI for metadata handling
5353
pkgs.subxt
54+
pkgs.cargo-nextest
5455
# # Code coverage tool
5556
# pkgs.cargo-llvm-cov # marked as broken
5657
];
@@ -59,7 +60,17 @@
5960
checks = pkgs.mkShell {
6061
pre-commit-check = pre-commit-hooks.lib.${system}.run {
6162
src = ./.;
62-
hooks = { rustfmt.enable = true; };
63+
hooks = {
64+
rustfmt.enable = true;
65+
66+
push = {
67+
enable = true;
68+
name = "Tests & Stuff";
69+
entry = "just test";
70+
pass_filenames = false;
71+
stages = ["pre-push"];
72+
};
73+
};
6374
};
6475
};
6576

justfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ build-testnet:
1010

1111
# Development
1212

13-
check:
13+
check: fmt
1414
cargo clippy --tests
1515

16-
test:
17-
cargo test
16+
test: check
17+
SKIP_WASM_BUILD=1 cargo nextest run
1818

1919
fmt:
2020
cargo fmt

pallets/emission0/src/benchmarking.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@ mod benchmarks {
1616

1717
#[benchmark]
1818
fn set_weights() {
19-
let module_key: T::AccountId = account("ModuleKey", 0, 2);
20-
let module_key2: T::AccountId = account("ModuleKey2", 0, 3);
19+
let module_key: T::AccountId = account("agent", 0, 2);
20+
let module_key2: T::AccountId = account("agent2", 0, 3);
2121

2222
<T::Governance>::set_allocator(&module_key2);
2323

24-
<T::Torus>::force_register_agent(&module_key, vec![], vec![], vec![])
24+
<T::Torus>::force_register_agent(&module_key, b"agent".to_vec(), vec![], vec![])
2525
.expect("failed to register agent");
26-
<T::Torus>::force_register_agent(&module_key2, vec![], vec![], vec![])
26+
<T::Torus>::force_register_agent(&module_key2, b"agent2".to_vec(), vec![], vec![])
2727
.expect("failed to register agent");
2828

2929
<T::Governance>::force_set_whitelisted(&module_key);
3030
<T::Governance>::force_set_whitelisted(&module_key2);
3131

3232
<T::Governance>::set_allocator(&module_key2);
33-
let _ = <T::Currency>::deposit_creating(&module_key2, <T::Torus>::min_validator_stake());
33+
let _ =
34+
<T::Currency>::deposit_creating(&module_key2, <T::Torus>::min_validator_stake() * 2);
35+
3436
<T::Torus>::force_set_stake(
3537
&module_key2,
3638
&module_key2,
@@ -46,12 +48,12 @@ mod benchmarks {
4648

4749
#[benchmark]
4850
fn delegate_weight_control() {
49-
let module_key: T::AccountId = account("ModuleKey", 0, 2);
50-
let module_key2: T::AccountId = account("ModuleKey2", 0, 3);
51+
let module_key: T::AccountId = account("agent", 0, 2);
52+
let module_key2: T::AccountId = account("agent2", 0, 3);
5153

52-
<T::Torus>::force_register_agent(&module_key, vec![], vec![], vec![])
54+
<T::Torus>::force_register_agent(&module_key, b"agent".to_vec(), vec![], vec![])
5355
.expect("failed to register agent");
54-
<T::Torus>::force_register_agent(&module_key2, vec![], vec![], vec![])
56+
<T::Torus>::force_register_agent(&module_key2, b"agent2".to_vec(), vec![], vec![])
5557
.expect("failed to register agent");
5658

5759
<T::Governance>::force_set_whitelisted(&module_key);
@@ -65,12 +67,12 @@ mod benchmarks {
6567

6668
#[benchmark]
6769
fn regain_weight_control() {
68-
let module_key: T::AccountId = account("ModuleKey", 0, 2);
69-
let module_key2: T::AccountId = account("ModuleKey2", 0, 3);
70+
let module_key: T::AccountId = account("agent", 0, 2);
71+
let module_key2: T::AccountId = account("agent2", 0, 3);
7072

71-
<T::Torus>::force_register_agent(&module_key, vec![], vec![], vec![])
73+
<T::Torus>::force_register_agent(&module_key, b"agent".to_vec(), vec![], vec![])
7274
.expect("failed to register agent");
73-
<T::Torus>::force_register_agent(&module_key2, vec![], vec![], vec![])
75+
<T::Torus>::force_register_agent(&module_key2, b"agent2".to_vec(), vec![], vec![])
7476
.expect("failed to register agent");
7577

7678
<T::Governance>::force_set_whitelisted(&module_key);

pallets/emission0/src/distribute.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use pallet_governance_api::GovernanceApi;
2-
use pallet_permission0_api::Permission0EmissionApi;
2+
use pallet_permission0_api::Permission0StreamApi;
33
use pallet_torus0_api::Torus0Api;
44
use polkadot_sdk::{
55
frame_support::{
@@ -336,7 +336,7 @@ fn linear_rewards<T: Config>(mut emission: NegativeImbalanceOf<T>) -> NegativeIm
336336
} else {
337337
// This is an impossible case, but if something changes in the future,
338338
// the code is here.
339-
<T::Permission0>::accumulate_emissions(
339+
<T::Permission0>::accumulate_streams(
340340
delegating_to,
341341
&pallet_permission0_api::generate_root_stream_id(delegating_to),
342342
&mut stake,
@@ -368,7 +368,7 @@ fn linear_rewards<T: Config>(mut emission: NegativeImbalanceOf<T>) -> NegativeIm
368368
.zip(upscaled_dividends)
369369
{
370370
let add_stake = |staker, mut amount: NegativeImbalanceOf<T>| {
371-
<T::Permission0>::accumulate_emissions(
371+
<T::Permission0>::accumulate_streams(
372372
&staker,
373373
&pallet_permission0_api::generate_root_stream_id(&staker),
374374
&mut amount,

pallets/emission0/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod pallet {
3131
use frame::prelude::BlockNumberFor;
3232
use frame_system::ensure_signed;
3333
use pallet_governance_api::GovernanceApi;
34-
use pallet_permission0_api::{Permission0Api, Permission0EmissionApi};
34+
use pallet_permission0_api::{Permission0Api, Permission0StreamApi};
3535
use pallet_torus0_api::Torus0Api;
3636
use polkadot_sdk::sp_std;
3737
use weights::WeightInfo;
@@ -99,7 +99,7 @@ pub mod pallet {
9999
type Governance: GovernanceApi<Self::AccountId>;
100100

101101
type Permission0: Permission0Api<OriginFor<Self>>
102-
+ Permission0EmissionApi<
102+
+ Permission0StreamApi<
103103
Self::AccountId,
104104
OriginFor<Self>,
105105
BlockNumberFor<Self>,

0 commit comments

Comments
 (0)