Skip to content

Commit b4ae5b0

Browse files
ggwpezjoepetrowski
andauthored
Fellowship-core: add fast promote (#4877)
Add a `promote_fast` extrinsic to the `core-fellowship` pallet to allow promotions that ignore the promotion cooldown. It comes with a new `FastPromoteOrigin`. Supersedes #4778 --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: command-bot <>
1 parent 7506956 commit b4ae5b0

11 files changed

Lines changed: 309 additions & 55 deletions

File tree

cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ impl pallet_core_fellowship::Config<AmbassadorCoreInstance> for Runtime {
220220
>;
221221
type ApproveOrigin = PromoteOrigin;
222222
type PromoteOrigin = PromoteOrigin;
223+
type FastPromoteOrigin = Self::PromoteOrigin;
223224
type EvidenceSize = ConstU32<65536>;
224225
type MaxRank = ConstU32<9>;
225226
}

cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl pallet_core_fellowship::Config<FellowshipCoreInstance> for Runtime {
208208
>,
209209
EnsureCanPromoteTo,
210210
>;
211+
type FastPromoteOrigin = Self::PromoteOrigin;
211212
type EvidenceSize = ConstU32<65536>;
212213
type MaxRank = ConstU32<9>;
213214
}

cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<
171171
.saturating_add(T::DbWeight::get().reads(5))
172172
.saturating_add(T::DbWeight::get().writes(6))
173173
}
174+
fn promote_fast(r: u32, ) -> Weight {
175+
// Proof Size summary in bytes:
176+
// Measured: `16844`
177+
// Estimated: `19894 + r * (2489 ±0)`
178+
// Minimum execution time: 45_065_000 picoseconds.
179+
Weight::from_parts(34_090_392, 19894)
180+
// Standard Error: 18_620
181+
.saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into()))
182+
.saturating_add(T::DbWeight::get().reads(3_u64))
183+
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into())))
184+
.saturating_add(T::DbWeight::get().writes(3_u64))
185+
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into())))
186+
.saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into()))
187+
}
174188
/// Storage: `AmbassadorCollective::Members` (r:1 w:0)
175189
/// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
176190
/// Storage: `AmbassadorCore::Member` (r:1 w:1)

cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<
170170
.saturating_add(T::DbWeight::get().reads(5))
171171
.saturating_add(T::DbWeight::get().writes(6))
172172
}
173+
fn promote_fast(r: u32, ) -> Weight {
174+
// Proof Size summary in bytes:
175+
// Measured: `16844`
176+
// Estimated: `19894 + r * (2489 ±0)`
177+
// Minimum execution time: 45_065_000 picoseconds.
178+
Weight::from_parts(34_090_392, 19894)
179+
// Standard Error: 18_620
180+
.saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into()))
181+
.saturating_add(T::DbWeight::get().reads(3_u64))
182+
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into())))
183+
.saturating_add(T::DbWeight::get().writes(3_u64))
184+
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into())))
185+
.saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into()))
186+
}
173187
/// Storage: `FellowshipCollective::Members` (r:1 w:0)
174188
/// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
175189
/// Storage: `FellowshipCore::Member` (r:1 w:1)

prdoc/pr_4877.prdoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
title: "Core-Fellowship: new promote_fast call"
2+
3+
doc:
4+
- audience: Runtime User
5+
description: |
6+
Adds the ability to quickly promote someone within a collective by bypassing the promotion
7+
cooldown. This can help in special situations and comes with a new origin: `FastPromoteOrigin`.
8+
9+
crates:
10+
- name: pallet-core-fellowship
11+
bump: major
12+
- name: collectives-westend-runtime
13+
bump: major

substrate/bin/node/runtime/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,7 @@ impl pallet_core_fellowship::Config for Runtime {
18671867
type InductOrigin = pallet_core_fellowship::EnsureInducted<Runtime, (), 1>;
18681868
type ApproveOrigin = EnsureRootWithSuccess<AccountId, ConstU16<9>>;
18691869
type PromoteOrigin = EnsureRootWithSuccess<AccountId, ConstU16<9>>;
1870+
type FastPromoteOrigin = Self::PromoteOrigin;
18701871
type EvidenceSize = ConstU32<16_384>;
18711872
type MaxRank = ConstU32<9>;
18721873
}

substrate/frame/core-fellowship/src/benchmarking.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ mod benchmarks {
210210
Ok(())
211211
}
212212

213+
/// Benchmark the `promote_fast` extrinsic to promote someone up to `r`.
214+
#[benchmark]
215+
fn promote_fast(r: Linear<1, { T::MaxRank::get() as u32 }>) -> Result<(), BenchmarkError> {
216+
let r = r.try_into().expect("r is too large");
217+
let member = make_member::<T, I>(0)?;
218+
219+
ensure_evidence::<T, I>(&member)?;
220+
221+
#[extrinsic_call]
222+
_(RawOrigin::Root, member.clone(), r);
223+
224+
assert_eq!(T::Members::rank_of(&member), Some(r));
225+
assert!(!MemberEvidence::<T, I>::contains_key(&member));
226+
Ok(())
227+
}
228+
213229
#[benchmark]
214230
fn offboard() -> Result<(), BenchmarkError> {
215231
let member = make_member::<T, I>(0)?;

substrate/frame/core-fellowship/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ pub mod pallet {
209209
/// rank to which it can promote.
210210
type PromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
211211

212+
/// The origin that has permission to "fast" promote a member by ignoring promotion periods
213+
/// and skipping ranks. The `Success` value is the maximum rank to which it can promote.
214+
type FastPromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
215+
212216
/// The maximum size in bytes submitted evidence is allowed to be.
213217
#[pallet::constant]
214218
type EvidenceSize: Get<u32>;
@@ -498,6 +502,44 @@ pub mod pallet {
498502
Ok(())
499503
}
500504

505+
/// Fast promotions can skip ranks and ignore the `min_promotion_period`.
506+
///
507+
/// This is useful for out-of-band promotions, hence it has its own `FastPromoteOrigin` to
508+
/// be (possibly) more restrictive than `PromoteOrigin`. Note that the member must already
509+
/// be inducted.
510+
#[pallet::weight(T::WeightInfo::promote_fast(*to_rank as u32))]
511+
#[pallet::call_index(10)]
512+
pub fn promote_fast(
513+
origin: OriginFor<T>,
514+
who: T::AccountId,
515+
to_rank: RankOf<T, I>,
516+
) -> DispatchResult {
517+
match T::FastPromoteOrigin::try_origin(origin) {
518+
Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
519+
Err(origin) => ensure_root(origin)?,
520+
}
521+
ensure!(to_rank as u32 <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
522+
let curr_rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
523+
ensure!(to_rank > curr_rank, Error::<T, I>::UnexpectedRank);
524+
525+
let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
526+
let now = frame_system::Pallet::<T>::block_number();
527+
member.last_promotion = now;
528+
member.last_proof = now;
529+
530+
for rank in (curr_rank + 1)..=to_rank {
531+
T::Members::promote(&who)?;
532+
533+
// NOTE: We could factor this out, but it would destroy our invariants:
534+
Member::<T, I>::insert(&who, &member);
535+
536+
Self::dispose_evidence(who.clone(), rank.saturating_sub(1), Some(rank));
537+
Self::deposit_event(Event::<T, I>::Promoted { who: who.clone(), to_rank: rank });
538+
}
539+
540+
Ok(())
541+
}
542+
501543
/// Stop tracking a prior member who is now not a ranked member of the collective.
502544
///
503545
/// - `origin`: A `Signed` origin of an account.

substrate/frame/core-fellowship/src/tests/integration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl Config for Test {
7878
type InductOrigin = EnsureInducted<Test, (), 1>;
7979
type ApproveOrigin = TryMapSuccess<EnsureSignedBy<IsInVec<ZeroToNine>, u64>, TryMorphInto<u16>>;
8080
type PromoteOrigin = TryMapSuccess<EnsureSignedBy<IsInVec<ZeroToNine>, u64>, TryMorphInto<u16>>;
81+
type FastPromoteOrigin = Self::PromoteOrigin;
8182
type EvidenceSize = EvidenceSize;
8283
type MaxRank = ConstU32<9>;
8384
}

substrate/frame/core-fellowship/src/tests/unit.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::collections::BTreeMap;
2121

2222
use core::cell::RefCell;
2323
use frame_support::{
24-
assert_noop, assert_ok, derive_impl, ord_parameter_types,
24+
assert_noop, assert_ok, derive_impl, hypothetically, ord_parameter_types,
2525
pallet_prelude::Weight,
2626
parameter_types,
2727
traits::{tokens::GetSalary, ConstU32, IsInVec, TryMapSuccess},
@@ -115,6 +115,7 @@ impl Config for Test {
115115
type InductOrigin = EnsureInducted<Test, (), 1>;
116116
type ApproveOrigin = TryMapSuccess<EnsureSignedBy<IsInVec<ZeroToNine>, u64>, TryMorphInto<u16>>;
117117
type PromoteOrigin = TryMapSuccess<EnsureSignedBy<IsInVec<ZeroToNine>, u64>, TryMorphInto<u16>>;
118+
type FastPromoteOrigin = Self::PromoteOrigin;
118119
type EvidenceSize = ConstU32<1024>;
119120
type MaxRank = ConstU32<9>;
120121
}
@@ -256,6 +257,99 @@ fn promote_works() {
256257
});
257258
}
258259

260+
#[test]
261+
fn promote_fast_works() {
262+
let alice = 1;
263+
264+
new_test_ext().execute_with(|| {
265+
assert_noop!(
266+
CoreFellowship::promote_fast(signed(alice), alice, 1),
267+
Error::<Test>::Unranked
268+
);
269+
set_rank(alice, 0);
270+
assert_noop!(
271+
CoreFellowship::promote_fast(signed(alice), alice, 1),
272+
Error::<Test>::NotTracked
273+
);
274+
assert_ok!(CoreFellowship::import(signed(alice)));
275+
276+
// Cannot fast promote to the same rank:
277+
assert_noop!(
278+
CoreFellowship::promote_fast(signed(alice), alice, 0),
279+
Error::<Test>::UnexpectedRank
280+
);
281+
assert_ok!(CoreFellowship::promote_fast(signed(alice), alice, 1));
282+
assert_eq!(TestClub::rank_of(&alice), Some(1));
283+
284+
// Cannot promote normally because of the period:
285+
assert_noop!(CoreFellowship::promote(signed(2), alice, 2), Error::<Test>::TooSoon);
286+
// But can fast promote:
287+
assert_ok!(CoreFellowship::promote_fast(signed(2), alice, 2));
288+
assert_eq!(TestClub::rank_of(&alice), Some(2));
289+
290+
// Cannot promote to lower rank:
291+
assert_noop!(
292+
CoreFellowship::promote_fast(signed(alice), alice, 0),
293+
Error::<Test>::UnexpectedRank
294+
);
295+
assert_noop!(
296+
CoreFellowship::promote_fast(signed(alice), alice, 1),
297+
Error::<Test>::UnexpectedRank
298+
);
299+
// Permission is checked:
300+
assert_noop!(
301+
CoreFellowship::promote_fast(signed(alice), alice, 2),
302+
Error::<Test>::NoPermission
303+
);
304+
305+
// Can fast promote up to the maximum:
306+
assert_ok!(CoreFellowship::promote_fast(signed(9), alice, 9));
307+
// But not past the maximum:
308+
assert_noop!(
309+
CoreFellowship::promote_fast(RuntimeOrigin::root(), alice, 10),
310+
Error::<Test>::InvalidRank
311+
);
312+
});
313+
}
314+
315+
/// Compare the storage root hashes of a normal promote and a fast promote.
316+
#[test]
317+
fn promote_fast_identical_to_promote() {
318+
let alice = 1;
319+
320+
new_test_ext().execute_with(|| {
321+
set_rank(alice, 0);
322+
assert_eq!(TestClub::rank_of(&alice), Some(0));
323+
assert_ok!(CoreFellowship::import(signed(alice)));
324+
run_to(3);
325+
assert_eq!(TestClub::rank_of(&alice), Some(0));
326+
assert_ok!(CoreFellowship::submit_evidence(
327+
signed(alice),
328+
Wish::Promotion,
329+
bounded_vec![0; 1024]
330+
));
331+
332+
let root_promote = hypothetically!({
333+
assert_ok!(CoreFellowship::promote(signed(alice), alice, 1));
334+
// Don't clean the events since they should emit the same events:
335+
sp_io::storage::root(sp_runtime::StateVersion::V1)
336+
});
337+
338+
// This is using thread locals instead of storage...
339+
TestClub::demote(&alice).unwrap();
340+
341+
let root_promote_fast = hypothetically!({
342+
assert_ok!(CoreFellowship::promote_fast(signed(alice), alice, 1));
343+
344+
sp_io::storage::root(sp_runtime::StateVersion::V1)
345+
});
346+
347+
assert_eq!(root_promote, root_promote_fast);
348+
// Ensure that we don't compare trivial stuff like `()` from a type error above.
349+
assert_eq!(root_promote.len(), 32);
350+
});
351+
}
352+
259353
#[test]
260354
fn sync_works() {
261355
new_test_ext().execute_with(|| {

0 commit comments

Comments
 (0)