Skip to content
Open
Show file tree
Hide file tree
Changes from 228 commits
Commits
Show all changes
231 commits
Select commit Hold shift + click to select a range
2f76d6e
scaffold democracy pallet
l0r1s Oct 22, 2025
19c96e0
added genesis config
l0r1s Oct 22, 2025
98599ec
added logic to set allowed proposers/triumvirate
l0r1s Oct 23, 2025
05bd1ef
rename democracy to governance
l0r1s Oct 24, 2025
a5186cd
added logic to create proposals
l0r1s Oct 24, 2025
e8d56ae
added logic to vote on proposals + remove unused param type
l0r1s Oct 27, 2025
97f481a
fix vote + emit 2 events voted and scheduled/cancelled
l0r1s Oct 29, 2025
a524303
reorg storage items
l0r1s Oct 29, 2025
b5ef91b
handle cleanup on schedule/cancel + store proposer
l0r1s Oct 29, 2025
a4ebc0a
cleanup votes/proposals on triumvirate/proposers changes
l0r1s Oct 30, 2025
857ca73
add struct freeze
l0r1s Oct 30, 2025
c9605d6
added economic and building collective + basic rotation
l0r1s Oct 30, 2025
d6d92ff
some renaming
l0r1s Nov 5, 2025
da4691d
added collective voting conditional logic + basic tests
l0r1s Nov 6, 2025
0ea776b
add fast track/cancellation logic + tests
l0r1s Nov 7, 2025
4eecd13
added cleanup logic + readme
l0r1s Nov 7, 2025
d787ac3
make collective size 16
l0r1s Nov 10, 2025
802f655
fmt readme
l0r1s Nov 12, 2025
1e7acf1
update doc initial delay to 1h
l0r1s Nov 12, 2025
646c6fd
combine both collectives
l0r1s Nov 12, 2025
cc35013
adjust delay using net score
l0r1s Nov 13, 2025
fd8be5f
fix tests
l0r1s Nov 14, 2025
f9c0246
fix fast track + tests
l0r1s Nov 18, 2025
14858a4
check voting end
l0r1s Nov 18, 2025
b4e9671
fix test
l0r1s Nov 18, 2025
97ee7ee
handle case where we have a vote on already fast tracked proposal
l0r1s Nov 18, 2025
bea53bc
handle edge case try to fast track on next block scheduled proposal
l0r1s Nov 18, 2025
f056456
added way to mark a member as eligible
l0r1s Nov 19, 2025
e810c50
renaming
l0r1s Nov 24, 2025
d472587
voting on seat replacement + tests
l0r1s Nov 24, 2025
b594b55
cargo clippy
l0r1s Nov 24, 2025
79c408d
cargo fix
l0r1s Nov 24, 2025
517f668
cargo fmt
l0r1s Nov 24, 2025
ae5cfe4
fix clippy
l0r1s Nov 24, 2025
70a0e8f
cargo clippy
l0r1s Nov 24, 2025
37ddfc3
fix test naming
l0r1s Nov 24, 2025
d2a00e4
cleanup v1 without triumvirate seat election
l0r1s Dec 16, 2025
9e7b2da
Merge branch 'devnet-ready' into governance
l0r1s Dec 16, 2025
91e8b25
added pallet-governance to runtime
l0r1s Dec 17, 2025
3c013e9
add benchmarks + weights
l0r1s Dec 17, 2025
37325d8
update weights
l0r1s Dec 17, 2025
f7c613d
fix frame weight template
l0r1s Dec 17, 2025
f0e6c71
rocksdbweight to paritydbweight
l0r1s Dec 17, 2025
41bf245
add good defaults for governance
l0r1s Dec 17, 2025
632da16
fix clippy
l0r1s Dec 18, 2025
48220cd
update readme
l0r1s Dec 18, 2025
a69b5cc
Merge branch 'devnet-ready' into governance
l0r1s Dec 18, 2025
cf2a069
update calls doc
l0r1s Dec 18, 2025
cfe284f
Merge branch 'devnet-ready' into governance
l0r1s Jan 22, 2026
2ad07e9
Merge branch 'devnet-ready' into governance
l0r1s Mar 30, 2026
90830e9
working on anymous voting
l0r1s Apr 7, 2026
6c37a52
Merge branch 'devnet-ready' into governance
l0r1s Apr 7, 2026
694df32
Added multi-collective pallet draft
l0r1s Apr 8, 2026
23113ce
Added signed-voting pallet
l0r1s Apr 9, 2026
1e807bc
Refactor signed-voting pallet
l0r1s Apr 9, 2026
8a0cb1e
Added traits to subtensor runtime common
l0r1s Apr 9, 2026
2c0fdf1
cargo fmt
l0r1s Apr 9, 2026
51a4f5d
Use EnsureOriginWithArg in multi-collective config
l0r1s Apr 9, 2026
3d47dbe
VoteTally into concrete type
l0r1s Apr 9, 2026
5212392
wip
l0r1s Apr 9, 2026
e70be61
Temporary disable clippy warnings
shamil-gadelshin Apr 20, 2026
5b8040d
Modify referenda pallet.
shamil-gadelshin Apr 23, 2026
11a5865
Refactor crypto lib.
shamil-gadelshin Apr 23, 2026
2dbdb43
Alter common lib
shamil-gadelshin Apr 23, 2026
1a3a3c2
Modify signed-voting pallet
shamil-gadelshin Apr 23, 2026
8397764
Modify multi-collective pallet
shamil-gadelshin Apr 23, 2026
60143f0
Add multi-collective pallet tests
shamil-gadelshin Apr 23, 2026
db8b258
Add signed-voting tests
shamil-gadelshin Apr 23, 2026
76177f5
Update referenda after changed signed-voting
shamil-gadelshin Apr 23, 2026
3ef9e5d
Add more referenda tests
shamil-gadelshin Apr 24, 2026
e8136ef
Patch compilation issues
shamil-gadelshin Apr 24, 2026
858997e
Migrate V1 governance to V2
shamil-gadelshin Apr 24, 2026
13e1466
Update referenda
shamil-gadelshin Apr 25, 2026
7c7dce8
Remove polls limit.
shamil-gadelshin Apr 28, 2026
304abc7
Add force_rotate to multi-collective
shamil-gadelshin Apr 28, 2026
0d2fb79
Add collective management
shamil-gadelshin Apr 28, 2026
9fee2a0
Move runtime governance tracks.
shamil-gadelshin Apr 28, 2026
1981570
Make on_tally_updated async and handle 2 step proposals (approve -> r…
l0r1s Apr 28, 2026
f9a6cd4
Rework tests
l0r1s Apr 29, 2026
d96112b
Move types to their own file and add documentation
l0r1s Apr 29, 2026
a39afb5
Added integrity test to check if tracks are correctly defined
l0r1s Apr 29, 2026
c8696a6
cargo fmt
l0r1s Apr 29, 2026
e0076db
Move integrity test to TracksInfo
l0r1s Apr 29, 2026
3606d81
Rework documentation and add diagrams
l0r1s Apr 29, 2026
6315da9
Fix runtime wiring with 2 step proposal (triumvirate approval -> coll…
l0r1s Apr 29, 2026
6bbfd18
Remove version comments
shamil-gadelshin Apr 29, 2026
3cc9ad3
Use impl_for_tuples
shamil-gadelshin Apr 29, 2026
d16bb2e
Apply clippy fixes
shamil-gadelshin Apr 29, 2026
952d40d
- Added TS dev tests
evgeny-s Apr 29, 2026
41a1983
Merge remote-tracking branch 'origin/governance-refactoring' into gov…
evgeny-s Apr 29, 2026
a06dd4a
Merge pull request #2620 from opentensor/governance-refactoring
shamil-gadelshin Apr 29, 2026
566051c
Move governance documents
shamil-gadelshin Apr 29, 2026
71b3d9d
Remove governance pallet
shamil-gadelshin Apr 29, 2026
5fe0afa
Update configuration
shamil-gadelshin Apr 29, 2026
ca76284
Split polls traits and prepare for benchmarking
l0r1s Apr 29, 2026
20b97d0
Added benchmarks for referenda
l0r1s Apr 29, 2026
8e2a6a3
zepter run default
l0r1s Apr 29, 2026
722f5d0
Refactor governance pallets
shamil-gadelshin Apr 30, 2026
fd42f13
Adjust minimum collective members
shamil-gadelshin Apr 30, 2026
55e6ef8
Add scheduler error handling
shamil-gadelshin Apr 30, 2026
234399f
Merge branch 'devnet-ready' into governance
evgeny-s Apr 30, 2026
f06e848
Adjust reaper alarm logic
shamil-gadelshin Apr 30, 2026
cc5b4d8
Remove double vote for duplicated members
shamil-gadelshin Apr 30, 2026
af5b092
Fix some clippy issues
l0r1s Apr 30, 2026
7a0a569
Fix worst case referenda benchmarks
l0r1s Apr 30, 2026
f26939b
Added dev test for the setCode tx
evgeny-s Apr 30, 2026
72d1c7d
zepter run default
shamil-gadelshin Apr 30, 2026
f44bce1
Install cargo for updated rn build
evgeny-s Apr 30, 2026
68f9bb2
- update import
evgeny-s Apr 30, 2026
544b188
Make members sorted in multi collectiv
l0r1s Apr 30, 2026
537928a
reset_members to set_members
l0r1s Apr 30, 2026
8c099b1
Move OnMembersChanged to subtensor common
l0r1s Apr 30, 2026
69e5d95
- Fixed incorrect runtime feature
evgeny-s Apr 30, 2026
02e732b
Merge pull request #2628 from opentensor/governance-set-state-e2e
evgeny-s Apr 30, 2026
9db33b2
Merge pull request #2627 from opentensor/governance-update-1
l0r1s May 1, 2026
82384e6
Benchmarks for the multi collective pallet
l0r1s May 1, 2026
26c5377
Merge branch 'governance-update-2' into referenda-benchmarks
l0r1s May 4, 2026
ea43f56
cargo fmt
l0r1s May 4, 2026
7eb5584
Merge branch 'governance' into referenda-benchmarks
l0r1s May 4, 2026
836bd82
Merge pull request #2626 from opentensor/referenda-benchmarks
l0r1s May 4, 2026
b9efd96
v1 not v2
l0r1s May 4, 2026
a2375a8
Remove unused anonymous-voting + crypto primitive for now
l0r1s May 4, 2026
6000575
Lazy clean up of votes for signed-voting pallet, added documentation
l0r1s May 4, 2026
848d73c
Benchmarks for signed-voting pallet
l0r1s May 4, 2026
2d00c6c
Fixed tests for signed voting
l0r1s May 4, 2026
71ba306
Documentation and readme for signed-voting
l0r1s May 4, 2026
9955dc0
A bit of clean up for the multi collective pallet
l0r1s May 5, 2026
f5fd7a9
Set pallet storage version to fix try-runtime warnings
l0r1s May 5, 2026
6edb18e
Documentation and readme
l0r1s May 5, 2026
2cfef1c
Deleted DESIGN doc
l0r1s May 5, 2026
cb136b3
Some fixes for multi collective
l0r1s May 6, 2026
57368ce
Fix tests and added try_state for invariants
l0r1s May 6, 2026
c2815bb
Update readme for multi-collective
l0r1s May 6, 2026
6aef3f6
Document contracts for polls traits
l0r1s May 6, 2026
0dd927c
Fix VoteTally creation
l0r1s May 6, 2026
01f15d4
Added integrity test for signed-voting
l0r1s May 6, 2026
d00cc17
Make hooks idempotent and fixed tests
l0r1s May 6, 2026
2fc876d
Update storage info and doc
l0r1s May 6, 2026
9aa0b63
Added tests and fixed existing ones for signed-voting
l0r1s May 6, 2026
f7a32d1
Update readme
l0r1s May 6, 2026
0bae8f8
Remove CanRotate and OnMembersChanged logic, not used anymore
l0r1s May 7, 2026
853b297
Set fixed index for collective id enum members
l0r1s May 7, 2026
14cd532
None proposer_set for review track
l0r1s May 7, 2026
741c29c
Ensure non-empty voter set
l0r1s May 7, 2026
3e647d7
Fix mock
l0r1s May 7, 2026
ce27a89
Update runtime wiring
l0r1s May 7, 2026
2b703c1
Update Cargo.lock
l0r1s May 7, 2026
bcfe4a1
Make execution fail-closed
l0r1s May 7, 2026
9a6842d
Check for non-empty voter set in schedule_for_review and try_state
l0r1s May 7, 2026
57aef90
Extract pad_name to subtensor common
l0r1s May 7, 2026
222ba19
Rework and simplify the transition to enacted
l0r1s May 7, 2026
6ffffa6
Fast track made fail-closed
l0r1s May 7, 2026
dfb7db3
Added per-proposer active proposal limit to mitigate spam/compromise
l0r1s May 8, 2026
4da2716
Fix leaking preimages
l0r1s May 8, 2026
5ec099a
Snapshot decision strategy inside the referendum
l0r1s May 8, 2026
1d9911e
Make it possible to kill non enacted scheduled referenda
l0r1s May 8, 2026
8611716
Fix alarm cleaning error on normal completion and update doc
l0r1s May 8, 2026
9d53798
Simplify ongoing_info
l0r1s May 9, 2026
304ad79
Bind alarm before cancelling previous one
l0r1s May 9, 2026
0f7d7a1
Make integrity test more complete
l0r1s May 9, 2026
6d8ab95
Fix testing gaps
l0r1s May 9, 2026
bbca0a8
Missing freeze struct and fix logging error when cancelling missing a…
l0r1s May 9, 2026
ce1a507
Added missing tests for OnMembersChanged
l0r1s May 10, 2026
b31c77a
Add tests for integrity check and OnTallyUpdated
l0r1s May 10, 2026
f6bd78f
Fix storage version for referenda
l0r1s May 10, 2026
6029f69
Comment and doc update, added readme
l0r1s May 10, 2026
f99e2b2
Rework delay calculation and add max_delay and AdjustmentCurve
l0r1s May 10, 2026
10ccf5e
Reorganize tests
l0r1s May 10, 2026
bf519a4
Fix referenda mock
l0r1s May 10, 2026
224c54a
Reorganize governance wiring
l0r1s May 10, 2026
44ee9c9
Comments and invariant fixes
l0r1s May 11, 2026
8663c97
Merge branch 'devnet-ready' into governance
l0r1s May 11, 2026
4bd5a0d
Restore MaxScheduledPerBlock
l0r1s May 11, 2026
8a5c475
Added try_join to multi collective with specific policy and eviction …
l0r1s May 11, 2026
33fc7a7
Tests for try_join
l0r1s May 11, 2026
3f1e378
Added benchmark for try_join
l0r1s May 11, 2026
f75032c
Clean up tests
l0r1s May 11, 2026
9605ab3
Clean up tests
l0r1s May 11, 2026
8d03699
Remove try_join and expose add_member/remove_member
l0r1s May 12, 2026
aa0a88a
Sync the root registration with the collective
l0r1s May 12, 2026
94bf6ee
Init root registered hotkey count
l0r1s May 12, 2026
916d4ea
Added try_state checks to runtime
l0r1s May 12, 2026
de713d8
New economic_eligible collective added to runtime
l0r1s May 12, 2026
7fb4f42
Merge remote-tracking branch 'origin/governance' into governance
l0r1s May 12, 2026
fc44282
Added logic to compute EMA for root registered keys
l0r1s May 14, 2026
bf80e4c
Some renaming
l0r1s May 14, 2026
c29af32
Testing for EMA + root register ref counting
l0r1s May 14, 2026
5c8952c
RootRegisteredStakeEma -> RootRegisteredEma, more flexible
l0r1s May 14, 2026
a64d8cd
Use RAII guards for test setup
l0r1s May 14, 2026
829ab4c
Added new invariant for root register tracked coldkey and ema
l0r1s May 14, 2026
6516415
Extracted MemberSet to its own file + added tests
l0r1s May 14, 2026
1aa65f6
Added benchmarks for do_add_member and do_remove_member
l0r1s May 14, 2026
0031463
Correct weight accounting on root registration
l0r1s May 14, 2026
49ca28e
Extracted TermManagement from collectives config
l0r1s May 14, 2026
3a7a78c
Remove EmaSamplingInterval
l0r1s May 18, 2026
991c6a5
Partitioned EMA computation with flexible value provider
l0r1s May 19, 2026
933c2ec
Move try_state to root_registered module
l0r1s May 20, 2026
86dc20d
EMA sampler storage items on subtensor pallet
l0r1s May 20, 2026
7b04eaf
EMA sampling tick tests
l0r1s May 20, 2026
3dca355
Added StakeValueProvider to sample the EMA over total stake.
l0r1s May 20, 2026
9e37d87
Fix benchmarks non-rotatable collective
l0r1s May 20, 2026
22cf575
Update benchmark script for governance
l0r1s May 20, 2026
5feff66
Updated some comments
l0r1s May 20, 2026
43d1ac6
Make both collectives fixed in size
l0r1s May 20, 2026
d16042f
Wire benchmark in runtime + update subtensor config for governance
l0r1s May 20, 2026
0a8d052
Refactor term management logic
l0r1s May 20, 2026
7cdbdf8
Tests for top_validators/top_subnet_owners
l0r1s May 20, 2026
849442e
Exract do_set_members and use it for apply_rotation
l0r1s May 20, 2026
1fef1f0
FIx governance benchmarks
l0r1s May 20, 2026
207d538
Merge branch 'devnet-ready' into governance
l0r1s May 20, 2026
1257cb1
rust fmt
l0r1s May 20, 2026
6ab2a5c
EaseOut for adjustment curve
l0r1s May 21, 2026
ed6c885
Update documentation
l0r1s May 21, 2026
8136574
Merge branch 'devnet-ready' into governance
l0r1s May 21, 2026
dd52a81
Added checks
l0r1s May 21, 2026
a445dc5
E2E tests update for governance
l0r1s May 21, 2026
3bda8a1
Fix build script stack overflow
l0r1s May 21, 2026
8e763c1
Fix rust
l0r1s May 21, 2026
2c5e4b5
Fix governance benchmarks
l0r1s May 25, 2026
791fa1c
Fix rust
l0r1s May 25, 2026
0dc7744
Merge branch 'devnet-ready' into governance
l0r1s May 25, 2026
a1831d5
Bump spec version to 408
l0r1s May 25, 2026
9825099
auto-update benchmark weights
github-actions[bot] May 25, 2026
b89c796
Fix try_state for collectives not yet initialized
l0r1s May 25, 2026
bbfef42
Merge remote-tracking branch 'origin/governance' into governance
l0r1s May 25, 2026
f160705
Fix maths
l0r1s May 25, 2026
80cccc5
cargo fmt
l0r1s May 25, 2026
0d23bfe
Isolate pallet-multi-collective changes
l0r1s Jun 1, 2026
1837b8a
Remove from benchmark because not wired
l0r1s Jun 2, 2026
128a16c
Fast fail for set_members over max members
l0r1s Jun 2, 2026
1b71f44
Merge branch 'devnet-ready' into governance
l0r1s Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pallet-subtensor = { path = "pallets/subtensor", default-features = false }
pallet-subtensor-swap = { path = "pallets/swap", default-features = false }
pallet-subtensor-swap-runtime-api = { path = "pallets/swap/runtime-api", default-features = false }
pallet-subtensor-swap-rpc = { path = "pallets/swap/rpc", default-features = false }
pallet-multi-collective = { path = "pallets/multi-collective", default-features = false }
procedural-fork = { path = "support/procedural-fork", default-features = false }
safe-math = { path = "primitives/safe-math", default-features = false }
share-pool = { path = "primitives/share-pool", default-features = false }
Expand All @@ -87,6 +88,7 @@ enumflags2 = "0.7.9"
futures = "0.3.30"
hex = { version = "0.4", default-features = false }
hex-literal = "0.4.1"
impl-trait-for-tuples = "0.2.3"
jsonrpsee = { version = "0.24.9", default-features = false }
libsecp256k1 = { version = "0.7.2", default-features = false }
lencode = "0.1.6"
Expand Down Expand Up @@ -117,7 +119,7 @@ toml_edit = "0.22"
derive-syn-parse = "0.2"
Inflector = "0.11"
cfg-expr = "0.15"
itertools = "0.10"
itertools = { version = "0.10", default-features = false }
macro_magic = { version = "0.5", default-features = false }
frame-support-procedural-tools = { version = "10.0.0", default-features = false }
proc-macro-warning = { version = "1", default-features = false }
Expand Down
75 changes: 41 additions & 34 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,45 +29,52 @@ fn main() {
// as we process each Rust file
let (tx, rx) = channel();

// Parse each rust file with syn and run the linting suite on it in parallel
rust_files.par_iter().for_each_with(tx.clone(), |tx, file| {
let is_test = file.display().to_string().contains("test");
let Ok(content) = fs::read_to_string(file) else {
return;
};
let Ok(parsed_tokens) = proc_macro2::TokenStream::from_str(&content) else {
return;
};
let Ok(parsed_file) = syn::parse2::<syn::File>(parsed_tokens) else {
return;
};
let pool = rayon::ThreadPoolBuilder::new()
.stack_size(64 * 1024 * 1024)
.build()
.expect("build script lint thread pool can be created");

let track_lint = |result: Result| {
let Err(errors) = result else {
pool.install(|| {
// Parse each rust file with syn and run the linting suite on it in parallel.
rust_files.par_iter().for_each_with(tx.clone(), |tx, file| {
let is_test = file.display().to_string().contains("test");
let Ok(content) = fs::read_to_string(file) else {
return;
};
let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path());
for error in errors {
let loc = error.span().start();
let file_path = relative_path.display();
// note that spans can't go across thread boundaries without losing their location
// info so we we serialize here and send a String
tx.send(format!(
"cargo:warning={}:{}:{}: {}",
file_path, loc.line, loc.column, error,
))
.unwrap();
}
};
let Ok(parsed_tokens) = proc_macro2::TokenStream::from_str(&content) else {
return;
};
let Ok(parsed_file) = syn::parse2::<syn::File>(parsed_tokens) else {
return;
};

let track_lint = |result: Result| {
let Err(errors) = result else {
return;
};
let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path());
for error in errors {
let loc = error.span().start();
let file_path = relative_path.display();
// note that spans can't go across thread boundaries without losing their location
// info so we we serialize here and send a String
tx.send(format!(
"cargo:warning={}:{}:{}: {}",
file_path, loc.line, loc.column, error,
))
.unwrap();
}
};

track_lint(ForbidAsPrimitiveConversion::lint(&parsed_file));
track_lint(ForbidKeysRemoveCall::lint(&parsed_file));
track_lint(RequireFreezeStruct::lint(&parsed_file));
track_lint(RequireExplicitPalletIndex::lint(&parsed_file));
track_lint(ForbidAsPrimitiveConversion::lint(&parsed_file));
track_lint(ForbidKeysRemoveCall::lint(&parsed_file));
track_lint(RequireFreezeStruct::lint(&parsed_file));
track_lint(RequireExplicitPalletIndex::lint(&parsed_file));

if is_test {
track_lint(ForbidSaturatingMath::lint(&parsed_file));
}
if is_test {
track_lint(ForbidSaturatingMath::lint(&parsed_file));
}
});
});

// Collect and print all errors after the parallel processing is done
Expand Down
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ substrate-fixed.workspace = true
subtensor-macros.workspace = true
runtime-common.workspace = true
approx = { workspace = true, optional = true }
impl-trait-for-tuples.workspace = true

[lints]
workspace = true
Expand Down
2 changes: 2 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ use subtensor_macros::freeze_struct;

pub use currency::*;
pub use evm_context::*;
pub use traits::*;
pub use transaction_error::*;

mod currency;
mod evm_context;
mod traits;
mod transaction_error;

/// Balance of an account.
Expand Down
34 changes: 34 additions & 0 deletions common/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use frame_support::pallet_prelude::*;

/// Handler for when the members of a collective have changed.
pub trait OnMembersChanged<CollectiveId, AccountId> {
/// A collective's members have changed, `incoming` members have joined and
/// `outgoing` members have left.
fn on_members_changed(
collective_id: CollectiveId,
incoming: &[AccountId],
outgoing: &[AccountId],
);
/// Worst-case upper bound on `on_members_changed`'s weight. The
/// implementation is responsible for bounding its own iteration over
/// `incoming`/`outgoing` against the relevant `MaxMembers` constant.
fn weight() -> Weight;
}

#[impl_trait_for_tuples::impl_for_tuples(10)]
impl<CollectiveId: Clone, AccountId> OnMembersChanged<CollectiveId, AccountId> for Tuple {
fn on_members_changed(
collective_id: CollectiveId,
incoming: &[AccountId],
outgoing: &[AccountId],
) {
for_tuples!( #( Tuple::on_members_changed(collective_id.clone(), incoming, outgoing); )* );
}

fn weight() -> Weight {
#[allow(clippy::let_and_return)]
let mut weight = Weight::zero();
for_tuples!( #( weight.saturating_accrue(Tuple::weight()); )* );
weight
}
}
54 changes: 54 additions & 0 deletions pallets/multi-collective/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[package]
name = "pallet-multi-collective"
version = "1.0.0"
authors = ["Bittensor Nucleus Team"]
edition.workspace = true
license = "Apache-2.0"
homepage = "https://bittensor.com"
description = "Membership for named collectives, with per-call origins and optional scheduled rotation."
readme = "README.md"

[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { workspace = true, features = ["max-encoded-len"] }
scale-info = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-system = { workspace = true }
frame-support = { workspace = true }
impl-trait-for-tuples = { workspace = true }
num-traits = { workspace = true }
subtensor-runtime-common = { workspace = true }

[dev-dependencies]
sp-io = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }

[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-benchmarking?/std",
"frame-system/std",
"frame-support/std",
"num-traits/std",
"subtensor-runtime-common/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"subtensor-runtime-common/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime"
]
99 changes: 99 additions & 0 deletions pallets/multi-collective/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# pallet-multi-collective

Membership storage for one or more named collectives, keyed by a
runtime-defined `CollectiveId`. Each collective is configured by a
`CollectivesInfo` impl: name, min/max members, optional term duration.

The pallet only stores membership. Voting, proposing, and tallying are
left to the consumer (e.g. `pallet-referenda` + `pallet-signed-voting`),
which read members through the `CollectiveInspect` trait.

## Concepts

| Type | Provided by | Purpose |
| ---- | ----------- | ------- |
| `CollectiveId` | runtime | Enum naming each collective. |
| `CollectivesInfo` | runtime | Returns the static config for each id (name, bounds, term). |
| `CollectiveInfo` | this crate | `{ name, min_members, max_members, term_duration }`. |
| `Members<_>` | this crate | `BoundedVec<AccountId, MaxMembers>` per id, sorted by `AccountId`. |

## Extrinsics

| Call | Origin | Effect |
| ---- | ------ | ------ |
| `add_member` | `T::AddOrigin` | Insert one member. Fails on `AlreadyMember`, `TooManyMembers`, `CollectiveNotFound`. |
| `remove_member` | `T::RemoveOrigin` | Remove one member. Fails on `NotMember`, `TooFewMembers`, `CollectiveNotFound`. |
| `swap_member` | `T::SwapOrigin` | Atomic remove + insert. Count is preserved, so the per-collective `min_members` / `max_members` bounds are not re-checked; works at either boundary. |
| `set_members` | `T::SetOrigin` | Replace the full list. Sorts the input and rejects `DuplicateAccounts` if any duplicates are present (the input is not silently deduplicated). |
| `force_rotate` | `T::RotateOrigin` | Trigger `OnNewTerm` for a rotating collective on demand. |

Every mutation fires `T::OnMembersChanged` with the incoming and
outgoing accounts so downstream pallets can react (e.g. clean up
votes). The Subtensor runtime currently wires this to `()`: active
polls snapshot the voter set at creation, so member changes cannot
retroactively invalidate votes, and no cleanup is needed.

## Rotation

A collective whose `CollectiveInfo::term_duration` is `Some(d)` rotates
every `d` blocks: `on_initialize` calls `T::OnNewTerm::on_new_term(id)`
when `block_number % d == 0`. The runtime-supplied handler typically
recomputes membership from on-chain data and writes it back through
`set_members`.

`force_rotate` runs the same hook on demand. Used to bootstrap the
first term (the natural cadence only fires after the first boundary,
which can be days or months in) and as a privileged override during
incidents. Calls against a collective with `term_duration: None` are
rejected with `CollectiveDoesNotRotate`.

Curated collectives (no term duration) are managed directly via the
membership extrinsics.

## Integrity check

`integrity_test` runs at runtime construction and panics on a
misconfigured `CollectivesInfo`:

- `min_members > T::MaxMembers` (collective can't reach its min)
- `max_members > T::MaxMembers` (storage can't hold the declared max)
- `min_members > max_members` (collective is unreachable)
- `term_duration: Some(0)` (silently disables rotation; use `None` to opt out)

## Migrations

Pinned at `StorageVersion::new(0)` to satisfy try-runtime CLI; the
project tracks migration runs through a per-pallet `HasMigrationRun`
storage map (see `pallet-crowdloan`), not via FRAME's `StorageVersion`
bump. Add a `migrations` module and an `on_runtime_upgrade` hook on
the next breaking change to `Members<_>` or any future persisted state.

## Configuration

```rust
parameter_types! {
pub const MaxMembers: u32 = 20;
}

impl pallet_multi_collective::Config for Runtime {
type CollectiveId = CollectiveId;
type Collectives = Collectives;
type AddOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type RemoveOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type SwapOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type SetOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type RotateOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type OnMembersChanged = ();
type OnNewTerm = TermManagement;
type MaxMembers = MaxMembers;
type WeightInfo = pallet_multi_collective::weights::SubstrateWeight<Runtime>;
}
```

`T::MaxMembers` bounds storage; per-collective `max_members` from
`CollectivesInfo` may be smaller but never larger (enforced by
`integrity_test`).

## License

Apache-2.0.
Loading
Loading