From e3a5bb29a76abf5be22cc705b590c6cb3e966186 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 7 Oct 2022 17:47:42 +0100 Subject: [PATCH 01/79] More drafting --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + frame/ranked-collective/src/lib.rs | 2 ++ frame/support/src/traits.rs | 2 +- frame/support/src/traits/members.rs | 15 +++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2f0a2df0f101b..ac95cde68be78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6057,6 +6057,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-paymaster" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 02bc6aede8669..f1d2a6815d901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ members = [ "frame/nicks", "frame/node-authorization", "frame/offences", + "frame/paymaster", "frame/preimage", "frame/proxy", "frame/nomination-pools", diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index fa3a473fe7d73..1c632a63dc601 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -718,3 +718,5 @@ pub mod pallet { } } } + +// TODO: Impl `RankedMembers` for this pallet. diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d51c32649a797..8d07d526013c9 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -36,7 +36,7 @@ pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Everything, EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, - SortedMembers, TheseExcept, + RankedMembers, SortedMembers, TheseExcept, }; mod validation; diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index daf2d3aa6517d..82540f4249e07 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -18,6 +18,7 @@ //! Traits for dealing with the idea of membership. use impl_trait_for_tuples::impl_for_tuples; +use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_std::{marker::PhantomData, prelude::*}; /// A trait for querying whether a type can be said to "contain" a value. @@ -265,6 +266,20 @@ pub trait ContainsLengthBound { fn max_len() -> usize; } +/// Ranked membership data structure. +pub trait RankedMembers { + type AccountId; + type Rank: AtLeast32BitUnsigned; + + /// Return the rank of the given ID, or `None` if they are not a member. + fn rank_of(who: &Self::AccountId) -> Option; + + /// Remove a member from the group. This does not result in a call to `removed`. + fn remove(who: &Self::AccountId); + /// Change a member's rank. This does not result in a call to `changed`. + fn change(who: &Self::AccountId, rank: Self::Rank); +} + /// Trait for type that can handle the initialization of account IDs at genesis. pub trait InitializeMembers { /// Initialize the members to the given `members`. From 07ab87a86759a6f81fe087e221d6fead0b777a13 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 11 Oct 2022 14:55:17 +0100 Subject: [PATCH 02/79] Paymaster pallet --- frame/paymaster/Cargo.toml | 49 +++ frame/paymaster/README.md | 1 + frame/paymaster/src/benchmarking.rs | 45 +++ frame/paymaster/src/lib.rs | 189 ++++++++++ frame/paymaster/src/tests.rs | 506 ++++++++++++++++++++++++++ frame/paymaster/src/weights.rs | 76 ++++ frame/support/src/traits/preimages.rs | 122 ++++++- 7 files changed, 981 insertions(+), 7 deletions(-) create mode 100644 frame/paymaster/Cargo.toml create mode 100644 frame/paymaster/README.md create mode 100644 frame/paymaster/src/benchmarking.rs create mode 100644 frame/paymaster/src/lib.rs create mode 100644 frame/paymaster/src/tests.rs create mode 100644 frame/paymaster/src/weights.rs diff --git a/frame/paymaster/Cargo.toml b/frame/paymaster/Cargo.toml new file mode 100644 index 0000000000000..4f02964bdafe9 --- /dev/null +++ b/frame/paymaster/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-paymaster" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Paymaster" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/paymaster/README.md b/frame/paymaster/README.md new file mode 100644 index 0000000000000..7270167b7eceb --- /dev/null +++ b/frame/paymaster/README.md @@ -0,0 +1 @@ +# Paymaster \ No newline at end of file diff --git a/frame/paymaster/src/benchmarking.rs b/frame/paymaster/src/benchmarking.rs new file mode 100644 index 0000000000000..a26151654f256 --- /dev/null +++ b/frame/paymaster/src/benchmarking.rs @@ -0,0 +1,45 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Staking pallet benchmarking. + +use super::*; +#[allow(unused_imports)] +use crate::Pallet as RankedCollective; + +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_support::{assert_ok, dispatch::UnfilteredDispatchable}; +use frame_system::RawOrigin as SystemOrigin; + +const SEED: u32 = 0; + +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +benchmarks_instance_pallet! { + add_member { + let origin = T::PromoteOrigin::successful_origin(); + let call = Call::::add_member { }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(MemberCount::::get(0), 1); + assert_last_event::(Event::MemberAdded { who }.into()); + } + + impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/paymaster/src/lib.rs b/frame/paymaster/src/lib.rs new file mode 100644 index 0000000000000..05de49b3435ca --- /dev/null +++ b/frame/paymaster/src/lib.rs @@ -0,0 +1,189 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Proof-of-Personhood system. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use codec::Codec; +use scale_info::TypeInfo; +use sp_arithmetic::traits::Saturating; +use sp_core::bounded::BoundedVec; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Convert, StaticLookup}, + ArithmeticError::Overflow, + Perbill, RuntimeDebug, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +use frame_support::{ + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{DispatchError, DispatchResultWithPostInfo, PostDispatchInfo}, + ensure, + traits::{EnsureOrigin, PollStatus, Polling, RankedMembers, Time, VoteTally}, + CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// Payroll cycle. +pub type Cycle = u32; + +// Can be implemented by Pot pallet with a fixed Currency impl, but can also be implemented with +// XCM/MultiAsset and made generic over assets. +pub trait Pay { + /// The type by which we measure units of the currency in which we make payments. + type Balance: AtLeast32BitUnsigned + Codec + MaxEncodedLen; + /// The type by which we identify the individuals to whom a payment may be made. + type AccountId; + /// An identifier given to an individual payment. + type Id; + /// The amount of currency with which we have to make payments in this period. It may be a fixed + /// value or reduce as calls to `pay` are made. It should be called once prior to the series of + /// payments to determine the overall budget and then not again until the next series of + /// payments are to be made. + fn budget() -> Self::Balance; + /// Make a payment and return an identifier for later evaluation of success in some off-chain + /// mechanism (likely an event, but possibly not on this chain). + fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, storage::KeyLenOf, weights::PaysFee}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Means by which we can make payments to accounts. This also defines the currency and the + /// balance which we use to denote that currency. + type Paymaster: Pay::AccountId>; + + /// The current membership of payees. + type Members: RankedMembers::AccountId>; + + /// The maximum payout to be made for a single period to an active member of the given rank. + type ActiveSalaryForRank: Convert; + + /// The number of blocks between sequential payout cycles. + #[pallet::constant] + type CyclePeriod: Get; + } + + /// The next payout cycle to pay, and the block nunber at which it should be paid. + #[pallet::storage] + pub(super) type LastPayout, I: 'static = ()> = + StorageValue<_, T::BlockNumber, OptionQuery>; + + /// The most recent cycle which was paid to a member. None implies that a member has not yet + /// been paid. + #[pallet::storage] + pub(super) type LastClaim, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A member is inducted into the payroll. + Inducted { who: T::AccountId }, + /// A payment happened. + Paid { who: T::AccountId }, + } + + #[pallet::error] + pub enum Error { + /// The account is not a ranked member. + NotMember, + // The account is not yet inducted into the system. + NotInducted, + /// The member does not have a current valid claim. + NoClaim, + /// Cycle is not yet over. + NotYet, + /// The payout cycles have not yet started. + NotStarted, + } + + #[pallet::call] + impl, I: 'static> Pallet { + #[pallet::weight(T::WeightInfo::add_member())] + pub fn induct(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let last_payout = LastPayout::::get().unwrap_or_else(Zero::zero); + LastClaim::::put(who, last_payout); + Self::deposit_event(Event::::Inducted { who }); + Ok(Pays::No.into()) + } + + // TODO: preregistration of activity for the next cycle. + + /// Request a payout. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + #[pallet::weight(T::WeightInfo::add_member())] + pub fn payout(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let _rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let last_claim = LastClaim::::get(who).ok_or(Error::::NotInducted)?; + let last_payout = LastPayout::::get().or_or(Error::::NotStarted)?; + ensure!(last_claim < last_payout, Error::::NoClaim); + LastClaim::::put(who, last_payout); + + // TODO: make payout according to rank and activity. + let amount = Self::deposit_event(Event::::Paid { who }); + Ok(Pays::No.into()) + } + + /// Move to next payout cycle, assuming that the present block + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + #[pallet::weight(T::WeightInfo::add_member())] + pub fn next_cycle(origin: OriginFor) -> DispatchResult { + let _ = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + let last_payout = LastPayout::::get().or_or(Error::::NoClaim)?; + let next_payout = last_payout.saturating_add(T::CyclePeriod::get()); + ensure!(now >= next_payout, Error::::NotYet); + LastPayout::::put(next_payout); + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet {} +} diff --git a/frame/paymaster/src/tests.rs b/frame/paymaster/src/tests.rs new file mode 100644 index 0000000000000..68bb79f3d07f7 --- /dev/null +++ b/frame/paymaster/src/tests.rs @@ -0,0 +1,506 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, + error::BadOrigin, + pallet_prelude::Weight, + parameter_types, + traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, +}; + +use super::*; +use crate as pallet_ranked_collective; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Club: pallet_ranked_collective::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestPollState { + Ongoing(TallyOf, Rank), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = vec![ + (1, Completed(1, true)), + (2, Completed(2, false)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 1)), + ].into_iter().collect(); +} + +pub struct TestPolls; +impl Polling> for TestPolls { + type Index = u8; + type Votes = Votes; + type Moment = u64; + type Class = Rank; + fn classes() -> Vec { + vec![0, 1, 2] + } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) + } + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, + ) -> R { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }; + Polls::set(polls); + r + } + fn try_access_poll( + index: Self::Index, + f: impl FnOnce( + PollStatus<&mut TallyOf, Self::Moment, Self::Class>, + ) -> Result, + ) -> Result { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }?; + Polls::set(polls); + Ok(r) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, Ongoing(Tally::new(class), class)); + Polls::set(polls); + Ok(i) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut polls = Polls::get(); + match polls.get(&index) { + Some(Ongoing(..)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(index, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type PromoteOrigin = EitherOf< + // Root can promote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can promote up to the rank of 2 below them. + MapSuccess, ReduceBy>>, + >; + type DemoteOrigin = EitherOf< + // Root can demote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can demote up to the rank of 3 below them. + MapSuccess, ReduceBy>>, + >; + type Polls = TestPolls; + type MinRankOfClass = Identity; + type VoteWeight = Geometric; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +fn member_count(r: Rank) -> MemberIndex { + MemberCount::::get(r) +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn tally(index: u8) -> TallyOf { + >>::as_ongoing(index).expect("No poll").0 +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn unknown_poll_should_panic() { + let _ = tally(0); +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn completed_poll_should_panic() { + let _ = tally(1); +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + }); +} + +#[test] +fn member_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 0); + assert_eq!(member_count(1), 0); + }); +} + +#[test] +fn add_remove_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 0); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_eq!(member_count(0), 3); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 3)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 0); + }); +} + +#[test] +fn promote_demote_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 2); + + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 1); + }); +} + +#[test] +fn promote_demote_by_rank_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + for _ in 0..7 { + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + } + + // #1 can add #2 and promote to rank 1 + assert_ok!(Club::add_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + // #2 as rank 1 cannot do anything privileged + assert_noop!(Club::add_member(RuntimeOrigin::signed(2), 3), BadOrigin); + + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + // #2 as rank 2 can add #3. + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 3)); + + // #2 as rank 2 cannot promote #3 to rank 1 + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(2), 3), + Error::::NoPermission + ); + + // #1 as rank 7 can promote #2 only up to rank 5 and once there cannot demote them. + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(1), 2), + Error::::NoPermission + ); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 2), Error::::NoPermission); + + // #2 as rank 5 can promote #3 only up to rank 3 and once there cannot demote them. + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); + assert_noop!( + Club::promote_member(RuntimeOrigin::signed(2), 3), + Error::::NoPermission + ); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(2), 3), Error::::NoPermission); + + // #2 can add #4 & #5 as rank 0 and #6 & #7 as rank 1. + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 4)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 5)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 7)); + assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 7)); + + // #3 as rank 3 can demote/remove #4 & #5 but not #6 & #7 + assert_ok!(Club::demote_member(RuntimeOrigin::signed(3), 4)); + assert_ok!(Club::remove_member(RuntimeOrigin::signed(3), 5, 0)); + assert_noop!(Club::demote_member(RuntimeOrigin::signed(3), 6), Error::::NoPermission); + assert_noop!( + Club::remove_member(RuntimeOrigin::signed(3), 7, 1), + Error::::NoPermission + ); + + // #2 as rank 5 can demote/remove #6 & #7 + assert_ok!(Club::demote_member(RuntimeOrigin::signed(2), 6)); + assert_ok!(Club::remove_member(RuntimeOrigin::signed(2), 7, 1)); + }); +} + +#[test] +fn voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + assert_noop!(Club::vote(RuntimeOrigin::signed(0), 3, true), Error::::RankTooLow); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 1, 0)); + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 1)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 3, 1)); + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 4)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 6, 4)); + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + }); +} + +#[test] +fn cleanup_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); + assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); + assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); + + assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), Error::::Ongoing); + Polls::set( + vec![(1, Completed(1, true)), (2, Completed(2, false)), (3, Completed(3, true))] + .into_iter() + .collect(), + ); + assert_ok!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10)); + // NOTE: This will fail until #10016 is merged. + // assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), + // Error::::NoneRemaining); + }); +} + +#[test] +fn ensure_ranked_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + use frame_support::traits::OriginTrait; + type Rank1 = EnsureRanked; + type Rank2 = EnsureRanked; + type Rank3 = EnsureRanked; + type Rank4 = EnsureRanked; + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(1)).unwrap(), 1); + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank2::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank3::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!( + Rank3::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + 2 + ); + assert_eq!(Rank3::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + 1 + ); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + 2 + ); + assert_eq!( + Rank4::try_origin(RuntimeOrigin::signed(3)).unwrap_err().as_signed().unwrap(), + 3 + ); + }); +} + +#[test] +fn do_add_member_to_rank_works() { + new_test_ext().execute_with(|| { + let max_rank = 9u16; + assert_ok!(Club::do_add_member_to_rank(69, max_rank / 2)); + assert_ok!(Club::do_add_member_to_rank(1337, max_rank)); + for i in 0..=max_rank { + if i <= max_rank / 2 { + assert_eq!(member_count(i), 2); + } else { + assert_eq!(member_count(i), 1); + } + } + assert_eq!(member_count(max_rank + 1), 0); + }) +} diff --git a/frame/paymaster/src/weights.rs b/frame/paymaster/src/weights.rs new file mode 100644 index 0000000000000..172c3ca85c1cf --- /dev/null +++ b/frame/paymaster/src/weights.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_ranked_collective +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /Users/gav/Core/substrate/target/release/substrate +// benchmark +// pallet +// --pallet +// pallet-ranked-collective +// --extrinsic=* +// --chain=dev +// --steps=50 +// --repeat=20 +// --output=../../../frame/ranked-collective/src/weights.rs +// --template=../../../.maintain/frame-weight-template.hbs +// --header=../../../HEADER-APACHE2 +// --record-proof + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_ranked_collective. +pub trait WeightInfo { + fn add_member() -> Weight; +} + +/// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/frame/support/src/traits/preimages.rs b/frame/support/src/traits/preimages.rs index 594532ba96903..32ea38a2abcde 100644 --- a/frame/support/src/traits/preimages.rs +++ b/frame/support/src/traits/preimages.rs @@ -26,6 +26,58 @@ use sp_std::borrow::Cow; pub type Hash = H256; pub type BoundedInline = crate::BoundedVec>; +#[derive( + Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, +)] +#[codec(mel_bound())] +pub enum BoundedBlob { + /// A bounded blob. Its encoding must be at most 128 bytes. + Inline(BoundedInline), + /// A Blake2-256 hash of the blob together with an upper limit for its size. + Lookup { hash: Hash, len: u32 }, +} + +impl BoundedBlob { + /// Returns the hash of the preimage. + /// + /// The hash is re-calculated every time if the preimage is inlined. + pub fn hash(&self) -> H256 { + match self { + Self::Inline(x) => blake2_256(x.as_ref()).into(), + Self::Lookup { hash, .. } => *hash, + } + } + + /// Returns the length of the preimage. + pub fn len(&self) -> u32 { + match self { + Self::Inline(i) => Some(i.len() as u32), + Self::Lookup { len, .. } => Some(*len), + } + } + + /// Returns whether the image will require a lookup to be peeked. + pub fn lookup_needed(&self) -> bool { + match self { + Self::Inline(..) => false, + Self::Lookup { .. } => true, + } + } + + /// The maximum length of the lookup that is needed to peek `Self`. + pub fn lookup_len(&self) -> Option { + match self { + Self::Inline(..) => None, + Self::Lookup { len, .. } => Some(*len), + } + } + + /// Constructs a `Lookup` bounded item. + pub fn unrequested(hash: Hash, len: u32) -> Self { + Self::Lookup { hash, len } + } +} + #[derive( Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, )] @@ -35,12 +87,15 @@ pub enum Bounded { /// do not support creation of this except for transitioning from legacy state. /// In the future we will make this a pure `Dummy` item storing only the final `dummy` field. Legacy { hash: Hash, dummy: sp_std::marker::PhantomData }, - /// A an bounded `Call`. Its encoding must be at most 128 bytes. + /// A bounded `T`. Its encoding must be at most 128 bytes. Inline(BoundedInline), - /// A Blake2-256 hash of the call together with an upper limit for its size. + /// A Blake2-256 hash of the data together with an upper limit for its size. Lookup { hash: Hash, len: u32 }, } +// The maximum we expect a single legacy hash lookup to be. +const MAX_LEGACY_LEN: u32 = 1_000_000; + impl Bounded { /// Casts the wrapped type into something that encodes alike. /// @@ -75,12 +130,7 @@ impl Bounded { Lookup { hash, .. } => *hash, } } -} -// The maximum we expect a single legacy hash lookup to be. -const MAX_LEGACY_LEN: u32 = 1_000_000; - -impl Bounded { /// Returns the length of the preimage or `None` if the length is unknown. pub fn len(&self) -> Option { match self { @@ -204,6 +254,64 @@ pub trait QueryPreimage { Self::drop(bounded); Ok(r) } + + /// Request that the data required for decoding the given `bounded` blob is made available. + fn hold_blob(bounded: &BoundedBlob) { + use BoundedBlob::*; + match bounded { + Inline(..) => {}, + Lookup { hash, .. } => Self::request(hash), + } + } + + /// No longer request that the data required for decoding the given `bounded` blob is made + /// available. + fn drop_blob(bounded: &BoundedBlob) { + use BoundedBlob::*; + match bounded { + Inline(..) => {}, + Lookup { hash, .. } => Self::unrequest(hash), + } + } + + /// Check to see if all data required for the given `bounded` blob is available. + fn have_blob(bounded: &BoundedBlob) -> bool { + use BoundedBlob::*; + match bounded { + Inline(..) => true, + Lookup { hash, .. } => Self::len(hash).is_some(), + } + } + + /// Create a `BoundedBlob` based on the `hash` and `len` of the encoded value. This may not + /// be `peek`-able or `realize`-able. + fn pick_blob(hash: Hash, len: u32) -> BoundedBlob { + Self::request(&hash); + BoundedBlob::Lookup { hash, len } + } + + /// Convert the given `bounded` blob back into its original data. + /// + /// NOTE: This does not remove any data needed for realization. If you will no longer use the + /// `bounded`, call `realize` instead or call `drop` afterwards. + fn peek_blob(bounded: &BoundedBlob) -> Result, DispatchError> { + use BoundedBlob::*; + Ok(match bounded { + Inline(data) => data, + Lookup { hash, len } => { + Self::fetch(hash, Some(*len))? + }, + }) + } + + /// Convert the given `bounded` value back into its original data. If successful, + /// `drop` any data backing it. This will not break the realisability of independently + /// created instances of `Bounded` which happen to have identical data. + fn realize_blob(bounded: &BoundedBlob) -> Result, DispatchError> { + let r = Self::peek(bounded)?; + Self::drop(bounded); + Ok(r) + } } /// A interface for managing preimages to hashes on chain. From 958d34f35d63eee71e9f7fa8691cb74cca4f9070 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 11:23:44 +0100 Subject: [PATCH 03/79] Fix build --- frame/paymaster/Cargo.toml | 10 +- frame/paymaster/src/lib.rs | 91 ++++++++------- frame/support/src/traits/preimages.rs | 152 +++----------------------- 3 files changed, 68 insertions(+), 185 deletions(-) diff --git a/frame/paymaster/Cargo.toml b/frame/paymaster/Cargo.toml index 4f02964bdafe9..08e45cbff6365 100644 --- a/frame/paymaster/Cargo.toml +++ b/frame/paymaster/Cargo.toml @@ -19,11 +19,11 @@ scale-info = { version = "2.0.1", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/paymaster/src/lib.rs b/frame/paymaster/src/lib.rs index 05de49b3435ca..97e60067d4072 100644 --- a/frame/paymaster/src/lib.rs +++ b/frame/paymaster/src/lib.rs @@ -20,23 +20,14 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] -use codec::Codec; +use codec::{MaxEncodedLen, FullCodec}; use scale_info::TypeInfo; -use sp_arithmetic::traits::Saturating; -use sp_core::bounded::BoundedVec; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, StaticLookup}, - ArithmeticError::Overflow, - Perbill, RuntimeDebug, -}; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_arithmetic::traits::{Zero, Saturating}; +use sp_runtime::traits::{AtLeast32BitUnsigned, Convert}; +use sp_std::{marker::PhantomData, fmt::Debug, prelude::*}; use frame_support::{ - codec::{Decode, Encode, MaxEncodedLen}, - dispatch::{DispatchError, DispatchResultWithPostInfo, PostDispatchInfo}, - ensure, - traits::{EnsureOrigin, PollStatus, Polling, RankedMembers, Time, VoteTally}, - CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, }; #[cfg(test)] @@ -56,11 +47,11 @@ pub type Cycle = u32; // XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. - type Balance: AtLeast32BitUnsigned + Codec + MaxEncodedLen; + type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo; /// The type by which we identify the individuals to whom a payment may be made. type AccountId; /// An identifier given to an individual payment. - type Id; + type Id: FullCodec + MaxEncodedLen + TypeInfo + Clone + Eq + PartialEq + Debug; /// The amount of currency with which we have to make payments in this period. It may be a fixed /// value or reduce as calls to `pay` are made. It should be called once prior to the series of /// payments to determine the overall budget and then not again until the next series of @@ -74,7 +65,7 @@ pub trait Pay { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, storage::KeyLenOf, weights::PaysFee}; + use frame_support::{pallet_prelude::*, dispatch::Pays}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -98,23 +89,26 @@ pub mod pallet { type Members: RankedMembers::AccountId>; /// The maximum payout to be made for a single period to an active member of the given rank. - type ActiveSalaryForRank: Convert; + type ActiveSalaryForRank: Convert<::Rank, ::Balance>; /// The number of blocks between sequential payout cycles. #[pallet::constant] type CyclePeriod: Get; } - /// The next payout cycle to pay, and the block nunber at which it should be paid. + pub type CycleIndexOf = ::BlockNumber; + + /// The current payout cycle, the block nunber at which it started and the remaining balance in + /// this cycle's budget. #[pallet::storage] - pub(super) type LastPayout, I: 'static = ()> = - StorageValue<_, T::BlockNumber, OptionQuery>; + pub(super) type Status, I: 'static = ()> = + StorageValue<_, (CycleIndexOf, T::BlockNumber, ::Balance), OptionQuery>; /// The most recent cycle which was paid to a member. None implies that a member has not yet /// been paid. #[pallet::storage] pub(super) type LastClaim, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, OptionQuery>; + StorageMap<_, Twox64Concat, T::AccountId, CycleIndexOf, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -122,7 +116,7 @@ pub mod pallet { /// A member is inducted into the payroll. Inducted { who: T::AccountId }, /// A payment happened. - Paid { who: T::AccountId }, + Paid { who: T::AccountId, id: ::Id }, } #[pallet::error] @@ -133,54 +127,69 @@ pub mod pallet { NotInducted, /// The member does not have a current valid claim. NoClaim, + /// The member's claim is zero. + ClaimZero, /// Cycle is not yet over. NotYet, /// The payout cycles have not yet started. NotStarted, + /// There is no budget left for the payout. + Bankrupt, + /// There was some issue with the mechanism of payment. + PayError, } #[pallet::call] impl, I: 'static> Pallet { + /// Induct oneself into the payout system. #[pallet::weight(T::WeightInfo::add_member())] - pub fn induct(origin: OriginFor) -> DispatchResult { + #[pallet::call_index(0)] + pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - let last_payout = LastPayout::::get().unwrap_or_else(Zero::zero); - LastClaim::::put(who, last_payout); + let last_payout = Status::::get().map_or(Zero::zero(), |x| x.1); + LastClaim::::insert(&who, last_payout); Self::deposit_event(Event::::Inducted { who }); Ok(Pays::No.into()) } - // TODO: preregistration of activity for the next cycle. - /// Request a payout. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] - pub fn payout(origin: OriginFor) -> DispatchResult { + #[pallet::call_index(1)] + pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let _rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - let last_claim = LastClaim::::get(who).ok_or(Error::::NotInducted)?; - let last_payout = LastPayout::::get().or_or(Error::::NotStarted)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let payout = T::ActiveSalaryForRank::convert(rank); + ensure!(!payout.is_zero(), Error::::ClaimZero); + let last_claim = LastClaim::::get(&who).ok_or(Error::::NotInducted)?; + let (_, last_payout, budget) = Status::::get().ok_or(Error::::NotStarted)?; ensure!(last_claim < last_payout, Error::::NoClaim); - LastClaim::::put(who, last_payout); + ensure!(payout <= budget, Error::::Bankrupt); + + let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; - // TODO: make payout according to rank and activity. - let amount = Self::deposit_event(Event::::Paid { who }); + LastClaim::::insert(&who, last_payout); + Self::deposit_event(Event::::Paid { who, id }); Ok(Pays::No.into()) } - /// Move to next payout cycle, assuming that the present block + /// Move to next payout cycle, assuming that the present block is now within that cycle. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] - pub fn next_cycle(origin: OriginFor) -> DispatchResult { + #[pallet::call_index(2)] + pub fn next_cycle(origin: OriginFor) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let last_payout = LastPayout::::get().or_or(Error::::NoClaim)?; - let next_payout = last_payout.saturating_add(T::CyclePeriod::get()); - ensure!(now >= next_payout, Error::::NotYet); - LastPayout::::put(next_payout); + let (mut cycle_index, mut cycle_start, _) = Status::::get() + .ok_or(Error::::NoClaim)?; + cycle_start.saturating_accrue(T::CyclePeriod::get()); + ensure!(now >= cycle_start, Error::::NotYet); + cycle_index.saturating_inc(); + let budget = T::Paymaster::budget(); + Status::::put((cycle_index, cycle_start, budget)); Ok(Pays::No.into()) } } diff --git a/frame/support/src/traits/preimages.rs b/frame/support/src/traits/preimages.rs index c909da3881265..594532ba96903 100644 --- a/frame/support/src/traits/preimages.rs +++ b/frame/support/src/traits/preimages.rs @@ -26,61 +26,6 @@ use sp_std::borrow::Cow; pub type Hash = H256; pub type BoundedInline = crate::BoundedVec>; -/// The maximum we expect a single legacy hash lookup to be. -const MAX_LEGACY_LEN: u32 = 1_000_000; - -#[derive( - Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, -)] -#[codec(mel_bound())] -pub enum BoundedBlob { - /// A bounded blob. Its encoding must be at most 128 bytes. - Inline(BoundedInline), - /// A Blake2-256 hash of the blob together with an upper limit for its size. - Lookup { hash: Hash, len: u32 }, -} - -impl BoundedBlob { - /// Returns the hash of the preimage. - /// - /// The hash is re-calculated every time if the preimage is inlined. - pub fn hash(&self) -> H256 { - match self { - Self::Inline(x) => blake2_256(x.as_ref()).into(), - Self::Lookup { hash, .. } => *hash, - } - } - - /// Returns the length of the preimage. - pub fn len(&self) -> u32 { - match self { - Self::Inline(i) => Some(i.len() as u32), - Self::Lookup { len, .. } => Some(*len), - } - } - - /// Returns whether the image will require a lookup to be peeked. - pub fn lookup_needed(&self) -> bool { - match self { - Self::Inline(..) => false, - Self::Lookup { .. } => true, - } - } - - /// The maximum length of the lookup that is needed to peek `Self`. - pub fn lookup_len(&self) -> Option { - match self { - Self::Inline(..) => None, - Self::Lookup { len, .. } => Some(*len), - } - } - - /// Constructs a `Lookup` bounded item. - pub fn unrequested(hash: Hash, len: u32) -> Self { - Self::Lookup { hash, len } - } -} - #[derive( Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, )] @@ -90,15 +35,12 @@ pub enum Bounded { /// do not support creation of this except for transitioning from legacy state. /// In the future we will make this a pure `Dummy` item storing only the final `dummy` field. Legacy { hash: Hash, dummy: sp_std::marker::PhantomData }, - /// A bounded `T`. Its encoding must be at most 128 bytes. + /// A an bounded `Call`. Its encoding must be at most 128 bytes. Inline(BoundedInline), - /// A Blake2-256 hash of the data together with an upper limit for its size. + /// A Blake2-256 hash of the call together with an upper limit for its size. Lookup { hash: Hash, len: u32 }, } -// The maximum we expect a single legacy hash lookup to be. -const MAX_LEGACY_LEN: u32 = 1_000_000; - impl Bounded { /// Casts the wrapped type into something that encodes alike. /// @@ -125,25 +67,20 @@ impl Bounded { /// Returns the hash of the preimage. /// /// The hash is re-calculated every time if the preimage is inlined. - pub fn hash(&self) -> Hash { + pub fn hash(&self) -> H256 { use Bounded::*; match self { - Lookup { hash, .. } | Legacy { hash, .. } => *hash, + Legacy { hash, .. } => *hash, Inline(x) => blake2_256(x.as_ref()).into(), + Lookup { hash, .. } => *hash, } } +} - /// Returns the hash to lookup the preimage. - /// - /// If this is a `Bounded::Inline`, `None` is returned as no lookup is required. - pub fn lookup_hash(&self) -> Option { - use Bounded::*; - match self { - Lookup { hash, .. } | Legacy { hash, .. } => Some(*hash), - Inline(_) => None, - } - } +// The maximum we expect a single legacy hash lookup to be. +const MAX_LEGACY_LEN: u32 = 1_000_000; +impl Bounded { /// Returns the length of the preimage or `None` if the length is unknown. pub fn len(&self) -> Option { match self { @@ -231,11 +168,8 @@ pub trait QueryPreimage { } } - /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. - /// - /// It also directly requests the given `hash` using [`Self::request`]. - /// - /// This may not be `peek`-able or `realize`-able. + /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. This may not + /// be `peek`-able or `realize`-able. fn pick(hash: Hash, len: u32) -> Bounded { Self::request(&hash); Bounded::Lookup { hash, len } @@ -270,64 +204,6 @@ pub trait QueryPreimage { Self::drop(bounded); Ok(r) } - - /// Request that the data required for decoding the given `bounded` blob is made available. - fn hold_blob(bounded: &BoundedBlob) { - use BoundedBlob::*; - match bounded { - Inline(..) => {}, - Lookup { hash, .. } => Self::request(hash), - } - } - - /// No longer request that the data required for decoding the given `bounded` blob is made - /// available. - fn drop_blob(bounded: &BoundedBlob) { - use BoundedBlob::*; - match bounded { - Inline(..) => {}, - Lookup { hash, .. } => Self::unrequest(hash), - } - } - - /// Check to see if all data required for the given `bounded` blob is available. - fn have_blob(bounded: &BoundedBlob) -> bool { - use BoundedBlob::*; - match bounded { - Inline(..) => true, - Lookup { hash, .. } => Self::len(hash).is_some(), - } - } - - /// Create a `BoundedBlob` based on the `hash` and `len` of the encoded value. This may not - /// be `peek`-able or `realize`-able. - fn pick_blob(hash: Hash, len: u32) -> BoundedBlob { - Self::request(&hash); - BoundedBlob::Lookup { hash, len } - } - - /// Convert the given `bounded` blob back into its original data. - /// - /// NOTE: This does not remove any data needed for realization. If you will no longer use the - /// `bounded`, call `realize` instead or call `drop` afterwards. - fn peek_blob(bounded: &BoundedBlob) -> Result, DispatchError> { - use BoundedBlob::*; - Ok(match bounded { - Inline(data) => data, - Lookup { hash, len } => { - Self::fetch(hash, Some(*len))? - }, - }) - } - - /// Convert the given `bounded` value back into its original data. If successful, - /// `drop` any data backing it. This will not break the realisability of independently - /// created instances of `Bounded` which happen to have identical data. - fn realize_blob(bounded: &BoundedBlob) -> Result, DispatchError> { - let r = Self::peek(bounded)?; - Self::drop(bounded); - Ok(r) - } } /// A interface for managing preimages to hashes on chain. @@ -352,12 +228,10 @@ pub trait StorePreimage: QueryPreimage { Self::unrequest(hash) } - /// Convert an otherwise unbounded or large value into a type ready for placing in storage. - /// - /// The result is a type whose `MaxEncodedLen` is 131 bytes. + /// Convert an otherwise unbounded or large value into a type ready for placing in storage. The + /// result is a type whose `MaxEncodedLen` is 131 bytes. /// /// NOTE: Once this API is used, you should use either `drop` or `realize`. - /// The value is also noted using [`Self::note`]. fn bound(t: T) -> Result, DispatchError> { let data = t.encode(); let len = data.len() as u32; From 91cb1f58dd9ee13b87105e44975290b5180cea33 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 14:43:16 +0000 Subject: [PATCH 04/79] More tests --- frame/paymaster/src/lib.rs | 99 ++++++-- frame/paymaster/src/tests.rs | 477 +++++++++-------------------------- 2 files changed, 193 insertions(+), 383 deletions(-) diff --git a/frame/paymaster/src/lib.rs b/frame/paymaster/src/lib.rs index 97e60067d4072..65f98aa6694d6 100644 --- a/frame/paymaster/src/lib.rs +++ b/frame/paymaster/src/lib.rs @@ -20,14 +20,14 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] -use codec::{MaxEncodedLen, FullCodec}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_arithmetic::traits::{Zero, Saturating}; -use sp_runtime::traits::{AtLeast32BitUnsigned, Convert}; -use sp_std::{marker::PhantomData, fmt::Debug, prelude::*}; +use sp_arithmetic::traits::{Saturating, Zero}; +use sp_runtime::traits::{AtLeast32BitUnsigned, CheckedSub, Convert}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, + dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, RuntimeDebug, }; #[cfg(test)] @@ -47,7 +47,7 @@ pub type Cycle = u32; // XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. - type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo; + type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo + Debug; /// The type by which we identify the individuals to whom a payment may be made. type AccountId; /// An identifier given to an individual payment. @@ -62,10 +62,17 @@ pub trait Pay { fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result; } +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct StatusType { + cycle_index: CycleIndex, + cycle_start: BlockNumber, + remaining_budget: Balance, +} + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, dispatch::Pays}; + use frame_support::{dispatch::Pays, pallet_prelude::*}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -89,7 +96,10 @@ pub mod pallet { type Members: RankedMembers::AccountId>; /// The maximum payout to be made for a single period to an active member of the given rank. - type ActiveSalaryForRank: Convert<::Rank, ::Balance>; + type ActiveSalaryForRank: Convert< + ::Rank, + ::Balance, + >; /// The number of blocks between sequential payout cycles. #[pallet::constant] @@ -97,15 +107,20 @@ pub mod pallet { } pub type CycleIndexOf = ::BlockNumber; + pub type StatusOf = StatusType< + CycleIndexOf, + ::BlockNumber, + <>::Paymaster as Pay>::Balance, + >; /// The current payout cycle, the block nunber at which it started and the remaining balance in /// this cycle's budget. #[pallet::storage] pub(super) type Status, I: 'static = ()> = - StorageValue<_, (CycleIndexOf, T::BlockNumber, ::Balance), OptionQuery>; + StorageValue<_, StatusOf, OptionQuery>; - /// The most recent cycle which was paid to a member. None implies that a member has not yet - /// been paid. + /// The most recent cycle index which was paid to a member. None implies that a member has not + /// yet been paid. #[pallet::storage] pub(super) type LastClaim, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, CycleIndexOf, OptionQuery>; @@ -117,12 +132,16 @@ pub mod pallet { Inducted { who: T::AccountId }, /// A payment happened. Paid { who: T::AccountId, id: ::Id }, + /// The next cycle begins. + CycleStarted { index: CycleIndexOf, budget: ::Balance }, } #[pallet::error] pub enum Error { /// The account is not a ranked member. NotMember, + /// The account is already inducted. + AlreadyInducted, // The account is not yet inducted into the system. NotInducted, /// The member does not have a current valid claim. @@ -146,9 +165,12 @@ pub mod pallet { #[pallet::call_index(0)] pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + let cycle_index = Status::::get().ok_or(Error::::NotStarted)?.cycle_index; let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - let last_payout = Status::::get().map_or(Zero::zero(), |x| x.1); - LastClaim::::insert(&who, last_payout); + ensure!(!LastClaim::::contains_key(&who), Error::::AlreadyInducted); + + LastClaim::::insert(&who, cycle_index); + Self::deposit_event(Event::::Inducted { who }); Ok(Pays::No.into()) } @@ -163,14 +185,16 @@ pub mod pallet { let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; let payout = T::ActiveSalaryForRank::convert(rank); ensure!(!payout.is_zero(), Error::::ClaimZero); - let last_claim = LastClaim::::get(&who).ok_or(Error::::NotInducted)?; - let (_, last_payout, budget) = Status::::get().ok_or(Error::::NotStarted)?; - ensure!(last_claim < last_payout, Error::::NoClaim); - ensure!(payout <= budget, Error::::Bankrupt); + let last_claim = Self::last_claim(&who)?; + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + ensure!(last_claim < status.cycle_index, Error::::NoClaim); + status.remaining_budget = + status.remaining_budget.checked_sub(&payout).ok_or(Error::::Bankrupt)?; let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; + LastClaim::::insert(&who, status.cycle_index); + Status::::put(&status); - LastClaim::::insert(&who, last_payout); Self::deposit_event(Event::::Paid { who, id }); Ok(Pays::No.into()) } @@ -183,16 +207,39 @@ pub mod pallet { pub fn next_cycle(origin: OriginFor) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let (mut cycle_index, mut cycle_start, _) = Status::::get() - .ok_or(Error::::NoClaim)?; - cycle_start.saturating_accrue(T::CyclePeriod::get()); - ensure!(now >= cycle_start, Error::::NotYet); - cycle_index.saturating_inc(); - let budget = T::Paymaster::budget(); - Status::::put((cycle_index, cycle_start, budget)); + let mut status = match Status::::get() { + // Not first time... (move along start block and bump index) + Some(mut status) => { + status.cycle_start.saturating_accrue(T::CyclePeriod::get()); + ensure!(now >= status.cycle_start, Error::::NotYet); + status.cycle_index.saturating_inc(); + status + }, + // First time... (initialize) + None => StatusType { + cycle_index: Zero::zero(), + cycle_start: now, + remaining_budget: Zero::zero(), + }, + }; + status.remaining_budget = T::Paymaster::budget(); + + Status::::put(&status); + + Self::deposit_event(Event::::CycleStarted { + index: status.cycle_index, + budget: status.remaining_budget, + }); Ok(Pays::No.into()) } } - impl, I: 'static> Pallet {} + impl, I: 'static> Pallet { + pub fn status() -> Option> { + Status::::get() + } + pub fn last_claim(who: &T::AccountId) -> Result, DispatchError> { + LastClaim::::get(&who).ok_or(Error::::NotInducted.into()) + } + } } diff --git a/frame/paymaster/src/tests.rs b/frame/paymaster/src/tests.rs index 68bb79f3d07f7..3bf7dc92bc288 100644 --- a/frame/paymaster/src/tests.rs +++ b/frame/paymaster/src/tests.rs @@ -21,19 +21,19 @@ use std::collections::BTreeMap; use frame_support::{ assert_noop, assert_ok, - error::BadOrigin, pallet_prelude::Weight, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, + traits::{ConstU32, ConstU64, Everything}, }; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, + traits::{BlakeTwo256, Identity, IdentityLookup}, }; +use sp_std::cell::RefCell; use super::*; -use crate as pallet_ranked_collective; +use crate as pallet_paymaster; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -45,7 +45,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Club: pallet_ranked_collective::{Pallet, Call, Storage, Event}, + Paymaster: pallet_paymaster::{Pallet, Call, Storage, Event}, } ); @@ -80,113 +80,62 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum TestPollState { - Ongoing(TallyOf, Rank), - Completed(u64, bool), +thread_local! { + pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); + pub static BUDGET: RefCell = RefCell::new(10u64); + pub static LAST_ID: RefCell = RefCell::new(0u64); } -use TestPollState::*; -parameter_types! { - pub static Polls: BTreeMap = vec![ - (1, Completed(1, true)), - (2, Completed(2, false)), - (3, Ongoing(Tally::from_parts(0, 0, 0), 1)), - ].into_iter().collect(); +fn paid(who: u64) -> u64 { + PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) } -pub struct TestPolls; -impl Polling> for TestPolls { - type Index = u8; - type Votes = Votes; - type Moment = u64; - type Class = Rank; - fn classes() -> Vec { - vec![0, 1, 2] - } - fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { - Polls::get().remove(&index).and_then(|x| { - if let TestPollState::Ongoing(t, c) = x { - Some((t, c)) - } else { - None - } - }) - } - fn access_poll( - index: Self::Index, - f: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, - ) -> R { - let mut polls = Polls::get(); - let entry = polls.get_mut(&index); - let r = match entry { - Some(Ongoing(ref mut tally_mut_ref, class)) => - f(PollStatus::Ongoing(tally_mut_ref, *class)), - Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), - None => f(PollStatus::None), - }; - Polls::set(polls); - r - } - fn try_access_poll( - index: Self::Index, - f: impl FnOnce( - PollStatus<&mut TallyOf, Self::Moment, Self::Class>, - ) -> Result, - ) -> Result { - let mut polls = Polls::get(); - let entry = polls.get_mut(&index); - let r = match entry { - Some(Ongoing(ref mut tally_mut_ref, class)) => - f(PollStatus::Ongoing(tally_mut_ref, *class)), - Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), - None => f(PollStatus::None), - }?; - Polls::set(polls); - Ok(r) +pub struct TestPay; +impl Pay for TestPay { + type AccountId = u64; + type Balance = u64; + type Id = u64; + + fn budget() -> Self::Balance { + BUDGET.with(|b| *b.borrow()) } - #[cfg(feature = "runtime-benchmarks")] - fn create_ongoing(class: Self::Class) -> Result { - let mut polls = Polls::get(); - let i = polls.keys().rev().next().map_or(0, |x| x + 1); - polls.insert(i, Ongoing(Tally::new(class), class)); - Polls::set(polls); - Ok(i) + fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { + PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount); + Ok(LAST_ID.with(|lid| { + let x = *lid.borrow(); + lid.replace(x + 1); + x + })) } +} - #[cfg(feature = "runtime-benchmarks")] - fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { - let mut polls = Polls::get(); - match polls.get(&index) { - Some(Ongoing(..)) => {}, - _ => return Err(()), - } - let now = frame_system::Pallet::::block_number(); - polls.insert(index, Completed(now, approved)); - Polls::set(polls); - Ok(()) +thread_local! { + pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); +} + +pub struct TestClub; +impl RankedMembers for TestClub { + type AccountId = u64; + type Rank = u64; + fn rank_of(who: &Self::AccountId) -> Option { + CLUB.with(|club| club.borrow().get(who).cloned()) + } + fn remove(who: &Self::AccountId) { + CLUB.with(|club| club.borrow_mut().remove(&who)); + } + fn change(who: &Self::AccountId, rank: Self::Rank) { + CLUB.with(|club| club.borrow_mut().insert(*who, rank)); } } impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; - type PromoteOrigin = EitherOf< - // Root can promote arbitrarily. - frame_system::EnsureRootWithSuccess>, - // Members can promote up to the rank of 2 below them. - MapSuccess, ReduceBy>>, - >; - type DemoteOrigin = EitherOf< - // Root can demote arbitrarily. - frame_system::EnsureRootWithSuccess>, - // Members can demote up to the rank of 3 below them. - MapSuccess, ReduceBy>>, - >; - type Polls = TestPolls; - type MinRankOfClass = Identity; - type VoteWeight = Geometric; + type Paymaster = TestPay; + type Members = TestClub; + type ActiveSalaryForRank = Identity; + type CyclePeriod = ConstU64<4>; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -200,10 +149,6 @@ fn next_block() { System::set_block_number(System::block_number() + 1); } -fn member_count(r: Rank) -> MemberIndex { - MemberCount::::get(r) -} - #[allow(dead_code)] fn run_to(n: u64) { while System::block_number() < n { @@ -211,296 +156,114 @@ fn run_to(n: u64) { } } -fn tally(index: u8) -> TallyOf { - >>::as_ongoing(index).expect("No poll").0 -} - -#[test] -#[ignore] -#[should_panic(expected = "No poll")] -fn unknown_poll_should_panic() { - let _ = tally(0); -} - -#[test] -#[ignore] -#[should_panic(expected = "No poll")] -fn completed_poll_should_panic() { - let _ = tally(1); -} - #[test] fn basic_stuff() { new_test_ext().execute_with(|| { - assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + assert!(Paymaster::last_claim(&0).is_err()); + assert_eq!(Paymaster::status(), None); }); } #[test] -fn member_lifecycle_works() { +fn can_start() { new_test_ext().execute_with(|| { - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 0); - assert_eq!(member_count(1), 0); - }); -} - -#[test] -fn add_remove_works() { - new_test_ext().execute_with(|| { - assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 1); - - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 0); - - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 1); - - assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); - assert_eq!(member_count(0), 2); - - assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); - assert_eq!(member_count(0), 3); - - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 3)); - assert_eq!(member_count(0), 2); - - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 1); - - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 2)); - assert_eq!(member_count(0), 0); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_eq!( + Paymaster::status(), + Some(StatusType { cycle_index: 0, cycle_start: 1, remaining_budget: 10 }) + ); }); } #[test] -fn promote_demote_works() { +fn next_cycle_works() { new_test_ext().execute_with(|| { - assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 1); - assert_eq!(member_count(1), 0); - - assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); - assert_eq!(member_count(0), 2); - assert_eq!(member_count(1), 0); - - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 2); - assert_eq!(member_count(1), 1); - - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_eq!(member_count(0), 2); - assert_eq!(member_count(1), 2); - - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 2); - assert_eq!(member_count(1), 1); - - assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); - assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); - assert_eq!(member_count(0), 1); - assert_eq!(member_count(1), 1); - }); -} + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + run_to(4); + assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); -#[test] -fn promote_demote_by_rank_works() { - new_test_ext().execute_with(|| { - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - for _ in 0..7 { - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - } - - // #1 can add #2 and promote to rank 1 - assert_ok!(Club::add_member(RuntimeOrigin::signed(1), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); - // #2 as rank 1 cannot do anything privileged - assert_noop!(Club::add_member(RuntimeOrigin::signed(2), 3), BadOrigin); - - assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); - // #2 as rank 2 can add #3. - assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 3)); - - // #2 as rank 2 cannot promote #3 to rank 1 - assert_noop!( - Club::promote_member(RuntimeOrigin::signed(2), 3), - Error::::NoPermission + run_to(5); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_eq!( + Paymaster::status(), + Some(StatusType { cycle_index: 1, cycle_start: 5, remaining_budget: 10 }) ); - // #1 as rank 7 can promote #2 only up to rank 5 and once there cannot demote them. - assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(1), 2)); - assert_noop!( - Club::promote_member(RuntimeOrigin::signed(1), 2), - Error::::NoPermission - ); - assert_noop!(Club::demote_member(RuntimeOrigin::signed(1), 2), Error::::NoPermission); - - // #2 as rank 5 can promote #3 only up to rank 3 and once there cannot demote them. - assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 3)); - assert_noop!( - Club::promote_member(RuntimeOrigin::signed(2), 3), - Error::::NoPermission - ); - assert_noop!(Club::demote_member(RuntimeOrigin::signed(2), 3), Error::::NoPermission); - - // #2 can add #4 & #5 as rank 0 and #6 & #7 as rank 1. - assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 4)); - assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 5)); - assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 6)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 6)); - assert_ok!(Club::add_member(RuntimeOrigin::signed(2), 7)); - assert_ok!(Club::promote_member(RuntimeOrigin::signed(2), 7)); - - // #3 as rank 3 can demote/remove #4 & #5 but not #6 & #7 - assert_ok!(Club::demote_member(RuntimeOrigin::signed(3), 4)); - assert_ok!(Club::remove_member(RuntimeOrigin::signed(3), 5, 0)); - assert_noop!(Club::demote_member(RuntimeOrigin::signed(3), 6), Error::::NoPermission); - assert_noop!( - Club::remove_member(RuntimeOrigin::signed(3), 7, 1), - Error::::NoPermission - ); + run_to(8); + assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); - // #2 as rank 5 can demote/remove #6 & #7 - assert_ok!(Club::demote_member(RuntimeOrigin::signed(2), 6)); - assert_ok!(Club::remove_member(RuntimeOrigin::signed(2), 7, 1)); + BUDGET.with(|b| b.replace(5)); + run_to(9); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_eq!( + Paymaster::status(), + Some(StatusType { cycle_index: 2, cycle_start: 9, remaining_budget: 5 }) + ); }); } #[test] -fn voting_works() { +fn induct_works() { new_test_ext().execute_with(|| { - assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - - assert_noop!(Club::vote(RuntimeOrigin::signed(0), 3, true), Error::::RankTooLow); - assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); - - assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); - assert_eq!(tally(3), Tally::from_parts(1, 1, 0)); - assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, false)); - assert_eq!(tally(3), Tally::from_parts(0, 0, 1)); - - assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, true)); - assert_eq!(tally(3), Tally::from_parts(1, 3, 1)); - assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); - assert_eq!(tally(3), Tally::from_parts(0, 0, 4)); - - assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); - assert_eq!(tally(3), Tally::from_parts(1, 6, 4)); - assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, false)); - assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + + assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::NotMember); + TestClub::change(&1, 1); + assert!(Paymaster::last_claim(&1).is_err()); + assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + assert_eq!(Paymaster::last_claim(&1).unwrap(), 0); + assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); }); } #[test] -fn cleanup_works() { +fn payment_works() { new_test_ext().execute_with(|| { - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - - assert_ok!(Club::vote(RuntimeOrigin::signed(1), 3, true)); - assert_ok!(Club::vote(RuntimeOrigin::signed(2), 3, false)); - assert_ok!(Club::vote(RuntimeOrigin::signed(3), 3, true)); - - assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), Error::::Ongoing); - Polls::set( - vec![(1, Completed(1, true)), (2, Completed(2, false)), (3, Completed(3, true))] - .into_iter() - .collect(), - ); - assert_ok!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10)); - // NOTE: This will fail until #10016 is merged. - // assert_noop!(Club::cleanup_poll(RuntimeOrigin::signed(4), 3, 10), - // Error::::NoneRemaining); + TestClub::change(&1, 1); + assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(5); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Paymaster::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Paymaster::status().unwrap().remaining_budget, 9); + assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Paymaster::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 2); + assert_eq!(Paymaster::status().unwrap().remaining_budget, 9); }); } #[test] -fn ensure_ranked_works() { +fn zero_payment_fails() { new_test_ext().execute_with(|| { - assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); - assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); - - use frame_support::traits::OriginTrait; - type Rank1 = EnsureRanked; - type Rank2 = EnsureRanked; - type Rank3 = EnsureRanked; - type Rank4 = EnsureRanked; - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(1)).unwrap(), 1); - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); - assert_eq!( - Rank2::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), - 1 - ); - assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); - assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); - assert_eq!( - Rank3::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), - 1 - ); - assert_eq!( - Rank3::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), - 2 - ); - assert_eq!(Rank3::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); - assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), - 1 - ); - assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), - 2 - ); - assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(3)).unwrap_err().as_signed().unwrap(), - 3 - ); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + TestClub::change(&1, 0); + assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + run_to(5); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); }); } #[test] -fn do_add_member_to_rank_works() { +fn bankrupt_fails_gracefully() { new_test_ext().execute_with(|| { - let max_rank = 9u16; - assert_ok!(Club::do_add_member_to_rank(69, max_rank / 2)); - assert_ok!(Club::do_add_member_to_rank(1337, max_rank)); - for i in 0..=max_rank { - if i <= max_rank / 2 { - assert_eq!(member_count(i), 2); - } else { - assert_eq!(member_count(i), 1); - } - } - assert_eq!(member_count(max_rank + 1), 0); - }) + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + TestClub::change(&1, 11); + assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + run_to(5); + assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::Bankrupt); + assert_eq!(paid(1), 0); + }); } From c53d372435ad754508e3cd4dde247dc877623771 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 14:46:01 +0000 Subject: [PATCH 05/79] Rename --- Cargo.lock | 2 +- frame/paymaster/README.md | 1 - frame/{paymaster => salary}/Cargo.toml | 2 +- frame/salary/README.md | 3 +++ frame/{paymaster => salary}/src/benchmarking.rs | 0 frame/{paymaster => salary}/src/lib.rs | 2 +- frame/{paymaster => salary}/src/tests.rs | 4 ++-- frame/{paymaster => salary}/src/weights.rs | 0 8 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 frame/paymaster/README.md rename frame/{paymaster => salary}/Cargo.toml (98%) create mode 100644 frame/salary/README.md rename frame/{paymaster => salary}/src/benchmarking.rs (100%) rename frame/{paymaster => salary}/src/lib.rs (99%) rename frame/{paymaster => salary}/src/tests.rs (98%) rename frame/{paymaster => salary}/src/weights.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index ce7d4a3a4fc09..da84dc55b4138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6204,7 +6204,7 @@ dependencies = [ ] [[package]] -name = "pallet-paymaster" +name = "pallet-salary" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", diff --git a/frame/paymaster/README.md b/frame/paymaster/README.md deleted file mode 100644 index 7270167b7eceb..0000000000000 --- a/frame/paymaster/README.md +++ /dev/null @@ -1 +0,0 @@ -# Paymaster \ No newline at end of file diff --git a/frame/paymaster/Cargo.toml b/frame/salary/Cargo.toml similarity index 98% rename from frame/paymaster/Cargo.toml rename to frame/salary/Cargo.toml index 08e45cbff6365..86cae16bb28b7 100644 --- a/frame/paymaster/Cargo.toml +++ b/frame/salary/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-paymaster" +name = "pallet-salary" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" diff --git a/frame/salary/README.md b/frame/salary/README.md new file mode 100644 index 0000000000000..25c1be0e805d7 --- /dev/null +++ b/frame/salary/README.md @@ -0,0 +1,3 @@ +# Salary + +Make periodic payment to members of a ranked collective according to rank. \ No newline at end of file diff --git a/frame/paymaster/src/benchmarking.rs b/frame/salary/src/benchmarking.rs similarity index 100% rename from frame/paymaster/src/benchmarking.rs rename to frame/salary/src/benchmarking.rs diff --git a/frame/paymaster/src/lib.rs b/frame/salary/src/lib.rs similarity index 99% rename from frame/paymaster/src/lib.rs rename to frame/salary/src/lib.rs index 65f98aa6694d6..3fc393c1f799b 100644 --- a/frame/paymaster/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Proof-of-Personhood system. +//! Make periodic payment to members of a ranked collective according to rank. #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] diff --git a/frame/paymaster/src/tests.rs b/frame/salary/src/tests.rs similarity index 98% rename from frame/paymaster/src/tests.rs rename to frame/salary/src/tests.rs index 3bf7dc92bc288..2e999bdd0f3dc 100644 --- a/frame/paymaster/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -33,7 +33,7 @@ use sp_runtime::{ use sp_std::cell::RefCell; use super::*; -use crate as pallet_paymaster; +use crate as pallet_salary; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -45,7 +45,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Paymaster: pallet_paymaster::{Pallet, Call, Storage, Event}, + Paymaster: pallet_salary::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/paymaster/src/weights.rs b/frame/salary/src/weights.rs similarity index 100% rename from frame/paymaster/src/weights.rs rename to frame/salary/src/weights.rs From 0806559e67386966d7ec49578fd7ec628848f981 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 14:46:49 +0000 Subject: [PATCH 06/79] Rename --- frame/salary/src/tests.rs | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 2e999bdd0f3dc..180ebc0c9ed60 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -45,7 +45,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Paymaster: pallet_salary::{Pallet, Call, Storage, Event}, + Salary: pallet_salary::{Pallet, Call, Storage, Event}, } ); @@ -132,7 +132,7 @@ impl RankedMembers for TestClub { impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; - type Paymaster = TestPay; + type Salary = TestPay; type Members = TestClub; type ActiveSalaryForRank = Identity; type CyclePeriod = ConstU64<4>; @@ -159,17 +159,17 @@ fn run_to(n: u64) { #[test] fn basic_stuff() { new_test_ext().execute_with(|| { - assert!(Paymaster::last_claim(&0).is_err()); - assert_eq!(Paymaster::status(), None); + assert!(Salary::last_claim(&0).is_err()); + assert_eq!(Salary::status(), None); }); } #[test] fn can_start() { new_test_ext().execute_with(|| { - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); assert_eq!( - Paymaster::status(), + Salary::status(), Some(StatusType { cycle_index: 0, cycle_start: 1, remaining_budget: 10 }) ); }); @@ -178,25 +178,25 @@ fn can_start() { #[test] fn next_cycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); run_to(4); - assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(5); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); assert_eq!( - Paymaster::status(), + Salary::status(), Some(StatusType { cycle_index: 1, cycle_start: 5, remaining_budget: 10 }) ); run_to(8); - assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); BUDGET.with(|b| b.replace(5)); run_to(9); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); assert_eq!( - Paymaster::status(), + Salary::status(), Some(StatusType { cycle_index: 2, cycle_start: 9, remaining_budget: 5 }) ); }); @@ -205,14 +205,14 @@ fn next_cycle_works() { #[test] fn induct_works() { new_test_ext().execute_with(|| { - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); - assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::NotMember); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); TestClub::change(&1, 1); - assert!(Paymaster::last_claim(&1).is_err()); - assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); - assert_eq!(Paymaster::last_claim(&1).unwrap(), 0); - assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); + assert!(Salary::last_claim(&1).is_err()); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_eq!(Salary::last_claim(&1).unwrap(), 0); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); }); } @@ -220,50 +220,50 @@ fn induct_works() { fn payment_works() { new_test_ext().execute_with(|| { TestClub::change(&1, 1); - assert_noop!(Paymaster::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); - assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); - assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. - assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(5); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); - assert_ok!(Paymaster::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); - assert_eq!(Paymaster::status().unwrap().remaining_budget, 9); - assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_eq!(Salary::status().unwrap().remaining_budget, 9); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(8); - assert_noop!(Paymaster::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); - assert_ok!(Paymaster::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 2); - assert_eq!(Paymaster::status().unwrap().remaining_budget, 9); + assert_eq!(Salary::status().unwrap().remaining_budget, 9); }); } #[test] fn zero_payment_fails() { new_test_ext().execute_with(|| { - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); TestClub::change(&1, 0); - assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(5); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); - assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); }); } #[test] fn bankrupt_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); TestClub::change(&1, 11); - assert_ok!(Paymaster::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(5); - assert_ok!(Paymaster::next_cycle(RuntimeOrigin::signed(1))); - assert_noop!(Paymaster::payout(RuntimeOrigin::signed(1)), Error::::Bankrupt); + assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::Bankrupt); assert_eq!(paid(1), 0); }); } From 5cadabf1446a44b4c0aefe370cdbc6f4438ed6f9 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 14:52:36 +0000 Subject: [PATCH 07/79] Renaming --- Cargo.lock | 34 +++++++++++++++++----------------- Cargo.toml | 2 +- frame/salary/src/tests.rs | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da84dc55b4138..1e2e9ccfe7c5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6203,23 +6203,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-salary" -version = "4.0.0-dev" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-preimage" version = "4.0.0-dev" @@ -6360,6 +6343,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-salary" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-scheduler" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 377a935e22430..4fa419638b0aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,6 @@ members = [ "frame/node-authorization", "frame/offences", "frame/offences/benchmarking", - "frame/paymaster", "frame/preimage", "frame/proxy", "frame/message-queue", @@ -134,6 +133,7 @@ members = [ "frame/recovery", "frame/referenda", "frame/remark", + "frame/salary", "frame/scheduler", "frame/scored-pool", "frame/session", diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 180ebc0c9ed60..812a384fff815 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -132,7 +132,7 @@ impl RankedMembers for TestClub { impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; - type Salary = TestPay; + type Paymaster = TestPay; type Members = TestClub; type ActiveSalaryForRank = Identity; type CyclePeriod = ConstU64<4>; From 712118bc2e3367966029a0111370ece15a71cde0 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 13 Feb 2023 14:54:09 +0000 Subject: [PATCH 08/79] Revert old changes --- frame/support/src/traits/preimages.rs | 35 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/frame/support/src/traits/preimages.rs b/frame/support/src/traits/preimages.rs index 594532ba96903..ce3537c792c08 100644 --- a/frame/support/src/traits/preimages.rs +++ b/frame/support/src/traits/preimages.rs @@ -26,6 +26,9 @@ use sp_std::borrow::Cow; pub type Hash = H256; pub type BoundedInline = crate::BoundedVec>; +/// The maximum we expect a single legacy hash lookup to be. +const MAX_LEGACY_LEN: u32 = 1_000_000; + #[derive( Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, )] @@ -67,20 +70,25 @@ impl Bounded { /// Returns the hash of the preimage. /// /// The hash is re-calculated every time if the preimage is inlined. - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> Hash { use Bounded::*; match self { - Legacy { hash, .. } => *hash, + Lookup { hash, .. } | Legacy { hash, .. } => *hash, Inline(x) => blake2_256(x.as_ref()).into(), - Lookup { hash, .. } => *hash, } } -} -// The maximum we expect a single legacy hash lookup to be. -const MAX_LEGACY_LEN: u32 = 1_000_000; + /// Returns the hash to lookup the preimage. + /// + /// If this is a `Bounded::Inline`, `None` is returned as no lookup is required. + pub fn lookup_hash(&self) -> Option { + use Bounded::*; + match self { + Lookup { hash, .. } | Legacy { hash, .. } => Some(*hash), + Inline(_) => None, + } + } -impl Bounded { /// Returns the length of the preimage or `None` if the length is unknown. pub fn len(&self) -> Option { match self { @@ -168,8 +176,11 @@ pub trait QueryPreimage { } } - /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. This may not - /// be `peek`-able or `realize`-able. + /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. + /// + /// It also directly requests the given `hash` using [`Self::request`]. + /// + /// This may not be `peek`-able or `realize`-able. fn pick(hash: Hash, len: u32) -> Bounded { Self::request(&hash); Bounded::Lookup { hash, len } @@ -228,10 +239,12 @@ pub trait StorePreimage: QueryPreimage { Self::unrequest(hash) } - /// Convert an otherwise unbounded or large value into a type ready for placing in storage. The - /// result is a type whose `MaxEncodedLen` is 131 bytes. + /// Convert an otherwise unbounded or large value into a type ready for placing in storage. + /// + /// The result is a type whose `MaxEncodedLen` is 131 bytes. /// /// NOTE: Once this API is used, you should use either `drop` or `realize`. + /// The value is also noted using [`Self::note`]. fn bound(t: T) -> Result, DispatchError> { let data = t.encode(); let len = data.len() as u32; From 07b785c4c99f17071d815d37cf9ca1489d326860 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 15 Feb 2023 13:20:42 +0000 Subject: [PATCH 09/79] Multi-phase payouts to avoid bank-runs --- .gitignore | 1 + frame/salary/src/lib.rs | 228 ++++++++++++++++++++++++++++---------- frame/salary/src/tests.rs | 52 ++++----- 3 files changed, 196 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index 5cd013e054e4f..efb63520e1e1b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ rls*.log *.bin *.iml scripts/ci/node-template-release/Cargo.lock +substrate.code-workspace diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 3fc393c1f799b..aafe237ce0039 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -23,11 +23,17 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; -use sp_runtime::traits::{AtLeast32BitUnsigned, CheckedSub, Convert}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, CheckedSub, Convert}, + Perbill, +}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, RuntimeDebug, + dispatch::DispatchResultWithPostInfo, + ensure, + traits::{schedule::v3::Anon as ScheduleAnon, RankedMembers}, + RuntimeDebug, }; #[cfg(test)] @@ -47,16 +53,11 @@ pub type Cycle = u32; // XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. - type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo + Debug; + type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo + Debug + Copy; /// The type by which we identify the individuals to whom a payment may be made. type AccountId; /// An identifier given to an individual payment. type Id: FullCodec + MaxEncodedLen + TypeInfo + Clone + Eq + PartialEq + Debug; - /// The amount of currency with which we have to make payments in this period. It may be a fixed - /// value or reduce as calls to `pay` are made. It should be called once prior to the series of - /// payments to determine the overall budget and then not again until the next series of - /// payments are to be made. - fn budget() -> Self::Balance; /// Make a payment and return an identifier for later evaluation of success in some off-chain /// mechanism (likely an event, but possibly not on this chain). fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result; @@ -66,7 +67,17 @@ pub trait Pay { pub struct StatusType { cycle_index: CycleIndex, cycle_start: BlockNumber, - remaining_budget: Balance, + budget: Balance, + total_registrations: Balance, + total_unregistered_paid: Balance, +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ClaimantStatus { + /// The most recent cycle in which the claimant was active. + last_active: CycleIndex, + /// The amount reserved in `last_active` cycle, or `None` if paid. + unpaid: Option, } #[frame_support::pallet] @@ -101,39 +112,55 @@ pub mod pallet { ::Balance, >; - /// The number of blocks between sequential payout cycles. + /// The number of block within a cycle which accounts have to register their intent to + /// claim. + /// + /// The number of blocks between sequential payout cycles is the sum of this and + /// `PayoutPeriod`. + #[pallet::constant] + type RegistrationPeriod: Get; + + /// The number of block within a cycle which accounts have to claim the payout. + /// + /// The number of blocks between sequential payout cycles is the sum of this and + /// `RegistrationPeriod`. + #[pallet::constant] + type PayoutPeriod: Get; + + /// The total budget per cycle. + /// + /// This may change over the course of a cycle without any problem. #[pallet::constant] - type CyclePeriod: Get; + type Budget: Get>; } pub type CycleIndexOf = ::BlockNumber; - pub type StatusOf = StatusType< - CycleIndexOf, - ::BlockNumber, - <>::Paymaster as Pay>::Balance, - >; - - /// The current payout cycle, the block nunber at which it started and the remaining balance in - /// this cycle's budget. + pub type BalanceOf = <>::Paymaster as Pay>::Balance; + pub type StatusOf = + StatusType, ::BlockNumber, BalanceOf>; + pub type ClaimantStatusOf = ClaimantStatus, BalanceOf>; + + /// The overall status of the system. #[pallet::storage] pub(super) type Status, I: 'static = ()> = StorageValue<_, StatusOf, OptionQuery>; - /// The most recent cycle index which was paid to a member. None implies that a member has not - /// yet been paid. + /// The status of a claimant. #[pallet::storage] - pub(super) type LastClaim, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, CycleIndexOf, OptionQuery>; + pub(super) type Claimant, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, ClaimantStatusOf, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { /// A member is inducted into the payroll. Inducted { who: T::AccountId }, + /// The next cycle begins. + Registered { who: T::AccountId, amount: BalanceOf }, /// A payment happened. - Paid { who: T::AccountId, id: ::Id }, + Paid { who: T::AccountId, amount: BalanceOf, id: ::Id }, /// The next cycle begins. - CycleStarted { index: CycleIndexOf, budget: ::Balance }, + CycleStarted { index: CycleIndexOf }, } #[pallet::error] @@ -148,6 +175,10 @@ pub mod pallet { NoClaim, /// The member's claim is zero. ClaimZero, + /// Current cycle's registration period is over. + TooLate, + /// Current cycle's payment period is not yet begun. + TooEarly, /// Cycle is not yet over. NotYet, /// The payout cycles have not yet started. @@ -167,79 +198,156 @@ pub mod pallet { let who = ensure_signed(origin)?; let cycle_index = Status::::get().ok_or(Error::::NotStarted)?.cycle_index; let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - ensure!(!LastClaim::::contains_key(&who), Error::::AlreadyInducted); + ensure!(!Claimant::::contains_key(&who), Error::::AlreadyInducted); - LastClaim::::insert(&who, cycle_index); + Claimant::::insert( + &who, + ClaimantStatus { last_active: cycle_index, unpaid: None }, + ); Self::deposit_event(Event::::Inducted { who }); Ok(Pays::No.into()) } - /// Request a payout. + /// Move to next payout cycle, assuming that the present block is now within that cycle. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] #[pallet::call_index(1)] - pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn bump(origin: OriginFor) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + let cycle_period = T::RegistrationPeriod::get() + T::PayoutPeriod::get(); + let mut status = match Status::::get() { + // Not first time... (move along start block and bump index) + Some(mut status) => { + status.cycle_start.saturating_accrue(cycle_period); + ensure!(now >= status.cycle_start, Error::::NotYet); + status.cycle_index.saturating_inc(); + status.budget = T::Budget::get(); + status.total_registrations = Zero::zero(); + status.total_unregistered_paid = Zero::zero(); + status + }, + // First time... (initialize) + None => StatusType { + cycle_index: Zero::zero(), + cycle_start: now, + budget: T::Budget::get(), + total_registrations: Zero::zero(), + total_unregistered_paid: Zero::zero(), + }, + }; + Status::::put(&status); + + Self::deposit_event(Event::::CycleStarted { index: status.cycle_index }); + Ok(Pays::No.into()) + } + + /// Register for a payout. + /// + /// Will only work if we are in the first `RegistrationPeriod` blocks since the cycle + /// started. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::call_index(2)] + pub fn register(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + let now = frame_system::Pallet::::block_number(); + ensure!( + now < status.cycle_start + T::RegistrationPeriod::get(), + Error::::TooLate + ); + ensure!(claimant.last_active < status.cycle_index, Error::::NoClaim); let payout = T::ActiveSalaryForRank::convert(rank); ensure!(!payout.is_zero(), Error::::ClaimZero); - let last_claim = Self::last_claim(&who)?; - let mut status = Status::::get().ok_or(Error::::NotStarted)?; - ensure!(last_claim < status.cycle_index, Error::::NoClaim); - status.remaining_budget = - status.remaining_budget.checked_sub(&payout).ok_or(Error::::Bankrupt)?; + claimant.last_active = status.cycle_index; + claimant.unpaid = Some(payout); + status.total_registrations.saturating_accrue(payout); let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; - LastClaim::::insert(&who, status.cycle_index); + Claimant::::insert(&who, &claimant); Status::::put(&status); - Self::deposit_event(Event::::Paid { who, id }); + Self::deposit_event(Event::::Registered { who, amount: payout }); Ok(Pays::No.into()) } - /// Move to next payout cycle, assuming that the present block is now within that cycle. + /// Request a payout. + /// + /// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle + /// started but by no more than `PayoutPeriod` blocks. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] - #[pallet::call_index(2)] - pub fn next_cycle(origin: OriginFor) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; + #[pallet::call_index(3)] + pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + let now = frame_system::Pallet::::block_number(); - let mut status = match Status::::get() { - // Not first time... (move along start block and bump index) - Some(mut status) => { - status.cycle_start.saturating_accrue(T::CyclePeriod::get()); - ensure!(now >= status.cycle_start, Error::::NotYet); - status.cycle_index.saturating_inc(); - status - }, - // First time... (initialize) - None => StatusType { - cycle_index: Zero::zero(), - cycle_start: now, - remaining_budget: Zero::zero(), - }, + ensure!( + now >= status.cycle_start + T::RegistrationPeriod::get(), + Error::::TooEarly + ); + + let payout = if let Some(unpaid) = claimant.unpaid { + ensure!(claimant.last_active == status.cycle_index, Error::::NoClaim); + + // Registered for this cycle. Pay accordingly. + if status.total_registrations <= status.budget { + // Can pay in full. + unpaid + } else { + // Must be reduced pro-rata + Perbill::from_rational(status.budget, status.total_registrations) * unpaid + } + } else { + ensure!(claimant.last_active < status.cycle_index, Error::::NoClaim); + + // Not registered for this cycle. Pay from whatever is left. + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let ideal_payout = T::ActiveSalaryForRank::convert(rank); + + let pot = status + .budget + .saturating_sub(status.total_registrations) + .saturating_sub(status.total_unregistered_paid); + + let payout = ideal_payout.min(pot); + ensure!(!payout.is_zero(), Error::::ClaimZero); + + status.total_unregistered_paid.saturating_reduce(payout); + payout }; - status.remaining_budget = T::Paymaster::budget(); + claimant.unpaid = None; + claimant.last_active = status.cycle_index; + + let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; + + Claimant::::insert(&who, &claimant); Status::::put(&status); - Self::deposit_event(Event::::CycleStarted { - index: status.cycle_index, - budget: status.remaining_budget, - }); + Self::deposit_event(Event::::Paid { who, amount: payout, id }); Ok(Pays::No.into()) } } impl, I: 'static> Pallet { + /* pub fn status() -> Option> { Status::::get() } pub fn last_claim(who: &T::AccountId) -> Result, DispatchError> { - LastClaim::::get(&who).ok_or(Error::::NotInducted.into()) + Claimant::::get(&who).ok_or(Error::::NotInducted.into()) } + */ } } diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 812a384fff815..db78c17fe7751 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -82,7 +82,6 @@ impl frame_system::Config for Test { thread_local! { pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); - pub static BUDGET: RefCell = RefCell::new(10u64); pub static LAST_ID: RefCell = RefCell::new(0u64); } @@ -96,10 +95,6 @@ impl Pay for TestPay { type Balance = u64; type Id = u64; - fn budget() -> Self::Balance { - BUDGET.with(|b| *b.borrow()) - } - fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount); Ok(LAST_ID.with(|lid| { @@ -129,13 +124,19 @@ impl RankedMembers for TestClub { } } +parameter_types! { + pub static Budget: u64 = 10; +} + impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type Paymaster = TestPay; type Members = TestClub; type ActiveSalaryForRank = Identity; - type CyclePeriod = ConstU64<4>; + type RegistrationPeriod = ConstU64<2>; + type PayoutPeriod = ConstU64<2>; + type Budget = Budget; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -159,15 +160,15 @@ fn run_to(n: u64) { #[test] fn basic_stuff() { new_test_ext().execute_with(|| { - assert!(Salary::last_claim(&0).is_err()); - assert_eq!(Salary::status(), None); + // assert!(Salary::last_claim(&0).is_err()); + // assert_eq!(Salary::status(), None); }); } - +/* #[test] fn can_start() { new_test_ext().execute_with(|| { - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), Some(StatusType { cycle_index: 0, cycle_start: 1, remaining_budget: 10 }) @@ -176,25 +177,25 @@ fn can_start() { } #[test] -fn next_cycle_works() { +fn bump_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); run_to(4); - assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(5); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), Some(StatusType { cycle_index: 1, cycle_start: 5, remaining_budget: 10 }) ); run_to(8); - assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); BUDGET.with(|b| b.replace(5)); run_to(9); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), Some(StatusType { cycle_index: 2, cycle_start: 9, remaining_budget: 5 }) @@ -205,7 +206,7 @@ fn next_cycle_works() { #[test] fn induct_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); TestClub::change(&1, 1); @@ -221,22 +222,22 @@ fn payment_works() { new_test_ext().execute_with(|| { TestClub::change(&1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(5); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().remaining_budget, 9); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(8); - assert_noop!(Salary::next_cycle(RuntimeOrigin::signed(1)), Error::::NotYet); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 2); assert_eq!(Salary::status().unwrap().remaining_budget, 9); @@ -246,11 +247,11 @@ fn payment_works() { #[test] fn zero_payment_fails() { new_test_ext().execute_with(|| { - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); TestClub::change(&1, 0); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(5); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); }); } @@ -258,12 +259,13 @@ fn zero_payment_fails() { #[test] fn bankrupt_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); TestClub::change(&1, 11); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(5); - assert_ok!(Salary::next_cycle(RuntimeOrigin::signed(1))); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::Bankrupt); assert_eq!(paid(1), 0); }); } +*/ From 7fb95d0b5496d47c496623dbb6708cedbfe9ca6e Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 15 Feb 2023 13:45:17 +0000 Subject: [PATCH 10/79] Tests --- frame/salary/src/lib.rs | 18 +++++---------- frame/salary/src/tests.rs | 47 +++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index aafe237ce0039..55fd8075bf1b3 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -24,16 +24,13 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedSub, Convert}, + traits::{AtLeast32BitUnsigned, Convert}, Perbill, }; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, - ensure, - traits::{schedule::v3::Anon as ScheduleAnon, RankedMembers}, - RuntimeDebug, + dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, RuntimeDebug, }; #[cfg(test)] @@ -218,7 +215,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); let cycle_period = T::RegistrationPeriod::get() + T::PayoutPeriod::get(); - let mut status = match Status::::get() { + let status = match Status::::get() { // Not first time... (move along start block and bump index) Some(mut status) => { status.cycle_start.saturating_accrue(cycle_period); @@ -269,7 +266,6 @@ pub mod pallet { claimant.unpaid = Some(payout); status.total_registrations.saturating_accrue(payout); - let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; Claimant::::insert(&who, &claimant); Status::::put(&status); @@ -323,7 +319,7 @@ pub mod pallet { let payout = ideal_payout.min(pot); ensure!(!payout.is_zero(), Error::::ClaimZero); - status.total_unregistered_paid.saturating_reduce(payout); + status.total_unregistered_paid.saturating_accrue(payout); payout }; @@ -341,13 +337,11 @@ pub mod pallet { } impl, I: 'static> Pallet { - /* pub fn status() -> Option> { Status::::get() } - pub fn last_claim(who: &T::AccountId) -> Result, DispatchError> { - Claimant::::get(&who).ok_or(Error::::NotInducted.into()) + pub fn last_active(who: &T::AccountId) -> Result, DispatchError> { + Ok(Claimant::::get(&who).ok_or(Error::::NotInducted)?.last_active) } - */ } } diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index db78c17fe7751..6d9225b614f02 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -160,18 +160,24 @@ fn run_to(n: u64) { #[test] fn basic_stuff() { new_test_ext().execute_with(|| { - // assert!(Salary::last_claim(&0).is_err()); - // assert_eq!(Salary::status(), None); + assert!(Salary::last_active(&0).is_err()); + assert_eq!(Salary::status(), None); }); } -/* + #[test] fn can_start() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), - Some(StatusType { cycle_index: 0, cycle_start: 1, remaining_budget: 10 }) + Some(StatusType { + cycle_index: 0, + cycle_start: 1, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0, + }) ); }); } @@ -187,7 +193,13 @@ fn bump_works() { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), - Some(StatusType { cycle_index: 1, cycle_start: 5, remaining_budget: 10 }) + Some(StatusType { + cycle_index: 1, + cycle_start: 5, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0 + }) ); run_to(8); @@ -198,7 +210,13 @@ fn bump_works() { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), - Some(StatusType { cycle_index: 2, cycle_start: 9, remaining_budget: 5 }) + Some(StatusType { + cycle_index: 2, + cycle_start: 9, + budget: 5, + total_registrations: 0, + total_unregistered_paid: 0 + }) ); }); } @@ -210,9 +228,9 @@ fn induct_works() { assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); TestClub::change(&1, 1); - assert!(Salary::last_claim(&1).is_err()); + assert!(Salary::last_active(&1).is_err()); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_eq!(Salary::last_claim(&1).unwrap(), 0); + assert_eq!(Salary::last_active(&1).unwrap(), 0); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); }); } @@ -226,24 +244,29 @@ fn payment_works() { assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(3); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - run_to(5); + run_to(6); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(7); assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().remaining_budget, 9); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(8); assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 2); - assert_eq!(Salary::status().unwrap().remaining_budget, 9); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); }); } - +/* #[test] fn zero_payment_fails() { new_test_ext().execute_with(|| { From 5c38814d056d8b31cbe4b2a0da2fd2491f52dfd3 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 16 Feb 2023 11:11:03 +0000 Subject: [PATCH 11/79] Tests --- frame/salary/src/tests.rs | 144 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 6d9225b614f02..ba5e076f548d8 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -236,7 +236,7 @@ fn induct_works() { } #[test] -fn payment_works() { +fn unregistered_payment_works() { new_test_ext().execute_with(|| { TestClub::change(&1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); @@ -266,29 +266,159 @@ fn payment_works() { assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); }); } -/* + +#[test] +fn registered_payment_works() { + new_test_ext().execute_with(|| { + TestClub::change(&1, 1); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(3); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 0); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 2); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + #[test] fn zero_payment_fails() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); TestClub::change(&1, 0); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(5); + run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); }); } #[test] -fn bankrupt_fails_gracefully() { +fn unregistered_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 11); + TestClub::change(&1, 2); + TestClub::change(&2, 6); + TestClub::change(&3, 12); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn registered_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + TestClub::change(&1, 2); + TestClub::change(&2, 6); + TestClub::change(&3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 1); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 6); + }); +} + +#[test] +fn mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + TestClub::change(&1, 2); + TestClub::change(&2, 6); + TestClub::change(&3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + run_to(5); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::Bankrupt); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn other_mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + TestClub::change(&1, 2); + TestClub::change(&2, 6); + TestClub::change(&3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_eq!(paid(1), 0); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 7); }); } -*/ From bfdf333fcb1d95eb343b426ce2216c43e44df7f4 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 16 Feb 2023 11:16:04 +0000 Subject: [PATCH 12/79] Allow payment to be targeted elsewhere --- frame/salary/src/lib.rs | 17 ++++++++++--- frame/salary/src/tests.rs | 53 ++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 55fd8075bf1b3..1cbbebb0f13c8 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -155,7 +155,12 @@ pub mod pallet { /// The next cycle begins. Registered { who: T::AccountId, amount: BalanceOf }, /// A payment happened. - Paid { who: T::AccountId, amount: BalanceOf, id: ::Id }, + Paid { + who: T::AccountId, + beneficiary: T::AccountId, + amount: BalanceOf, + id: ::Id, + }, /// The next cycle begins. CycleStarted { index: CycleIndexOf }, } @@ -281,7 +286,10 @@ pub mod pallet { /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] #[pallet::call_index(3)] - pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn payout( + origin: OriginFor, + beneficiary: T::AccountId, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let mut status = Status::::get().ok_or(Error::::NotStarted)?; @@ -326,12 +334,13 @@ pub mod pallet { claimant.unpaid = None; claimant.last_active = status.cycle_index; - let id = T::Paymaster::pay(&who, payout).map_err(|()| Error::::PayError)?; + let id = + T::Paymaster::pay(&beneficiary, payout).map_err(|()| Error::::PayError)?; Claimant::::insert(&who, &claimant); Status::::put(&status); - Self::deposit_event(Event::::Paid { who, amount: payout, id }); + Self::deposit_event(Event::::Paid { who, beneficiary, amount: payout, id }); Ok(Pays::No.into()) } } diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index ba5e076f548d8..0f8f6b4ad1ac5 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -241,28 +241,29 @@ fn unregistered_payment_works() { TestClub::change(&1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::TooEarly); run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); run_to(6); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::TooEarly); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); run_to(8); assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 2); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); }); } @@ -273,22 +274,22 @@ fn registered_payment_works() { TestClub::change(&1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); run_to(5); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_ok!(Salary::register(RuntimeOrigin::signed(1))); assert_eq!(Salary::status().unwrap().total_registrations, 1); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); @@ -296,7 +297,7 @@ fn registered_payment_works() { assert_ok!(Salary::register(RuntimeOrigin::signed(1))); assert_eq!(Salary::status().unwrap().total_registrations, 1); run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); assert_eq!(paid(1), 2); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); }); @@ -310,7 +311,7 @@ fn zero_payment_fails() { assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::ClaimZero); }); } @@ -328,9 +329,9 @@ fn unregistered_bankrupcy_fails_gracefully() { run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); assert_eq!(paid(1), 2); assert_eq!(paid(2), 6); @@ -357,9 +358,9 @@ fn registered_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(3))); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); assert_eq!(paid(1), 1); assert_eq!(paid(2), 3); @@ -385,9 +386,9 @@ fn mixed_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(2))); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); assert_eq!(paid(1), 2); assert_eq!(paid(2), 6); @@ -413,9 +414,9 @@ fn other_mixed_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(3))); run_to(7); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::ClaimZero); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); assert_eq!(paid(1), 0); assert_eq!(paid(2), 3); From 807a3c99c598b06b0e388ccc7361acdf4ad54ab1 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 16 Feb 2023 18:46:44 +0000 Subject: [PATCH 13/79] Proper ssync payment failure handling --- frame/salary/src/lib.rs | 103 ++++++++++++++++++++++++-------------- frame/salary/src/tests.rs | 7 +++ 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 1cbbebb0f13c8..ba44b66b69464 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -46,6 +46,14 @@ pub use weights::WeightInfo; /// Payroll cycle. pub type Cycle = u32; +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum PaymentStatus { + InProgress, + Unknown, + Success, + Failure, +} + // Can be implemented by Pot pallet with a fixed Currency impl, but can also be implemented with // XCM/MultiAsset and made generic over assets. pub trait Pay { @@ -54,10 +62,13 @@ pub trait Pay { /// The type by which we identify the individuals to whom a payment may be made. type AccountId; /// An identifier given to an individual payment. - type Id: FullCodec + MaxEncodedLen + TypeInfo + Clone + Eq + PartialEq + Debug; + type Id: FullCodec + MaxEncodedLen + TypeInfo + Clone + Eq + PartialEq + Debug + Copy; /// Make a payment and return an identifier for later evaluation of success in some off-chain /// mechanism (likely an event, but possibly not on this chain). fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result; + /// Check how a payment has proceeded. `id` must have been a previously returned by `pay` for + /// the result of this call to be meaningful. + fn check_payment(id: Self::Id) -> PaymentStatus; } #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] @@ -70,11 +81,22 @@ pub struct StatusType { } #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] -pub struct ClaimantStatus { +pub enum ClaimStatus { + /// No claim recorded. + Nothing, + /// Amount reserved when last active. + Registered(Balance), + /// Amount attempted to be paid when last active as well as the identity of the payment. + Attempted { amount: Balance, id: Id }, +} + +use ClaimStatus::*; + +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ClaimantStatus { /// The most recent cycle in which the claimant was active. last_active: CycleIndex, - /// The amount reserved in `last_active` cycle, or `None` if paid. - unpaid: Option, + status: ClaimStatus, } #[frame_support::pallet] @@ -133,9 +155,10 @@ pub mod pallet { pub type CycleIndexOf = ::BlockNumber; pub type BalanceOf = <>::Paymaster as Pay>::Balance; + pub type IdOf = <>::Paymaster as Pay>::Id; pub type StatusOf = StatusType, ::BlockNumber, BalanceOf>; - pub type ClaimantStatusOf = ClaimantStatus, BalanceOf>; + pub type ClaimantStatusOf = ClaimantStatus, BalanceOf, IdOf>; /// The overall status of the system. #[pallet::storage] @@ -204,7 +227,7 @@ pub mod pallet { Claimant::::insert( &who, - ClaimantStatus { last_active: cycle_index, unpaid: None }, + ClaimantStatus { last_active: cycle_index, status: Nothing }, ); Self::deposit_event(Event::::Inducted { who }); @@ -268,7 +291,7 @@ pub mod pallet { let payout = T::ActiveSalaryForRank::convert(rank); ensure!(!payout.is_zero(), Error::::ClaimZero); claimant.last_active = status.cycle_index; - claimant.unpaid = Some(payout); + claimant.status = Registered(payout); status.total_registrations.saturating_accrue(payout); Claimant::::insert(&who, &claimant); @@ -298,45 +321,51 @@ pub mod pallet { let now = frame_system::Pallet::::block_number(); ensure!( now >= status.cycle_start + T::RegistrationPeriod::get(), - Error::::TooEarly + Error::::TooEarly, ); - let payout = if let Some(unpaid) = claimant.unpaid { - ensure!(claimant.last_active == status.cycle_index, Error::::NoClaim); - - // Registered for this cycle. Pay accordingly. - if status.total_registrations <= status.budget { - // Can pay in full. - unpaid - } else { - // Must be reduced pro-rata - Perbill::from_rational(status.budget, status.total_registrations) * unpaid - } - } else { - ensure!(claimant.last_active < status.cycle_index, Error::::NoClaim); - - // Not registered for this cycle. Pay from whatever is left. - let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - let ideal_payout = T::ActiveSalaryForRank::convert(rank); - - let pot = status - .budget - .saturating_sub(status.total_registrations) - .saturating_sub(status.total_unregistered_paid); - - let payout = ideal_payout.min(pot); - ensure!(!payout.is_zero(), Error::::ClaimZero); - - status.total_unregistered_paid.saturating_accrue(payout); - payout + let payout = match claimant.status { + Attempted { amount, id } if claimant.last_active == status.cycle_index => { + let failed = matches!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + ensure!(failed, Error::::NoClaim); + amount + }, + Registered(unpaid) if claimant.last_active == status.cycle_index => { + // Registered for this cycle. Pay accordingly. + if status.total_registrations <= status.budget { + // Can pay in full. + unpaid + } else { + // Must be reduced pro-rata + Perbill::from_rational(status.budget, status.total_registrations) * unpaid + } + }, + Nothing | Attempted { .. } if claimant.last_active < status.cycle_index => { + // Not registered for this cycle. Pay from whatever is left. + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let ideal_payout = T::ActiveSalaryForRank::convert(rank); + + let pot = status + .budget + .saturating_sub(status.total_registrations) + .saturating_sub(status.total_unregistered_paid); + + let payout = ideal_payout.min(pot); + ensure!(!payout.is_zero(), Error::::ClaimZero); + + status.total_unregistered_paid.saturating_accrue(payout); + payout + }, + _ => return Err(Error::::NoClaim.into()), }; - claimant.unpaid = None; claimant.last_active = status.cycle_index; let id = T::Paymaster::pay(&beneficiary, payout).map_err(|()| Error::::PayError)?; + claimant.status = Attempted { amount: payout, id }; + Claimant::::insert(&who, &claimant); Status::::put(&status); diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 0f8f6b4ad1ac5..94225e1f09f7e 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -82,12 +82,16 @@ impl frame_system::Config for Test { thread_local! { pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); + pub static STATUS: RefCell> = RefCell::new(BTreeMap::new()); pub static LAST_ID: RefCell = RefCell::new(0u64); } fn paid(who: u64) -> u64 { PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) } +fn set_status(id: u64, s: PaymentStatus) { + STATUS.with(|m| m.borrow_mut().insert(id, s)); +} pub struct TestPay; impl Pay for TestPay { @@ -103,6 +107,9 @@ impl Pay for TestPay { x })) } + fn check_payment(id: Self::Id) -> PaymentStatus { + STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown)) + } } thread_local! { From cf1f36540a884b8bdbbf428a0cbbfc7da8d55a62 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 22 Feb 2023 15:27:32 +0000 Subject: [PATCH 14/79] Test for repayment --- frame/salary/src/tests.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 94225e1f09f7e..045a32494cb1d 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -89,6 +89,9 @@ thread_local! { fn paid(who: u64) -> u64 { PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) } +fn unpay(who: u64, amount: u64) { + PAID.with(|p| p.borrow_mut().entry(who).or_default().saturating_reduce(amount)) +} fn set_status(id: u64, s: PaymentStatus) { STATUS.with(|m| m.borrow_mut().insert(id, s)); } @@ -275,6 +278,40 @@ fn unregistered_payment_works() { }); } +#[test] +fn retry_payment_works() { + new_test_ext().execute_with(|| { + TestClub::change(&1, 1); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + #[test] fn registered_payment_works() { new_test_ext().execute_with(|| { From d82907801391078ba04385165906d18e6c3b7899 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 22 Feb 2023 18:33:35 +0000 Subject: [PATCH 15/79] Docs --- frame/salary/src/lib.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index ba44b66b69464..d7dcf062a78c4 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -46,11 +46,16 @@ pub use weights::WeightInfo; /// Payroll cycle. pub type Cycle = u32; +/// Status for making a payment via the `Pay::pay` trait function. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub enum PaymentStatus { + /// Payment is in progress. Nothing to report yet. InProgress, + /// Payment status is unknowable. It will never be reported successful or failed. Unknown, + /// Payment happened successfully. Success, + /// Payment failed. It may safely be retried. Failure, } @@ -71,17 +76,24 @@ pub trait Pay { fn check_payment(id: Self::Id) -> PaymentStatus; } +/// The status of the pallet instance. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct StatusType { + /// The index of the "current cycle" (i.e. the last cycle being processed). cycle_index: CycleIndex, + /// The first block of the "current cycle" (i.e. the last cycle being processed) cycle_start: BlockNumber, + /// The total budget available for all payments in the current cycle. budget: Balance, + /// The total amount of the payments registered in the current cycle. total_registrations: Balance, + /// The total amount of unregistered payments which have been made in the current cycle. total_unregistered_paid: Balance, } +/// The state of a specific payment claim. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] -pub enum ClaimStatus { +pub enum ClaimState { /// No claim recorded. Nothing, /// Amount reserved when last active. @@ -90,13 +102,15 @@ pub enum ClaimStatus { Attempted { amount: Balance, id: Id }, } -use ClaimStatus::*; +use ClaimState::*; +/// The status of a single payee/claimant. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct ClaimantStatus { /// The most recent cycle in which the claimant was active. last_active: CycleIndex, - status: ClaimStatus, + /// The state of the payment/claim with in the above cycle. + status: ClaimState, } #[frame_support::pallet] From 7162964df9835ce60847f5bedacb5daef823fc87 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 08:30:48 +0100 Subject: [PATCH 16/79] Impl RankedMembers for RankedCollective --- frame/ranked-collective/src/lib.rs | 77 ++++++++++++++++++++--------- frame/support/src/traits/members.rs | 21 +++++--- primitives/arithmetic/src/traits.rs | 14 ++++++ 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 36c595d3c08bf..f33312c08ba8d 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -54,7 +54,7 @@ use frame_support::{ codec::{Decode, Encode, MaxEncodedLen}, dispatch::{DispatchError, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, - traits::{EnsureOrigin, PollStatus, Polling, VoteTally}, + traits::{EnsureOrigin, PollStatus, Polling, RankedMembers, VoteTally}, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -465,24 +465,7 @@ pub mod pallet { pub fn demote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::DemoteOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; - let mut record = Self::ensure_member(&who)?; - let rank = record.rank; - ensure!(max_rank >= rank, Error::::NoPermission); - - Self::remove_from_rank(&who, rank)?; - let maybe_rank = rank.checked_sub(1); - match maybe_rank { - None => { - Members::::remove(&who); - Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); - }, - Some(rank) => { - record.rank = rank; - Members::::insert(&who, &record); - Self::deposit_event(Event::RankChanged { who, rank }); - }, - } - Ok(()) + Self::do_demote_member(who, Some(max_rank)) } /// Remove the member entirely. @@ -652,7 +635,7 @@ pub mod pallet { Ok(()) } - /// Promotes a member in the ranked collective into the next role. + /// Promotes a member in the ranked collective into the next higher rank. /// /// A `maybe_max_rank` may be provided to check that the member does not get promoted beyond /// a certain rank. Is `None` is provided, then the rank will be incremented without checks. @@ -674,6 +657,33 @@ pub mod pallet { Ok(()) } + /// Demotes a member in the ranked collective into the next lower rank. + /// + /// A `maybe_max_rank` may be provided to check that the member does not get demoted from + /// a certain rank. Is `None` is provided, then the rank will be decremented without checks. + fn do_demote_member(who: T::AccountId, maybe_max_rank: Option) -> DispatchResult { + let mut record = Self::ensure_member(&who)?; + let rank = record.rank; + if let Some(max_rank) = maybe_max_rank { + ensure!(max_rank >= rank, Error::::NoPermission); + } + + Self::remove_from_rank(&who, rank)?; + let maybe_rank = rank.checked_sub(1); + match maybe_rank { + None => { + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); + }, + Some(rank) => { + record.rank = rank; + Members::::insert(&who, &record); + Self::deposit_event(Event::RankChanged { who, rank }); + }, + } + Ok(()) + } + /// Add a member to the rank collective, and continue to promote them until a certain rank /// is reached. pub fn do_add_member_to_rank(who: T::AccountId, rank: Rank) -> DispatchResult { @@ -684,6 +694,29 @@ pub mod pallet { Ok(()) } } -} -// TODO: Impl `RankedMembers` for this pallet. + impl, I: 'static> RankedMembers for Pallet { + type AccountId = T::AccountId; + type Rank = Rank; + + fn lowest_rank() -> Self::Rank { + 0 + } + + fn rank_of(who: &Self::AccountId) -> Option { + Some(Self::ensure_member(&who).ok()?.rank) + } + + fn induct(who: &Self::AccountId) -> DispatchResult { + Self::do_add_member(who.clone()) + } + + fn promote(who: &Self::AccountId) -> DispatchResult { + Self::do_promote_member(who.clone(), None) + } + + fn demote(who: &Self::AccountId) -> DispatchResult { + Self::do_demote_member(who.clone(), None) + } + } +} diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index 82540f4249e07..130258d8e47c8 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -18,7 +18,8 @@ //! Traits for dealing with the idea of membership. use impl_trait_for_tuples::impl_for_tuples; -use sp_arithmetic::traits::AtLeast32BitUnsigned; +use sp_arithmetic::traits::AtLeast16BitUnsigned; +use sp_runtime::DispatchResult; use sp_std::{marker::PhantomData, prelude::*}; /// A trait for querying whether a type can be said to "contain" a value. @@ -269,15 +270,23 @@ pub trait ContainsLengthBound { /// Ranked membership data structure. pub trait RankedMembers { type AccountId; - type Rank: AtLeast32BitUnsigned; + type Rank: AtLeast16BitUnsigned; + + /// The lowest rank possible in this membership organisation. + fn lowest_rank() -> Self::Rank; /// Return the rank of the given ID, or `None` if they are not a member. fn rank_of(who: &Self::AccountId) -> Option; - /// Remove a member from the group. This does not result in a call to `removed`. - fn remove(who: &Self::AccountId); - /// Change a member's rank. This does not result in a call to `changed`. - fn change(who: &Self::AccountId, rank: Self::Rank); + /// Add a member to the group at the `lowest_rank()`. + fn induct(who: &Self::AccountId) -> DispatchResult; + + /// Promote a member to the next higher rank. + fn promote(who: &Self::AccountId) -> DispatchResult; + + /// Demote a member to the next lower rank; demoting beyond the `lowest_rank` removes the + /// member entirely. + fn demote(who: &Self::AccountId) -> DispatchResult; } /// Trait for type that can handle the initialization of account IDs at genesis. diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index dfba046754373..c0b9c7d11fee8 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -148,6 +148,20 @@ impl< { } +/// A meta trait for arithmetic. +/// +/// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to +/// be able to represent at least `u16` values without loss, hence the trait implies `From` +/// and smaller integers. All other conversions are fallible. +pub trait AtLeast16Bit: BaseArithmetic + From + From {} + +impl + From> AtLeast16Bit for T {} + +/// A meta trait for arithmetic. Same as [`AtLeast16Bit `], but also bounded to be unsigned. +pub trait AtLeast16BitUnsigned: AtLeast16Bit + Unsigned {} + +impl AtLeast16BitUnsigned for T {} + /// A meta trait for arithmetic. /// /// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to From 2e299f4271d3a363b767a2c0c480cfafc16fe1c1 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 09:07:46 +0100 Subject: [PATCH 17/79] Implement Pay for Pot (i.e. basic account). --- frame/ranked-collective/src/lib.rs | 2 +- frame/salary/src/lib.rs | 20 ++++++++- frame/salary/src/tests.rs | 67 ++++++++++++++++++++--------- frame/support/src/traits/members.rs | 6 +-- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index f33312c08ba8d..3ce77b86fd087 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -699,7 +699,7 @@ pub mod pallet { type AccountId = T::AccountId; type Rank = Rank; - fn lowest_rank() -> Self::Rank { + fn min_rank() -> Self::Rank { 0 } diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index d7dcf062a78c4..263e201b61866 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -23,6 +23,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; +use sp_core::TypedGet; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert}, Perbill, @@ -30,7 +31,10 @@ use sp_runtime::{ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, ensure, traits::RankedMembers, RuntimeDebug, + dispatch::DispatchResultWithPostInfo, + ensure, + traits::{tokens::fungible, RankedMembers}, + RuntimeDebug, }; #[cfg(test)] @@ -76,6 +80,20 @@ pub trait Pay { fn check_payment(id: Self::Id) -> PaymentStatus; } +pub struct Pot(sp_std::marker::PhantomData<(F, A)>); +impl> Pay for Pot { + type Balance = F::Balance; + type AccountId = A::Type; + type Id = (); + fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { + F::transfer(&A::get(), who, amount, false).map_err(|_| ())?; + Ok(()) + } + fn check_payment(_: ()) -> PaymentStatus { + PaymentStatus::Success + } +} + /// The status of the pallet instance. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct StatusType { diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 045a32494cb1d..3f123682118c9 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -29,6 +29,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, Identity, IdentityLookup}, + DispatchResult, }; use sp_std::cell::RefCell; @@ -123,17 +124,41 @@ pub struct TestClub; impl RankedMembers for TestClub { type AccountId = u64; type Rank = u64; + fn min_rank() -> Self::Rank { + 0 + } fn rank_of(who: &Self::AccountId) -> Option { CLUB.with(|club| club.borrow().get(who).cloned()) } - fn remove(who: &Self::AccountId) { - CLUB.with(|club| club.borrow_mut().remove(&who)); + fn induct(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| club.borrow_mut().insert(*who, 0)); + Ok(()) + } + fn promote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| { + club.borrow_mut().entry(*who).and_modify(|r| *r += 1); + }); + Ok(()) } - fn change(who: &Self::AccountId, rank: Self::Rank) { - CLUB.with(|club| club.borrow_mut().insert(*who, rank)); + fn demote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| match club.borrow().get(who) { + None => Err(sp_runtime::DispatchError::Unavailable), + Some(&0) => { + club.borrow_mut().remove(&who); + Ok(()) + }, + Some(_) => { + club.borrow_mut().entry(*who).and_modify(|x| *x -= 1); + Ok(()) + }, + }) } } +fn set_rank(who: u64, rank: u64) { + CLUB.with(|club| club.borrow_mut().insert(who, rank)); +} + parameter_types! { pub static Budget: u64 = 10; } @@ -237,7 +262,7 @@ fn induct_works() { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); - TestClub::change(&1, 1); + set_rank(1, 1); assert!(Salary::last_active(&1).is_err()); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); assert_eq!(Salary::last_active(&1).unwrap(), 0); @@ -248,7 +273,7 @@ fn induct_works() { #[test] fn unregistered_payment_works() { new_test_ext().execute_with(|| { - TestClub::change(&1, 1); + set_rank(1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); @@ -281,7 +306,7 @@ fn unregistered_payment_works() { #[test] fn retry_payment_works() { new_test_ext().execute_with(|| { - TestClub::change(&1, 1); + set_rank(1, 1); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(6); @@ -315,7 +340,7 @@ fn retry_payment_works() { #[test] fn registered_payment_works() { new_test_ext().execute_with(|| { - TestClub::change(&1, 1); + set_rank(1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); @@ -351,7 +376,7 @@ fn registered_payment_works() { fn zero_payment_fails() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 0); + set_rank(1, 0); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); @@ -363,9 +388,9 @@ fn zero_payment_fails() { fn unregistered_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 2); - TestClub::change(&2, 6); - TestClub::change(&3, 12); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); @@ -387,9 +412,9 @@ fn unregistered_bankrupcy_fails_gracefully() { fn registered_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 2); - TestClub::change(&2, 6); - TestClub::change(&3, 12); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); @@ -416,9 +441,9 @@ fn registered_bankrupcy_fails_gracefully() { fn mixed_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 2); - TestClub::change(&2, 6); - TestClub::change(&3, 12); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); @@ -444,9 +469,9 @@ fn mixed_bankrupcy_fails_gracefully() { fn other_mixed_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - TestClub::change(&1, 2); - TestClub::change(&2, 6); - TestClub::change(&3, 12); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index 130258d8e47c8..c93217e8b8ab6 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -273,18 +273,18 @@ pub trait RankedMembers { type Rank: AtLeast16BitUnsigned; /// The lowest rank possible in this membership organisation. - fn lowest_rank() -> Self::Rank; + fn min_rank() -> Self::Rank; /// Return the rank of the given ID, or `None` if they are not a member. fn rank_of(who: &Self::AccountId) -> Option; - /// Add a member to the group at the `lowest_rank()`. + /// Add a member to the group at the `min_rank()`. fn induct(who: &Self::AccountId) -> DispatchResult; /// Promote a member to the next higher rank. fn promote(who: &Self::AccountId) -> DispatchResult; - /// Demote a member to the next lower rank; demoting beyond the `lowest_rank` removes the + /// Demote a member to the next lower rank; demoting beyond the `min_rank` removes the /// member entirely. fn demote(who: &Self::AccountId) -> DispatchResult; } From 5995eebd9b10712a246bdb74bc75f2aef92f38df Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 17:41:23 +0100 Subject: [PATCH 18/79] Benchmarks --- frame/salary/src/benchmarking.rs | 172 ++++++++++++++++++++--- frame/salary/src/lib.rs | 215 ++++++++++++++++++++--------- frame/salary/src/tests.rs | 225 +++++++++++++++++++++++++------ 3 files changed, 496 insertions(+), 116 deletions(-) diff --git a/frame/salary/src/benchmarking.rs b/frame/salary/src/benchmarking.rs index a26151654f256..0d609ab3374f4 100644 --- a/frame/salary/src/benchmarking.rs +++ b/frame/salary/src/benchmarking.rs @@ -15,31 +15,169 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Staking pallet benchmarking. +//! Salary pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] use super::*; -#[allow(unused_imports)] -use crate::Pallet as RankedCollective; +use crate::Pallet as Salary; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; -use frame_support::{assert_ok, dispatch::UnfilteredDispatchable}; -use frame_system::RawOrigin as SystemOrigin; +use frame_benchmarking::v2::*; +use frame_system::{Pallet as System, RawOrigin}; +use sp_core::Get; const SEED: u32 = 0; -fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { - frame_system::Pallet::::assert_last_event(generic_event.into()); +fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { + // induct if not a member. + if T::Members::rank_of(who).is_none() { + T::Members::induct(who).unwrap(); + } + // promote until they have a salary. + for _ in 0..255 { + let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); + if !T::ActiveSalaryForRank::convert(r).is_zero() { + break + } + T::Members::promote(who).unwrap(); + } } -benchmarks_instance_pallet! { - add_member { - let origin = T::PromoteOrigin::successful_origin(); - let call = Call::::add_member { }; - }: { call.dispatch_bypass_filter(origin)? } - verify { - assert_eq!(MemberCount::::get(0), 1); - assert_last_event::(Event::MemberAdded { who }.into()); +#[instance_benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn init() { + let caller: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::status().is_some()); + } + + #[benchmark] + fn bump() { + let caller: T::AccountId = whitelisted_caller(); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::status().unwrap().cycle_index, 1u32.into()); + } + + #[benchmark] + fn induct() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::last_active(&caller).is_ok()); + } + + #[benchmark] + fn register() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::last_active(&caller).unwrap(), 1u32.into()); } - impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); + #[benchmark] + fn payout() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + T::Paymaster::ensure_successful(&caller, salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn payout_other() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), recipient.clone()); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn check_payment() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, salary); + Salary::::payout(RawOrigin::Signed(caller.clone()).into()).unwrap(); + let id = match Claimant::::get(&caller).unwrap().status { + Attempted { id, .. } => id, + _ => panic!("No claim made"), + }; + T::Paymaster::ensure_concluded(id); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(!matches!(Claimant::::get(&caller).unwrap().status, Attempted { .. })); + } + + impl_benchmark_test_suite! { + Salary, + crate::tests::new_test_ext(), + crate::tests::Test, + } } diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 263e201b61866..6284c3fa72e89 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -78,20 +78,37 @@ pub trait Pay { /// Check how a payment has proceeded. `id` must have been a previously returned by `pay` for /// the result of this call to be meaningful. fn check_payment(id: Self::Id) -> PaymentStatus; + /// Ensure that a call to pay with the given parameters will be successful if done immediately + /// after this call. Used in benchmarking code. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &Self::AccountId, amount: Self::Balance); + /// Ensure that a call to [check_payment] with the given parameters will return either [Success] + /// or [Failure]. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id); } -pub struct Pot(sp_std::marker::PhantomData<(F, A)>); -impl> Pay for Pot { +/// Simple implementation of `Pay` which makes a payment from a "pot" - i.e. a single account. +pub struct PayFromAccount(sp_std::marker::PhantomData<(F, A)>); +impl + fungible::Mutate> Pay + for PayFromAccount +{ type Balance = F::Balance; type AccountId = A::Type; type Id = (); fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { - F::transfer(&A::get(), who, amount, false).map_err(|_| ())?; + >::transfer(&A::get(), who, amount, false).map_err(|_| ())?; Ok(()) } fn check_payment(_: ()) -> PaymentStatus { PaymentStatus::Success } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::AccountId, amount: Self::Balance) { + >::mint_into(&A::get(), amount).unwrap(); + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(_: Self::Id) {} } /// The status of the pallet instance. @@ -117,7 +134,7 @@ pub enum ClaimState { /// Amount reserved when last active. Registered(Balance), /// Amount attempted to be paid when last active as well as the identity of the payment. - Attempted { amount: Balance, id: Id }, + Attempted { registered: Option, id: Id, amount: Balance }, } use ClaimState::*; @@ -158,6 +175,8 @@ pub mod pallet { type Members: RankedMembers::AccountId>; /// The maximum payout to be made for a single period to an active member of the given rank. + /// + /// The benchmarks require that this be non-zero for some rank at most 255. type ActiveSalaryForRank: Convert< ::Rank, ::Balance, @@ -222,6 +241,8 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The salary system has already been started. + AlreadyStarted, /// The account is not a ranked member. NotMember, /// The account is already inducted. @@ -244,25 +265,33 @@ pub mod pallet { Bankrupt, /// There was some issue with the mechanism of payment. PayError, + /// The payment has neither failed nor succeeded yet. + Inconclusive, + /// The cycle is after that in which the payment was made. + NotCurrent, } #[pallet::call] impl, I: 'static> Pallet { - /// Induct oneself into the payout system. + /// Start the first payout cycle. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] #[pallet::call_index(0)] - pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let cycle_index = Status::::get().ok_or(Error::::NotStarted)?.cycle_index; - let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - ensure!(!Claimant::::contains_key(&who), Error::::AlreadyInducted); - - Claimant::::insert( - &who, - ClaimantStatus { last_active: cycle_index, status: Nothing }, - ); + pub fn init(origin: OriginFor) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + ensure!(!Status::::exists(), Error::::AlreadyStarted); + let status = StatusType { + cycle_index: Zero::zero(), + cycle_start: now, + budget: T::Budget::get(), + total_registrations: Zero::zero(), + total_unregistered_paid: Zero::zero(), + }; + Status::::put(&status); - Self::deposit_event(Event::::Inducted { who }); + Self::deposit_event(Event::::CycleStarted { index: status.cycle_index }); Ok(Pays::No.into()) } @@ -274,33 +303,38 @@ pub mod pallet { pub fn bump(origin: OriginFor) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let cycle_period = T::RegistrationPeriod::get() + T::PayoutPeriod::get(); - let status = match Status::::get() { - // Not first time... (move along start block and bump index) - Some(mut status) => { - status.cycle_start.saturating_accrue(cycle_period); - ensure!(now >= status.cycle_start, Error::::NotYet); - status.cycle_index.saturating_inc(); - status.budget = T::Budget::get(); - status.total_registrations = Zero::zero(); - status.total_unregistered_paid = Zero::zero(); - status - }, - // First time... (initialize) - None => StatusType { - cycle_index: Zero::zero(), - cycle_start: now, - budget: T::Budget::get(), - total_registrations: Zero::zero(), - total_unregistered_paid: Zero::zero(), - }, - }; + let cycle_period = Self::cycle_period(); + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + status.cycle_start.saturating_accrue(cycle_period); + ensure!(now >= status.cycle_start, Error::::NotYet); + status.cycle_index.saturating_inc(); + status.budget = T::Budget::get(); + status.total_registrations = Zero::zero(); + status.total_unregistered_paid = Zero::zero(); Status::::put(&status); Self::deposit_event(Event::::CycleStarted { index: status.cycle_index }); Ok(Pays::No.into()) } + /// Induct oneself into the payout system. + #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::call_index(2)] + pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let cycle_index = Status::::get().ok_or(Error::::NotStarted)?.cycle_index; + let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(!Claimant::::contains_key(&who), Error::::AlreadyInducted); + + Claimant::::insert( + &who, + ClaimantStatus { last_active: cycle_index, status: Nothing }, + ); + + Self::deposit_event(Event::::Inducted { who }); + Ok(Pays::No.into()) + } + /// Register for a payout. /// /// Will only work if we are in the first `RegistrationPeriod` blocks since the cycle @@ -308,7 +342,7 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] - #[pallet::call_index(2)] + #[pallet::call_index(3)] pub fn register(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; @@ -340,13 +374,87 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. #[pallet::weight(T::WeightInfo::add_member())] - #[pallet::call_index(3)] - pub fn payout( + #[pallet::call_index(4)] + pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_payout(who.clone(), who)?; + Ok(Pays::No.into()) + } + + /// Request a payout to a secondary account. + /// + /// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle + /// started but by no more than `PayoutPeriod` blocks. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + /// - `beneficiary`: The account to receive payment. + #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::call_index(5)] + pub fn payout_other( origin: OriginFor, beneficiary: T::AccountId, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + Self::do_payout(who, beneficiary)?; + Ok(Pays::No.into()) + } + + /// Update a payment's status; if it failed, alter the state so the payment can be retried. + /// + /// This must be called within the same cycle as the failed payment. It will fail with + /// [Event::NotCurrent] otherwise. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members` who has + /// received a payment this cycle. + #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::call_index(6)] + pub fn check_payment(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + ensure!(claimant.last_active == status.cycle_index, Error::::NotCurrent); + let (id, registered, amount) = match claimant.status { + Attempted { id, registered, amount } => (id, registered, amount), + _ => return Err(Error::::NoClaim.into()), + }; + match T::Paymaster::check_payment(id) { + PaymentStatus::Failure => { + // Payment failed: we reset back to the status prior to payment. + if let Some(amount) = registered { + // Account registered; this makes it simple to roll back and allow retry. + claimant.status = ClaimState::Registered(amount); + } else { + // Account didn't register; we set it to `Nothing` but must decrement + // the `last_active` also to ensure a retry works. + claimant.last_active.saturating_reduce(1u32.into()); + claimant.status = ClaimState::Nothing; + // Since it is not registered, we must walk back our counter for what has + // been paid. + status.total_unregistered_paid.saturating_reduce(amount); + } + }, + PaymentStatus::Success => claimant.status = ClaimState::Nothing, + _ => return Err(Error::::Inconclusive.into()), + } + Claimant::::insert(&who, &claimant); + Status::::put(&status); + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet { + pub fn status() -> Option> { + Status::::get() + } + pub fn last_active(who: &T::AccountId) -> Result, DispatchError> { + Ok(Claimant::::get(&who).ok_or(Error::::NotInducted)?.last_active) + } + pub fn cycle_period() -> T::BlockNumber { + T::RegistrationPeriod::get() + T::PayoutPeriod::get() + } + fn do_payout(who: T::AccountId, beneficiary: T::AccountId) -> DispatchResult { let mut status = Status::::get().ok_or(Error::::NotStarted)?; let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; @@ -356,21 +464,17 @@ pub mod pallet { Error::::TooEarly, ); - let payout = match claimant.status { - Attempted { amount, id } if claimant.last_active == status.cycle_index => { - let failed = matches!(T::Paymaster::check_payment(id), PaymentStatus::Failure); - ensure!(failed, Error::::NoClaim); - amount - }, + let (payout, registered) = match claimant.status { Registered(unpaid) if claimant.last_active == status.cycle_index => { // Registered for this cycle. Pay accordingly. - if status.total_registrations <= status.budget { + let payout = if status.total_registrations <= status.budget { // Can pay in full. unpaid } else { // Must be reduced pro-rata Perbill::from_rational(status.budget, status.total_registrations) * unpaid - } + }; + (payout, Some(unpaid)) }, Nothing | Attempted { .. } if claimant.last_active < status.cycle_index => { // Not registered for this cycle. Pay from whatever is left. @@ -386,7 +490,7 @@ pub mod pallet { ensure!(!payout.is_zero(), Error::::ClaimZero); status.total_unregistered_paid.saturating_accrue(payout); - payout + (payout, None) }, _ => return Err(Error::::NoClaim.into()), }; @@ -396,22 +500,13 @@ pub mod pallet { let id = T::Paymaster::pay(&beneficiary, payout).map_err(|()| Error::::PayError)?; - claimant.status = Attempted { amount: payout, id }; + claimant.status = Attempted { registered, id, amount: payout }; Claimant::::insert(&who, &claimant); Status::::put(&status); Self::deposit_event(Event::::Paid { who, beneficiary, amount: payout, id }); - Ok(Pays::No.into()) - } - } - - impl, I: 'static> Pallet { - pub fn status() -> Option> { - Status::::get() - } - pub fn last_active(who: &T::AccountId) -> Result, DispatchError> { - Ok(Claimant::::get(&who).ok_or(Error::::NotInducted)?.last_active) + Ok(()) } } } diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 3f123682118c9..ab785ea166314 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -114,6 +114,12 @@ impl Pay for TestPay { fn check_payment(id: Self::Id) -> PaymentStatus { STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown)) } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::AccountId, _: Self::Balance) {} + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id) { + set_status(id, PaymentStatus::Failure) + } } thread_local! { @@ -203,7 +209,7 @@ fn basic_stuff() { #[test] fn can_start() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); assert_eq!( Salary::status(), Some(StatusType { @@ -220,7 +226,7 @@ fn can_start() { #[test] fn bump_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); run_to(4); assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); @@ -259,7 +265,7 @@ fn bump_works() { #[test] fn induct_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); set_rank(1, 1); @@ -275,28 +281,28 @@ fn unregistered_payment_works() { new_test_ext().execute_with(|| { set_rank(1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::TooEarly); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(6); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::TooEarly); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(8); assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 10)); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); assert_eq!(paid(1), 1); assert_eq!(paid(10), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); @@ -307,30 +313,171 @@ fn unregistered_payment_works() { fn retry_payment_works() { new_test_ext().execute_with(|| { set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_registered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(6); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); // Payment failed. unpay(1, 1); set_status(0, PaymentStatus::Failure); + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); // Allowed to try again. - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + +#[test] +fn retry_payment_later_is_not_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + // Next cycle. + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + + // Payment did fail but now too late to retry. + assert_noop!(Salary::check_payment(RuntimeOrigin::signed(1)), Error::::NotCurrent); + // We do get this cycle's payout, but we must wait for the payout period to start. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + }); +} + +#[test] +fn retry_payment_later_without_bump_is_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + // Next cycle. + run_to(9); + + // Payment did fail but we can still retry as long as we don't `bump`. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_payment_to_other_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + // Payment failed. + unpay(10, 1); + set_status(0, PaymentStatus::Failure); + + // Can't just retry. + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); run_to(8); assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 10)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); assert_eq!(paid(10), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); @@ -342,23 +489,23 @@ fn registered_payment_works() { new_test_ext().execute_with(|| { set_rank(1, 1); assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NotInducted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); // No claim on the cycle active during induction. assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(5); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); assert_ok!(Salary::register(RuntimeOrigin::signed(1))); assert_eq!(Salary::status().unwrap().total_registrations, 1); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 1); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::NoClaim); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); run_to(9); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); @@ -366,7 +513,7 @@ fn registered_payment_works() { assert_ok!(Salary::register(RuntimeOrigin::signed(1))); assert_eq!(Salary::status().unwrap().total_registrations, 1); run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 2); assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); }); @@ -375,19 +522,19 @@ fn registered_payment_works() { #[test] fn zero_payment_fails() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); set_rank(1, 0); assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::ClaimZero); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); }); } #[test] fn unregistered_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); set_rank(1, 2); set_rank(2, 6); set_rank(3, 12); @@ -398,9 +545,9 @@ fn unregistered_bankrupcy_fails_gracefully() { run_to(7); assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); assert_eq!(paid(1), 2); assert_eq!(paid(2), 6); @@ -411,7 +558,7 @@ fn unregistered_bankrupcy_fails_gracefully() { #[test] fn registered_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); set_rank(1, 2); set_rank(2, 6); set_rank(3, 12); @@ -427,9 +574,9 @@ fn registered_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(3))); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); assert_eq!(paid(1), 1); assert_eq!(paid(2), 3); @@ -440,7 +587,7 @@ fn registered_bankrupcy_fails_gracefully() { #[test] fn mixed_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); set_rank(1, 2); set_rank(2, 6); set_rank(3, 12); @@ -455,9 +602,9 @@ fn mixed_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(2))); run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1), 1)); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); assert_eq!(paid(1), 2); assert_eq!(paid(2), 6); @@ -468,7 +615,7 @@ fn mixed_bankrupcy_fails_gracefully() { #[test] fn other_mixed_bankrupcy_fails_gracefully() { new_test_ext().execute_with(|| { - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); set_rank(1, 2); set_rank(2, 6); set_rank(3, 12); @@ -483,9 +630,9 @@ fn other_mixed_bankrupcy_fails_gracefully() { assert_ok!(Salary::register(RuntimeOrigin::signed(3))); run_to(7); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1), 1), Error::::ClaimZero); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2), 2)); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3), 3)); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); assert_eq!(paid(1), 0); assert_eq!(paid(2), 3); From c9c2ce898824a976ef4a9916d521d616bcae2f51 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 17:46:32 +0100 Subject: [PATCH 19/79] Weights --- frame/salary/src/lib.rs | 14 +++---- frame/salary/src/weights.rs | 80 ++++++++++++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 6284c3fa72e89..59262592326e6 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -276,7 +276,7 @@ pub mod pallet { /// Start the first payout cycle. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(0)] pub fn init(origin: OriginFor) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; @@ -298,7 +298,7 @@ pub mod pallet { /// Move to next payout cycle, assuming that the present block is now within that cycle. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::bump())] #[pallet::call_index(1)] pub fn bump(origin: OriginFor) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; @@ -318,7 +318,7 @@ pub mod pallet { } /// Induct oneself into the payout system. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::induct())] #[pallet::call_index(2)] pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -341,7 +341,7 @@ pub mod pallet { /// started. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::register())] #[pallet::call_index(3)] pub fn register(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -373,7 +373,7 @@ pub mod pallet { /// started but by no more than `PayoutPeriod` blocks. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::payout())] #[pallet::call_index(4)] pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -388,7 +388,7 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of an account which is a member of `Members`. /// - `beneficiary`: The account to receive payment. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::payout_other())] #[pallet::call_index(5)] pub fn payout_other( origin: OriginFor, @@ -406,7 +406,7 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of an account which is a member of `Members` who has /// received a payment this cycle. - #[pallet::weight(T::WeightInfo::add_member())] + #[pallet::weight(T::WeightInfo::check_payment())] #[pallet::call_index(6)] pub fn check_payment(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; diff --git a/frame/salary/src/weights.rs b/frame/salary/src/weights.rs index 172c3ca85c1cf..140e46631e590 100644 --- a/frame/salary/src/weights.rs +++ b/frame/salary/src/weights.rs @@ -45,17 +45,49 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_ranked_collective. pub trait WeightInfo { - fn add_member() -> Weight; + fn init() -> Weight; + fn bump() -> Weight; + fn induct() -> Weight; + fn register() -> Weight; + fn payout() -> Weight; + fn payout_other() -> Weight; + fn check_payment() -> Weight; } /// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) - fn add_member() -> Weight { + fn init() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn bump() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn induct() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn register() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn payout() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn payout_other() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn check_payment() -> Weight { Weight::from_ref_time(11_000_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) @@ -64,11 +96,37 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) - fn add_member() -> Weight { + fn init() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn bump() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn induct() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn register() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn payout() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn payout_other() -> Weight { + Weight::from_ref_time(11_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn check_payment() -> Weight { Weight::from_ref_time(11_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) From 16bc55ae2dd1d395cb606f25058cc448a5e4bef0 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 23:16:49 +0100 Subject: [PATCH 20/79] Introduce Salary benchmark into node --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 4 ++++ bin/node/runtime/src/lib.rs | 27 ++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1e2e9ccfe7c5b..e0b17136b0734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3571,6 +3571,7 @@ dependencies = [ "pallet-referenda", "pallet-remark", "pallet-root-testing", + "pallet-salary", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 1d2e6f057d517..628790f38ffbf 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -93,6 +93,7 @@ pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../ pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" } pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" } pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = "../../../frame/root-testing" } +pallet-salary = { version = "4.0.0-dev", default-features = false, path = "../../../frame/salary" } pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } @@ -176,6 +177,7 @@ std = [ "sp-staking/std", "pallet-staking/std", "pallet-state-trie-migration/std", + "pallet-salary/std", "sp-session/std", "pallet-sudo/std", "frame-support/std", @@ -249,6 +251,7 @@ runtime-benchmarks = [ "pallet-referenda/runtime-benchmarks", "pallet-recovery/runtime-benchmarks", "pallet-remark/runtime-benchmarks", + "pallet-salary/runtime-benchmarks", "pallet-session-benchmarking/runtime-benchmarks", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", @@ -306,6 +309,7 @@ try-runtime = [ "pallet-referenda/try-runtime", "pallet-remark/try-runtime", "pallet-root-testing/try-runtime", + "pallet-salary/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8f8a7ceef3cfe..595e099b1c480 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -63,7 +63,7 @@ pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdj use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, ConstU64, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, @@ -1564,6 +1564,29 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +parameter_types! { + pub const Budget: Balance = 10_000 * DOLLARS; + pub TreasuryAccount: AccountId = Treasury::account_id(); +} + +pub struct SalaryForRank; +impl Convert for SalaryForRank { + fn convert(a: u16) -> Balance { + Balance::from(a) * 1000 * DOLLARS + } +} + +impl pallet_salary::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Paymaster = pallet_salary::PayFromAccount; + type Members = RankedCollective; + type ActiveSalaryForRank = SalaryForRank; + type RegistrationPeriod = ConstU32<200>; + type PayoutPeriod = ConstU32<200>; + type Budget = Budget; +} + parameter_types! { pub Features: PalletFeatures = PalletFeatures::all_enabled(); } @@ -1754,6 +1777,7 @@ construct_runtime!( Nis: pallet_nis, Uniques: pallet_uniques, Nfts: pallet_nfts, + Salary: pallet_salary, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, @@ -1873,6 +1897,7 @@ mod benches { [pallet_referenda, Referenda] [pallet_recovery, Recovery] [pallet_remark, Remark] + [pallet_salary, Salary] [pallet_scheduler, Scheduler] [pallet_session, SessionBench::] [pallet_staking, Staking] From 2fc7fab216d7db99806c83e37da16dca173fd1c7 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 26 Feb 2023 23:17:14 +0100 Subject: [PATCH 21/79] Fix warning --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 595e099b1c480..2ee4302818d6d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -63,7 +63,7 @@ pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdj use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_core::{crypto::KeyTypeId, ConstU64, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, From 42a7927da693946ee00c8a3dcd0b726252ab12a5 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Sun, 26 Feb 2023 22:41:35 +0000 Subject: [PATCH 22/79] ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_salary --- frame/salary/src/weights.rs | 258 ++++++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 58 deletions(-) diff --git a/frame/salary/src/weights.rs b/frame/salary/src/weights.rs index 140e46631e590..f678d086dfcd9 100644 --- a/frame/salary/src/weights.rs +++ b/frame/salary/src/weights.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -15,26 +15,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_ranked_collective +//! Autogenerated weights for pallet_salary //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2023-02-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /Users/gav/Core/substrate/target/release/substrate +// target/production/substrate // benchmark // pallet -// --pallet -// pallet-ranked-collective -// --extrinsic=* -// --chain=dev // --steps=50 // --repeat=20 -// --output=../../../frame/ranked-collective/src/weights.rs -// --template=../../../.maintain/frame-weight-template.hbs -// --header=../../../HEADER-APACHE2 -// --record-proof +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_salary +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/salary/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -43,7 +47,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_ranked_collective. +/// Weight functions needed for pallet_salary. pub trait WeightInfo { fn init() -> Weight; fn bump() -> Weight; @@ -54,81 +58,219 @@ pub trait WeightInfo { fn check_payment() -> Weight; } -/// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. +/// Weights for pallet_salary using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) fn init() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1120` + // Estimated: `551` + // Minimum execution time: 21_058 nanoseconds. + Weight::from_ref_time(21_381_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) fn bump() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1234` + // Estimated: `551` + // Minimum execution time: 22_272 nanoseconds. + Weight::from_ref_time(22_923_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: Salary Status (r:1 w:0) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn induct() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1510` + // Estimated: `5621` + // Minimum execution time: 32_223 nanoseconds. + Weight::from_ref_time(32_663_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn register() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1616` + // Estimated: `5621` + // Minimum execution time: 38_279 nanoseconds. + Weight::from_ref_time(38_996_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn payout() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `2241` + // Estimated: `5621` + // Minimum execution time: 68_868 nanoseconds. + Weight::from_ref_time(70_160_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn payout_other() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `2189` + // Estimated: `8224` + // Minimum execution time: 68_804 nanoseconds. + Weight::from_ref_time(69_223_000) + .saturating_add(Weight::from_proof_size(8224)) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn check_payment() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `880` + // Estimated: `3104` + // Minimum execution time: 19_027 nanoseconds. + Weight::from_ref_time(19_360_000) + .saturating_add(Weight::from_proof_size(3104)) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) fn init() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1120` + // Estimated: `551` + // Minimum execution time: 21_058 nanoseconds. + Weight::from_ref_time(21_381_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) fn bump() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1234` + // Estimated: `551` + // Minimum execution time: 22_272 nanoseconds. + Weight::from_ref_time(22_923_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: Salary Status (r:1 w:0) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn induct() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1510` + // Estimated: `5621` + // Minimum execution time: 32_223 nanoseconds. + Weight::from_ref_time(32_663_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn register() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1616` + // Estimated: `5621` + // Minimum execution time: 38_279 nanoseconds. + Weight::from_ref_time(38_996_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn payout() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `2241` + // Estimated: `5621` + // Minimum execution time: 68_868 nanoseconds. + Weight::from_ref_time(70_160_000) + .saturating_add(Weight::from_proof_size(5621)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn payout_other() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `2189` + // Estimated: `8224` + // Minimum execution time: 68_804 nanoseconds. + Weight::from_ref_time(69_223_000) + .saturating_add(Weight::from_proof_size(8224)) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) fn check_payment() -> Weight { - Weight::from_ref_time(11_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `880` + // Estimated: `3104` + // Minimum execution time: 19_027 nanoseconds. + Weight::from_ref_time(19_360_000) + .saturating_add(Weight::from_proof_size(3104)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } From 7dc1529d662a93e497539a2aea36e23c4ec8c557 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:46:53 +0100 Subject: [PATCH 23/79] Update primitives/arithmetic/src/traits.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> --- primitives/arithmetic/src/traits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 80ba74cccc697..84e3f60aa6a7d 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -153,9 +153,9 @@ impl< /// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to /// be able to represent at least `u16` values without loss, hence the trait implies `From` /// and smaller integers. All other conversions are fallible. -pub trait AtLeast16Bit: BaseArithmetic + From + From {} +pub trait AtLeast16Bit: BaseArithmetic + From {} -impl + From> AtLeast16Bit for T {} +impl> AtLeast16Bit for T {} /// A meta trait for arithmetic. Same as [`AtLeast16Bit `], but also bounded to be unsigned. pub trait AtLeast16BitUnsigned: AtLeast16Bit + Unsigned {} From 366267ca70cc5928924ebe21ab15cf699ae3bc6e Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:47:53 +0100 Subject: [PATCH 24/79] Update frame/salary/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 59262592326e6..4ef3ece3d160a 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -67,7 +67,7 @@ pub enum PaymentStatus { // XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. - type Balance: AtLeast32BitUnsigned + FullCodec + MaxEncodedLen + TypeInfo + Debug + Copy; + type Balance: Balance; /// The type by which we identify the individuals to whom a payment may be made. type AccountId; /// An identifier given to an individual payment. From 26f1ab04c928277de5bdc0a490454b8c3b0e85f2 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:49:37 +0100 Subject: [PATCH 25/79] Update lib.rs --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 4ef3ece3d160a..06ceadef64bb3 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -33,7 +33,7 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - traits::{tokens::fungible, RankedMembers}, + traits::{tokens::{Balance, fungible}, RankedMembers}, RuntimeDebug, }; From 872a047b5ac48d5102346d1545d0295037117675 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:50:35 +0100 Subject: [PATCH 26/79] Update frame/salary/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/salary/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 06ceadef64bb3..c6b9725d0ca51 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -63,8 +63,8 @@ pub enum PaymentStatus { Failure, } -// Can be implemented by Pot pallet with a fixed Currency impl, but can also be implemented with -// XCM/MultiAsset and made generic over assets. +/// Can be implemented by Pot pallet with a fixed Currency impl, but can also be implemented with +/// XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. type Balance: Balance; From e2fd13431d528c264357b8d6e2745a0c47d131cc Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 27 Feb 2023 15:52:24 +0100 Subject: [PATCH 27/79] Docs --- frame/salary/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index c6b9725d0ca51..d6a11e108a4f2 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -33,7 +33,10 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - traits::{tokens::{Balance, fungible}, RankedMembers}, + traits::{ + tokens::{fungible, Balance}, + RankedMembers, + }, RuntimeDebug, }; @@ -63,7 +66,7 @@ pub enum PaymentStatus { Failure, } -/// Can be implemented by Pot pallet with a fixed Currency impl, but can also be implemented with +/// Can be implemented by `PayFromAccount` using a `fungible` impl, but can also be implemented with /// XCM/MultiAsset and made generic over assets. pub trait Pay { /// The type by which we measure units of the currency in which we make payments. From 8d8a5bfe104da2a1b73af8bbb54fe9068668b1e1 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:58:20 +0100 Subject: [PATCH 28/79] Update frame/salary/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index d6a11e108a4f2..bb30d9a7b56e9 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -119,7 +119,7 @@ impl + fungible::Mutate> Pa pub struct StatusType { /// The index of the "current cycle" (i.e. the last cycle being processed). cycle_index: CycleIndex, - /// The first block of the "current cycle" (i.e. the last cycle being processed) + /// The first block of the "current cycle" (i.e. the last cycle being processed). cycle_start: BlockNumber, /// The total budget available for all payments in the current cycle. budget: Balance, From 9849a6546274ce2147ee01ad5ed8843155b0b3a8 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 27 Feb 2023 15:58:37 +0100 Subject: [PATCH 29/79] Update frame/salary/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index bb30d9a7b56e9..7d5a0979e249d 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -229,7 +229,7 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// A member is inducted into the payroll. Inducted { who: T::AccountId }, - /// The next cycle begins. + /// A member registered for a payout. Registered { who: T::AccountId, amount: BalanceOf }, /// A payment happened. Paid { From 0b4aafa0143c86ff4065b0cc5638938677549b8b Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 27 Feb 2023 16:01:04 +0100 Subject: [PATCH 30/79] Fix --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index d6a11e108a4f2..2fa216e253789 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -278,7 +278,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// Start the first payout cycle. /// - /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + /// - `origin`: A `Signed` origin of an account. #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(0)] pub fn init(origin: OriginFor) -> DispatchResultWithPostInfo { From a21b1bcc0365166db81e88142c8b129a6b86de24 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 27 Feb 2023 16:01:55 +0100 Subject: [PATCH 31/79] Fixes --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 169e80d8f49eb..6265a94e5b2d4 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -300,7 +300,7 @@ pub mod pallet { /// Move to next payout cycle, assuming that the present block is now within that cycle. /// - /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + /// - `origin`: A `Signed` origin of an account. #[pallet::weight(T::WeightInfo::bump())] #[pallet::call_index(1)] pub fn bump(origin: OriginFor) -> DispatchResultWithPostInfo { From 4c85f02c43fd044ab3aa5985f6d250b0d36af054 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 27 Feb 2023 16:26:04 +0100 Subject: [PATCH 32/79] Fixes --- bin/node/runtime/src/lib.rs | 6 +++--- frame/salary/src/benchmarking.rs | 8 ++++---- frame/salary/src/lib.rs | 27 ++++++++++++++++++++------- frame/salary/src/tests.rs | 2 +- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8365481e6b677..4498ce8c21b06 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1576,8 +1576,8 @@ parameter_types! { } pub struct SalaryForRank; -impl Convert for SalaryForRank { - fn convert(a: u16) -> Balance { +impl pallet_salary::GetSalary for SalaryForRank { + fn get_salary(a: u16, _: &AccountId) -> Balance { Balance::from(a) * 1000 * DOLLARS } } @@ -1587,7 +1587,7 @@ impl pallet_salary::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Paymaster = pallet_salary::PayFromAccount; type Members = RankedCollective; - type ActiveSalaryForRank = SalaryForRank; + type Salary = SalaryForRank; type RegistrationPeriod = ConstU32<200>; type PayoutPeriod = ConstU32<200>; type Budget = Budget; diff --git a/frame/salary/src/benchmarking.rs b/frame/salary/src/benchmarking.rs index 0d609ab3374f4..339185b37cb7b 100644 --- a/frame/salary/src/benchmarking.rs +++ b/frame/salary/src/benchmarking.rs @@ -36,7 +36,7 @@ fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { // promote until they have a salary. for _ in 0..255 { let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); - if !T::ActiveSalaryForRank::convert(r).is_zero() { + if !T::Salary::get_salary(r, &who).is_zero() { break } T::Members::promote(who).unwrap(); @@ -106,7 +106,7 @@ mod benchmarks { Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); T::Paymaster::ensure_successful(&caller, salary); #[extrinsic_call] @@ -132,7 +132,7 @@ mod benchmarks { Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); let recipient: T::AccountId = account("recipient", 0, SEED); T::Paymaster::ensure_successful(&recipient, salary); @@ -159,7 +159,7 @@ mod benchmarks { Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - let salary = T::ActiveSalaryForRank::convert(T::Members::rank_of(&caller).unwrap()); + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); let recipient: T::AccountId = account("recipient", 0, SEED); T::Paymaster::ensure_successful(&recipient, salary); Salary::::payout(RawOrigin::Signed(caller.clone()).into()).unwrap(); diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 6265a94e5b2d4..40f7484ddae6f 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -24,10 +24,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; use sp_core::TypedGet; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert}, - Perbill, -}; +use sp_runtime::{traits::Convert, Perbill}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ @@ -53,6 +50,21 @@ pub use weights::WeightInfo; /// Payroll cycle. pub type Cycle = u32; +/// Retrieve the salary for a member of a particular rank. +pub trait GetSalary { + /// Retrieve the salary for a given rank. The account ID is also supplied in case this changes + /// things. + fn get_salary(rank: Rank, who: &AccountId) -> Balance; +} + +/// Adapter for a rank-to-salary `Convert` implementation into a `GetSalary` implementation. +pub struct ConvertRank(sp_std::marker::PhantomData); +impl> GetSalary for ConvertRank { + fn get_salary(rank: R, _: &A) -> B { + C::convert(rank) + } +} + /// Status for making a payment via the `Pay::pay` trait function. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub enum PaymentStatus { @@ -180,8 +192,9 @@ pub mod pallet { /// The maximum payout to be made for a single period to an active member of the given rank. /// /// The benchmarks require that this be non-zero for some rank at most 255. - type ActiveSalaryForRank: Convert< + type Salary: GetSalary< ::Rank, + Self::AccountId, ::Balance, >; @@ -357,7 +370,7 @@ pub mod pallet { Error::::TooLate ); ensure!(claimant.last_active < status.cycle_index, Error::::NoClaim); - let payout = T::ActiveSalaryForRank::convert(rank); + let payout = T::Salary::get_salary(rank, &who); ensure!(!payout.is_zero(), Error::::ClaimZero); claimant.last_active = status.cycle_index; claimant.status = Registered(payout); @@ -482,7 +495,7 @@ pub mod pallet { Nothing | Attempted { .. } if claimant.last_active < status.cycle_index => { // Not registered for this cycle. Pay from whatever is left. let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - let ideal_payout = T::ActiveSalaryForRank::convert(rank); + let ideal_payout = T::Salary::get_salary(rank, &who); let pot = status .budget diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index ab785ea166314..726ee3f13bbb1 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -174,7 +174,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; type Paymaster = TestPay; type Members = TestClub; - type ActiveSalaryForRank = Identity; + type Salary = ConvertRank; type RegistrationPeriod = ConstU64<2>; type PayoutPeriod = ConstU64<2>; type Budget = Budget; From d5696c1f03d9f1ff2170b089ff88de4c31ec6556 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 1 Mar 2023 13:01:41 +0100 Subject: [PATCH 33/79] Move some salary traits stuff to a shared location --- bin/node/runtime/src/lib.rs | 4 ++-- frame/salary/src/lib.rs | 21 +++------------------ frame/salary/src/tests.rs | 2 +- frame/support/src/traits/tokens.rs | 2 +- frame/support/src/traits/tokens/misc.rs | 15 +++++++++++++++ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 4498ce8c21b06..55be34f21fd62 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -35,7 +35,7 @@ use frame_support::{ fungible::ItemOf, tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, - U128CurrencyToVote, WithdrawReasons, + U128CurrencyToVote, WithdrawReasons, tokens::GetSalary, }, weights::{ constants::{ @@ -1576,7 +1576,7 @@ parameter_types! { } pub struct SalaryForRank; -impl pallet_salary::GetSalary for SalaryForRank { +impl GetSalary for SalaryForRank { fn get_salary(a: u16, _: &AccountId) -> Balance { Balance::from(a) * 1000 * DOLLARS } diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 40f7484ddae6f..c49584d84ad1b 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -22,7 +22,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_arithmetic::traits::{Saturating, Zero}; +use sp_arithmetic::traits::{Saturating, Zero, AtLeast16BitUnsigned}; use sp_core::TypedGet; use sp_runtime::{traits::Convert, Perbill}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; @@ -31,7 +31,7 @@ use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, traits::{ - tokens::{fungible, Balance}, + tokens::{fungible, Balance, GetSalary}, RankedMembers, }, RuntimeDebug, @@ -50,21 +50,6 @@ pub use weights::WeightInfo; /// Payroll cycle. pub type Cycle = u32; -/// Retrieve the salary for a member of a particular rank. -pub trait GetSalary { - /// Retrieve the salary for a given rank. The account ID is also supplied in case this changes - /// things. - fn get_salary(rank: Rank, who: &AccountId) -> Balance; -} - -/// Adapter for a rank-to-salary `Convert` implementation into a `GetSalary` implementation. -pub struct ConvertRank(sp_std::marker::PhantomData); -impl> GetSalary for ConvertRank { - fn get_salary(rank: R, _: &A) -> B { - C::convert(rank) - } -} - /// Status for making a payment via the `Pay::pay` trait function. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub enum PaymentStatus { @@ -525,4 +510,4 @@ pub mod pallet { Ok(()) } } -} +} \ No newline at end of file diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 726ee3f13bbb1..518a171baca1b 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, pallet_prelude::Weight, parameter_types, - traits::{ConstU32, ConstU64, Everything}, + traits::{ConstU32, ConstU64, Everything, tokens::ConvertRank}, }; use sp_core::H256; use sp_runtime::{ diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 6055fde2180f0..3accc4979341d 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -29,5 +29,5 @@ pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, - ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, + ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, GetSalary, ConvertRank, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index b4bd4640116a7..e42f386ed6bdf 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -225,3 +225,18 @@ impl Locker for () { false } } + +/// Retrieve the salary for a member of a particular rank. +pub trait GetSalary { + /// Retrieve the salary for a given rank. The account ID is also supplied in case this changes + /// things. + fn get_salary(rank: Rank, who: &AccountId) -> Balance; +} + +/// Adapter for a rank-to-salary `Convert` implementation into a `GetSalary` implementation. +pub struct ConvertRank(sp_std::marker::PhantomData); +impl> GetSalary for ConvertRank { + fn get_salary(rank: R, _: &A) -> B { + C::convert(rank) + } +} From caaf99ea090a9b06adfbab85d763f43e45201c56 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 1 Mar 2023 13:02:09 +0100 Subject: [PATCH 34/79] Initial draft --- Cargo.lock | 19 + Cargo.toml | 1 + frame/core-fellowship/Cargo.toml | 53 ++ frame/core-fellowship/README.md | 3 + frame/core-fellowship/src/benchmarking.rs | 183 ++++++ frame/core-fellowship/src/lib.rs | 392 +++++++++++++ frame/core-fellowship/src/tests.rs | 641 ++++++++++++++++++++++ frame/core-fellowship/src/weights.rs | 86 +++ 8 files changed, 1378 insertions(+) create mode 100644 frame/core-fellowship/Cargo.toml create mode 100644 frame/core-fellowship/README.md create mode 100644 frame/core-fellowship/src/benchmarking.rs create mode 100644 frame/core-fellowship/src/lib.rs create mode 100644 frame/core-fellowship/src/tests.rs create mode 100644 frame/core-fellowship/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index ed7f9d10d20b8..104b858248e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5694,6 +5694,25 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-core-fellowship" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-ranked-collective", + "pallet-salary", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-democracy" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 523314b07aea2..6f6859c4d87a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ members = [ "frame/contracts/proc-macro", "frame/contracts/primitives", "frame/conviction-voting", + "frame/core-fellowship", "frame/democracy", "frame/fast-unstake", "frame/try-runtime", diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml new file mode 100644 index 0000000000000..90d489283a2bd --- /dev/null +++ b/frame/core-fellowship/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-core-fellowship" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Paymaster" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, path = "../ranked-collective" } +pallet-salary = { version = "4.0.0-dev", default-features = false, path = "../salary" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/core-fellowship/README.md b/frame/core-fellowship/README.md new file mode 100644 index 0000000000000..3c9b1f63e0896 --- /dev/null +++ b/frame/core-fellowship/README.md @@ -0,0 +1,3 @@ +# Core Fellowship + +Logic specific to the core Polkadot Fellowship. \ No newline at end of file diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs new file mode 100644 index 0000000000000..339185b37cb7b --- /dev/null +++ b/frame/core-fellowship/src/benchmarking.rs @@ -0,0 +1,183 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Salary pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::Pallet as Salary; + +use frame_benchmarking::v2::*; +use frame_system::{Pallet as System, RawOrigin}; +use sp_core::Get; + +const SEED: u32 = 0; + +fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { + // induct if not a member. + if T::Members::rank_of(who).is_none() { + T::Members::induct(who).unwrap(); + } + // promote until they have a salary. + for _ in 0..255 { + let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); + if !T::Salary::get_salary(r, &who).is_zero() { + break + } + T::Members::promote(who).unwrap(); + } +} + +#[instance_benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn init() { + let caller: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::status().is_some()); + } + + #[benchmark] + fn bump() { + let caller: T::AccountId = whitelisted_caller(); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::status().unwrap().cycle_index, 1u32.into()); + } + + #[benchmark] + fn induct() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::last_active(&caller).is_ok()); + } + + #[benchmark] + fn register() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::last_active(&caller).unwrap(), 1u32.into()); + } + + #[benchmark] + fn payout() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + T::Paymaster::ensure_successful(&caller, salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn payout_other() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), recipient.clone()); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn check_payment() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, salary); + Salary::::payout(RawOrigin::Signed(caller.clone()).into()).unwrap(); + let id = match Claimant::::get(&caller).unwrap().status { + Attempted { id, .. } => id, + _ => panic!("No claim made"), + }; + T::Paymaster::ensure_concluded(id); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(!matches!(Claimant::::get(&caller).unwrap().status, Attempted { .. })); + } + + impl_benchmark_test_suite! { + Salary, + crate::tests::new_test_ext(), + crate::tests::Test, + } +} diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs new file mode 100644 index 0000000000000..58f91de640b9e --- /dev/null +++ b/frame/core-fellowship/src/lib.rs @@ -0,0 +1,392 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Additional logic for the Core Fellowship. This determines salary, registers activity/passivity +//! and handles promotion and demotion periods. +//! +//! This only handles members of non-zero rank. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Saturating, Zero}; +use sp_runtime::{traits::AtLeast32BitUnsigned}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; + +use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, + traits::{ + tokens::Balance as BalanceTrait, + RankedMembers, + }, + RuntimeDebug, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The status of the pallet instance. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ParamsType { + /// The total amount of funding per payment cycle. + budget: Balance, + /// The amounts to be paid when a given grade is active. + active_salary: [Balance; 9], + /// The minimum percent discount when passive. + passive_salary: [Balance; 9], + /// The period between which unproven members become demoted. + demotion_period: [BlockNumber; 9], + /// The period between which members must wait before they may proceed to this rank. + min_promotion_period: [BlockNumber; 9], +} + +impl< + Balance: BalanceTrait, + BlockNumber: AtLeast32BitUnsigned + Copy, +> Default for ParamsType { + fn default() -> Self { + Self { + budget: 1000u32.into(), + active_salary: [100u32.into(); 9], + passive_salary: [10u32.into(); 9], + demotion_period: [100u32.into(); 9], + min_promotion_period: [100u32.into(); 9], + } + } +} + +/// The status of a single member. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct MemberStatus { + /// Are they currently active> + is_active: bool, + /// The block number at which we last promoted them. + last_promotion: Option, + /// The last time a member was demoted, promoted or proved their rank. + last_proof: Option, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::Pays, pallet_prelude::*, traits::{EnsureOrigin, tokens::GetSalary, EnsureOriginWithArg}}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// The current membership of the fellowship. + type Members: RankedMembers::AccountId>; + + /// The type in which salaries/budgets are measured. + type Balance: BalanceTrait; + + /// The origin which has permission update the parameters. + type ParamsOrigin: EnsureOrigin; + + /// The origin which has permission to issue a proof that a member may retain their rank. + /// The `Success` value is the maximum rank of members it is able to prove. + type ProofOrigin: EnsureOrigin>; + + /// The origin which has permission to promote a member. The `Success` value is the maximum + /// rank to which it can promote. + type PromoteOrigin: EnsureOrigin>; + + /// The period before auto-demotion that a member can be (re-)approved for their rank. + #[pallet::constant] + type ApprovePeriod = Get; + } + + pub type ParamsOf = + ParamsType<>::Balance, ::BlockNumber>; + pub type MemberStatusOf = MemberStatus<::BlockNumber>; + pub type RankOf = <>::Members as RankedMembers<_>>::Rank; + + /// The overall status of the system. + #[pallet::storage] + pub(super) type Params, I: 'static = ()> = + StorageValue<_, ParamsOf, ValueQuery>; + + /// The status of a claimant. + #[pallet::storage] + pub(super) type Member, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// Parameters for the pallet have changed. + ParamsChanged { params: ParamsOf }, + /// Member activity flag has been set. + ActiveChanged { who: T::AccountId, is_active: bool }, + /// Member has begun being tracked in this pallet (i.e. because rank is now non-zero). + Inducted { who: T::AccountId }, + /// Member has been removed from being tracked in this pallet (i.e. because rank is now + /// zero). + Offboarded { who: T::AccountId }, + /// Member has been promoted to the given rank. + Promoted { who: T::AccountId, to_rank: RankOf }, + /// Member has been demoted to the given (non-zero) rank. + Demoted { who: T::AccountId, to_rank: RankOf }, + } + + #[pallet::error] + pub enum Error { + /// Member's rank is too low. + Unranked, + /// Member's rank is not zero. + Ranked, + /// Member's rank is not as expected. + UnexpectedRank, + /// The account is not a member of the collective. + NotMember, + /// The member is not tracked by this pallet (call `prove` on member first). + NotProved, + /// The origin does not have enough permission to do this operation. + NoPermission, + /// No work needs to be done at present for this member. + NothingDoing, + /// The candidate has already been inducted. This should never happen since it would require + /// a candidate (rank 0) to already be tracked in the pallet. + AlreadyInducted, + /// The candidate has not been inducted, so cannot be offboarded from this pallet. + NotInducted, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Bump the state of a member. + /// + /// This will demote a member whose `last_proof` is now beyond their rank's + /// `demotion_period`. + /// + /// - `origin`: A `Signed` origin of an account. + /// - `who`: A member account whose state is to be updated. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(0)] + pub fn bump(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + + let params = Params::::get(); + let demotion_period = params.demotion_period[Self::rank_to_index(rank)]; + let demotion_block = member.last_proof.saturating_add(demotion_period); + + // Ensure enough time has passed. + let now = frame_system::Pallet::::block_number(); + if now >= demotion_block { + let to_rank = rank.saturating_sub(1u32.into()); + T::Members::demote(&who); + let event = if to_rank.is_zero() { + Member::::remove(&who); + Event::::Offboarded { who } + } else { + member.last_proof = now; + Member::::insert(&who, &member); + Event::::Demoted { who, to_rank } + }; + Self::deposit_event(event); + Ok(Pays::No.into()) + } + + Err(Error::::NothingDoing.into()) + } + + /// Set the parameters. + /// + /// - `origin`: A origin complying with `ParamsOrigin`. + /// - `params`: The new parameters for the pallet. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(1)] + pub fn set_params(origin: OriginFor, params: ParamsOf) -> DispatchResultWithPostInfo { + T::ParamsOrigin::ensure_origin(origin)?; + Params::::put(¶ms); + Self::deposit_event(Events::::ParamsChanged { params }); + Ok(Pays::No.into()) + } + + /// Set whether a member is active or not. + /// + /// - `origin`: A `Signed` origin of a member's account. + /// - `active`: `true` iff the member is active. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(2)] + pub fn set_active(origin: OriginFor, is_active: bool) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()), Error::::Unranked); + let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; + member.is_active = is_active; + Member::::insert(&who, &member); + Self::deposit_event(Event::::ActiveChanged { who, is_active }); + Ok(()) + } + + /// Approve a member to continue at their rank. + /// + /// This resets `last_proof` to the current block, thereby delaying any automatic demotion. + /// + /// If `who` is not already tracked by this pallet, then it will become tracked. + /// `last_promotion` will be set to zero. + /// + /// - `origin`: An origin which satisfies `ProofOrigin`. + /// - `who`: A member (i.e. of non-zero rank). + /// - `at_rank`: The rank of member. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(3)] + pub fn prove(origin: OriginFor, who: T::AccountId, at_rank: RankOf) -> DispatchResultWithPostInfo { + let allow_rank = T::ProofOrigin::ensure_origin(origin)?; + ensure!(allow_rank >= at_rank, Error::::NoPermission); + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(rank == at_rank, Error::::UnexpectedRank); + + // Maybe consider requiring it be at least `ApprovePeriod` prior to the auto-demotion. + let now = frame_system::Pallet::::block_number(); + let mut member = Member::::get(&who).unwrap_or_else(|| MemberStatus { + is_active: true, + last_promotion: 0u32.into(), + last_proof: now, + }); + member.last_proof = now; + + Ok(Pays::No.into()) + } + + /// Make a "promotion" from a candiate (rank zero) into a member (rank one). + /// + /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of 1 or + /// more. + /// - `who`: The account ID of the candidate to be inducted and become a member. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(4)] + pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; + ensure!(allow_rank >= 1u32.into(), Error::::NoPermission); + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(rank.is_zero(), Error::::UnexpectedRank); + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + + let now = frame_system::Pallet::::block_number(); + + T::Members::promote(&who); + + Member::::insert(&who, MemberStatus { + is_active: true, + last_promotion: now, + last_proof: now, + }); + + Self::deposit_event(Event::::Inducted { who }); + + Ok(Pays::No.into()) + } + + /// Make a promotion from a non-zero rank. + /// + /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of + /// `to_rank` or more. + /// - `who`: The account ID of the member to be promoted to `to_rank`. + /// - `to_rank`: One more than the current rank of `who`. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(5)] + pub fn promote(origin: OriginFor, who: T::AccountId, to_rank: RankOf) -> DispatchResultWithPostInfo { + let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; + ensure!(allow_rank >= to_rank, Error::::NoPermission); + + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(rank + One::one() == to_rank, Error::::UnexpectedRank); + + let mut member = Member::::get(&who).ok_or(Error::::Unranked); + let now = frame_system::Pallet::::block_number(); + + let params = Params::::get(); + let min_period = params.min_promotion_period[Self::rank_to_index(to_rank)]; + // Ensure enough time has passed. + ensure!(member.last_promotion.saturating_add(min_period) < now, Error::::TooSoon); + + T::Members::promote(&who); + member.last_promotion = now; + member.last_proof = now; + Member::::insert(&who, &member); + + Self::deposit_event(Event::::Promoted { who, to_rank }); + + Ok(Pays::No.into()) + } + + /// Make a "promotion" from a candiate (rank zero) into a member (rank one). + /// + /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of 1 or + /// more. + /// - `who`: The account ID of the candidate to be inducted and become a member. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(6)] + pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + ensure!(T::Members::rank_of(&who).is_zero(), Error::::Ranked); + ensure!(Member::::contains_key(&who), Error::::NotInducted); + Member::::remove(&who); + Self::deposit_event(Event::::Offboarded { who }); + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet { + /// Convert a rank into a 0..9 index suitable for the arrays in Params. + /// + /// Rank 1 becomes index 0, rank 9 becomes index 8. Any rank not in the range 1..=9 is + /// `None`. + fn rank_to_index(rank: RankOf) -> Option { + match usize::try_from(rank) { + Ok(r) if r <= 9 && r > 0 => Some(r - 1), + _ => return None, + }; + } + } + + impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { + fn get_salary(rank: RankOf, who: &T::AccountId) -> T::Balance { + let index = match Self::rank_to_index(rank) { Some(i) => i, None => return Zero::zero() }; + let params = T::Params::get(); + if Member::::get(who).is_active { + params.active_salary[index] + } else { + params.passive_salary[index] + } + } + } +} diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs new file mode 100644 index 0000000000000..518a171baca1b --- /dev/null +++ b/frame/core-fellowship/src/tests.rs @@ -0,0 +1,641 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, + pallet_prelude::Weight, + parameter_types, + traits::{ConstU32, ConstU64, Everything, tokens::ConvertRank}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup}, + DispatchResult, +}; +use sp_std::cell::RefCell; + +use super::*; +use crate as pallet_salary; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Salary: pallet_salary::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +thread_local! { + pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); + pub static STATUS: RefCell> = RefCell::new(BTreeMap::new()); + pub static LAST_ID: RefCell = RefCell::new(0u64); +} + +fn paid(who: u64) -> u64 { + PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) +} +fn unpay(who: u64, amount: u64) { + PAID.with(|p| p.borrow_mut().entry(who).or_default().saturating_reduce(amount)) +} +fn set_status(id: u64, s: PaymentStatus) { + STATUS.with(|m| m.borrow_mut().insert(id, s)); +} + +pub struct TestPay; +impl Pay for TestPay { + type AccountId = u64; + type Balance = u64; + type Id = u64; + + fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { + PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount); + Ok(LAST_ID.with(|lid| { + let x = *lid.borrow(); + lid.replace(x + 1); + x + })) + } + fn check_payment(id: Self::Id) -> PaymentStatus { + STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown)) + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::AccountId, _: Self::Balance) {} + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id) { + set_status(id, PaymentStatus::Failure) + } +} + +thread_local! { + pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); +} + +pub struct TestClub; +impl RankedMembers for TestClub { + type AccountId = u64; + type Rank = u64; + fn min_rank() -> Self::Rank { + 0 + } + fn rank_of(who: &Self::AccountId) -> Option { + CLUB.with(|club| club.borrow().get(who).cloned()) + } + fn induct(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| club.borrow_mut().insert(*who, 0)); + Ok(()) + } + fn promote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| { + club.borrow_mut().entry(*who).and_modify(|r| *r += 1); + }); + Ok(()) + } + fn demote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| match club.borrow().get(who) { + None => Err(sp_runtime::DispatchError::Unavailable), + Some(&0) => { + club.borrow_mut().remove(&who); + Ok(()) + }, + Some(_) => { + club.borrow_mut().entry(*who).and_modify(|x| *x -= 1); + Ok(()) + }, + }) + } +} + +fn set_rank(who: u64, rank: u64) { + CLUB.with(|club| club.borrow_mut().insert(who, rank)); +} + +parameter_types! { + pub static Budget: u64 = 10; +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Paymaster = TestPay; + type Members = TestClub; + type Salary = ConvertRank; + type RegistrationPeriod = ConstU64<2>; + type PayoutPeriod = ConstU64<2>; + type Budget = Budget; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert!(Salary::last_active(&0).is_err()); + assert_eq!(Salary::status(), None); + }); +} + +#[test] +fn can_start() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 0, + cycle_start: 1, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0, + }) + ); + }); +} + +#[test] +fn bump_works() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + run_to(4); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 1, + cycle_start: 5, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0 + }) + ); + + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + + BUDGET.with(|b| b.replace(5)); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 2, + cycle_start: 9, + budget: 5, + total_registrations: 0, + total_unregistered_paid: 0 + }) + ); + }); +} + +#[test] +fn induct_works() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); + set_rank(1, 1); + assert!(Salary::last_active(&1).is_err()); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_eq!(Salary::last_active(&1).unwrap(), 0); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); + }); +} + +#[test] +fn unregistered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(3); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_registered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + +#[test] +fn retry_payment_later_is_not_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + // Next cycle. + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + + // Payment did fail but now too late to retry. + assert_noop!(Salary::check_payment(RuntimeOrigin::signed(1)), Error::::NotCurrent); + + // We do get this cycle's payout, but we must wait for the payout period to start. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + }); +} + +#[test] +fn retry_payment_later_without_bump_is_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + // Next cycle. + run_to(9); + + // Payment did fail but we can still retry as long as we don't `bump`. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_payment_to_other_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + // Payment failed. + unpay(10, 1); + set_status(0, PaymentStatus::Failure); + + // Can't just retry. + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn registered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(3); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 0); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 2); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + +#[test] +fn zero_payment_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 0); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + }); +} + +#[test] +fn unregistered_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn registered_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 1); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 6); + }); +} + +#[test] +fn mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn other_mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 0); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 7); + }); +} diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs new file mode 100644 index 0000000000000..77970671f8fbe --- /dev/null +++ b/frame/core-fellowship/src/weights.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_salary +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-02-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_salary +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/salary/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_salary. +pub trait WeightInfo { + fn init() -> Weight; +} + +/// Weights for pallet_salary using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn init() -> Weight { + // Proof Size summary in bytes: + // Measured: `1120` + // Estimated: `551` + // Minimum execution time: 21_058 nanoseconds. + Weight::from_ref_time(21_381_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn init() -> Weight { + // Proof Size summary in bytes: + // Measured: `1120` + // Estimated: `551` + // Minimum execution time: 21_058 nanoseconds. + Weight::from_ref_time(21_381_000) + .saturating_add(Weight::from_proof_size(551)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} From 3d70833278e2b5e0d3dd3c9c14dd13307af99c52 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 2 Mar 2023 15:54:54 +0100 Subject: [PATCH 35/79] Comment out bits --- frame/core-fellowship/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 58f91de640b9e..915119a5e95ad 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -145,7 +145,7 @@ pub mod pallet { #[pallet::storage] pub(super) type Member, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; - +/* #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -186,9 +186,10 @@ pub mod pallet { /// The candidate has not been inducted, so cannot be offboarded from this pallet. NotInducted, } - +*/ #[pallet::call] impl, I: 'static> Pallet { + /* /// Bump the state of a member. /// /// This will demote a member whose `last_proof` is now beyond their rank's @@ -363,8 +364,10 @@ pub mod pallet { Self::deposit_event(Event::::Offboarded { who }); Ok(Pays::No.into()) } + */ } +/* impl, I: 'static> Pallet { /// Convert a rank into a 0..9 index suitable for the arrays in Params. /// @@ -377,7 +380,8 @@ pub mod pallet { }; } } - +*/ +/* impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { fn get_salary(rank: RankOf, who: &T::AccountId) -> T::Balance { let index = match Self::rank_to_index(rank) { Some(i) => i, None => return Zero::zero() }; @@ -389,4 +393,5 @@ pub mod pallet { } } } + */ } From ddd4d3e7ebd1b5bb443277a1a5e5bf700af16628 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 2 Mar 2023 15:55:45 +0100 Subject: [PATCH 36/79] Fix --- frame/support/src/traits/tokens/misc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index e42f386ed6bdf..eceee45c6ccfa 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -20,7 +20,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; -use sp_runtime::{ArithmeticError, DispatchError, TokenError}; +use sp_runtime::{ArithmeticError, DispatchError, TokenError, traits::Convert}; use sp_std::fmt::Debug; /// One of a number of consequences of withdrawing a fungible from an account. From 3fcc5900abf81d9c25691e9bf9ba6ec7cae89843 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 2 Mar 2023 17:30:41 +0100 Subject: [PATCH 37/79] First couple of tests --- frame/core-fellowship/src/lib.rs | 86 +++-- frame/core-fellowship/src/tests.rs | 526 +++------------------------- frame/salary/src/lib.rs | 4 +- primitives/arithmetic/src/traits.rs | 32 ++ primitives/runtime/src/traits.rs | 18 + 5 files changed, 138 insertions(+), 528 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 915119a5e95ad..1695b83f9eed2 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -23,11 +23,11 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] -use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{traits::AtLeast32BitUnsigned}; -use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -50,10 +50,8 @@ pub use pallet::*; pub use weights::WeightInfo; /// The status of the pallet instance. -#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug, Default)] pub struct ParamsType { - /// The total amount of funding per payment cycle. - budget: Balance, /// The amounts to be paid when a given grade is active. active_salary: [Balance; 9], /// The minimum percent discount when passive. @@ -64,13 +62,14 @@ pub struct ParamsType { min_promotion_period: [BlockNumber; 9], } +/* +// move to benchmark code. No need for it here. impl< Balance: BalanceTrait, BlockNumber: AtLeast32BitUnsigned + Copy, > Default for ParamsType { fn default() -> Self { Self { - budget: 1000u32.into(), active_salary: [100u32.into(); 9], passive_salary: [10u32.into(); 9], demotion_period: [100u32.into(); 9], @@ -78,6 +77,7 @@ impl< } } } +*/ /// The status of a single member. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] @@ -85,16 +85,17 @@ pub struct MemberStatus { /// Are they currently active> is_active: bool, /// The block number at which we last promoted them. - last_promotion: Option, + last_promotion: BlockNumber, /// The last time a member was demoted, promoted or proved their rank. - last_proof: Option, + last_proof: BlockNumber, } #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::Pays, pallet_prelude::*, traits::{EnsureOrigin, tokens::GetSalary, EnsureOriginWithArg}}; + use frame_support::{dispatch::Pays, pallet_prelude::*, traits::{EnsureOrigin, tokens::GetSalary}}; use frame_system::pallet_prelude::*; + use sp_core::Get; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -110,7 +111,7 @@ pub mod pallet { + IsType<::RuntimeEvent>; /// The current membership of the fellowship. - type Members: RankedMembers::AccountId>; + type Members: RankedMembers::AccountId, Rank = u16>; /// The type in which salaries/budgets are measured. type Balance: BalanceTrait; @@ -128,13 +129,13 @@ pub mod pallet { /// The period before auto-demotion that a member can be (re-)approved for their rank. #[pallet::constant] - type ApprovePeriod = Get; + type ApprovePeriod: Get; } pub type ParamsOf = ParamsType<>::Balance, ::BlockNumber>; pub type MemberStatusOf = MemberStatus<::BlockNumber>; - pub type RankOf = <>::Members as RankedMembers<_>>::Rank; + pub type RankOf = <>::Members as RankedMembers>::Rank; /// The overall status of the system. #[pallet::storage] @@ -145,7 +146,7 @@ pub mod pallet { #[pallet::storage] pub(super) type Member, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; -/* + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -172,6 +173,8 @@ pub mod pallet { Ranked, /// Member's rank is not as expected. UnexpectedRank, + /// The given rank is invalid - this generally means it's not between 1 and 9. + InvalidRank, /// The account is not a member of the collective. NotMember, /// The member is not tracked by this pallet (call `prove` on member first). @@ -185,11 +188,12 @@ pub mod pallet { AlreadyInducted, /// The candidate has not been inducted, so cannot be offboarded from this pallet. NotInducted, + /// Operation cannot be done yet since not enough time has passed. + TooSoon, } -*/ + #[pallet::call] impl, I: 'static> Pallet { - /* /// Bump the state of a member. /// /// This will demote a member whose `last_proof` is now beyond their rank's @@ -205,14 +209,15 @@ pub mod pallet { let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; let params = Params::::get(); - let demotion_period = params.demotion_period[Self::rank_to_index(rank)]; + let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; + let demotion_period = params.demotion_period[rank_index]; let demotion_block = member.last_proof.saturating_add(demotion_period); // Ensure enough time has passed. let now = frame_system::Pallet::::block_number(); if now >= demotion_block { - let to_rank = rank.saturating_sub(1u32.into()); - T::Members::demote(&who); + let to_rank = rank.clone().saturating_less_one(); + T::Members::demote(&who)?; let event = if to_rank.is_zero() { Member::::remove(&who); Event::::Offboarded { who } @@ -222,7 +227,7 @@ pub mod pallet { Event::::Demoted { who, to_rank } }; Self::deposit_event(event); - Ok(Pays::No.into()) + return Ok(Pays::No.into()); } Err(Error::::NothingDoing.into()) @@ -237,7 +242,7 @@ pub mod pallet { pub fn set_params(origin: OriginFor, params: ParamsOf) -> DispatchResultWithPostInfo { T::ParamsOrigin::ensure_origin(origin)?; Params::::put(¶ms); - Self::deposit_event(Events::::ParamsChanged { params }); + Self::deposit_event(Event::::ParamsChanged { params }); Ok(Pays::No.into()) } @@ -296,23 +301,19 @@ pub mod pallet { #[pallet::call_index(4)] pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; - ensure!(allow_rank >= 1u32.into(), Error::::NoPermission); + ensure!(allow_rank >= 1, Error::::NoPermission); let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; ensure!(rank.is_zero(), Error::::UnexpectedRank); ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + T::Members::promote(&who)?; let now = frame_system::Pallet::::block_number(); - - T::Members::promote(&who); - Member::::insert(&who, MemberStatus { is_active: true, last_promotion: now, last_proof: now, }); - Self::deposit_event(Event::::Inducted { who }); - Ok(Pays::No.into()) } @@ -329,17 +330,18 @@ pub mod pallet { ensure!(allow_rank >= to_rank, Error::::NoPermission); let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - ensure!(rank + One::one() == to_rank, Error::::UnexpectedRank); + ensure!(rank.checked_add(1).map_or(false, |i| i == to_rank), Error::::UnexpectedRank); - let mut member = Member::::get(&who).ok_or(Error::::Unranked); + let mut member = Member::::get(&who).ok_or(Error::::Unranked)?; let now = frame_system::Pallet::::block_number(); let params = Params::::get(); - let min_period = params.min_promotion_period[Self::rank_to_index(to_rank)]; + let rank_index = Self::rank_to_index(to_rank).ok_or(Error::::InvalidRank)?; + let min_period = params.min_promotion_period[rank_index]; // Ensure enough time has passed. ensure!(member.last_promotion.saturating_add(min_period) < now, Error::::TooSoon); - T::Members::promote(&who); + T::Members::promote(&who)?; member.last_promotion = now; member.last_proof = now; Member::::insert(&who, &member); @@ -358,40 +360,34 @@ pub mod pallet { #[pallet::call_index(6)] pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - ensure!(T::Members::rank_of(&who).is_zero(), Error::::Ranked); + ensure!(T::Members::rank_of(&who).map_or(true, |r| r.is_zero()), Error::::Ranked); ensure!(Member::::contains_key(&who), Error::::NotInducted); Member::::remove(&who); Self::deposit_event(Event::::Offboarded { who }); Ok(Pays::No.into()) } - */ } -/* impl, I: 'static> Pallet { /// Convert a rank into a 0..9 index suitable for the arrays in Params. /// /// Rank 1 becomes index 0, rank 9 becomes index 8. Any rank not in the range 1..=9 is /// `None`. - fn rank_to_index(rank: RankOf) -> Option { - match usize::try_from(rank) { + pub(crate) fn rank_to_index(rank: RankOf) -> Option { + match TryInto::::try_into(rank) { Ok(r) if r <= 9 && r > 0 => Some(r - 1), _ => return None, - }; + } } } -*/ -/* + impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { fn get_salary(rank: RankOf, who: &T::AccountId) -> T::Balance { let index = match Self::rank_to_index(rank) { Some(i) => i, None => return Zero::zero() }; - let params = T::Params::get(); - if Member::::get(who).is_active { - params.active_salary[index] - } else { - params.passive_salary[index] - } + let member = match Member::::get(who) { Some(m) => m, None => return Zero::zero() }; + let params = Params::::get(); + let salary = if member.is_active { params.active_salary } else { params.passive_salary }; + salary[index] } } - */ } diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index 518a171baca1b..d9f8a540a24d0 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -20,21 +20,21 @@ use std::collections::BTreeMap; use frame_support::{ - assert_noop, assert_ok, pallet_prelude::Weight, parameter_types, - traits::{ConstU32, ConstU64, Everything, tokens::ConvertRank}, + traits::{ConstU32, ConstU64, Everything, IsInVec, TryMapSuccess, tokens::GetSalary}, ord_parameter_types, assert_noop, assert_ok, }; +use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Identity, IdentityLookup}, - DispatchResult, + traits::{BlakeTwo256, IdentityLookup, TryMorphInto}, + DispatchResult, DispatchError, }; use sp_std::cell::RefCell; use super::*; -use crate as pallet_salary; +use crate as pallet_core_fellowship; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -46,7 +46,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Salary: pallet_salary::{Pallet, Call, Storage, Event}, + CoreFellowship: pallet_core_fellowship::{Pallet, Call, Storage, Event}, } ); @@ -82,54 +82,13 @@ impl frame_system::Config for Test { } thread_local! { - pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); - pub static STATUS: RefCell> = RefCell::new(BTreeMap::new()); - pub static LAST_ID: RefCell = RefCell::new(0u64); -} - -fn paid(who: u64) -> u64 { - PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) -} -fn unpay(who: u64, amount: u64) { - PAID.with(|p| p.borrow_mut().entry(who).or_default().saturating_reduce(amount)) -} -fn set_status(id: u64, s: PaymentStatus) { - STATUS.with(|m| m.borrow_mut().insert(id, s)); -} - -pub struct TestPay; -impl Pay for TestPay { - type AccountId = u64; - type Balance = u64; - type Id = u64; - - fn pay(who: &Self::AccountId, amount: Self::Balance) -> Result { - PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount); - Ok(LAST_ID.with(|lid| { - let x = *lid.borrow(); - lid.replace(x + 1); - x - })) - } - fn check_payment(id: Self::Id) -> PaymentStatus { - STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown)) - } - #[cfg(feature = "runtime-benchmarks")] - fn ensure_successful(_: &Self::AccountId, _: Self::Balance) {} - #[cfg(feature = "runtime-benchmarks")] - fn ensure_concluded(id: Self::Id) { - set_status(id, PaymentStatus::Failure) - } -} - -thread_local! { - pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); + pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); } pub struct TestClub; impl RankedMembers for TestClub { type AccountId = u64; - type Rank = u64; + type Rank = u16; fn min_rank() -> Self::Rank { 0 } @@ -161,23 +120,26 @@ impl RankedMembers for TestClub { } } -fn set_rank(who: u64, rank: u64) { +fn set_rank(who: u64, rank: u16) { CLUB.with(|club| club.borrow_mut().insert(who, rank)); } parameter_types! { - pub static Budget: u64 = 10; + pub OneToNine: Vec = vec![1u64, 2, 3, 4, 5, 6, 7, 8, 9]; +} +ord_parameter_types! { + pub const One: u64 = 1; } impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; - type Paymaster = TestPay; type Members = TestClub; - type Salary = ConvertRank; - type RegistrationPeriod = ConstU64<2>; - type PayoutPeriod = ConstU64<2>; - type Budget = Budget; + type Balance = u64; + type ParamsOrigin = EnsureSignedBy; + type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; + type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; + type ApprovePeriod = ConstU64<2>; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -198,444 +160,46 @@ fn run_to(n: u64) { } } -#[test] -fn basic_stuff() { - new_test_ext().execute_with(|| { - assert!(Salary::last_active(&0).is_err()); - assert_eq!(Salary::status(), None); - }); -} - -#[test] -fn can_start() { - new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_eq!( - Salary::status(), - Some(StatusType { - cycle_index: 0, - cycle_start: 1, - budget: 10, - total_registrations: 0, - total_unregistered_paid: 0, - }) - ); - }); -} - -#[test] -fn bump_works() { - new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - run_to(4); - assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); - - run_to(5); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_eq!( - Salary::status(), - Some(StatusType { - cycle_index: 1, - cycle_start: 5, - budget: 10, - total_registrations: 0, - total_unregistered_paid: 0 - }) - ); - - run_to(8); - assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); - - BUDGET.with(|b| b.replace(5)); - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_eq!( - Salary::status(), - Some(StatusType { - cycle_index: 2, - cycle_start: 9, - budget: 5, - total_registrations: 0, - total_unregistered_paid: 0 - }) - ); - }); -} - -#[test] -fn induct_works() { - new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - - assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); - set_rank(1, 1); - assert!(Salary::last_active(&1).is_err()); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_eq!(Salary::last_active(&1).unwrap(), 0); - assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); - }); -} - -#[test] -fn unregistered_payment_works() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - // No claim on the cycle active during induction. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); - run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - run_to(8); - assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(11); - assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); - assert_eq!(paid(1), 1); - assert_eq!(paid(10), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - }); -} - -#[test] -fn retry_payment_works() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - // Payment failed. - unpay(1, 1); - set_status(0, PaymentStatus::Failure); - - assert_eq!(paid(1), 0); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - - // Can't just retry. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - // Check status. - assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); - // Allowed to try again. - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - run_to(8); - assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(11); - assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); - assert_eq!(paid(1), 1); - assert_eq!(paid(10), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - }); -} - -#[test] -fn retry_registered_payment_works() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - // Payment failed. - unpay(1, 1); - set_status(0, PaymentStatus::Failure); - - assert_eq!(paid(1), 0); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - // Check status. - assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); - // Allowed to try again. - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - }); -} - -#[test] -fn retry_payment_later_is_not_allowed() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - // Payment failed. - unpay(1, 1); - set_status(0, PaymentStatus::Failure); - - assert_eq!(paid(1), 0); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - - // Can't just retry. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - - // Next cycle. - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - - // Payment did fail but now too late to retry. - assert_noop!(Salary::check_payment(RuntimeOrigin::signed(1)), Error::::NotCurrent); - - // We do get this cycle's payout, but we must wait for the payout period to start. - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); - - run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - }); -} - -#[test] -fn retry_payment_later_without_bump_is_allowed() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - // Payment failed. - unpay(1, 1); - set_status(0, PaymentStatus::Failure); - - // Next cycle. - run_to(9); - - // Payment did fail but we can still retry as long as we don't `bump`. - assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - }); -} - -#[test] -fn retry_payment_to_other_works() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(6); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); - - // Payment failed. - unpay(10, 1); - set_status(0, PaymentStatus::Failure); - - // Can't just retry. - assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); - // Check status. - assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); - // Allowed to try again. - assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); - - assert_eq!(paid(10), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - - assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); - run_to(8); - assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 1); - assert_eq!(paid(10), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); - }); -} - -#[test] -fn registered_payment_works() { - new_test_ext().execute_with(|| { - set_rank(1, 1); - assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - // No claim on the cycle active during induction. - assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); - run_to(3); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - - run_to(5); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(1))); - assert_eq!(Salary::status().unwrap().total_registrations, 1); - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 1); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); - - run_to(9); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_eq!(Salary::status().unwrap().total_registrations, 0); - assert_ok!(Salary::register(RuntimeOrigin::signed(1))); - assert_eq!(Salary::status().unwrap().total_registrations, 1); - run_to(11); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_eq!(paid(1), 2); - assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); - }); -} - -#[test] -fn zero_payment_fails() { - new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - set_rank(1, 0); - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - run_to(7); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); - }); -} - -#[test] -fn unregistered_bankrupcy_fails_gracefully() { - new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - set_rank(1, 2); - set_rank(2, 6); - set_rank(3, 12); - - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); - - run_to(7); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); - - assert_eq!(paid(1), 2); - assert_eq!(paid(2), 6); - assert_eq!(paid(3), 2); - }); +fn signed(who: u64) -> RuntimeOrigin { + RuntimeOrigin::signed(who) } #[test] -fn registered_bankrupcy_fails_gracefully() { +fn basic_stuff() { new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - set_rank(1, 2); - set_rank(2, 6); - set_rank(3, 12); - - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); - - run_to(5); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(2))); - assert_ok!(Salary::register(RuntimeOrigin::signed(3))); - - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); - - assert_eq!(paid(1), 1); - assert_eq!(paid(2), 3); - assert_eq!(paid(3), 6); + assert_eq!(CoreFellowship::rank_to_index(0), None); + assert_eq!(CoreFellowship::rank_to_index(1), Some(0)); + assert_eq!(CoreFellowship::rank_to_index(9), Some(8)); + assert_eq!(CoreFellowship::rank_to_index(10), None); + assert_noop!(CoreFellowship::induct(signed(1), 1), Error::::NotMember); + assert_eq!(CoreFellowship::get_salary(0, &1), 0); }); } #[test] -fn mixed_bankrupcy_fails_gracefully() { +fn set_params_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - set_rank(1, 2); - set_rank(2, 6); - set_rank(3, 12); - - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); - - run_to(5); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(2))); - - run_to(7); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); - - assert_eq!(paid(1), 2); - assert_eq!(paid(2), 6); - assert_eq!(paid(3), 2); + let params = ParamsType { + active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], + passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], + demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], + min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], + }; + assert_noop!(CoreFellowship::set_params(signed(2), params.clone()), DispatchError::BadOrigin); + assert_ok!(CoreFellowship::set_params(signed(1), params)); }); } #[test] -fn other_mixed_bankrupcy_fails_gracefully() { +fn set_params_works() { new_test_ext().execute_with(|| { - assert_ok!(Salary::init(RuntimeOrigin::signed(1))); - set_rank(1, 2); - set_rank(2, 6); - set_rank(3, 12); - - assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); - assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); - - run_to(5); - assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); - assert_ok!(Salary::register(RuntimeOrigin::signed(2))); - assert_ok!(Salary::register(RuntimeOrigin::signed(3))); - - run_to(7); - assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); - assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); - assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); - - assert_eq!(paid(1), 0); - assert_eq!(paid(2), 3); - assert_eq!(paid(3), 7); + let params = ParamsType { + active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], + passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], + demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], + min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], + }; + assert_noop!(CoreFellowship::set_params(signed(2), params.clone()), DispatchError::BadOrigin); + assert_ok!(CoreFellowship::set_params(signed(1), params)); }); } diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index c49584d84ad1b..27eede62fd2b3 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -22,9 +22,9 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_arithmetic::traits::{Saturating, Zero, AtLeast16BitUnsigned}; +use sp_arithmetic::traits::{Saturating, Zero}; use sp_core::TypedGet; -use sp_runtime::{traits::Convert, Perbill}; +use sp_runtime::Perbill; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 84e3f60aa6a7d..5342cbb6e9b2f 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -148,6 +148,20 @@ impl< { } +/// A meta trait for arithmetic. +/// +/// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to +/// be able to represent at least `u8` values without loss, hence the trait implies `From` +/// and smaller integers. All other conversions are fallible. +pub trait AtLeast8Bit: BaseArithmetic + From {} + +impl> AtLeast8Bit for T {} + +/// A meta trait for arithmetic. Same as [`AtLeast8Bit `], but also bounded to be unsigned. +pub trait AtLeast8BitUnsigned: AtLeast8Bit + Unsigned {} + +impl AtLeast8BitUnsigned for T {} + /// A meta trait for arithmetic. /// /// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to @@ -220,6 +234,24 @@ pub trait Saturating { /// instead of overflowing. fn saturating_pow(self, exp: usize) -> Self; + /// Decrement self by one, saturating at zero. + fn saturating_less_one(mut self) -> Self + where + Self: One, + { + self.saturating_dec(); + self + } + + /// Decrement self by one, saturating at zero. + fn saturating_plus_one(mut self) -> Self + where + Self: One, + { + self.saturating_inc(); + self + } + /// Increment self by one, saturating. fn saturating_inc(&mut self) where diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index e853cad61d81e..4bba6f6bc1283 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -319,6 +319,24 @@ impl TryMorph for Identity { } } +/// Implementation of `Morph` which converts between types using `Into`. +pub struct MorphInto(sp_std::marker::PhantomData); +impl> Morph for MorphInto { + type Outcome = T; + fn morph(a: A) -> T { + a.into() + } +} + +/// Implementation of `TryMorph` which attmepts to convert between types using `TryInto`. +pub struct TryMorphInto(sp_std::marker::PhantomData); +impl> TryMorph for TryMorphInto { + type Outcome = T; + fn try_morph(a: A) -> Result { + a.try_into().map_err(|_| ()) + } +} + /// Create a `Morph` and/or `TryMorph` impls with a simple closure-like expression. /// /// # Examples From f60ed83ff2a12a23763e9271ea41ec7ef37dbcc4 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 2 Mar 2023 17:37:22 +0100 Subject: [PATCH 38/79] One more test --- frame/core-fellowship/src/lib.rs | 5 +++-- frame/core-fellowship/src/tests.rs | 33 +++++++++++++++++------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 1695b83f9eed2..b4080016fa0df 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -26,7 +26,6 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; -use sp_runtime::{traits::AtLeast32BitUnsigned}; use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ @@ -175,6 +174,8 @@ pub mod pallet { UnexpectedRank, /// The given rank is invalid - this generally means it's not between 1 and 9. InvalidRank, + /// The account is not a candidate in the collective. + NotCandidate, /// The account is not a member of the collective. NotMember, /// The member is not tracked by this pallet (call `prove` on member first). @@ -302,7 +303,7 @@ pub mod pallet { pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; ensure!(allow_rank >= 1, Error::::NoPermission); - let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::NotCandidate)?; ensure!(rank.is_zero(), Error::::UnexpectedRank); ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index d9f8a540a24d0..b155a00297fe4 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -125,7 +125,7 @@ fn set_rank(who: u64, rank: u16) { } parameter_types! { - pub OneToNine: Vec = vec![1u64, 2, 3, 4, 5, 6, 7, 8, 9]; + pub ZeroToNine: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; } ord_parameter_types! { pub const One: u64 = 1; @@ -137,15 +137,24 @@ impl Config for Test { type Members = TestClub; type Balance = u64; type ParamsOrigin = EnsureSignedBy; - type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; - type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; + type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; + type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; type ApprovePeriod = ConstU64<2>; } pub fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + let params = ParamsType { + active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], + passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], + demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], + min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], + }; + assert_ok!(CoreFellowship::set_params(signed(1), params)); + System::set_block_number(1); + }); ext } @@ -171,7 +180,6 @@ fn basic_stuff() { assert_eq!(CoreFellowship::rank_to_index(1), Some(0)); assert_eq!(CoreFellowship::rank_to_index(9), Some(8)); assert_eq!(CoreFellowship::rank_to_index(10), None); - assert_noop!(CoreFellowship::induct(signed(1), 1), Error::::NotMember); assert_eq!(CoreFellowship::get_salary(0, &1), 0); }); } @@ -191,15 +199,12 @@ fn set_params_works() { } #[test] -fn set_params_works() { +fn induct_works() { new_test_ext().execute_with(|| { - let params = ParamsType { - active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], - passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], - demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], - min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], - }; - assert_noop!(CoreFellowship::set_params(signed(2), params.clone()), DispatchError::BadOrigin); - assert_ok!(CoreFellowship::set_params(signed(1), params)); + assert_noop!(CoreFellowship::induct(signed(1), 10), Error::::NotCandidate); + set_rank(10, 0); + assert_noop!(CoreFellowship::induct(signed(10), 10), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::induct(signed(0), 10), Error::::NoPermission); + assert_ok!(CoreFellowship::induct(signed(1), 10)); }); } From 3704efe9bac7a6451e9af5b1826a7e1db539aad0 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Fri, 3 Mar 2023 11:43:51 +0000 Subject: [PATCH 39/79] Update frame/salary/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index c49584d84ad1b..addef2d73745e 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -183,7 +183,7 @@ pub mod pallet { ::Balance, >; - /// The number of block within a cycle which accounts have to register their intent to + /// The number of blocks within a cycle which accounts have to register their intent to /// claim. /// /// The number of blocks between sequential payout cycles is the sum of this and From 93a845dc886adb999bc855e06e509b544bcba19d Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Fri, 3 Mar 2023 11:43:58 +0000 Subject: [PATCH 40/79] Update frame/salary/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index addef2d73745e..efe77a7cb9254 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -191,7 +191,7 @@ pub mod pallet { #[pallet::constant] type RegistrationPeriod: Get; - /// The number of block within a cycle which accounts have to claim the payout. + /// The number of blocks within a cycle which accounts have to claim the payout. /// /// The number of blocks between sequential payout cycles is the sum of this and /// `RegistrationPeriod`. From 2ef3e145d970b98dfd529904460f0cea4d82e05e Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 11:50:12 +0000 Subject: [PATCH 41/79] Mul floor --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 27eede62fd2b3..9677b18a90c42 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -473,7 +473,7 @@ pub mod pallet { unpaid } else { // Must be reduced pro-rata - Perbill::from_rational(status.budget, status.total_registrations) * unpaid + Perbill::from_rational(status.budget, status.total_registrations).mul_floor(unpaid) }; (payout, Some(unpaid)) }, From d992c0da025010a79044ab2233480b7ba49dd432 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 11:50:48 +0000 Subject: [PATCH 42/79] Tests --- bin/node/runtime/src/lib.rs | 9 ++- frame/core-fellowship/src/lib.rs | 102 ++++++++++++++++-------- frame/core-fellowship/src/tests.rs | 67 ++++++++++++++-- frame/salary/src/lib.rs | 5 +- frame/salary/src/tests.rs | 2 +- frame/support/src/traits/tokens.rs | 5 +- frame/support/src/traits/tokens/misc.rs | 2 +- 7 files changed, 145 insertions(+), 47 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 55be34f21fd62..d5fd46a4c09a6 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -32,10 +32,11 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, - Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, - U128CurrencyToVote, WithdrawReasons, tokens::GetSalary, + fungible::ItemOf, + tokens::{nonfungibles_v2::Inspect, GetSalary}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, + EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, + LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, }, weights::{ constants::{ diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index b4080016fa0df..9455497b4c3a9 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -31,10 +31,7 @@ use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - traits::{ - tokens::Balance as BalanceTrait, - RankedMembers, - }, + traits::{tokens::Balance as BalanceTrait, RankedMembers}, RuntimeDebug, }; @@ -92,7 +89,11 @@ pub struct MemberStatus { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::Pays, pallet_prelude::*, traits::{EnsureOrigin, tokens::GetSalary}}; + use frame_support::{ + dispatch::Pays, + pallet_prelude::*, + traits::{tokens::GetSalary, EnsureOrigin}, + }; use frame_system::pallet_prelude::*; use sp_core::Get; @@ -110,7 +111,10 @@ pub mod pallet { + IsType<::RuntimeEvent>; /// The current membership of the fellowship. - type Members: RankedMembers::AccountId, Rank = u16>; + type Members: RankedMembers< + AccountId = ::AccountId, + Rank = u16, + >; /// The type in which salaries/budgets are measured. type Balance: BalanceTrait; @@ -162,6 +166,9 @@ pub mod pallet { Promoted { who: T::AccountId, to_rank: RankOf }, /// Member has been demoted to the given (non-zero) rank. Demoted { who: T::AccountId, to_rank: RankOf }, + /// Member has been proven at their current rank, postponing auto-demotion. `new` is `true` + /// if this pallet did not previously track the member. + Proven { who: T::AccountId, at_rank: RankOf, new: bool }, } #[pallet::error] @@ -184,8 +191,8 @@ pub mod pallet { NoPermission, /// No work needs to be done at present for this member. NothingDoing, - /// The candidate has already been inducted. This should never happen since it would require - /// a candidate (rank 0) to already be tracked in the pallet. + /// The candidate has already been inducted. This should never happen since it would + /// require a candidate (rank 0) to already be tracked in the pallet. AlreadyInducted, /// The candidate has not been inducted, so cannot be offboarded from this pallet. NotInducted, @@ -208,12 +215,12 @@ pub mod pallet { let _ = ensure_signed(origin)?; let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - + let params = Params::::get(); let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; let demotion_period = params.demotion_period[rank_index]; let demotion_block = member.last_proof.saturating_add(demotion_period); - + // Ensure enough time has passed. let now = frame_system::Pallet::::block_number(); if now >= demotion_block { @@ -228,7 +235,7 @@ pub mod pallet { Event::::Demoted { who, to_rank } }; Self::deposit_event(event); - return Ok(Pays::No.into()); + return Ok(Pays::No.into()) } Err(Error::::NothingDoing.into()) @@ -240,7 +247,10 @@ pub mod pallet { /// - `params`: The new parameters for the pallet. #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(1)] - pub fn set_params(origin: OriginFor, params: ParamsOf) -> DispatchResultWithPostInfo { + pub fn set_params( + origin: OriginFor, + params: ParamsOf, + ) -> DispatchResultWithPostInfo { T::ParamsOrigin::ensure_origin(origin)?; Params::::put(¶ms); Self::deposit_event(Event::::ParamsChanged { params }); @@ -255,7 +265,10 @@ pub mod pallet { #[pallet::call_index(2)] pub fn set_active(origin: OriginFor, is_active: bool) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()), Error::::Unranked); + ensure!( + T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()), + Error::::Unranked + ); let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; member.is_active = is_active; Member::::insert(&who, &member); @@ -275,7 +288,11 @@ pub mod pallet { /// - `at_rank`: The rank of member. #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(3)] - pub fn prove(origin: OriginFor, who: T::AccountId, at_rank: RankOf) -> DispatchResultWithPostInfo { + pub fn prove( + origin: OriginFor, + who: T::AccountId, + at_rank: RankOf, + ) -> DispatchResultWithPostInfo { let allow_rank = T::ProofOrigin::ensure_origin(origin)?; ensure!(allow_rank >= at_rank, Error::::NoPermission); let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; @@ -283,12 +300,17 @@ pub mod pallet { // Maybe consider requiring it be at least `ApprovePeriod` prior to the auto-demotion. let now = frame_system::Pallet::::block_number(); - let mut member = Member::::get(&who).unwrap_or_else(|| MemberStatus { - is_active: true, - last_promotion: 0u32.into(), - last_proof: now, - }); - member.last_proof = now; + let maybe_member = Member::::get(&who); + let existed = maybe_member.is_some(); + let mut member = maybe_member.map_or_else( + || MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, + |mut m| { + m.last_proof = now; + m + }, + ); + Member::::insert(&who, &member); + Self::deposit_event(Event::::Proven { who, at_rank, new: !existed }); Ok(Pays::No.into()) } @@ -303,17 +325,16 @@ pub mod pallet { pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; ensure!(allow_rank >= 1, Error::::NoPermission); + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); let rank = T::Members::rank_of(&who).ok_or(Error::::NotCandidate)?; ensure!(rank.is_zero(), Error::::UnexpectedRank); - ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); T::Members::promote(&who)?; let now = frame_system::Pallet::::block_number(); - Member::::insert(&who, MemberStatus { - is_active: true, - last_promotion: now, - last_proof: now, - }); + Member::::insert( + &who, + MemberStatus { is_active: true, last_promotion: now, last_proof: now }, + ); Self::deposit_event(Event::::Inducted { who }); Ok(Pays::No.into()) } @@ -326,12 +347,19 @@ pub mod pallet { /// - `to_rank`: One more than the current rank of `who`. #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(5)] - pub fn promote(origin: OriginFor, who: T::AccountId, to_rank: RankOf) -> DispatchResultWithPostInfo { + pub fn promote( + origin: OriginFor, + who: T::AccountId, + to_rank: RankOf, + ) -> DispatchResultWithPostInfo { let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; ensure!(allow_rank >= to_rank, Error::::NoPermission); let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; - ensure!(rank.checked_add(1).map_or(false, |i| i == to_rank), Error::::UnexpectedRank); + ensure!( + rank.checked_add(1).map_or(false, |i| i == to_rank), + Error::::UnexpectedRank + ); let mut member = Member::::get(&who).ok_or(Error::::Unranked)?; let now = frame_system::Pallet::::block_number(); @@ -340,7 +368,10 @@ pub mod pallet { let rank_index = Self::rank_to_index(to_rank).ok_or(Error::::InvalidRank)?; let min_period = params.min_promotion_period[rank_index]; // Ensure enough time has passed. - ensure!(member.last_promotion.saturating_add(min_period) < now, Error::::TooSoon); + ensure!( + member.last_promotion.saturating_add(min_period) <= now, + Error::::TooSoon + ); T::Members::promote(&who)?; member.last_promotion = now; @@ -384,10 +415,17 @@ pub mod pallet { impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { fn get_salary(rank: RankOf, who: &T::AccountId) -> T::Balance { - let index = match Self::rank_to_index(rank) { Some(i) => i, None => return Zero::zero() }; - let member = match Member::::get(who) { Some(m) => m, None => return Zero::zero() }; + let index = match Self::rank_to_index(rank) { + Some(i) => i, + None => return Zero::zero(), + }; + let member = match Member::::get(who) { + Some(m) => m, + None => return Zero::zero(), + }; let params = Params::::get(); - let salary = if member.is_active { params.active_salary } else { params.passive_salary }; + let salary = + if member.is_active { params.active_salary } else { params.passive_salary }; salary[index] } } diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index b155a00297fe4..5e657d455b43e 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -20,16 +20,17 @@ use std::collections::BTreeMap; use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, pallet_prelude::Weight, parameter_types, - traits::{ConstU32, ConstU64, Everything, IsInVec, TryMapSuccess, tokens::GetSalary}, ord_parameter_types, assert_noop, assert_ok, + traits::{tokens::GetSalary, ConstU32, ConstU64, Everything, IsInVec, TryMapSuccess}, }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, TryMorphInto}, - DispatchResult, DispatchError, + DispatchError, DispatchResult, }; use sp_std::cell::RefCell; @@ -173,6 +174,12 @@ fn signed(who: u64) -> RuntimeOrigin { RuntimeOrigin::signed(who) } +fn next_demotion(who: u64) -> u64 { + let member = Member::::get(who).unwrap(); + let demotion_period = Params::::get().demotion_period; + member.last_promotion + demotion_period[TestClub::rank_of(&who).unwrap() as usize - 1] +} + #[test] fn basic_stuff() { new_test_ext().execute_with(|| { @@ -190,10 +197,13 @@ fn set_params_works() { let params = ParamsType { active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], - demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], - min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], + demotion_period: [1, 2, 3, 4, 5, 6, 7, 8, 9], + min_promotion_period: [1, 2, 3, 4, 5, 10, 15, 20, 30], }; - assert_noop!(CoreFellowship::set_params(signed(2), params.clone()), DispatchError::BadOrigin); + assert_noop!( + CoreFellowship::set_params(signed(2), params.clone()), + DispatchError::BadOrigin + ); assert_ok!(CoreFellowship::set_params(signed(1), params)); }); } @@ -206,5 +216,52 @@ fn induct_works() { assert_noop!(CoreFellowship::induct(signed(10), 10), DispatchError::BadOrigin); assert_noop!(CoreFellowship::induct(signed(0), 10), Error::::NoPermission); assert_ok!(CoreFellowship::induct(signed(1), 10)); + assert_noop!(CoreFellowship::induct(signed(1), 10), Error::::AlreadyInducted); + }); +} + +#[test] +fn promote_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::NotMember); + set_rank(10, 0); + assert_ok!(CoreFellowship::induct(signed(1), 10)); + assert_noop!(CoreFellowship::promote(signed(10), 10, 2), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::promote(signed(1), 10, 2), Error::::NoPermission); + assert_noop!(CoreFellowship::promote(signed(3), 10, 3), Error::::UnexpectedRank); + assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::TooSoon); + run_to(2); + assert_ok!(CoreFellowship::promote(signed(2), 10, 2)); + set_rank(11, 1); + assert_noop!(CoreFellowship::promote(signed(2), 11, 2), Error::::Unranked); + }); +} + +#[test] +fn proof_into_high_rank_works() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_noop!(CoreFellowship::prove(signed(4), 10, 5), Error::::NoPermission); + assert_noop!(CoreFellowship::prove(signed(6), 10, 6), Error::::UnexpectedRank); + assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert!(Member::::contains_key(10)); + assert_eq!(next_demotion(10), 6); + }); +} + +#[test] +fn auto_demote_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::NotMember); + set_rank(10, 0); + assert_ok!(CoreFellowship::induct(signed(1), 10)); + assert_noop!(CoreFellowship::promote(signed(10), 10, 2), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::promote(signed(1), 10, 2), Error::::NoPermission); + assert_noop!(CoreFellowship::promote(signed(3), 10, 3), Error::::UnexpectedRank); + assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::TooSoon); + run_to(2); + assert_ok!(CoreFellowship::promote(signed(2), 10, 2)); + set_rank(11, 1); + assert_noop!(CoreFellowship::promote(signed(2), 11, 2), Error::::Unranked); }); } diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 9677b18a90c42..9edc1ea0c7611 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -473,7 +473,8 @@ pub mod pallet { unpaid } else { // Must be reduced pro-rata - Perbill::from_rational(status.budget, status.total_registrations).mul_floor(unpaid) + Perbill::from_rational(status.budget, status.total_registrations) + .mul_floor(unpaid) }; (payout, Some(unpaid)) }, @@ -510,4 +511,4 @@ pub mod pallet { Ok(()) } } -} \ No newline at end of file +} diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 518a171baca1b..1a98e17218b98 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, pallet_prelude::Weight, parameter_types, - traits::{ConstU32, ConstU64, Everything, tokens::ConvertRank}, + traits::{tokens::ConvertRank, ConstU32, ConstU64, Everything}, }; use sp_core::H256; use sp_runtime::{ diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 3accc4979341d..f8dcf68159f1a 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -28,6 +28,7 @@ pub mod nonfungibles; pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ - AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, - ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, GetSalary, ConvertRank, + AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, ConvertRank, + DepositConsequence, ExistenceRequirement, GetSalary, Locker, WithdrawConsequence, + WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index eceee45c6ccfa..eff65f3620a32 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -20,7 +20,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; -use sp_runtime::{ArithmeticError, DispatchError, TokenError, traits::Convert}; +use sp_runtime::{traits::Convert, ArithmeticError, DispatchError, TokenError}; use sp_std::fmt::Debug; /// One of a number of consequences of withdrawing a fungible from an account. From e5c07330a49f53463756229edcbe10be6ac617fb Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 11:50:12 +0000 Subject: [PATCH 43/79] Mul floor --- frame/salary/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index c49584d84ad1b..0cb42d65fbc87 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -473,7 +473,7 @@ pub mod pallet { unpaid } else { // Must be reduced pro-rata - Perbill::from_rational(status.budget, status.total_registrations) * unpaid + Perbill::from_rational(status.budget, status.total_registrations).mul_floor(unpaid) }; (payout, Some(unpaid)) }, From b3d160e08d6fc9d60317f3b86809ac19d8cfe6e8 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 12:48:45 +0000 Subject: [PATCH 44/79] Fix warnings --- bin/node/runtime/src/lib.rs | 9 +++++---- frame/salary/src/lib.rs | 9 +++++---- frame/salary/src/tests.rs | 2 +- frame/support/src/traits/tokens.rs | 5 +++-- frame/support/src/traits/tokens/misc.rs | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 55be34f21fd62..d5fd46a4c09a6 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -32,10 +32,11 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, - Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, - U128CurrencyToVote, WithdrawReasons, tokens::GetSalary, + fungible::ItemOf, + tokens::{nonfungibles_v2::Inspect, GetSalary}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, + EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, + LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, }, weights::{ constants::{ diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index 24db91da428af..b6e838e672f40 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -22,9 +22,9 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_arithmetic::traits::{Saturating, Zero, AtLeast16BitUnsigned}; +use sp_arithmetic::traits::{Saturating, Zero}; use sp_core::TypedGet; -use sp_runtime::{traits::Convert, Perbill}; +use sp_runtime::Perbill; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use frame_support::{ @@ -473,7 +473,8 @@ pub mod pallet { unpaid } else { // Must be reduced pro-rata - Perbill::from_rational(status.budget, status.total_registrations).mul_floor(unpaid) + Perbill::from_rational(status.budget, status.total_registrations) + .mul_floor(unpaid) }; (payout, Some(unpaid)) }, @@ -510,4 +511,4 @@ pub mod pallet { Ok(()) } } -} \ No newline at end of file +} diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 518a171baca1b..1a98e17218b98 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, pallet_prelude::Weight, parameter_types, - traits::{ConstU32, ConstU64, Everything, tokens::ConvertRank}, + traits::{tokens::ConvertRank, ConstU32, ConstU64, Everything}, }; use sp_core::H256; use sp_runtime::{ diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 3accc4979341d..f8dcf68159f1a 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -28,6 +28,7 @@ pub mod nonfungibles; pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ - AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, - ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, GetSalary, ConvertRank, + AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, ConvertRank, + DepositConsequence, ExistenceRequirement, GetSalary, Locker, WithdrawConsequence, + WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index eceee45c6ccfa..eff65f3620a32 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -20,7 +20,7 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; -use sp_runtime::{ArithmeticError, DispatchError, TokenError, traits::Convert}; +use sp_runtime::{traits::Convert, ArithmeticError, DispatchError, TokenError}; use sp_std::fmt::Debug; /// One of a number of consequences of withdrawing a fungible from an account. From b9623a460cd82b5c4a890fb039ac6136c2d5374b Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 13:07:11 +0000 Subject: [PATCH 45/79] Fix test --- frame/salary/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs index 1a98e17218b98..8649291225bc3 100644 --- a/frame/salary/src/tests.rs +++ b/frame/salary/src/tests.rs @@ -636,6 +636,6 @@ fn other_mixed_bankrupcy_fails_gracefully() { assert_eq!(paid(1), 0); assert_eq!(paid(2), 3); - assert_eq!(paid(3), 7); + assert_eq!(paid(3), 6); }); } From 3580ea2db93e42aa4af6ea0c9865e6adc60a78a0 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 14:26:23 +0000 Subject: [PATCH 46/79] Tests --- frame/core-fellowship/src/benchmarking.rs | 17 ++++++ frame/core-fellowship/src/lib.rs | 38 +++++--------- frame/core-fellowship/src/tests.rs | 64 ++++++++++++++++------- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 339185b37cb7b..358688a664ece 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -43,6 +43,23 @@ fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { } } +/* +// move to benchmark code. No need for it here. +impl< + Balance: BalanceTrait, + BlockNumber: AtLeast32BitUnsigned + Copy, +> Default for ParamsType { + fn default() -> Self { + Self { + active_salary: [100u32.into(); 9], + passive_salary: [10u32.into(); 9], + demotion_period: [100u32.into(); 9], + min_promotion_period: [100u32.into(); 9], + } + } +} +*/ + #[instance_benchmarks] mod benchmarks { use super::*; diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 9455497b4c3a9..3db6d17b47797 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -58,23 +58,6 @@ pub struct ParamsType { min_promotion_period: [BlockNumber; 9], } -/* -// move to benchmark code. No need for it here. -impl< - Balance: BalanceTrait, - BlockNumber: AtLeast32BitUnsigned + Copy, -> Default for ParamsType { - fn default() -> Self { - Self { - active_salary: [100u32.into(); 9], - passive_salary: [10u32.into(); 9], - demotion_period: [100u32.into(); 9], - min_promotion_period: [100u32.into(); 9], - } - } -} -*/ - /// The status of a single member. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct MemberStatus { @@ -300,16 +283,19 @@ pub mod pallet { // Maybe consider requiring it be at least `ApprovePeriod` prior to the auto-demotion. let now = frame_system::Pallet::::block_number(); - let maybe_member = Member::::get(&who); - let existed = maybe_member.is_some(); - let mut member = maybe_member.map_or_else( - || MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, - |mut m| { + let existed = Member::::mutate(&who, |maybe| { + if let Some(m) = maybe { m.last_proof = now; - m - }, - ); - Member::::insert(&who, &member); + true + } else { + *maybe = Some(MemberStatus { + is_active: true, + last_promotion: 0u32.into(), + last_proof: now, + }); + false + } + }); Self::deposit_event(Event::::Proven { who, at_rank, new: !existed }); Ok(Pays::No.into()) diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index 5e657d455b43e..96887190d3b88 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -53,7 +53,7 @@ frame_support::construct_runtime!( parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, u64::max_value())); } impl frame_system::Config for Test { type BaseCallFilter = Everything; @@ -107,9 +107,9 @@ impl RankedMembers for TestClub { Ok(()) } fn demote(who: &Self::AccountId) -> DispatchResult { - CLUB.with(|club| match club.borrow().get(who) { + CLUB.with(|club| match Self::rank_of(who) { None => Err(sp_runtime::DispatchError::Unavailable), - Some(&0) => { + Some(0) => { club.borrow_mut().remove(&who); Ok(()) }, @@ -150,8 +150,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let params = ParamsType { active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], - demotion_period: [3, 3, 3, 6, 6, 6, 1000, 1000, 1000], - min_promotion_period: [1, 1, 1, 1, 2, 2, 6, 10, 20], + demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], + min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], }; assert_ok!(CoreFellowship::set_params(signed(1), params)); System::set_block_number(1); @@ -177,7 +177,7 @@ fn signed(who: u64) -> RuntimeOrigin { fn next_demotion(who: u64) -> u64 { let member = Member::::get(who).unwrap(); let demotion_period = Params::::get().demotion_period; - member.last_promotion + demotion_period[TestClub::rank_of(&who).unwrap() as usize - 1] + member.last_proof + demotion_period[TestClub::rank_of(&who).unwrap() as usize - 1] } #[test] @@ -229,8 +229,9 @@ fn promote_works() { assert_noop!(CoreFellowship::promote(signed(10), 10, 2), DispatchError::BadOrigin); assert_noop!(CoreFellowship::promote(signed(1), 10, 2), Error::::NoPermission); assert_noop!(CoreFellowship::promote(signed(3), 10, 3), Error::::UnexpectedRank); + run_to(6); assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::TooSoon); - run_to(2); + run_to(7); assert_ok!(CoreFellowship::promote(signed(2), 10, 2)); set_rank(11, 1); assert_noop!(CoreFellowship::promote(signed(2), 11, 2), Error::::Unranked); @@ -245,23 +246,48 @@ fn proof_into_high_rank_works() { assert_noop!(CoreFellowship::prove(signed(6), 10, 6), Error::::UnexpectedRank); assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); assert!(Member::::contains_key(10)); - assert_eq!(next_demotion(10), 6); + assert_eq!(next_demotion(10), 11); }); } #[test] fn auto_demote_works() { new_test_ext().execute_with(|| { - assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::NotMember); - set_rank(10, 0); - assert_ok!(CoreFellowship::induct(signed(1), 10)); - assert_noop!(CoreFellowship::promote(signed(10), 10, 2), DispatchError::BadOrigin); - assert_noop!(CoreFellowship::promote(signed(1), 10, 2), Error::::NoPermission); - assert_noop!(CoreFellowship::promote(signed(3), 10, 3), Error::::UnexpectedRank); - assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::TooSoon); - run_to(2); - assert_ok!(CoreFellowship::promote(signed(2), 10, 2)); - set_rank(11, 1); - assert_noop!(CoreFellowship::promote(signed(2), 11, 2), Error::::Unranked); + set_rank(10, 5); + assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + + run_to(10); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + run_to(11); + assert_ok!(CoreFellowship::bump(signed(0), 10)); + assert_eq!(TestClub::rank_of(&10), Some(4)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + assert_eq!(next_demotion(10), 19); + }); +} + +#[test] +fn proof_postpones_auto_demote() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + + run_to(11); + assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_eq!(next_demotion(10), 21); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + }); +} + +#[test] +fn promote_postpones_auto_demote() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + + run_to(19); + assert_ok!(CoreFellowship::promote(signed(6), 10, 6)); + assert_eq!(next_demotion(10), 31); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); }); } From 31bf7b651e33e6026a84174fdcf7217d9705765e Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 15:53:41 +0000 Subject: [PATCH 47/79] Last tests --- frame/core-fellowship/src/lib.rs | 8 ++--- frame/core-fellowship/src/tests.rs | 55 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 3db6d17b47797..92382e1b53d21 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -369,11 +369,11 @@ pub mod pallet { Ok(Pays::No.into()) } - /// Make a "promotion" from a candiate (rank zero) into a member (rank one). + /// Stop tracking an prior member who is now not a ranked member of the collective. /// - /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of 1 or - /// more. - /// - `who`: The account ID of the candidate to be inducted and become a member. + /// - `origin`: A `Signed` origin of an account. + /// - `who`: The ID of an account which was tracked in this pallet but which is now not a + /// ranked member of the collective. #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(6)] pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index 96887190d3b88..ce83bb512e3d4 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -266,6 +266,36 @@ fn auto_demote_works() { }); } +#[test] +fn auto_demote_offboard_works() { + new_test_ext().execute_with(|| { + set_rank(10, 1); + assert_ok!(CoreFellowship::prove(signed(1), 10, 1)); + + run_to(3); + assert_ok!(CoreFellowship::bump(signed(0), 10)); + assert_eq!(TestClub::rank_of(&10), Some(0)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); + }); +} + +#[test] +fn offboard_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotInducted); + set_rank(10, 1); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); + + assert_ok!(CoreFellowship::prove(signed(1), 10, 1)); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); + + set_rank(10, 0); + assert_ok!(CoreFellowship::offboard(signed(0), 10)); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotInducted); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); + }); +} + #[test] fn proof_postpones_auto_demote() { new_test_ext().execute_with(|| { @@ -291,3 +321,28 @@ fn promote_postpones_auto_demote() { assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); }); } + +#[test] +fn get_salary_works() { + new_test_ext().execute_with(|| { + for i in 1..=9 { + set_rank(10, i); + assert_ok!(CoreFellowship::prove(signed(i as u64), 10, i)); + assert_eq!(CoreFellowship::get_salary(i, &10), i as u64 * 10); + } + }); +} + +#[test] +fn active_changing_get_salary_works() { + new_test_ext().execute_with(|| { + for i in 1..=9 { + set_rank(10, i); + assert_ok!(CoreFellowship::prove(signed(i as u64), 10, i)); + assert_ok!(CoreFellowship::set_active(signed(10), false)); + assert_eq!(CoreFellowship::get_salary(i, &10), i as u64); + assert_ok!(CoreFellowship::set_active(signed(10), true)); + assert_eq!(CoreFellowship::get_salary(i, &10), i as u64 * 10); + } + }); +} From c08c01087895fc53e6f83e4f737618e6219fac81 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 3 Mar 2023 15:57:36 +0000 Subject: [PATCH 48/79] Docs --- frame/salary/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs index b6e838e672f40..2169952f1a7f3 100644 --- a/frame/salary/src/lib.rs +++ b/frame/salary/src/lib.rs @@ -82,8 +82,8 @@ pub trait Pay { /// after this call. Used in benchmarking code. #[cfg(feature = "runtime-benchmarks")] fn ensure_successful(who: &Self::AccountId, amount: Self::Balance); - /// Ensure that a call to [check_payment] with the given parameters will return either [Success] - /// or [Failure]. + /// Ensure that a call to check_payment with the given parameters will return either Success + /// or Failure. #[cfg(feature = "runtime-benchmarks")] fn ensure_concluded(id: Self::Id); } @@ -403,7 +403,7 @@ pub mod pallet { /// Update a payment's status; if it failed, alter the state so the payment can be retried. /// /// This must be called within the same cycle as the failed payment. It will fail with - /// [Event::NotCurrent] otherwise. + /// `Event::NotCurrent` otherwise. /// /// - `origin`: A `Signed` origin of an account which is a member of `Members` who has /// received a payment this cycle. From 6c15f6146d4b8ba61c790d35fb93afa5a1f85661 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 4 Mar 2023 12:13:10 +0000 Subject: [PATCH 49/79] Fix warnings --- frame/core-fellowship/src/weights.rs | 6 ++-- frame/salary/src/weights.rs | 42 ++++++++++------------------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 77970671f8fbe..e35d04f45a466 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -62,8 +62,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `1120` // Estimated: `551` // Minimum execution time: 21_058 nanoseconds. - Weight::from_ref_time(21_381_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(21_381_000, 551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -78,8 +77,7 @@ impl WeightInfo for () { // Measured: `1120` // Estimated: `551` // Minimum execution time: 21_058 nanoseconds. - Weight::from_ref_time(21_381_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(21_381_000, 551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/frame/salary/src/weights.rs b/frame/salary/src/weights.rs index f678d086dfcd9..1aff178971440 100644 --- a/frame/salary/src/weights.rs +++ b/frame/salary/src/weights.rs @@ -68,8 +68,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `1120` // Estimated: `551` // Minimum execution time: 21_058 nanoseconds. - Weight::from_ref_time(21_381_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(21_381_000, 551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -80,8 +79,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `1234` // Estimated: `551` // Minimum execution time: 22_272 nanoseconds. - Weight::from_ref_time(22_923_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(22_923_000, 551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -96,8 +94,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `1510` // Estimated: `5621` // Minimum execution time: 32_223 nanoseconds. - Weight::from_ref_time(32_663_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(32_663_000, 5621) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -112,8 +109,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `1616` // Estimated: `5621` // Minimum execution time: 38_279 nanoseconds. - Weight::from_ref_time(38_996_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(38_996_000, 5621) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -128,8 +124,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `2241` // Estimated: `5621` // Minimum execution time: 68_868 nanoseconds. - Weight::from_ref_time(70_160_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(70_160_000, 5621) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -146,8 +141,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `2189` // Estimated: `8224` // Minimum execution time: 68_804 nanoseconds. - Weight::from_ref_time(69_223_000) - .saturating_add(Weight::from_proof_size(8224)) + Weight::from_parts(69_223_000, 8224) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -160,8 +154,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `880` // Estimated: `3104` // Minimum execution time: 19_027 nanoseconds. - Weight::from_ref_time(19_360_000) - .saturating_add(Weight::from_proof_size(3104)) + Weight::from_parts(19_360_000, 3104) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -176,8 +169,7 @@ impl WeightInfo for () { // Measured: `1120` // Estimated: `551` // Minimum execution time: 21_058 nanoseconds. - Weight::from_ref_time(21_381_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(21_381_000, 551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -188,8 +180,7 @@ impl WeightInfo for () { // Measured: `1234` // Estimated: `551` // Minimum execution time: 22_272 nanoseconds. - Weight::from_ref_time(22_923_000) - .saturating_add(Weight::from_proof_size(551)) + Weight::from_parts(22_923_000, 551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -204,8 +195,7 @@ impl WeightInfo for () { // Measured: `1510` // Estimated: `5621` // Minimum execution time: 32_223 nanoseconds. - Weight::from_ref_time(32_663_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(32_663_000, 5621) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -220,8 +210,7 @@ impl WeightInfo for () { // Measured: `1616` // Estimated: `5621` // Minimum execution time: 38_279 nanoseconds. - Weight::from_ref_time(38_996_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(38_996_000, 5621) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -236,8 +225,7 @@ impl WeightInfo for () { // Measured: `2241` // Estimated: `5621` // Minimum execution time: 68_868 nanoseconds. - Weight::from_ref_time(70_160_000) - .saturating_add(Weight::from_proof_size(5621)) + Weight::from_parts(70_160_000, 5621) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -254,8 +242,7 @@ impl WeightInfo for () { // Measured: `2189` // Estimated: `8224` // Minimum execution time: 68_804 nanoseconds. - Weight::from_ref_time(69_223_000) - .saturating_add(Weight::from_proof_size(8224)) + Weight::from_parts(69_223_000, 8224) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -268,8 +255,7 @@ impl WeightInfo for () { // Measured: `880` // Estimated: `3104` // Minimum execution time: 19_027 nanoseconds. - Weight::from_ref_time(19_360_000) - .saturating_add(Weight::from_proof_size(3104)) + Weight::from_parts(19_360_000, 3104) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } From 9d947a5719ced37bc6b30650125365aefd06eff9 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 4 Mar 2023 15:32:16 +0000 Subject: [PATCH 50/79] Benchmarks --- frame/core-fellowship/Cargo.toml | 2 + frame/core-fellowship/src/benchmarking.rs | 219 ++++++++++------------ frame/core-fellowship/src/lib.rs | 23 ++- frame/system/src/lib.rs | 7 +- 4 files changed, 118 insertions(+), 133 deletions(-) diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml index 90d489283a2bd..001d5a39d1583 100644 --- a/frame/core-fellowship/Cargo.toml +++ b/frame/core-fellowship/Cargo.toml @@ -49,5 +49,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-ranked-collective/runtime-benchmarks", + "pallet-salary/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 358688a664ece..d1cca9f1af4ef 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -20,180 +20,161 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use crate::Pallet as Salary; +use crate::Pallet as CoreFellowship; use frame_benchmarking::v2::*; -use frame_system::{Pallet as System, RawOrigin}; -use sp_core::Get; +use frame_system::RawOrigin; +use sp_arithmetic::traits::Bounded; const SEED: u32 = 0; -fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { - // induct if not a member. - if T::Members::rank_of(who).is_none() { - T::Members::induct(who).unwrap(); - } - // promote until they have a salary. - for _ in 0..255 { - let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); - if !T::Salary::get_salary(r, &who).is_zero() { - break - } - T::Members::promote(who).unwrap(); - } -} +#[instance_benchmarks] +mod benchmarks { + use super::*; -/* -// move to benchmark code. No need for it here. -impl< - Balance: BalanceTrait, - BlockNumber: AtLeast32BitUnsigned + Copy, -> Default for ParamsType { - fn default() -> Self { - Self { + #[benchmark] + fn set_params() { + let params = ParamsType { active_salary: [100u32.into(); 9], passive_salary: [10u32.into(); 9], demotion_period: [100u32.into(); 9], min_promotion_period: [100u32.into(); 9], - } + }; + + #[extrinsic_call] + _(RawOrigin::Root, params.clone()); + + assert_eq!(Params::::get(), params); } -} -*/ -#[instance_benchmarks] -mod benchmarks { - use super::*; + #[benchmark] + fn bump_offboard() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + + // Set it to the max value to ensure that any possible auto-demotion period has passed. + frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); + assert!(Member::::contains_key(&member)); + + #[extrinsic_call] + CoreFellowship::::bump(RawOrigin::Signed(member.clone()), member.clone()); + + assert!(!Member::::contains_key(&member)); + } #[benchmark] - fn init() { - let caller: T::AccountId = whitelisted_caller(); + fn bump_demote() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 2u8.into()).unwrap(); + + // Set it to the max value to ensure that any possible auto-demotion period has passed. + frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); + assert!(Member::::contains_key(&member)); + assert_eq!(T::Members::rank_of(&member), Some(2)); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); + CoreFellowship::::bump(RawOrigin::Signed(member.clone()), member.clone()); - assert!(Salary::::status().is_some()); + assert!(Member::::contains_key(&member)); + assert_eq!(T::Members::rank_of(&member), Some(1)); } #[benchmark] - fn bump() { - let caller: T::AccountId = whitelisted_caller(); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + fn set_active() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + assert!(Member::::get(&member).unwrap().is_active); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); + _(RawOrigin::Signed(member.clone()), false); - assert_eq!(Salary::::status().unwrap().cycle_index, 1u32.into()); + assert!(!Member::::get(&member).unwrap().is_active); } #[benchmark] fn induct() { - let caller = whitelisted_caller(); - ensure_member_with_salary::(&caller); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + let candidate = account("candidate", 0, SEED); + T::Members::induct(&candidate).unwrap(); + assert_eq!(T::Members::rank_of(&candidate), Some(0)); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); + _(RawOrigin::Root, candidate.clone()); - assert!(Salary::::last_active(&caller).is_ok()); + assert_eq!(T::Members::rank_of(&candidate), Some(1)); } #[benchmark] - fn register() { - let caller = whitelisted_caller(); - ensure_member_with_salary::(&caller); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); - Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + Salary::::cycle_period()); - Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + fn promote() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + assert_eq!(T::Members::rank_of(&member), Some(1)); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); + _(RawOrigin::Root, member.clone(), 2u8.into()); - assert_eq!(Salary::::last_active(&caller).unwrap(), 1u32.into()); + assert_eq!(T::Members::rank_of(&member), Some(2)); } #[benchmark] - fn payout() { - let caller = whitelisted_caller(); - ensure_member_with_salary::(&caller); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); - Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + Salary::::cycle_period()); - Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - - let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); - T::Paymaster::ensure_successful(&caller, salary); + fn offboard() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + T::Members::demote(&member).unwrap(); + + assert_eq!(T::Members::rank_of(&member), Some(0)); + assert!(Member::::contains_key(&member)); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); - - match Claimant::::get(&caller) { - Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { - assert_eq!(last_active, 1u32.into()); - assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); - }, - _ => panic!("No claim made"), - } - assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + _(RawOrigin::Signed(member.clone()), member.clone()); + + assert!(!Member::::contains_key(&member)); } #[benchmark] - fn payout_other() { - let caller = whitelisted_caller(); - ensure_member_with_salary::(&caller); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); - Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + Salary::::cycle_period()); - Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - - let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); - let recipient: T::AccountId = account("recipient", 0, SEED); - T::Paymaster::ensure_successful(&recipient, salary); + fn prove_new() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + assert!(!Member::::contains_key(&member)); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient.clone()); - - match Claimant::::get(&caller) { - Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { - assert_eq!(last_active, 1u32.into()); - assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); - }, - _ => panic!("No claim made"), - } - assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + CoreFellowship::::prove(RawOrigin::Root, member.clone(), 1u8.into()); + + assert!(Member::::contains_key(&member)); } #[benchmark] - fn check_payment() { - let caller = whitelisted_caller(); - ensure_member_with_salary::(&caller); - Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); - Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + Salary::::cycle_period()); - Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); - System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); - - let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); - let recipient: T::AccountId = account("recipient", 0, SEED); - T::Paymaster::ensure_successful(&recipient, salary); - Salary::::payout(RawOrigin::Signed(caller.clone()).into()).unwrap(); - let id = match Claimant::::get(&caller).unwrap().status { - Attempted { id, .. } => id, - _ => panic!("No claim made"), - }; - T::Paymaster::ensure_concluded(id); + fn prove_existing() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + + let then = frame_system::Pallet::::block_number(); + let now = then.saturating_plus_one(); + frame_system::Pallet::::set_block_number(now); + + assert_eq!(Member::::get(&member).unwrap().last_proof, then); #[extrinsic_call] - _(RawOrigin::Signed(caller.clone())); + CoreFellowship::::prove(RawOrigin::Root, member.clone(), 1u8.into()); - assert!(!matches!(Claimant::::get(&caller).unwrap().status, Attempted { .. })); + assert_eq!(Member::::get(&member).unwrap().last_proof, now); } impl_benchmark_test_suite! { - Salary, + CoreFellowship, crate::tests::new_test_ext(), crate::tests::Test, } diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 92382e1b53d21..982cf90048d4f 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -77,7 +77,7 @@ pub mod pallet { pallet_prelude::*, traits::{tokens::GetSalary, EnsureOrigin}, }; - use frame_system::pallet_prelude::*; + use frame_system::{ensure_root, pallet_prelude::*}; use sp_core::Get; #[pallet::pallet] @@ -234,7 +234,7 @@ pub mod pallet { origin: OriginFor, params: ParamsOf, ) -> DispatchResultWithPostInfo { - T::ParamsOrigin::ensure_origin(origin)?; + T::ParamsOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; Params::::put(¶ms); Self::deposit_event(Event::::ParamsChanged { params }); Ok(Pays::No.into()) @@ -276,8 +276,10 @@ pub mod pallet { who: T::AccountId, at_rank: RankOf, ) -> DispatchResultWithPostInfo { - let allow_rank = T::ProofOrigin::ensure_origin(origin)?; - ensure!(allow_rank >= at_rank, Error::::NoPermission); + match T::PromoteOrigin::try_origin(origin) { + Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::::NoPermission), + Err(origin) => ensure_root(origin)?, + } let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; ensure!(rank == at_rank, Error::::UnexpectedRank); @@ -309,8 +311,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::init())] #[pallet::call_index(4)] pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { - let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; - ensure!(allow_rank >= 1, Error::::NoPermission); + match T::PromoteOrigin::try_origin(origin) { + Ok(allow_rank) => ensure!(allow_rank >= 1, Error::::NoPermission), + Err(origin) => ensure_root(origin)?, + } ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); let rank = T::Members::rank_of(&who).ok_or(Error::::NotCandidate)?; ensure!(rank.is_zero(), Error::::UnexpectedRank); @@ -338,9 +342,10 @@ pub mod pallet { who: T::AccountId, to_rank: RankOf, ) -> DispatchResultWithPostInfo { - let allow_rank = T::PromoteOrigin::ensure_origin(origin)?; - ensure!(allow_rank >= to_rank, Error::::NoPermission); - + match T::PromoteOrigin::try_origin(origin) { + Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::::NoPermission), + Err(origin) => ensure_root(origin)?, + } let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; ensure!( rank.checked_add(1).map_or(false, |i| i == to_rank), diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 5e921ab854df1..0ec808d4c960f 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -878,12 +878,9 @@ impl< #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin() -> Result { - let zero_account_id = - AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; - let members = Who::sorted_members(); - let first_member = match members.get(0) { + let first_member = match Who::sorted_members().first() { Some(account) => account.clone(), - None => zero_account_id, + None => AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?, }; Ok(O::from(RawOrigin::Signed(first_member))) } From 10e30b3926ea57f7eb301da7254d749060449e0d Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 4 Mar 2023 15:40:03 +0000 Subject: [PATCH 51/79] Weights --- frame/core-fellowship/src/lib.rs | 14 ++++---- frame/core-fellowship/src/weights.rs | 50 +++++++++++++++------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 982cf90048d4f..9bd69b834eeff 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -192,7 +192,7 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of an account. /// - `who`: A member account whose state is to be updated. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::bump_offboard().max(T::WeightInfo::bump_demote()))] #[pallet::call_index(0)] pub fn bump(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; @@ -228,7 +228,7 @@ pub mod pallet { /// /// - `origin`: A origin complying with `ParamsOrigin`. /// - `params`: The new parameters for the pallet. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::set_params())] #[pallet::call_index(1)] pub fn set_params( origin: OriginFor, @@ -244,7 +244,7 @@ pub mod pallet { /// /// - `origin`: A `Signed` origin of a member's account. /// - `active`: `true` iff the member is active. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::set_active())] #[pallet::call_index(2)] pub fn set_active(origin: OriginFor, is_active: bool) -> DispatchResult { let who = ensure_signed(origin)?; @@ -269,7 +269,7 @@ pub mod pallet { /// - `origin`: An origin which satisfies `ProofOrigin`. /// - `who`: A member (i.e. of non-zero rank). /// - `at_rank`: The rank of member. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::prove_existing().max(T::WeightInfo::prove_new()))] #[pallet::call_index(3)] pub fn prove( origin: OriginFor, @@ -308,7 +308,7 @@ pub mod pallet { /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of 1 or /// more. /// - `who`: The account ID of the candidate to be inducted and become a member. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::induct())] #[pallet::call_index(4)] pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { match T::PromoteOrigin::try_origin(origin) { @@ -335,7 +335,7 @@ pub mod pallet { /// `to_rank` or more. /// - `who`: The account ID of the member to be promoted to `to_rank`. /// - `to_rank`: One more than the current rank of `who`. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::promote())] #[pallet::call_index(5)] pub fn promote( origin: OriginFor, @@ -379,7 +379,7 @@ pub mod pallet { /// - `origin`: A `Signed` origin of an account. /// - `who`: The ID of an account which was tracked in this pallet but which is now not a /// ranked member of the collective. - #[pallet::weight(T::WeightInfo::init())] + #[pallet::weight(T::WeightInfo::offboard())] #[pallet::call_index(6)] pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index e35d04f45a466..7b8aa62e53f50 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -49,36 +49,40 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_salary. pub trait WeightInfo { - fn init() -> Weight; + fn set_params() -> Weight; + fn bump_offboard() -> Weight; + fn bump_demote() -> Weight; + fn set_active() -> Weight; + fn induct() -> Weight; + fn promote() -> Weight; + fn offboard() -> Weight; + fn prove_new() -> Weight; + fn prove_existing() -> Weight; } /// Weights for pallet_salary using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: Salary Status (r:1 w:1) - /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) - fn init() -> Weight { - // Proof Size summary in bytes: - // Measured: `1120` - // Estimated: `551` - // Minimum execution time: 21_058 nanoseconds. - Weight::from_parts(21_381_000, 551) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } + fn set_params() -> Weight { Weight::from_parts(0, 0) } + fn bump_offboard() -> Weight { Weight::from_parts(0, 0) } + fn bump_demote() -> Weight { Weight::from_parts(0, 0) } + fn set_active() -> Weight { Weight::from_parts(0, 0) } + fn induct() -> Weight { Weight::from_parts(0, 0) } + fn promote() -> Weight { Weight::from_parts(0, 0) } + fn offboard() -> Weight { Weight::from_parts(0, 0) } + fn prove_new() -> Weight { Weight::from_parts(0, 0) } + fn prove_existing() -> Weight { Weight::from_parts(0, 0) } } // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: Salary Status (r:1 w:1) - /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) - fn init() -> Weight { - // Proof Size summary in bytes: - // Measured: `1120` - // Estimated: `551` - // Minimum execution time: 21_058 nanoseconds. - Weight::from_parts(21_381_000, 551) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } + fn set_params() -> Weight { Weight::from_parts(0, 0) } + fn bump_offboard() -> Weight { Weight::from_parts(0, 0) } + fn bump_demote() -> Weight { Weight::from_parts(0, 0) } + fn set_active() -> Weight { Weight::from_parts(0, 0) } + fn induct() -> Weight { Weight::from_parts(0, 0) } + fn promote() -> Weight { Weight::from_parts(0, 0) } + fn offboard() -> Weight { Weight::from_parts(0, 0) } + fn prove_new() -> Weight { Weight::from_parts(0, 0) } + fn prove_existing() -> Weight { Weight::from_parts(0, 0) } } From bda6348acfb07e5b9b06df56e76e07d8853e9e0c Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 4 Mar 2023 15:49:46 +0000 Subject: [PATCH 52/79] Integrate benchmark --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 4 ++++ bin/node/runtime/src/lib.rs | 12 ++++++++++++ frame/core-fellowship/src/lib.rs | 6 ------ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d013137a11e38..a180ab177701e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3511,6 +3511,7 @@ dependencies = [ "pallet-contracts", "pallet-contracts-primitives", "pallet-conviction-voting", + "pallet-core-fellowship", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index a610bbe5d8113..16f1d0a4cb532 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -65,6 +65,7 @@ pallet-collective = { version = "4.0.0-dev", default-features = false, path = ". pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" } pallet-contracts-primitives = { version = "7.0.0", default-features = false, path = "../../../frame/contracts/primitives/" } pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" } +pallet-core-fellowship = { version = "4.0.0-dev", default-features = false, path = "../../../frame/core-fellowship" } pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" } pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true } @@ -148,6 +149,7 @@ std = [ "pallet-contracts/std", "pallet-contracts-primitives/std", "pallet-conviction-voting/std", + "pallet-core-fellowship/std", "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-fast-unstake/std", @@ -235,6 +237,7 @@ runtime-benchmarks = [ "pallet-collective/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", + "pallet-core-fellowship/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", @@ -294,6 +297,7 @@ try-runtime = [ "pallet-collective/try-runtime", "pallet-contracts/try-runtime", "pallet-conviction-voting/try-runtime", + "pallet-core-fellowship/try-runtime", "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0c21fcc904fa1..9773c0415b9a0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1595,6 +1595,16 @@ impl pallet_salary::Config for Runtime { type Budget = Budget; } +impl pallet_core_fellowship::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Members = RankedCollective; + type Balance = Balance; + type ParamsOrigin = frame_system::EnsureRoot; + type ProofOrigin = frame_system::EnsureRootWithSuccess>; + type PromoteOrigin = frame_system::EnsureRootWithSuccess>; +} + parameter_types! { pub Features: PalletFeatures = PalletFeatures::all_enabled(); pub const MaxAttributesPerCall: u32 = 10; @@ -1791,6 +1801,7 @@ construct_runtime!( Uniques: pallet_uniques, Nfts: pallet_nfts, Salary: pallet_salary, + CoreFellowship: pallet_core_fellowship, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, @@ -1887,6 +1898,7 @@ mod benches { [pallet_collective, Council] [pallet_conviction_voting, ConvictionVoting] [pallet_contracts, Contracts] + [pallet_core_fellowship, CoreFellowship] [pallet_democracy, Democracy] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] [pallet_election_provider_support_benchmarking, EPSBench::] diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 9bd69b834eeff..d6f7e226ff464 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -78,7 +78,6 @@ pub mod pallet { traits::{tokens::GetSalary, EnsureOrigin}, }; use frame_system::{ensure_root, pallet_prelude::*}; - use sp_core::Get; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -112,10 +111,6 @@ pub mod pallet { /// The origin which has permission to promote a member. The `Success` value is the maximum /// rank to which it can promote. type PromoteOrigin: EnsureOrigin>; - - /// The period before auto-demotion that a member can be (re-)approved for their rank. - #[pallet::constant] - type ApprovePeriod: Get; } pub type ParamsOf = @@ -283,7 +278,6 @@ pub mod pallet { let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; ensure!(rank == at_rank, Error::::UnexpectedRank); - // Maybe consider requiring it be at least `ApprovePeriod` prior to the auto-demotion. let now = frame_system::Pallet::::block_number(); let existed = Member::::mutate(&who, |maybe| { if let Some(m) = maybe { From 47ad52c759423b420e63b3264550441fb8c0d343 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 4 Mar 2023 15:55:01 +0000 Subject: [PATCH 53/79] Fixes --- frame/core-fellowship/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index ce83bb512e3d4..c582ddb3ab41f 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -140,7 +140,6 @@ impl Config for Test { type ParamsOrigin = EnsureSignedBy; type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; - type ApprovePeriod = ConstU64<2>; } pub fn new_test_ext() -> sp_io::TestExternalities { From 6752c18f573658a1c02ff6a547ad2dc78b7e025e Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 08:23:25 +0000 Subject: [PATCH 54/79] Fix --- frame/core-fellowship/src/benchmarking.rs | 2 +- frame/core-fellowship/src/lib.rs | 6 +++--- frame/core-fellowship/src/tests.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index d1cca9f1af4ef..1ca81c3aaa544 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -42,7 +42,7 @@ mod benchmarks { }; #[extrinsic_call] - _(RawOrigin::Root, params.clone()); + _(RawOrigin::Root, Box::new(params.clone())); assert_eq!(Params::::get(), params); } diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index d6f7e226ff464..c92172acd0be2 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -227,11 +227,11 @@ pub mod pallet { #[pallet::call_index(1)] pub fn set_params( origin: OriginFor, - params: ParamsOf, + params: Box>, ) -> DispatchResultWithPostInfo { T::ParamsOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; - Params::::put(¶ms); - Self::deposit_event(Event::::ParamsChanged { params }); + Params::::put(params.as_ref()); + Self::deposit_event(Event::::ParamsChanged { params: *params }); Ok(Pays::No.into()) } diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index c582ddb3ab41f..99a3efdb00a95 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -152,7 +152,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], }; - assert_ok!(CoreFellowship::set_params(signed(1), params)); + assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); System::set_block_number(1); }); ext @@ -200,10 +200,10 @@ fn set_params_works() { min_promotion_period: [1, 2, 3, 4, 5, 10, 15, 20, 30], }; assert_noop!( - CoreFellowship::set_params(signed(2), params.clone()), + CoreFellowship::set_params(signed(2), Box::new(params.clone())), DispatchError::BadOrigin ); - assert_ok!(CoreFellowship::set_params(signed(1), params)); + assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); }); } From 3116351cf827afea4673c29dfe5c2b0705e61def Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Sun, 5 Mar 2023 10:24:03 +0000 Subject: [PATCH 55/79] ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_core_fellowship --- frame/core-fellowship/src/weights.rs | 312 ++++++++++++++++++++++++--- 1 file changed, 287 insertions(+), 25 deletions(-) diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 7b8aa62e53f50..1ee355ffbf356 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -15,10 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_salary +//! Autogenerated weights for pallet_core_fellowship //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-02-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-03-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -34,10 +34,10 @@ // --wasm-execution=compiled // --heap-pages=4096 // --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_salary +// --pallet=pallet_core_fellowship // --chain=dev // --header=./HEADER-APACHE2 -// --output=./frame/salary/src/weights.rs +// --output=./frame/core-fellowship/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -47,7 +47,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_salary. +/// Weight functions needed for pallet_core_fellowship. pub trait WeightInfo { fn set_params() -> Weight; fn bump_offboard() -> Weight; @@ -57,32 +57,294 @@ pub trait WeightInfo { fn promote() -> Weight; fn offboard() -> Weight; fn prove_new() -> Weight; - fn prove_existing() -> Weight; + fn prove_existing() -> Weight; } -/// Weights for pallet_salary using the Substrate node and recommended hardware. +/// Weights for pallet_core_fellowship using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - fn set_params() -> Weight { Weight::from_parts(0, 0) } - fn bump_offboard() -> Weight { Weight::from_parts(0, 0) } - fn bump_demote() -> Weight { Weight::from_parts(0, 0) } - fn set_active() -> Weight { Weight::from_parts(0, 0) } - fn induct() -> Weight { Weight::from_parts(0, 0) } - fn promote() -> Weight { Weight::from_parts(0, 0) } - fn offboard() -> Weight { Weight::from_parts(0, 0) } - fn prove_new() -> Weight { Weight::from_parts(0, 0) } - fn prove_existing() -> Weight { Weight::from_parts(0, 0) } + /// Storage: CoreFellowship Params (r:0 w:1) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + fn set_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_826_000 picoseconds. + Weight::from_parts(11_146_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn bump_offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `457` + // Estimated: `15864` + // Minimum execution time: 38_282_000 picoseconds. + Weight::from_parts(38_776_000, 15864) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn bump_demote() -> Weight { + // Proof Size summary in bytes: + // Measured: `509` + // Estimated: `15864` + // Minimum execution time: 39_384_000 picoseconds. + Weight::from_parts(39_788_000, 15864) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn set_active() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 19_412_000 picoseconds. + Weight::from_parts(19_687_000, 7021) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `10500` + // Minimum execution time: 32_505_000 picoseconds. + Weight::from_parts(33_049_000, 10500) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn promote() -> Weight { + // Proof Size summary in bytes: + // Measured: `377` + // Estimated: `12345` + // Minimum execution time: 35_992_000 picoseconds. + Weight::from_parts(37_227_000, 12345) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 18_963_000 picoseconds. + Weight::from_parts(19_313_000, 7021) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn prove_new() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `7021` + // Minimum execution time: 17_849_000 picoseconds. + Weight::from_parts(18_163_000, 7021) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn prove_existing() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 19_178_000 picoseconds. + Weight::from_parts(19_509_000, 7021) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests impl WeightInfo for () { - fn set_params() -> Weight { Weight::from_parts(0, 0) } - fn bump_offboard() -> Weight { Weight::from_parts(0, 0) } - fn bump_demote() -> Weight { Weight::from_parts(0, 0) } - fn set_active() -> Weight { Weight::from_parts(0, 0) } - fn induct() -> Weight { Weight::from_parts(0, 0) } - fn promote() -> Weight { Weight::from_parts(0, 0) } - fn offboard() -> Weight { Weight::from_parts(0, 0) } - fn prove_new() -> Weight { Weight::from_parts(0, 0) } - fn prove_existing() -> Weight { Weight::from_parts(0, 0) } + /// Storage: CoreFellowship Params (r:0 w:1) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + fn set_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_826_000 picoseconds. + Weight::from_parts(11_146_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn bump_offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `457` + // Estimated: `15864` + // Minimum execution time: 38_282_000 picoseconds. + Weight::from_parts(38_776_000, 15864) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn bump_demote() -> Weight { + // Proof Size summary in bytes: + // Measured: `509` + // Estimated: `15864` + // Minimum execution time: 39_384_000 picoseconds. + Weight::from_parts(39_788_000, 15864) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn set_active() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 19_412_000 picoseconds. + Weight::from_parts(19_687_000, 7021) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `10500` + // Minimum execution time: 32_505_000 picoseconds. + Weight::from_parts(33_049_000, 10500) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn promote() -> Weight { + // Proof Size summary in bytes: + // Measured: `377` + // Estimated: `12345` + // Minimum execution time: 35_992_000 picoseconds. + Weight::from_parts(37_227_000, 12345) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 18_963_000 picoseconds. + Weight::from_parts(19_313_000, 7021) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn prove_new() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `7021` + // Minimum execution time: 17_849_000 picoseconds. + Weight::from_parts(18_163_000, 7021) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn prove_existing() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `7021` + // Minimum execution time: 19_178_000 picoseconds. + Weight::from_parts(19_509_000, 7021) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } From 032f7c138e9e1b9e6f51b029ab037d48bf026b7f Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 15:06:34 +0000 Subject: [PATCH 56/79] Better process flow --- frame/core-fellowship/Cargo.toml | 2 +- frame/core-fellowship/src/benchmarking.rs | 41 +++- frame/core-fellowship/src/lib.rs | 219 +++++++++++++++++----- frame/core-fellowship/src/tests.rs | 84 +++++---- frame/ranked-collective/src/lib.rs | 3 + 5 files changed, 268 insertions(+), 81 deletions(-) diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml index 001d5a39d1583..2785346c7341f 100644 --- a/frame/core-fellowship/Cargo.toml +++ b/frame/core-fellowship/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "Paymaster" +description = "Logic as per the description of The Fellowship for core Polkadot technology" readme = "README.md" [package.metadata.docs.rs] diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 1ca81c3aaa544..8fa1f7a8bdea2 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -27,11 +27,21 @@ use frame_system::RawOrigin; use sp_arithmetic::traits::Bounded; const SEED: u32 = 0; - +// todo deposit evidence for prove/promote/bump. +// todo rename request to submit_evidence +// todo bench submit_evidence #[instance_benchmarks] mod benchmarks { use super::*; + fn ensure_evidence, I: 'static>(who: &T::AccountId) { + let evidence = BoundedVec::try_from(vec![0; Evidence::bound()]).unwrap(); + let wish = Wish::Retention; + let origin = RawOrigin::Signed(who.clone()).into(); + CoreFellowship::::submit_evidence(origin, wish, evidence).unwrap(); + assert!(MemberEvidence::::contains_key(who)); + } + #[benchmark] fn set_params() { let params = ParamsType { @@ -39,6 +49,7 @@ mod benchmarks { passive_salary: [10u32.into(); 9], demotion_period: [100u32.into(); 9], min_promotion_period: [100u32.into(); 9], + offboard_period: 1u32.into(), }; #[extrinsic_call] @@ -56,12 +67,14 @@ mod benchmarks { // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); + ensure_evidence::(&member); assert!(Member::::contains_key(&member)); #[extrinsic_call] CoreFellowship::::bump(RawOrigin::Signed(member.clone()), member.clone()); assert!(!Member::::contains_key(&member)); + assert!(!MemberEvidence::::contains_key(&member)); } #[benchmark] @@ -74,6 +87,7 @@ mod benchmarks { // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); + ensure_evidence::(&member); assert!(Member::::contains_key(&member)); assert_eq!(T::Members::rank_of(&member), Some(2)); @@ -82,6 +96,7 @@ mod benchmarks { assert!(Member::::contains_key(&member)); assert_eq!(T::Members::rank_of(&member), Some(1)); + assert!(!MemberEvidence::::contains_key(&member)); } #[benchmark] @@ -103,11 +118,13 @@ mod benchmarks { let candidate = account("candidate", 0, SEED); T::Members::induct(&candidate).unwrap(); assert_eq!(T::Members::rank_of(&candidate), Some(0)); + ensure_evidence::(&candidate); #[extrinsic_call] _(RawOrigin::Root, candidate.clone()); assert_eq!(T::Members::rank_of(&candidate), Some(1)); + assert!(!MemberEvidence::::contains_key(&candidate)); } #[benchmark] @@ -117,11 +134,13 @@ mod benchmarks { T::Members::promote(&member).unwrap(); CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); assert_eq!(T::Members::rank_of(&member), Some(1)); + ensure_evidence::(&member); #[extrinsic_call] _(RawOrigin::Root, member.clone(), 2u8.into()); assert_eq!(T::Members::rank_of(&member), Some(2)); + assert!(!MemberEvidence::::contains_key(&member)); } #[benchmark] @@ -146,6 +165,7 @@ mod benchmarks { let member = account("member", 0, SEED); T::Members::induct(&member).unwrap(); T::Members::promote(&member).unwrap(); + assert!(!Member::::contains_key(&member)); #[extrinsic_call] @@ -165,12 +185,31 @@ mod benchmarks { let now = then.saturating_plus_one(); frame_system::Pallet::::set_block_number(now); + ensure_evidence::(&member); + assert_eq!(Member::::get(&member).unwrap().last_proof, then); #[extrinsic_call] CoreFellowship::::prove(RawOrigin::Root, member.clone(), 1u8.into()); assert_eq!(Member::::get(&member).unwrap().last_proof, now); + assert!(!MemberEvidence::::contains_key(&member)); + } + + #[benchmark] + fn submit_evidence() { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + T::Members::promote(&member).unwrap(); + CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + let evidence = vec![0; Evidence::bound()].try_into().unwrap(); + + assert!(!MemberEvidence::::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(member.clone()), Wish::Retention, evidence); + + assert!(MemberEvidence::::contains_key(&member)); } impl_benchmark_test_suite! { diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index c92172acd0be2..d17cccc7c8190 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -19,6 +19,16 @@ //! and handles promotion and demotion periods. //! //! This only handles members of non-zero rank. +//! +//! # Process Flow +//! - Begin with a call to `induct`, where some privileged origin (perhaps a pre-existing member of +//! `rank > 1`) is able to make a candidate from an account and introduce it to be tracked in this +//! pallet in order to allow evidence to be submitted and promotion voted on. +//! - `submit_evidence` +//! - `promote` +//! - `approve`... +//! - `promote`... +//! - `bump` #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] @@ -26,13 +36,14 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; +use sp_core::ConstU32; use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - traits::{tokens::Balance as BalanceTrait, RankedMembers}, - RuntimeDebug, + traits::{tokens::Balance as BalanceTrait, EnsureOrigin, RankedMembers}, + BoundedVec, RuntimeDebug, }; #[cfg(test)] @@ -45,6 +56,17 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; +/// The "career" wish of a member. +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum Wish { + /// Member wishes only to retain their current rank. + Retention, + /// Member wishes to be promoted. + Promotion, +} + +pub type Evidence = BoundedVec>; + /// The status of the pallet instance. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug, Default)] pub struct ParamsType { @@ -56,6 +78,8 @@ pub struct ParamsType { demotion_period: [BlockNumber; 9], /// The period between which members must wait before they may proceed to this rank. min_promotion_period: [BlockNumber; 9], + /// Off-board period. + offboard_period: BlockNumber, } /// The status of a single member. @@ -104,6 +128,13 @@ pub mod pallet { /// The origin which has permission update the parameters. type ParamsOrigin: EnsureOrigin; + /// The origin which has permission to move a candidate into being tracked in this pallet. + /// Generally a very low-permission, such as a pre-existing member of rank 1 or above. + /// + /// This allows the candidate to deposit evidence for their request to be promoted to a + /// member. + type InductOrigin: EnsureOrigin; + /// The origin which has permission to issue a proof that a member may retain their rank. /// The `Success` value is the maximum rank of members it is able to prove. type ProofOrigin: EnsureOrigin>; @@ -128,6 +159,10 @@ pub mod pallet { pub(super) type Member, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; + #[pallet::storage] + pub(super) type MemberEvidence, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence), OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -135,7 +170,7 @@ pub mod pallet { ParamsChanged { params: ParamsOf }, /// Member activity flag has been set. ActiveChanged { who: T::AccountId, is_active: bool }, - /// Member has begun being tracked in this pallet (i.e. because rank is now non-zero). + /// Member has begun being tracked in this pallet. Inducted { who: T::AccountId }, /// Member has been removed from being tracked in this pallet (i.e. because rank is now /// zero). @@ -146,7 +181,18 @@ pub mod pallet { Demoted { who: T::AccountId, to_rank: RankOf }, /// Member has been proven at their current rank, postponing auto-demotion. `new` is `true` /// if this pallet did not previously track the member. - Proven { who: T::AccountId, at_rank: RankOf, new: bool }, + Proven { who: T::AccountId, at_rank: RankOf }, + /// Member has stated evidence of their efforts their request for rank. + Requested { who: T::AccountId, wish: Wish }, + EvidenceJudged { + who: T::AccountId, + wish: Wish, + evidence: Evidence, + old_rank: u16, + new_rank: u16, + }, + /// Pre-ranked account has been inducted at their current rank. + Synced { who: T::AccountId, rank: RankOf }, } #[pallet::error] @@ -173,9 +219,11 @@ pub mod pallet { /// require a candidate (rank 0) to already be tracked in the pallet. AlreadyInducted, /// The candidate has not been inducted, so cannot be offboarded from this pallet. - NotInducted, + NotTracked, /// Operation cannot be done yet since not enough time has passed. TooSoon, + /// A candidate cannot be off-boarded while evidence is submitted. + EvidenceSubmitted, } #[pallet::call] @@ -192,18 +240,25 @@ pub mod pallet { pub fn bump(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; - let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; let params = Params::::get(); - let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; - let demotion_period = params.demotion_period[rank_index]; + let demotion_period = if rank == 0 { + params.offboard_period + } else { + let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; + params.demotion_period[rank_index] + }; let demotion_block = member.last_proof.saturating_add(demotion_period); // Ensure enough time has passed. let now = frame_system::Pallet::::block_number(); if now >= demotion_block { let to_rank = rank.clone().saturating_less_one(); - T::Members::demote(&who)?; + Self::dispose_evidence(who.clone(), rank, to_rank); + if rank > 0 { + T::Members::demote(&who)?; + } let event = if to_rank.is_zero() { Member::::remove(&who); Event::::Offboarded { who } @@ -221,7 +276,7 @@ pub mod pallet { /// Set the parameters. /// - /// - `origin`: A origin complying with `ParamsOrigin`. + /// - `origin`: A origin complying with `ParamsOrigin` or root. /// - `params`: The new parameters for the pallet. #[pallet::weight(T::WeightInfo::set_params())] #[pallet::call_index(1)] @@ -261,11 +316,12 @@ pub mod pallet { /// If `who` is not already tracked by this pallet, then it will become tracked. /// `last_promotion` will be set to zero. /// - /// - `origin`: An origin which satisfies `ProofOrigin`. + /// - `origin`: An origin which satisfies `ProofOrigin` or root. /// - `who`: A member (i.e. of non-zero rank). /// - `at_rank`: The rank of member. #[pallet::weight(T::WeightInfo::prove_existing().max(T::WeightInfo::prove_new()))] #[pallet::call_index(3)] + // TODO: Rename to approve pub fn prove( origin: OriginFor, who: T::AccountId, @@ -275,45 +331,35 @@ pub mod pallet { Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::::NoPermission), Err(origin) => ensure_root(origin)?, } - let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(at_rank > 0, Error::::InvalidRank); + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; ensure!(rank == at_rank, Error::::UnexpectedRank); + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; - let now = frame_system::Pallet::::block_number(); - let existed = Member::::mutate(&who, |maybe| { - if let Some(m) = maybe { - m.last_proof = now; - true - } else { - *maybe = Some(MemberStatus { - is_active: true, - last_promotion: 0u32.into(), - last_proof: now, - }); - false - } - }); - Self::deposit_event(Event::::Proven { who, at_rank, new: !existed }); + member.last_proof = frame_system::Pallet::::block_number(); + Member::::insert(&who, &member); + + Self::dispose_evidence(who.clone(), at_rank, at_rank); + Self::deposit_event(Event::::Proven { who, at_rank }); Ok(Pays::No.into()) } - /// Make a "promotion" from a candiate (rank zero) into a member (rank one). + /// Introduce a new and unranked candidate (rank zero). /// - /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of 1 or - /// more. + /// - `origin`: An origin which satisfies `InductOrigin` or root. /// - `who`: The account ID of the candidate to be inducted and become a member. #[pallet::weight(T::WeightInfo::induct())] #[pallet::call_index(4)] pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { - match T::PromoteOrigin::try_origin(origin) { - Ok(allow_rank) => ensure!(allow_rank >= 1, Error::::NoPermission), + match T::InductOrigin::try_origin(origin) { + Ok(_) => {}, Err(origin) => ensure_root(origin)?, } ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); - let rank = T::Members::rank_of(&who).ok_or(Error::::NotCandidate)?; - ensure!(rank.is_zero(), Error::::UnexpectedRank); + ensure!(T::Members::rank_of(&who).is_none(), Error::::Ranked); - T::Members::promote(&who)?; + T::Members::induct(&who)?; let now = frame_system::Pallet::::block_number(); Member::::insert( &who, @@ -323,10 +369,10 @@ pub mod pallet { Ok(Pays::No.into()) } - /// Make a promotion from a non-zero rank. + /// Increment the rank of a ranked and tracked account. /// /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of - /// `to_rank` or more. + /// `to_rank` or more or root. /// - `who`: The account ID of the member to be promoted to `to_rank`. /// - `to_rank`: One more than the current rank of `who`. #[pallet::weight(T::WeightInfo::promote())] @@ -340,13 +386,13 @@ pub mod pallet { Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::::NoPermission), Err(origin) => ensure_root(origin)?, } - let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; ensure!( rank.checked_add(1).map_or(false, |i| i == to_rank), Error::::UnexpectedRank ); - let mut member = Member::::get(&who).ok_or(Error::::Unranked)?; + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; let now = frame_system::Pallet::::block_number(); let params = Params::::get(); @@ -355,20 +401,21 @@ pub mod pallet { // Ensure enough time has passed. ensure!( member.last_promotion.saturating_add(min_period) <= now, - Error::::TooSoon + Error::::TooSoon, ); T::Members::promote(&who)?; member.last_promotion = now; member.last_proof = now; Member::::insert(&who, &member); + Self::dispose_evidence(who.clone(), rank, to_rank); Self::deposit_event(Event::::Promoted { who, to_rank }); Ok(Pays::No.into()) } - /// Stop tracking an prior member who is now not a ranked member of the collective. + /// Stop tracking a prior member who is now not a ranked member of the collective. /// /// - `origin`: A `Signed` origin of an account. /// - `who`: The ID of an account which was tracked in this pallet but which is now not a @@ -377,12 +424,60 @@ pub mod pallet { #[pallet::call_index(6)] pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - ensure!(T::Members::rank_of(&who).map_or(true, |r| r.is_zero()), Error::::Ranked); - ensure!(Member::::contains_key(&who), Error::::NotInducted); + ensure!(T::Members::rank_of(&who).is_none(), Error::::Ranked); + ensure!(Member::::contains_key(&who), Error::::NotTracked); Member::::remove(&who); Self::deposit_event(Event::::Offboarded { who }); Ok(Pays::No.into()) } + + /// Provide evidence for peers. + /// + /// - `origin`: A `Signed` origin of an inducted and ranked account. + /// - `wish`: The stated desire of the member. + /// - `evidence`: A freeform dump of evidence to be considered. + #[pallet::weight(T::WeightInfo::offboard())] + #[pallet::call_index(7)] + pub fn submit_evidence( + origin: OriginFor, + wish: Wish, + evidence: Evidence, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(Member::::contains_key(&who), Error::::NotTracked); + let replaced = MemberEvidence::::contains_key(&who); + MemberEvidence::::insert(&who, (wish, evidence)); + Self::deposit_event(Event::::Requested { who, wish }); + Ok(if replaced { Pays::Yes } else { Pays::No }.into()) + } + + /// Introduce an already-ranked individual of the collective into this pallet. The rank may + /// still be zero. + /// + /// This resets `last_proof` to the current block, thereby delaying any automatic demotion. + /// + /// If `who` is not already tracked by this pallet, then it will become tracked. + /// `last_promotion` will be set to zero. + /// + /// - `origin`: A signed origin of a ranked but not yet inducted account. + /// - `who`: A member (i.e. of non-zero rank). + /// - `at_rank`: The rank of member. + #[pallet::weight(T::WeightInfo::offboard())] + #[pallet::call_index(8)] + pub fn sync(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + + let now = frame_system::Pallet::::block_number(); + Member::::insert( + &who, + MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, + ); + Self::deposit_event(Event::::Synced { who, rank }); + + Ok(Pays::No.into()) + } } impl, I: 'static> Pallet { @@ -396,6 +491,13 @@ pub mod pallet { _ => return None, } } + + fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: u16) { + if let Some((wish, evidence)) = MemberEvidence::::take(&who) { + let e = Event::::EvidenceJudged { who, wish, evidence, old_rank, new_rank }; + Self::deposit_event(e); + } + } } impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { @@ -415,3 +517,34 @@ pub mod pallet { } } } + +/// Guard to ensure that the given origin is inducted into this pallet with a given minimum rank. +/// The account ID of the member is the `Success` value. +pub struct EnsureInducted(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureInducted +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match T::Members::rank_of(&who) { + Some(rank) if rank >= MIN_RANK && Member::::contains_key(&who) => Ok(who), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = frame_benchmarking::account::("successful_origin", 0, 0); + if T::Members::rank_of(&who).is_none() { + T::Members::induct(&who).map_err(|_| ())?; + } + for _ in 0..MIN_RANK { + if T::Members::rank_of(&who).ok_or(())? < MIN_RANK { + T::Members::promote(&who).map_err(|_| ())?; + } + } + Ok(frame_system::RawOrigin::Signed(who).into()) + } +} diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index 99a3efdb00a95..ef2555af29d8a 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -125,6 +125,10 @@ fn set_rank(who: u64, rank: u16) { CLUB.with(|club| club.borrow_mut().insert(who, rank)); } +fn unrank(who: u64) { + CLUB.with(|club| club.borrow_mut().remove(&who)); +} + parameter_types! { pub ZeroToNine: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; } @@ -138,6 +142,7 @@ impl Config for Test { type Members = TestClub; type Balance = u64; type ParamsOrigin = EnsureSignedBy; + type InductOrigin = EnsureInducted; type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; } @@ -151,6 +156,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], + offboard_period: 1, }; assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); System::set_block_number(1); @@ -198,6 +204,7 @@ fn set_params_works() { passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], demotion_period: [1, 2, 3, 4, 5, 6, 7, 8, 9], min_promotion_period: [1, 2, 3, 4, 5, 10, 15, 20, 30], + offboard_period: 1, }; assert_noop!( CoreFellowship::set_params(signed(2), Box::new(params.clone())), @@ -210,10 +217,13 @@ fn set_params_works() { #[test] fn induct_works() { new_test_ext().execute_with(|| { - assert_noop!(CoreFellowship::induct(signed(1), 10), Error::::NotCandidate); - set_rank(10, 0); + set_rank(0, 0); + assert_ok!(CoreFellowship::sync(signed(0))); + set_rank(1, 1); + assert_ok!(CoreFellowship::sync(signed(1))); + assert_noop!(CoreFellowship::induct(signed(10), 10), DispatchError::BadOrigin); - assert_noop!(CoreFellowship::induct(signed(0), 10), Error::::NoPermission); + assert_noop!(CoreFellowship::induct(signed(0), 10), DispatchError::BadOrigin); assert_ok!(CoreFellowship::induct(signed(1), 10)); assert_noop!(CoreFellowship::induct(signed(1), 10), Error::::AlreadyInducted); }); @@ -222,28 +232,30 @@ fn induct_works() { #[test] fn promote_works() { new_test_ext().execute_with(|| { - assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::NotMember); - set_rank(10, 0); + set_rank(1, 1); + assert_ok!(CoreFellowship::sync(signed(1))); + assert_noop!(CoreFellowship::promote(signed(1), 10, 1), Error::::Unranked); + assert_ok!(CoreFellowship::induct(signed(1), 10)); - assert_noop!(CoreFellowship::promote(signed(10), 10, 2), DispatchError::BadOrigin); - assert_noop!(CoreFellowship::promote(signed(1), 10, 2), Error::::NoPermission); - assert_noop!(CoreFellowship::promote(signed(3), 10, 3), Error::::UnexpectedRank); - run_to(6); - assert_noop!(CoreFellowship::promote(signed(2), 10, 2), Error::::TooSoon); - run_to(7); - assert_ok!(CoreFellowship::promote(signed(2), 10, 2)); - set_rank(11, 1); - assert_noop!(CoreFellowship::promote(signed(2), 11, 2), Error::::Unranked); + assert_noop!(CoreFellowship::promote(signed(10), 10, 1), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::promote(signed(0), 10, 1), Error::::NoPermission); + assert_noop!(CoreFellowship::promote(signed(3), 10, 2), Error::::UnexpectedRank); + run_to(3); + assert_noop!(CoreFellowship::promote(signed(1), 10, 1), Error::::TooSoon); + run_to(4); + assert_ok!(CoreFellowship::promote(signed(1), 10, 1)); + set_rank(11, 0); + assert_noop!(CoreFellowship::promote(signed(1), 11, 1), Error::::NotTracked); }); } #[test] -fn proof_into_high_rank_works() { +fn sync_works() { new_test_ext().execute_with(|| { set_rank(10, 5); assert_noop!(CoreFellowship::prove(signed(4), 10, 5), Error::::NoPermission); assert_noop!(CoreFellowship::prove(signed(6), 10, 6), Error::::UnexpectedRank); - assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_ok!(CoreFellowship::sync(signed(10))); assert!(Member::::contains_key(10)); assert_eq!(next_demotion(10), 11); }); @@ -253,7 +265,7 @@ fn proof_into_high_rank_works() { fn auto_demote_works() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_ok!(CoreFellowship::sync(signed(10))); run_to(10); assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); @@ -269,7 +281,7 @@ fn auto_demote_works() { fn auto_demote_offboard_works() { new_test_ext().execute_with(|| { set_rank(10, 1); - assert_ok!(CoreFellowship::prove(signed(1), 10, 1)); + assert_ok!(CoreFellowship::sync(signed(10))); run_to(3); assert_ok!(CoreFellowship::bump(signed(0), 10)); @@ -281,16 +293,16 @@ fn auto_demote_offboard_works() { #[test] fn offboard_works() { new_test_ext().execute_with(|| { - assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotInducted); - set_rank(10, 1); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotTracked); + set_rank(10, 0); assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); - assert_ok!(CoreFellowship::prove(signed(1), 10, 1)); + assert_ok!(CoreFellowship::sync(signed(10))); assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); - set_rank(10, 0); + unrank(10); assert_ok!(CoreFellowship::offboard(signed(0), 10)); - assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotInducted); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotTracked); assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); }); } @@ -299,7 +311,7 @@ fn offboard_works() { fn proof_postpones_auto_demote() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_ok!(CoreFellowship::sync(signed(10))); run_to(11); assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); @@ -312,7 +324,7 @@ fn proof_postpones_auto_demote() { fn promote_postpones_auto_demote() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_ok!(CoreFellowship::sync(signed(10))); run_to(19); assert_ok!(CoreFellowship::promote(signed(6), 10, 6)); @@ -324,10 +336,10 @@ fn promote_postpones_auto_demote() { #[test] fn get_salary_works() { new_test_ext().execute_with(|| { - for i in 1..=9 { - set_rank(10, i); - assert_ok!(CoreFellowship::prove(signed(i as u64), 10, i)); - assert_eq!(CoreFellowship::get_salary(i, &10), i as u64 * 10); + for i in 1..=9u64 { + set_rank(10 + i, i as u16); + assert_ok!(CoreFellowship::sync(signed(10 + i))); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i * 10); } }); } @@ -335,13 +347,13 @@ fn get_salary_works() { #[test] fn active_changing_get_salary_works() { new_test_ext().execute_with(|| { - for i in 1..=9 { - set_rank(10, i); - assert_ok!(CoreFellowship::prove(signed(i as u64), 10, i)); - assert_ok!(CoreFellowship::set_active(signed(10), false)); - assert_eq!(CoreFellowship::get_salary(i, &10), i as u64); - assert_ok!(CoreFellowship::set_active(signed(10), true)); - assert_eq!(CoreFellowship::get_salary(i, &10), i as u64 * 10); + for i in 1..=9u64 { + set_rank(10 + i, i as u16); + assert_ok!(CoreFellowship::sync(signed(10 + i))); + assert_ok!(CoreFellowship::set_active(signed(10 + i), false)); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i); + assert_ok!(CoreFellowship::set_active(signed(10 + i), true)); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i * 10); } }); } diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 8ed1bbd2e01d6..a4d1972710c3f 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -431,6 +431,9 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { + // TODO: permissionless `submit_candidacy` which requires a deposit (but keep deposit logic + // in trait outside of here - don't want to have to bring in Currency). + /// Introduce a new member. /// /// - `origin`: Must be the `AdminOrigin`. From 1f69d4511e6eaea5ee325a6370cb6188e7b98ac9 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 15:26:28 +0000 Subject: [PATCH 57/79] Fix benchmarks & tests --- frame/core-fellowship/src/benchmarking.rs | 66 +++++++++-------------- frame/core-fellowship/src/lib.rs | 35 ++++++------ frame/core-fellowship/src/tests.rs | 9 ++-- frame/core-fellowship/src/weights.rs | 19 ++++--- 4 files changed, 61 insertions(+), 68 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 8fa1f7a8bdea2..63cf459cb2d6e 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -42,6 +42,16 @@ mod benchmarks { assert!(MemberEvidence::::contains_key(who)); } + fn make_member, I: 'static>(rank: u16) -> T::AccountId { + let member = account("member", 0, SEED); + T::Members::induct(&member).unwrap(); + for _ in 0..rank { + T::Members::promote(&member).unwrap(); + } + CoreFellowship::::sync(RawOrigin::Signed(member.clone()).into()).unwrap(); + member + } + #[benchmark] fn set_params() { let params = ParamsType { @@ -60,10 +70,7 @@ mod benchmarks { #[benchmark] fn bump_offboard() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + let member = make_member::(0); // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); @@ -79,11 +86,7 @@ mod benchmarks { #[benchmark] fn bump_demote() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 2u8.into()).unwrap(); + let member = make_member::(2); // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); @@ -101,10 +104,7 @@ mod benchmarks { #[benchmark] fn set_active() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + let member = make_member::(1); assert!(Member::::get(&member).unwrap().is_active); #[extrinsic_call] @@ -115,25 +115,18 @@ mod benchmarks { #[benchmark] fn induct() { - let candidate = account("candidate", 0, SEED); - T::Members::induct(&candidate).unwrap(); - assert_eq!(T::Members::rank_of(&candidate), Some(0)); - ensure_evidence::(&candidate); + let candidate: T::AccountId = account("candidate", 0, SEED); #[extrinsic_call] _(RawOrigin::Root, candidate.clone()); - assert_eq!(T::Members::rank_of(&candidate), Some(1)); - assert!(!MemberEvidence::::contains_key(&candidate)); + assert_eq!(T::Members::rank_of(&candidate), Some(0)); + assert!(Member::::contains_key(&candidate)); } #[benchmark] fn promote() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); - assert_eq!(T::Members::rank_of(&member), Some(1)); + let member = make_member::(1); ensure_evidence::(&member); #[extrinsic_call] @@ -145,13 +138,10 @@ mod benchmarks { #[benchmark] fn offboard() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + let member = make_member::(0); T::Members::demote(&member).unwrap(); - assert_eq!(T::Members::rank_of(&member), Some(0)); + assert!(T::Members::rank_of(&member).is_none()); assert!(Member::::contains_key(&member)); #[extrinsic_call] @@ -161,7 +151,7 @@ mod benchmarks { } #[benchmark] - fn prove_new() { + fn sync() { let member = account("member", 0, SEED); T::Members::induct(&member).unwrap(); T::Members::promote(&member).unwrap(); @@ -169,17 +159,14 @@ mod benchmarks { assert!(!Member::::contains_key(&member)); #[extrinsic_call] - CoreFellowship::::prove(RawOrigin::Root, member.clone(), 1u8.into()); + _(RawOrigin::Signed(member.clone())); assert!(Member::::contains_key(&member)); } #[benchmark] - fn prove_existing() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + fn approve() { + let member = make_member::(1); let then = frame_system::Pallet::::block_number(); let now = then.saturating_plus_one(); @@ -190,7 +177,7 @@ mod benchmarks { assert_eq!(Member::::get(&member).unwrap().last_proof, then); #[extrinsic_call] - CoreFellowship::::prove(RawOrigin::Root, member.clone(), 1u8.into()); + _(RawOrigin::Root, member.clone(), 1u8.into()); assert_eq!(Member::::get(&member).unwrap().last_proof, now); assert!(!MemberEvidence::::contains_key(&member)); @@ -198,10 +185,7 @@ mod benchmarks { #[benchmark] fn submit_evidence() { - let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); - CoreFellowship::::prove(RawOrigin::Root.into(), member.clone(), 1u8.into()).unwrap(); + let member = make_member::(1); let evidence = vec![0; Evidence::bound()].try_into().unwrap(); assert!(!MemberEvidence::::contains_key(&member)); diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index d17cccc7c8190..0f08ab9cfdd9c 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -188,8 +188,10 @@ pub mod pallet { who: T::AccountId, wish: Wish, evidence: Evidence, + /// The old rank, prior to this change. old_rank: u16, - new_rank: u16, + /// New rank. If `None` then candidate record was removed entirely. + new_rank: Option, }, /// Pre-ranked account has been inducted at their current rank. Synced { who: T::AccountId, rank: RankOf }, @@ -254,18 +256,16 @@ pub mod pallet { // Ensure enough time has passed. let now = frame_system::Pallet::::block_number(); if now >= demotion_block { - let to_rank = rank.clone().saturating_less_one(); - Self::dispose_evidence(who.clone(), rank, to_rank); - if rank > 0 { - T::Members::demote(&who)?; - } - let event = if to_rank.is_zero() { - Member::::remove(&who); - Event::::Offboarded { who } - } else { + T::Members::demote(&who)?; + let maybe_to_rank = T::Members::rank_of(&who); + Self::dispose_evidence(who.clone(), rank, maybe_to_rank); + let event = if let Some(to_rank) = maybe_to_rank { member.last_proof = now; Member::::insert(&who, &member); Event::::Demoted { who, to_rank } + } else { + Member::::remove(&who); + Event::::Offboarded { who } }; Self::deposit_event(event); return Ok(Pays::No.into()) @@ -319,10 +319,9 @@ pub mod pallet { /// - `origin`: An origin which satisfies `ProofOrigin` or root. /// - `who`: A member (i.e. of non-zero rank). /// - `at_rank`: The rank of member. - #[pallet::weight(T::WeightInfo::prove_existing().max(T::WeightInfo::prove_new()))] + #[pallet::weight(T::WeightInfo::approve())] #[pallet::call_index(3)] - // TODO: Rename to approve - pub fn prove( + pub fn approve( origin: OriginFor, who: T::AccountId, at_rank: RankOf, @@ -339,7 +338,7 @@ pub mod pallet { member.last_proof = frame_system::Pallet::::block_number(); Member::::insert(&who, &member); - Self::dispose_evidence(who.clone(), at_rank, at_rank); + Self::dispose_evidence(who.clone(), at_rank, Some(at_rank)); Self::deposit_event(Event::::Proven { who, at_rank }); Ok(Pays::No.into()) @@ -408,7 +407,7 @@ pub mod pallet { member.last_promotion = now; member.last_proof = now; Member::::insert(&who, &member); - Self::dispose_evidence(who.clone(), rank, to_rank); + Self::dispose_evidence(who.clone(), rank, Some(to_rank)); Self::deposit_event(Event::::Promoted { who, to_rank }); @@ -436,7 +435,7 @@ pub mod pallet { /// - `origin`: A `Signed` origin of an inducted and ranked account. /// - `wish`: The stated desire of the member. /// - `evidence`: A freeform dump of evidence to be considered. - #[pallet::weight(T::WeightInfo::offboard())] + #[pallet::weight(T::WeightInfo::submit_evidence())] #[pallet::call_index(7)] pub fn submit_evidence( origin: OriginFor, @@ -462,7 +461,7 @@ pub mod pallet { /// - `origin`: A signed origin of a ranked but not yet inducted account. /// - `who`: A member (i.e. of non-zero rank). /// - `at_rank`: The rank of member. - #[pallet::weight(T::WeightInfo::offboard())] + #[pallet::weight(T::WeightInfo::sync())] #[pallet::call_index(8)] pub fn sync(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -492,7 +491,7 @@ pub mod pallet { } } - fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: u16) { + fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: Option) { if let Some((wish, evidence)) = MemberEvidence::::take(&who) { let e = Event::::EvidenceJudged { who, wish, evidence, old_rank, new_rank }; Self::deposit_event(e); diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index ef2555af29d8a..73668e03b4909 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -253,8 +253,8 @@ fn promote_works() { fn sync_works() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_noop!(CoreFellowship::prove(signed(4), 10, 5), Error::::NoPermission); - assert_noop!(CoreFellowship::prove(signed(6), 10, 6), Error::::UnexpectedRank); + assert_noop!(CoreFellowship::approve(signed(4), 10, 5), Error::::NoPermission); + assert_noop!(CoreFellowship::approve(signed(6), 10, 6), Error::::UnexpectedRank); assert_ok!(CoreFellowship::sync(signed(10))); assert!(Member::::contains_key(10)); assert_eq!(next_demotion(10), 11); @@ -286,6 +286,9 @@ fn auto_demote_offboard_works() { run_to(3); assert_ok!(CoreFellowship::bump(signed(0), 10)); assert_eq!(TestClub::rank_of(&10), Some(0)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + run_to(4); + assert_ok!(CoreFellowship::bump(signed(0), 10)); assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); }); } @@ -314,7 +317,7 @@ fn proof_postpones_auto_demote() { assert_ok!(CoreFellowship::sync(signed(10))); run_to(11); - assert_ok!(CoreFellowship::prove(signed(5), 10, 5)); + assert_ok!(CoreFellowship::approve(signed(5), 10, 5)); assert_eq!(next_demotion(10), 21); assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); }); diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 1ee355ffbf356..3d1ff6f03f99f 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -56,8 +56,9 @@ pub trait WeightInfo { fn induct() -> Weight; fn promote() -> Weight; fn offboard() -> Weight; - fn prove_new() -> Weight; - fn prove_existing() -> Weight; + fn sync() -> Weight; + fn approve() -> Weight; + fn submit_evidence() -> Weight; } /// Weights for pallet_core_fellowship using the Substrate node and recommended hardware. @@ -181,7 +182,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn prove_new() -> Weight { + fn sync() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` @@ -194,7 +195,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn prove_existing() -> Weight { + fn approve() -> Weight { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` @@ -203,6 +204,9 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + fn submit_evidence() -> Weight { + Weight::from_parts(0, 0) + } } // For backwards compatibility and tests @@ -325,7 +329,7 @@ impl WeightInfo for () { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn prove_new() -> Weight { + fn sync() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` @@ -338,7 +342,7 @@ impl WeightInfo for () { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn prove_existing() -> Weight { + fn approve() -> Weight { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` @@ -347,4 +351,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn submit_evidence() -> Weight { + Weight::from_parts(0, 0) + } } From b1868e0aacb51f827d980021414fa5843034d24d Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 15:32:27 +0000 Subject: [PATCH 58/79] Docs --- frame/core-fellowship/src/lib.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 0f08ab9cfdd9c..2bd5efda6fe98 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -29,6 +29,20 @@ //! - `approve`... //! - `promote`... //! - `bump` +//! +//! Note there is a difference between having a rank of 0 (whereby the account is a *candidate*) and +//! having no rank at all (whereby we consider it *unranked*). An account can be demoted from rank +//! 0 to become unranked. This process is called being offboarded and there is an extrinsic to do +//! this explicitly when external factors to this pallet have caused the tracked account to become +//! unranked. At rank 0, there is not a "demotion" period after which the account may be bumped to +//! become offboarded but rather a "offboard timeout". +//! +//! Candidates may be introduced (i.e. an account to go from unranked to rank of 0) by an origin +//! of a different privilege to that for promotion. This allows the possibility for even a single +//! existing member to introduce a new candidate without payment. +//! +//! Only tracked/ranked accounts may submit evidence for their proof and promotion. Candidates +//! cannot be approved - they must proceed only to promotion prior to the offboard timeout elapsing. #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "128"] @@ -78,8 +92,8 @@ pub struct ParamsType { demotion_period: [BlockNumber; 9], /// The period between which members must wait before they may proceed to this rank. min_promotion_period: [BlockNumber; 9], - /// Off-board period. - offboard_period: BlockNumber, + /// Amount by which an account can remain at rank 0 (candidate before being offboard entirely). + offboard_timeout: BlockNumber, } /// The status of a single member. @@ -246,7 +260,7 @@ pub mod pallet { let params = Params::::get(); let demotion_period = if rank == 0 { - params.offboard_period + params.offboard_timeout } else { let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; params.demotion_period[rank_index] From ec0f7db3c79616a482852a577be9d7ba4d010216 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 15:33:05 +0000 Subject: [PATCH 59/79] Fixes --- frame/core-fellowship/src/benchmarking.rs | 2 +- frame/core-fellowship/src/tests.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 63cf459cb2d6e..46f8740476022 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -59,7 +59,7 @@ mod benchmarks { passive_salary: [10u32.into(); 9], demotion_period: [100u32.into(); 9], min_promotion_period: [100u32.into(); 9], - offboard_period: 1u32.into(), + offboard_timeout: 1u32.into(), }; #[extrinsic_call] diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index 73668e03b4909..c9e949c470ecc 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -156,7 +156,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], - offboard_period: 1, + offboard_timeout: 1, }; assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); System::set_block_number(1); @@ -204,7 +204,7 @@ fn set_params_works() { passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], demotion_period: [1, 2, 3, 4, 5, 6, 7, 8, 9], min_promotion_period: [1, 2, 3, 4, 5, 10, 15, 20, 30], - offboard_period: 1, + offboard_timeout: 1, }; assert_noop!( CoreFellowship::set_params(signed(2), Box::new(params.clone())), From 59d70c974b7cc282a61b04429951e25b7908bd4d Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 16:07:29 +0000 Subject: [PATCH 60/79] Fixes --- bin/node/runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9773c0415b9a0..cc31269ba17e8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1601,6 +1601,7 @@ impl pallet_core_fellowship::Config for Runtime { type Members = RankedCollective; type Balance = Balance; type ParamsOrigin = frame_system::EnsureRoot; + type InductOrigin = pallet_core_fellowship::EnsureInducted; type ProofOrigin = frame_system::EnsureRootWithSuccess>; type PromoteOrigin = frame_system::EnsureRootWithSuccess>; } From cdb8022df10ec4a485eb4fc3d7ce251967650af0 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 16:23:18 +0000 Subject: [PATCH 61/79] docs --- frame/ranked-collective/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index a4d1972710c3f..8ed1bbd2e01d6 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -431,9 +431,6 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { - // TODO: permissionless `submit_candidacy` which requires a deposit (but keep deposit logic - // in trait outside of here - don't want to have to bring in Currency). - /// Introduce a new member. /// /// - `origin`: Must be the `AdminOrigin`. From 308510a8a15090299ba9b77da3148e2eb5c54844 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Sun, 5 Mar 2023 16:31:40 +0000 Subject: [PATCH 62/79] ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_core_fellowship --- frame/core-fellowship/src/weights.rs | 208 ++++++++++++++++----------- 1 file changed, 122 insertions(+), 86 deletions(-) diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 3d1ff6f03f99f..406488ffa7bcc 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -65,13 +65,13 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: CoreFellowship Params (r:0 w:1) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_826_000 picoseconds. - Weight::from_parts(11_146_000, 0) + // Minimum execution time: 10_531_000 picoseconds. + Weight::from_parts(11_372_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) @@ -79,38 +79,42 @@ impl WeightInfo for SubstrateWeight { /// Storage: RankedCollective Members (r:1 w:1) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `457` - // Estimated: `15864` - // Minimum execution time: 38_282_000 picoseconds. - Weight::from_parts(38_776_000, 15864) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + // Measured: `1522` + // Estimated: `20400` + // Minimum execution time: 49_578_000 picoseconds. + Weight::from_parts(50_469_000, 20400) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: RankedCollective Members (r:1 w:1) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `509` - // Estimated: `15864` - // Minimum execution time: 39_384_000 picoseconds. - Weight::from_parts(39_788_000, 15864) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + // Measured: `1632` + // Estimated: `20400` + // Minimum execution time: 50_790_000 picoseconds. + Weight::from_parts(51_949_000, 20400) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: RankedCollective Members (r:1 w:0) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) @@ -120,8 +124,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` - // Minimum execution time: 19_412_000 picoseconds. - Weight::from_parts(19_687_000, 7021) + // Minimum execution time: 19_258_000 picoseconds. + Weight::from_parts(19_501_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -137,10 +141,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn induct() -> Weight { // Proof Size summary in bytes: - // Measured: `281` + // Measured: `113` // Estimated: `10500` - // Minimum execution time: 32_505_000 picoseconds. - Weight::from_parts(33_049_000, 10500) + // Minimum execution time: 28_917_000 picoseconds. + Weight::from_parts(29_617_000, 10500) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -149,21 +153,23 @@ impl WeightInfo for SubstrateWeight { /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) /// Storage: RankedCollective IndexToId (r:0 w:1) /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:0 w:1) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `377` - // Estimated: `12345` - // Minimum execution time: 35_992_000 picoseconds. - Weight::from_parts(37_227_000, 12345) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `1500` + // Estimated: `16881` + // Minimum execution time: 46_681_000 picoseconds. + Weight::from_parts(47_940_000, 16881) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: RankedCollective Members (r:1 w:0) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) @@ -171,23 +177,23 @@ impl WeightInfo for SubstrateWeight { /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `355` + // Measured: `292` // Estimated: `7021` - // Minimum execution time: 18_963_000 picoseconds. - Weight::from_parts(19_313_000, 7021) + // Minimum execution time: 17_703_000 picoseconds. + Weight::from_parts(17_980_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: RankedCollective Members (r:1 w:0) - /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn sync() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` - // Minimum execution time: 17_849_000 picoseconds. - Weight::from_parts(18_163_000, 7021) + // Minimum execution time: 18_578_000 picoseconds. + Weight::from_parts(19_109_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -195,30 +201,42 @@ impl WeightInfo for SubstrateWeight { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn approve() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `7021` - // Minimum execution time: 19_178_000 picoseconds. - Weight::from_parts(19_509_000, 7021) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `1478` + // Estimated: `11553` + // Minimum execution time: 30_248_000 picoseconds. + Weight::from_parts(30_541_000, 11553) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: CoreFellowship Member (r:1 w:0) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn submit_evidence() -> Weight { - Weight::from_parts(0, 0) + // Proof Size summary in bytes: + // Measured: `79` + // Estimated: `8046` + // Minimum execution time: 16_630_000 picoseconds. + Weight::from_parts(16_849_000, 8046) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { /// Storage: CoreFellowship Params (r:0 w:1) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_826_000 picoseconds. - Weight::from_parts(11_146_000, 0) + // Minimum execution time: 10_531_000 picoseconds. + Weight::from_parts(11_372_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) @@ -226,38 +244,42 @@ impl WeightInfo for () { /// Storage: RankedCollective Members (r:1 w:1) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `457` - // Estimated: `15864` - // Minimum execution time: 38_282_000 picoseconds. - Weight::from_parts(38_776_000, 15864) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + // Measured: `1522` + // Estimated: `20400` + // Minimum execution time: 49_578_000 picoseconds. + Weight::from_parts(50_469_000, 20400) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: RankedCollective Members (r:1 w:1) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `509` - // Estimated: `15864` - // Minimum execution time: 39_384_000 picoseconds. - Weight::from_parts(39_788_000, 15864) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + // Measured: `1632` + // Estimated: `20400` + // Minimum execution time: 50_790_000 picoseconds. + Weight::from_parts(51_949_000, 20400) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: RankedCollective Members (r:1 w:0) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) @@ -267,8 +289,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` - // Minimum execution time: 19_412_000 picoseconds. - Weight::from_parts(19_687_000, 7021) + // Minimum execution time: 19_258_000 picoseconds. + Weight::from_parts(19_501_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -284,10 +306,10 @@ impl WeightInfo for () { /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn induct() -> Weight { // Proof Size summary in bytes: - // Measured: `281` + // Measured: `113` // Estimated: `10500` - // Minimum execution time: 32_505_000 picoseconds. - Weight::from_parts(33_049_000, 10500) + // Minimum execution time: 28_917_000 picoseconds. + Weight::from_parts(29_617_000, 10500) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -296,21 +318,23 @@ impl WeightInfo for () { /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship Params (r:1 w:0) - /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(360), added: 855, mode: MaxEncodedLen) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) /// Storage: RankedCollective IndexToId (r:0 w:1) /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:0 w:1) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `377` - // Estimated: `12345` - // Minimum execution time: 35_992_000 picoseconds. - Weight::from_parts(37_227_000, 12345) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `1500` + // Estimated: `16881` + // Minimum execution time: 46_681_000 picoseconds. + Weight::from_parts(47_940_000, 16881) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: RankedCollective Members (r:1 w:0) /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) @@ -318,23 +342,23 @@ impl WeightInfo for () { /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `355` + // Measured: `292` // Estimated: `7021` - // Minimum execution time: 18_963_000 picoseconds. - Weight::from_parts(19_313_000, 7021) + // Minimum execution time: 17_703_000 picoseconds. + Weight::from_parts(17_980_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: RankedCollective Members (r:1 w:0) - /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn sync() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` - // Minimum execution time: 17_849_000 picoseconds. - Weight::from_parts(18_163_000, 7021) + // Minimum execution time: 18_578_000 picoseconds. + Weight::from_parts(19_109_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -342,16 +366,28 @@ impl WeightInfo for () { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn approve() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `7021` - // Minimum execution time: 19_178_000 picoseconds. - Weight::from_parts(19_509_000, 7021) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) + // Measured: `1478` + // Estimated: `11553` + // Minimum execution time: 30_248_000 picoseconds. + Weight::from_parts(30_541_000, 11553) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: CoreFellowship Member (r:1 w:0) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) fn submit_evidence() -> Weight { - Weight::from_parts(0, 0) + // Proof Size summary in bytes: + // Measured: `79` + // Estimated: `8046` + // Minimum execution time: 16_630_000 picoseconds. + Weight::from_parts(16_849_000, 8046) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } From 8c60756ac374d100d28005fa5b9779b3549ff2c4 Mon Sep 17 00:00:00 2001 From: Gav Date: Sun, 5 Mar 2023 16:50:27 +0000 Subject: [PATCH 63/79] Docs and allow custom evidence size --- bin/node/runtime/src/lib.rs | 3 +- frame/core-fellowship/src/benchmarking.rs | 8 +-- frame/core-fellowship/src/lib.rs | 88 +++++++++++++---------- frame/core-fellowship/src/tests.rs | 29 ++++---- frame/core-fellowship/src/weights.rs | 6 +- 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index cc31269ba17e8..fff3fec4094ca 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1602,8 +1602,9 @@ impl pallet_core_fellowship::Config for Runtime { type Balance = Balance; type ParamsOrigin = frame_system::EnsureRoot; type InductOrigin = pallet_core_fellowship::EnsureInducted; - type ProofOrigin = frame_system::EnsureRootWithSuccess>; + type ApproveOrigin = frame_system::EnsureRootWithSuccess>; type PromoteOrigin = frame_system::EnsureRootWithSuccess>; + type EvidenceSize = ConstU32<16_384>; } parameter_types! { diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 46f8740476022..6cd956af6bdc0 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -35,7 +35,7 @@ mod benchmarks { use super::*; fn ensure_evidence, I: 'static>(who: &T::AccountId) { - let evidence = BoundedVec::try_from(vec![0; Evidence::bound()]).unwrap(); + let evidence = BoundedVec::try_from(vec![0; Evidence::::bound()]).unwrap(); let wish = Wish::Retention; let origin = RawOrigin::Signed(who.clone()).into(); CoreFellowship::::submit_evidence(origin, wish, evidence).unwrap(); @@ -48,7 +48,7 @@ mod benchmarks { for _ in 0..rank { T::Members::promote(&member).unwrap(); } - CoreFellowship::::sync(RawOrigin::Signed(member.clone()).into()).unwrap(); + CoreFellowship::::import(RawOrigin::Signed(member.clone()).into()).unwrap(); member } @@ -151,7 +151,7 @@ mod benchmarks { } #[benchmark] - fn sync() { + fn import() { let member = account("member", 0, SEED); T::Members::induct(&member).unwrap(); T::Members::promote(&member).unwrap(); @@ -186,7 +186,7 @@ mod benchmarks { #[benchmark] fn submit_evidence() { let member = make_member::(1); - let evidence = vec![0; Evidence::bound()].try_into().unwrap(); + let evidence = vec![0; Evidence::::bound()].try_into().unwrap(); assert!(!MemberEvidence::::contains_key(&member)); diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 2bd5efda6fe98..d8e7306eb961a 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -24,11 +24,22 @@ //! - Begin with a call to `induct`, where some privileged origin (perhaps a pre-existing member of //! `rank > 1`) is able to make a candidate from an account and introduce it to be tracked in this //! pallet in order to allow evidence to be submitted and promotion voted on. -//! - `submit_evidence` -//! - `promote` -//! - `approve`... -//! - `promote`... -//! - `bump` +//! - The candidate then calls `submit_evidence` to apply for their promotion to rank 1. +//! - A `PromoteOrigin` of at least rank 1 calls `promote` on the candidate to elevate it to rank 1. +//! - Some time later but before rank 1's `demotion_period` elapses, candidate calls +//! `submit_evidence` with evidence of their efforts to apply for approval to stay at rank 1. +//! - An `ApproveOrigin` of at least rank 1 calls `approve` on the candidate to avoid imminent +//! demotion and keep it at rank 1. +//! - These last two steps continue until the candidate is ready to apply for a promotion, at which +//! point the previous two steps are repeated with a higher rank. +//! - If the member fails to get an approval within the `demotion_period` then anyone may call +//! `bump` to demote the candidate by one rank. +//! - If a candidate fails to be promoted to a member within the `offboard_timeout` period, then +//! anyone may call `bump` to remove the account's candidacy. +//! - Pre-existing members may call `import` to have their rank recognised and be inducted into this +//! pallet (to gain a salary and allow for eventual promotion). +//! - If, externally to this pallet, a member or candidate has their rank removed completely, then +//! `offboard` may be called to remove them entirely from this pallet. //! //! Note there is a difference between having a rank of 0 (whereby the account is a *candidate*) and //! having no rank at all (whereby we consider it *unranked*). An account can be demoted from rank @@ -50,13 +61,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; -use sp_core::ConstU32; use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - traits::{tokens::Balance as BalanceTrait, EnsureOrigin, RankedMembers}, + traits::{tokens::Balance as BalanceTrait, EnsureOrigin, Get, RankedMembers}, BoundedVec, RuntimeDebug, }; @@ -70,7 +80,7 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; -/// The "career" wish of a member. +/// The desired outcome for which evidence is presented. #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub enum Wish { /// Member wishes only to retain their current rank. @@ -79,7 +89,7 @@ pub enum Wish { Promotion, } -pub type Evidence = BoundedVec>; +pub type Evidence = BoundedVec>::EvidenceSize>; /// The status of the pallet instance. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug, Default)] @@ -151,11 +161,15 @@ pub mod pallet { /// The origin which has permission to issue a proof that a member may retain their rank. /// The `Success` value is the maximum rank of members it is able to prove. - type ProofOrigin: EnsureOrigin>; + type ApproveOrigin: EnsureOrigin>; /// The origin which has permission to promote a member. The `Success` value is the maximum /// rank to which it can promote. type PromoteOrigin: EnsureOrigin>; + + #[pallet::constant] + /// The maximum size in bytes submitted evidence is allowed to be. + type EvidenceSize: Get; } pub type ParamsOf = @@ -173,9 +187,10 @@ pub mod pallet { pub(super) type Member, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; + /// Some evidence together with the desired outcome for which it was presented. #[pallet::storage] pub(super) type MemberEvidence, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence), OptionQuery>; + StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence), OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -198,10 +213,15 @@ pub mod pallet { Proven { who: T::AccountId, at_rank: RankOf }, /// Member has stated evidence of their efforts their request for rank. Requested { who: T::AccountId, wish: Wish }, + /// Some submitted evidence was judged and removed. There may or may not have been a change + /// to the rank, but in any case, `last_proof` is reset. EvidenceJudged { + /// The member/candidate. who: T::AccountId, + /// The desired outcome for which the evidence was presented. wish: Wish, - evidence: Evidence, + /// The evidence of efforts. + evidence: Evidence, /// The old rank, prior to this change. old_rank: u16, /// New rank. If `None` then candidate record was removed entirely. @@ -217,16 +237,11 @@ pub mod pallet { Unranked, /// Member's rank is not zero. Ranked, - /// Member's rank is not as expected. + /// Member's rank is not as expected - generally means that the rank provided to the call + /// does not agree with the state of the system. UnexpectedRank, /// The given rank is invalid - this generally means it's not between 1 and 9. InvalidRank, - /// The account is not a candidate in the collective. - NotCandidate, - /// The account is not a member of the collective. - NotMember, - /// The member is not tracked by this pallet (call `prove` on member first). - NotProved, /// The origin does not have enough permission to do this operation. NoPermission, /// No work needs to be done at present for this member. @@ -238,8 +253,6 @@ pub mod pallet { NotTracked, /// Operation cannot be done yet since not enough time has passed. TooSoon, - /// A candidate cannot be off-boarded while evidence is submitted. - EvidenceSubmitted, } #[pallet::call] @@ -255,7 +268,7 @@ pub mod pallet { #[pallet::call_index(0)] pub fn bump(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; let params = Params::::get(); @@ -307,7 +320,7 @@ pub mod pallet { /// Set whether a member is active or not. /// /// - `origin`: A `Signed` origin of a member's account. - /// - `active`: `true` iff the member is active. + /// - `is_active`: `true` iff the member is active. #[pallet::weight(T::WeightInfo::set_active())] #[pallet::call_index(2)] pub fn set_active(origin: OriginFor, is_active: bool) -> DispatchResult { @@ -316,7 +329,7 @@ pub mod pallet { T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()), Error::::Unranked ); - let mut member = Member::::get(&who).ok_or(Error::::NotProved)?; + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; member.is_active = is_active; Member::::insert(&who, &member); Self::deposit_event(Event::::ActiveChanged { who, is_active }); @@ -330,7 +343,7 @@ pub mod pallet { /// If `who` is not already tracked by this pallet, then it will become tracked. /// `last_promotion` will be set to zero. /// - /// - `origin`: An origin which satisfies `ProofOrigin` or root. + /// - `origin`: An origin which satisfies `ApproveOrigin` or root. /// - `who`: A member (i.e. of non-zero rank). /// - `at_rank`: The rank of member. #[pallet::weight(T::WeightInfo::approve())] @@ -444,17 +457,22 @@ pub mod pallet { Ok(Pays::No.into()) } - /// Provide evidence for peers. + /// Provide evidence that a rank is deserved. + /// + /// This is free as long as no evidence for the forthcoming judgement is already submitted. + /// Evidence is cleared after an outcome (either demotion, promotion of approval). /// /// - `origin`: A `Signed` origin of an inducted and ranked account. /// - `wish`: The stated desire of the member. - /// - `evidence`: A freeform dump of evidence to be considered. + /// - `evidence`: A dump of evidence to be considered. This should generally be either a + /// Markdown-encoded document or a series of 32-byte hashes which can be found on a + /// decentralised content-based-indexing system such as IPFS. #[pallet::weight(T::WeightInfo::submit_evidence())] #[pallet::call_index(7)] pub fn submit_evidence( origin: OriginFor, wish: Wish, - evidence: Evidence, + evidence: Evidence, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!(Member::::contains_key(&who), Error::::NotTracked); @@ -467,17 +485,13 @@ pub mod pallet { /// Introduce an already-ranked individual of the collective into this pallet. The rank may /// still be zero. /// - /// This resets `last_proof` to the current block, thereby delaying any automatic demotion. - /// - /// If `who` is not already tracked by this pallet, then it will become tracked. - /// `last_promotion` will be set to zero. + /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, + /// thereby delaying any automatic demotion but allowing immediate promotion. /// - /// - `origin`: A signed origin of a ranked but not yet inducted account. - /// - `who`: A member (i.e. of non-zero rank). - /// - `at_rank`: The rank of member. - #[pallet::weight(T::WeightInfo::sync())] + /// - `origin`: A signed origin of a ranked, but not tracked, account. + #[pallet::weight(T::WeightInfo::import())] #[pallet::call_index(8)] - pub fn sync(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn import(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index c9e949c470ecc..eabdaff15d597 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -143,8 +143,9 @@ impl Config for Test { type Balance = u64; type ParamsOrigin = EnsureSignedBy; type InductOrigin = EnsureInducted; - type ProofOrigin = TryMapSuccess, u64>, TryMorphInto>; + type ApproveOrigin = TryMapSuccess, u64>, TryMorphInto>; type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; + type EvidenceSize = ConstU32<1024>; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -218,9 +219,9 @@ fn set_params_works() { fn induct_works() { new_test_ext().execute_with(|| { set_rank(0, 0); - assert_ok!(CoreFellowship::sync(signed(0))); + assert_ok!(CoreFellowship::import(signed(0))); set_rank(1, 1); - assert_ok!(CoreFellowship::sync(signed(1))); + assert_ok!(CoreFellowship::import(signed(1))); assert_noop!(CoreFellowship::induct(signed(10), 10), DispatchError::BadOrigin); assert_noop!(CoreFellowship::induct(signed(0), 10), DispatchError::BadOrigin); @@ -233,7 +234,7 @@ fn induct_works() { fn promote_works() { new_test_ext().execute_with(|| { set_rank(1, 1); - assert_ok!(CoreFellowship::sync(signed(1))); + assert_ok!(CoreFellowship::import(signed(1))); assert_noop!(CoreFellowship::promote(signed(1), 10, 1), Error::::Unranked); assert_ok!(CoreFellowship::induct(signed(1), 10)); @@ -255,7 +256,7 @@ fn sync_works() { set_rank(10, 5); assert_noop!(CoreFellowship::approve(signed(4), 10, 5), Error::::NoPermission); assert_noop!(CoreFellowship::approve(signed(6), 10, 6), Error::::UnexpectedRank); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); assert!(Member::::contains_key(10)); assert_eq!(next_demotion(10), 11); }); @@ -265,7 +266,7 @@ fn sync_works() { fn auto_demote_works() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); run_to(10); assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); @@ -281,7 +282,7 @@ fn auto_demote_works() { fn auto_demote_offboard_works() { new_test_ext().execute_with(|| { set_rank(10, 1); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); run_to(3); assert_ok!(CoreFellowship::bump(signed(0), 10)); @@ -289,7 +290,7 @@ fn auto_demote_offboard_works() { assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); run_to(4); assert_ok!(CoreFellowship::bump(signed(0), 10)); - assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotTracked); }); } @@ -300,13 +301,13 @@ fn offboard_works() { set_rank(10, 0); assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); unrank(10); assert_ok!(CoreFellowship::offboard(signed(0), 10)); assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotTracked); - assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotProved); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotTracked); }); } @@ -314,7 +315,7 @@ fn offboard_works() { fn proof_postpones_auto_demote() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); run_to(11); assert_ok!(CoreFellowship::approve(signed(5), 10, 5)); @@ -327,7 +328,7 @@ fn proof_postpones_auto_demote() { fn promote_postpones_auto_demote() { new_test_ext().execute_with(|| { set_rank(10, 5); - assert_ok!(CoreFellowship::sync(signed(10))); + assert_ok!(CoreFellowship::import(signed(10))); run_to(19); assert_ok!(CoreFellowship::promote(signed(6), 10, 6)); @@ -341,7 +342,7 @@ fn get_salary_works() { new_test_ext().execute_with(|| { for i in 1..=9u64 { set_rank(10 + i, i as u16); - assert_ok!(CoreFellowship::sync(signed(10 + i))); + assert_ok!(CoreFellowship::import(signed(10 + i))); assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i * 10); } }); @@ -352,7 +353,7 @@ fn active_changing_get_salary_works() { new_test_ext().execute_with(|| { for i in 1..=9u64 { set_rank(10 + i, i as u16); - assert_ok!(CoreFellowship::sync(signed(10 + i))); + assert_ok!(CoreFellowship::import(signed(10 + i))); assert_ok!(CoreFellowship::set_active(signed(10 + i), false)); assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i); assert_ok!(CoreFellowship::set_active(signed(10 + i), true)); diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 3d1ff6f03f99f..6ad5830d34cae 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -56,7 +56,7 @@ pub trait WeightInfo { fn induct() -> Weight; fn promote() -> Weight; fn offboard() -> Weight; - fn sync() -> Weight; + fn import() -> Weight; fn approve() -> Weight; fn submit_evidence() -> Weight; } @@ -182,7 +182,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn sync() -> Weight { + fn import() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` @@ -329,7 +329,7 @@ impl WeightInfo for () { /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - fn sync() -> Weight { + fn import() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` From dd07d0c5663b907eeb2ffc5276d131b84f5c94ce Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Sun, 5 Mar 2023 17:15:56 +0000 Subject: [PATCH 64/79] ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_core_fellowship --- frame/core-fellowship/src/weights.rs | 140 ++++++++++++++------------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs index 90c803347ae62..9f290635ec1cf 100644 --- a/frame/core-fellowship/src/weights.rs +++ b/frame/core-fellowship/src/weights.rs @@ -70,8 +70,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_531_000 picoseconds. - Weight::from_parts(11_372_000, 0) + // Minimum execution time: 10_489_000 picoseconds. + Weight::from_parts(10_873_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) @@ -85,13 +85,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `1522` - // Estimated: `20400` - // Minimum execution time: 49_578_000 picoseconds. - Weight::from_parts(50_469_000, 20400) + // Measured: `16886` + // Estimated: `35762` + // Minimum execution time: 61_794_000 picoseconds. + Weight::from_parts(62_455_000, 35762) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -106,13 +106,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `1632` - // Estimated: `20400` - // Minimum execution time: 50_790_000 picoseconds. - Weight::from_parts(51_949_000, 20400) + // Measured: `16996` + // Estimated: `35762` + // Minimum execution time: 63_921_000 picoseconds. + Weight::from_parts(64_871_000, 35762) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -124,8 +124,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` - // Minimum execution time: 19_258_000 picoseconds. - Weight::from_parts(19_501_000, 7021) + // Minimum execution time: 19_023_000 picoseconds. + Weight::from_parts(19_270_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -143,8 +143,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `113` // Estimated: `10500` - // Minimum execution time: 28_917_000 picoseconds. - Weight::from_parts(29_617_000, 10500) + // Minimum execution time: 29_089_000 picoseconds. + Weight::from_parts(29_718_000, 10500) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -157,17 +157,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) /// Storage: RankedCollective IndexToId (r:0 w:1) /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:0 w:1) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `1500` - // Estimated: `16881` - // Minimum execution time: 46_681_000 picoseconds. - Weight::from_parts(47_940_000, 16881) + // Measured: `16864` + // Estimated: `32243` + // Minimum execution time: 59_529_000 picoseconds. + Weight::from_parts(60_057_000, 32243) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -179,19 +179,21 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `292` // Estimated: `7021` - // Minimum execution time: 17_703_000 picoseconds. - Weight::from_parts(17_980_000, 7021) + // Minimum execution time: 17_221_000 picoseconds. + Weight::from_parts(17_585_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn import() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` - // Minimum execution time: 18_578_000 picoseconds. - Weight::from_parts(19_109_000, 7021) + // Minimum execution time: 18_602_000 picoseconds. + Weight::from_parts(18_813_000, 7021) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -200,26 +202,26 @@ impl WeightInfo for SubstrateWeight { /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn approve() -> Weight { // Proof Size summary in bytes: - // Measured: `1478` - // Estimated: `11553` - // Minimum execution time: 30_248_000 picoseconds. - Weight::from_parts(30_541_000, 11553) + // Measured: `16842` + // Estimated: `26915` + // Minimum execution time: 43_525_000 picoseconds. + Weight::from_parts(43_994_000, 26915) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: CoreFellowship Member (r:1 w:0) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn submit_evidence() -> Weight { // Proof Size summary in bytes: // Measured: `79` - // Estimated: `8046` - // Minimum execution time: 16_630_000 picoseconds. - Weight::from_parts(16_849_000, 8046) + // Estimated: `23408` + // Minimum execution time: 27_960_000 picoseconds. + Weight::from_parts(28_331_000, 23408) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -233,8 +235,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_531_000 picoseconds. - Weight::from_parts(11_372_000, 0) + // Minimum execution time: 10_489_000 picoseconds. + Weight::from_parts(10_873_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) @@ -248,13 +250,13 @@ impl WeightInfo for () { /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `1522` - // Estimated: `20400` - // Minimum execution time: 49_578_000 picoseconds. - Weight::from_parts(50_469_000, 20400) + // Measured: `16886` + // Estimated: `35762` + // Minimum execution time: 61_794_000 picoseconds. + Weight::from_parts(62_455_000, 35762) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -269,13 +271,13 @@ impl WeightInfo for () { /// Storage: RankedCollective IdToIndex (r:1 w:0) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `1632` - // Estimated: `20400` - // Minimum execution time: 50_790_000 picoseconds. - Weight::from_parts(51_949_000, 20400) + // Measured: `16996` + // Estimated: `35762` + // Minimum execution time: 63_921_000 picoseconds. + Weight::from_parts(64_871_000, 35762) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -287,8 +289,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `355` // Estimated: `7021` - // Minimum execution time: 19_258_000 picoseconds. - Weight::from_parts(19_501_000, 7021) + // Minimum execution time: 19_023_000 picoseconds. + Weight::from_parts(19_270_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -306,8 +308,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `113` // Estimated: `10500` - // Minimum execution time: 28_917_000 picoseconds. - Weight::from_parts(29_617_000, 10500) + // Minimum execution time: 29_089_000 picoseconds. + Weight::from_parts(29_718_000, 10500) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -320,17 +322,17 @@ impl WeightInfo for () { /// Storage: RankedCollective MemberCount (r:1 w:1) /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) /// Storage: RankedCollective IndexToId (r:0 w:1) /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// Storage: RankedCollective IdToIndex (r:0 w:1) /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `1500` - // Estimated: `16881` - // Minimum execution time: 46_681_000 picoseconds. - Weight::from_parts(47_940_000, 16881) + // Measured: `16864` + // Estimated: `32243` + // Minimum execution time: 59_529_000 picoseconds. + Weight::from_parts(60_057_000, 32243) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -342,19 +344,21 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `292` // Estimated: `7021` - // Minimum execution time: 17_703_000 picoseconds. - Weight::from_parts(17_980_000, 7021) + // Minimum execution time: 17_221_000 picoseconds. + Weight::from_parts(17_585_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) fn import() -> Weight { // Proof Size summary in bytes: // Measured: `280` // Estimated: `7021` - // Minimum execution time: 18_578_000 picoseconds. - Weight::from_parts(19_109_000, 7021) + // Minimum execution time: 18_602_000 picoseconds. + Weight::from_parts(18_813_000, 7021) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -363,26 +367,26 @@ impl WeightInfo for () { /// Storage: CoreFellowship Member (r:1 w:1) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn approve() -> Weight { // Proof Size summary in bytes: - // Measured: `1478` - // Estimated: `11553` - // Minimum execution time: 30_248_000 picoseconds. - Weight::from_parts(30_541_000, 11553) + // Measured: `16842` + // Estimated: `26915` + // Minimum execution time: 43_525_000 picoseconds. + Weight::from_parts(43_994_000, 26915) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: CoreFellowship Member (r:1 w:0) /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: CoreFellowship MemberEvidence (r:1 w:1) - /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(1067), added: 3542, mode: MaxEncodedLen) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) fn submit_evidence() -> Weight { // Proof Size summary in bytes: // Measured: `79` - // Estimated: `8046` - // Minimum execution time: 16_630_000 picoseconds. - Weight::from_parts(16_849_000, 8046) + // Estimated: `23408` + // Minimum execution time: 27_960_000 picoseconds. + Weight::from_parts(28_331_000, 23408) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From 8233b71ed6a0be61d53dddc8c414f05a5cfa4307 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 6 Mar 2023 16:12:26 +0000 Subject: [PATCH 65/79] Update frame/core-fellowship/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/core-fellowship/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index d8e7306eb961a..ff446df29ed00 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -167,8 +167,8 @@ pub mod pallet { /// rank to which it can promote. type PromoteOrigin: EnsureOrigin>; - #[pallet::constant] /// The maximum size in bytes submitted evidence is allowed to be. + #[pallet::constant] type EvidenceSize: Get; } From 1261201a65aecf1d230365abb47984d764046456 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 6 Mar 2023 16:12:41 +0000 Subject: [PATCH 66/79] Update frame/core-fellowship/src/tests.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/core-fellowship/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs index eabdaff15d597..87c0de112ac33 100644 --- a/frame/core-fellowship/src/tests.rs +++ b/frame/core-fellowship/src/tests.rs @@ -169,7 +169,6 @@ fn next_block() { System::set_block_number(System::block_number() + 1); } -#[allow(dead_code)] fn run_to(n: u64) { while System::block_number() < n { next_block(); From 8e524e984ce7094feb05fd2f23815b41bc3286d6 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 6 Mar 2023 16:13:49 +0000 Subject: [PATCH 67/79] Update frame/core-fellowship/src/benchmarking.rs --- frame/core-fellowship/src/benchmarking.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 6cd956af6bdc0..a785e5ea4b2c1 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -27,9 +27,6 @@ use frame_system::RawOrigin; use sp_arithmetic::traits::Bounded; const SEED: u32 = 0; -// todo deposit evidence for prove/promote/bump. -// todo rename request to submit_evidence -// todo bench submit_evidence #[instance_benchmarks] mod benchmarks { use super::*; From 063f1d7b0290f4025c72bd614824a8c177a02657 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 6 Mar 2023 16:14:09 +0000 Subject: [PATCH 68/79] Update frame/core-fellowship/src/benchmarking.rs --- frame/core-fellowship/src/benchmarking.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index a785e5ea4b2c1..7fa62d3ed4b79 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -27,6 +27,7 @@ use frame_system::RawOrigin; use sp_arithmetic::traits::Bounded; const SEED: u32 = 0; + #[instance_benchmarks] mod benchmarks { use super::*; From 76e521b63bf68665999c039c5fcf3ced1d93e4d0 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 6 Mar 2023 16:24:36 +0000 Subject: [PATCH 69/79] Apply suggestions from code review --- frame/core-fellowship/src/benchmarking.rs | 2 -- frame/core-fellowship/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 7fa62d3ed4b79..12b4522efd45c 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -165,11 +165,9 @@ mod benchmarks { #[benchmark] fn approve() { let member = make_member::(1); - let then = frame_system::Pallet::::block_number(); let now = then.saturating_plus_one(); frame_system::Pallet::::set_block_number(now); - ensure_evidence::(&member); assert_eq!(Member::::get(&member).unwrap().last_proof, then); diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index ff446df29ed00..689bb7ad0c558 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -46,7 +46,7 @@ //! 0 to become unranked. This process is called being offboarded and there is an extrinsic to do //! this explicitly when external factors to this pallet have caused the tracked account to become //! unranked. At rank 0, there is not a "demotion" period after which the account may be bumped to -//! become offboarded but rather a "offboard timeout". +//! become offboarded but rather an "offboard timeout". //! //! Candidates may be introduced (i.e. an account to go from unranked to rank of 0) by an origin //! of a different privilege to that for promotion. This allows the possibility for even a single @@ -109,7 +109,7 @@ pub struct ParamsType { /// The status of a single member. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct MemberStatus { - /// Are they currently active> + /// Are they currently active? is_active: bool, /// The block number at which we last promoted them. last_promotion: BlockNumber, From a86c095bb7241e3a167da03ec6bc7014dd2b980b Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 6 Mar 2023 16:25:52 +0000 Subject: [PATCH 70/79] Rename --- frame/core-fellowship/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 689bb7ad0c558..77eea1b669e62 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -228,7 +228,7 @@ pub mod pallet { new_rank: Option, }, /// Pre-ranked account has been inducted at their current rank. - Synced { who: T::AccountId, rank: RankOf }, + Imported { who: T::AccountId, rank: RankOf }, } #[pallet::error] @@ -501,7 +501,7 @@ pub mod pallet { &who, MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, ); - Self::deposit_event(Event::::Synced { who, rank }); + Self::deposit_event(Event::::Imported { who, rank }); Ok(Pays::No.into()) } From c9de385c0629ccbf2e994a9c1e71347178b03f29 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 7 Mar 2023 13:00:29 +0100 Subject: [PATCH 71/79] Update primitives/arithmetic/src/traits.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- primitives/arithmetic/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 5342cbb6e9b2f..33b4e376aaf9d 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -243,7 +243,7 @@ pub trait Saturating { self } - /// Decrement self by one, saturating at zero. + /// Increment self by one, saturating at the numeric bounds instead of overflowing. fn saturating_plus_one(mut self) -> Self where Self: One, From 8684952b7b9eaea1804e6652da8dacbd52d63d6d Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 7 Mar 2023 17:30:32 +0100 Subject: [PATCH 72/79] Reduce magic numbers --- frame/core-fellowship/src/lib.rs | 40 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 77eea1b669e62..35995a085b565 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -92,20 +92,34 @@ pub enum Wish { pub type Evidence = BoundedVec>::EvidenceSize>; /// The status of the pallet instance. -#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug, Default)] -pub struct ParamsType { +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ParamsType { /// The amounts to be paid when a given grade is active. - active_salary: [Balance; 9], + active_salary: [Balance; RANKS], /// The minimum percent discount when passive. - passive_salary: [Balance; 9], + passive_salary: [Balance; RANKS], /// The period between which unproven members become demoted. - demotion_period: [BlockNumber; 9], + demotion_period: [BlockNumber; RANKS], /// The period between which members must wait before they may proceed to this rank. - min_promotion_period: [BlockNumber; 9], + min_promotion_period: [BlockNumber; RANKS], /// Amount by which an account can remain at rank 0 (candidate before being offboard entirely). offboard_timeout: BlockNumber, } +impl Default + for ParamsType +{ + fn default() -> Self { + Self { + active_salary: [Balance::default(); RANKS], + passive_salary: [Balance::default(); RANKS], + demotion_period: [BlockNumber::default(); RANKS], + min_promotion_period: [BlockNumber::default(); RANKS], + offboard_timeout: BlockNumber::default(), + } + } +} + /// The status of a single member. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct MemberStatus { @@ -127,6 +141,8 @@ pub mod pallet { }; use frame_system::{ensure_root, pallet_prelude::*}; + const RANK_COUNT: usize = 9; + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); @@ -173,7 +189,7 @@ pub mod pallet { } pub type ParamsOf = - ParamsType<>::Balance, ::BlockNumber>; + ParamsType<>::Balance, ::BlockNumber, RANK_COUNT>; pub type MemberStatusOf = MemberStatus<::BlockNumber>; pub type RankOf = <>::Members as RankedMembers>::Rank; @@ -240,7 +256,7 @@ pub mod pallet { /// Member's rank is not as expected - generally means that the rank provided to the call /// does not agree with the state of the system. UnexpectedRank, - /// The given rank is invalid - this generally means it's not between 1 and 9. + /// The given rank is invalid - this generally means it's not between 1 and `RANK_COUNT`. InvalidRank, /// The origin does not have enough permission to do this operation. NoPermission, @@ -508,13 +524,13 @@ pub mod pallet { } impl, I: 'static> Pallet { - /// Convert a rank into a 0..9 index suitable for the arrays in Params. + /// Convert a rank into a `0..RANK_COUNT` index suitable for the arrays in Params. /// - /// Rank 1 becomes index 0, rank 9 becomes index 8. Any rank not in the range 1..=9 is - /// `None`. + /// Rank 1 becomes index 0, rank `RANK_COUNT` becomes index `RANK_COUNT - 1`. Any rank not + /// in the range `1..=RANK_COUNT` is `None`. pub(crate) fn rank_to_index(rank: RankOf) -> Option { match TryInto::::try_into(rank) { - Ok(r) if r <= 9 && r > 0 => Some(r - 1), + Ok(r) if r <= RANK_COUNT && r > 0 => Some(r - 1), _ => return None, } } From 091c6af33e064f5f231f3b03c17a7ccf93cca6d2 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 7 Mar 2023 18:12:00 +0100 Subject: [PATCH 73/79] Update frame/core-fellowship/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/core-fellowship/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 35995a085b565..8b9504ac1b886 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -89,6 +89,10 @@ pub enum Wish { Promotion, } +/// A piece of evidence to underpin a [Wish]. +/// +/// From the pallet's perspective, this is just a blob of data without meaning. The fellows can +/// decide how to concretely utilise it. This could be an IPFS hash, a URL or structured data. pub type Evidence = BoundedVec>::EvidenceSize>; /// The status of the pallet instance. From ecd0c46b8a639b37b4a25ccb2c937c028ead7c9f Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 7 Mar 2023 18:13:55 +0100 Subject: [PATCH 74/79] Update frame/core-fellowship/src/lib.rs Co-authored-by: Oliver Tale-Yazdi --- frame/core-fellowship/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 8b9504ac1b886..64239116bd160 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -228,8 +228,7 @@ pub mod pallet { Promoted { who: T::AccountId, to_rank: RankOf }, /// Member has been demoted to the given (non-zero) rank. Demoted { who: T::AccountId, to_rank: RankOf }, - /// Member has been proven at their current rank, postponing auto-demotion. `new` is `true` - /// if this pallet did not previously track the member. + /// Member has been proven at their current rank, postponing auto-demotion. Proven { who: T::AccountId, at_rank: RankOf }, /// Member has stated evidence of their efforts their request for rank. Requested { who: T::AccountId, wish: Wish }, From 749f9ac49e7e16d58620421dfdd4baaa601e7212 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 7 Mar 2023 18:45:24 +0100 Subject: [PATCH 75/79] Benchmark result --- frame/core-fellowship/src/benchmarking.rs | 78 ++++++++++++++--------- frame/core-fellowship/src/lib.rs | 5 +- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 12b4522efd45c..551ec30c19f01 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -28,30 +28,33 @@ use sp_arithmetic::traits::Bounded; const SEED: u32 = 0; +type BenchResult = Result<(), BenchmarkError>; + #[instance_benchmarks] mod benchmarks { use super::*; - fn ensure_evidence, I: 'static>(who: &T::AccountId) { + fn ensure_evidence, I: 'static>(who: &T::AccountId) -> BenchResult { let evidence = BoundedVec::try_from(vec![0; Evidence::::bound()]).unwrap(); let wish = Wish::Retention; let origin = RawOrigin::Signed(who.clone()).into(); - CoreFellowship::::submit_evidence(origin, wish, evidence).unwrap(); + CoreFellowship::::submit_evidence(origin, wish, evidence)?; assert!(MemberEvidence::::contains_key(who)); + Ok(()) } - fn make_member, I: 'static>(rank: u16) -> T::AccountId { + fn make_member, I: 'static>(rank: u16) -> Result { let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); + T::Members::induct(&member)?; for _ in 0..rank { - T::Members::promote(&member).unwrap(); + T::Members::promote(&member)?; } - CoreFellowship::::import(RawOrigin::Signed(member.clone()).into()).unwrap(); - member + CoreFellowship::::import(RawOrigin::Signed(member.clone()).into())?; + Ok(member) } #[benchmark] - fn set_params() { + fn set_params() -> Result<(), BenchmarkError> { let params = ParamsType { active_salary: [100u32.into(); 9], passive_salary: [10u32.into(); 9], @@ -64,15 +67,16 @@ mod benchmarks { _(RawOrigin::Root, Box::new(params.clone())); assert_eq!(Params::::get(), params); + Ok(()) } #[benchmark] - fn bump_offboard() { - let member = make_member::(0); + fn bump_offboard() -> Result<(), BenchmarkError> { + let member = make_member::(0)?; // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); - ensure_evidence::(&member); + ensure_evidence::(&member)?; assert!(Member::::contains_key(&member)); #[extrinsic_call] @@ -80,15 +84,16 @@ mod benchmarks { assert!(!Member::::contains_key(&member)); assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) } #[benchmark] - fn bump_demote() { - let member = make_member::(2); + fn bump_demote() -> Result<(), BenchmarkError> { + let member = make_member::(2)?; // Set it to the max value to ensure that any possible auto-demotion period has passed. frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); - ensure_evidence::(&member); + ensure_evidence::(&member)?; assert!(Member::::contains_key(&member)); assert_eq!(T::Members::rank_of(&member), Some(2)); @@ -98,21 +103,23 @@ mod benchmarks { assert!(Member::::contains_key(&member)); assert_eq!(T::Members::rank_of(&member), Some(1)); assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) } #[benchmark] - fn set_active() { - let member = make_member::(1); + fn set_active() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; assert!(Member::::get(&member).unwrap().is_active); #[extrinsic_call] _(RawOrigin::Signed(member.clone()), false); assert!(!Member::::get(&member).unwrap().is_active); + Ok(()) } #[benchmark] - fn induct() { + fn induct() -> Result<(), BenchmarkError> { let candidate: T::AccountId = account("candidate", 0, SEED); #[extrinsic_call] @@ -120,39 +127,45 @@ mod benchmarks { assert_eq!(T::Members::rank_of(&candidate), Some(0)); assert!(Member::::contains_key(&candidate)); + Ok(()) } #[benchmark] - fn promote() { - let member = make_member::(1); - ensure_evidence::(&member); + fn promote() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; + ensure_evidence::(&member)?; #[extrinsic_call] _(RawOrigin::Root, member.clone(), 2u8.into()); assert_eq!(T::Members::rank_of(&member), Some(2)); assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) } #[benchmark] - fn offboard() { - let member = make_member::(0); - T::Members::demote(&member).unwrap(); + fn offboard() -> Result<(), BenchmarkError> { + let member = make_member::(0)?; + T::Members::demote(&member)?; + ensure_evidence::(&member)?; assert!(T::Members::rank_of(&member).is_none()); assert!(Member::::contains_key(&member)); + assert!(MemberEvidence::::contains_key(&member)); #[extrinsic_call] _(RawOrigin::Signed(member.clone()), member.clone()); assert!(!Member::::contains_key(&member)); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) } #[benchmark] - fn import() { + fn import() -> Result<(), BenchmarkError> { let member = account("member", 0, SEED); - T::Members::induct(&member).unwrap(); - T::Members::promote(&member).unwrap(); + T::Members::induct(&member)?; + T::Members::promote(&member)?; assert!(!Member::::contains_key(&member)); @@ -160,15 +173,16 @@ mod benchmarks { _(RawOrigin::Signed(member.clone())); assert!(Member::::contains_key(&member)); + Ok(()) } #[benchmark] - fn approve() { - let member = make_member::(1); + fn approve() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; let then = frame_system::Pallet::::block_number(); let now = then.saturating_plus_one(); frame_system::Pallet::::set_block_number(now); - ensure_evidence::(&member); + ensure_evidence::(&member)?; assert_eq!(Member::::get(&member).unwrap().last_proof, then); @@ -177,11 +191,12 @@ mod benchmarks { assert_eq!(Member::::get(&member).unwrap().last_proof, now); assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) } #[benchmark] - fn submit_evidence() { - let member = make_member::(1); + fn submit_evidence() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; let evidence = vec![0; Evidence::::bound()].try_into().unwrap(); assert!(!MemberEvidence::::contains_key(&member)); @@ -190,6 +205,7 @@ mod benchmarks { _(RawOrigin::Signed(member.clone()), Wish::Retention, evidence); assert!(MemberEvidence::::contains_key(&member)); + Ok(()) } impl_benchmark_test_suite! { diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 8b9504ac1b886..08f50659eae16 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -98,9 +98,9 @@ pub type Evidence = BoundedVec>::EvidenceSize>; /// The status of the pallet instance. #[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub struct ParamsType { - /// The amounts to be paid when a given grade is active. + /// The amounts to be paid when a member of a given rank (-1) is active. active_salary: [Balance; RANKS], - /// The minimum percent discount when passive. + /// The amounts to be paid when a member of a given rank (-1) is passive. passive_salary: [Balance; RANKS], /// The period between which unproven members become demoted. demotion_period: [BlockNumber; RANKS], @@ -473,6 +473,7 @@ pub mod pallet { ensure!(T::Members::rank_of(&who).is_none(), Error::::Ranked); ensure!(Member::::contains_key(&who), Error::::NotTracked); Member::::remove(&who); + MemberEvidence::::remove(&who); Self::deposit_event(Event::::Offboarded { who }); Ok(Pays::No.into()) } From 04da40eb1c94cf4ed7534ea4f3b6281cc83c1b6c Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 8 Mar 2023 09:44:12 +0100 Subject: [PATCH 76/79] Remove dependency --- frame/core-fellowship/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml index 2785346c7341f..37dcfe1718e74 100644 --- a/frame/core-fellowship/Cargo.toml +++ b/frame/core-fellowship/Cargo.toml @@ -49,7 +49,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-ranked-collective/runtime-benchmarks", "pallet-salary/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] From ae5a5e257ce7d2ce99e3004e6e908b0d355be8a1 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 8 Mar 2023 09:46:00 +0100 Subject: [PATCH 77/79] set_params should pay --- frame/core-fellowship/src/lib.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 1baeb90b0b0af..d6839822f8d66 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -326,14 +326,11 @@ pub mod pallet { /// - `params`: The new parameters for the pallet. #[pallet::weight(T::WeightInfo::set_params())] #[pallet::call_index(1)] - pub fn set_params( - origin: OriginFor, - params: Box>, - ) -> DispatchResultWithPostInfo { + pub fn set_params(origin: OriginFor, params: Box>) -> DispatchResult { T::ParamsOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; Params::::put(params.as_ref()); Self::deposit_event(Event::::ParamsChanged { params: *params }); - Ok(Pays::No.into()) + Ok(()) } /// Set whether a member is active or not. From 554ac7d0dbb5f5ac0f12869ef02dc3e35dcd783d Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 8 Mar 2023 09:48:07 +0100 Subject: [PATCH 78/79] induct should pay --- frame/core-fellowship/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index d6839822f8d66..2aa77a97b859e 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -393,7 +393,7 @@ pub mod pallet { /// - `who`: The account ID of the candidate to be inducted and become a member. #[pallet::weight(T::WeightInfo::induct())] #[pallet::call_index(4)] - pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResult { match T::InductOrigin::try_origin(origin) { Ok(_) => {}, Err(origin) => ensure_root(origin)?, @@ -408,7 +408,7 @@ pub mod pallet { MemberStatus { is_active: true, last_promotion: now, last_proof: now }, ); Self::deposit_event(Event::::Inducted { who }); - Ok(Pays::No.into()) + Ok(()) } /// Increment the rank of a ranked and tracked account. From cc9f07c5cf0f1be989e15cfd1b784de45da52c2c Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 8 Mar 2023 09:51:42 +0100 Subject: [PATCH 79/79] Remove some other free calls --- frame/core-fellowship/Cargo.toml | 1 + frame/core-fellowship/src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml index 37dcfe1718e74..2785346c7341f 100644 --- a/frame/core-fellowship/Cargo.toml +++ b/frame/core-fellowship/Cargo.toml @@ -49,6 +49,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-ranked-collective/runtime-benchmarks", "pallet-salary/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs index 2aa77a97b859e..6ba60dbaf2559 100644 --- a/frame/core-fellowship/src/lib.rs +++ b/frame/core-fellowship/src/lib.rs @@ -368,7 +368,7 @@ pub mod pallet { origin: OriginFor, who: T::AccountId, at_rank: RankOf, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { match T::PromoteOrigin::try_origin(origin) { Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::::NoPermission), Err(origin) => ensure_root(origin)?, @@ -384,7 +384,7 @@ pub mod pallet { Self::dispose_evidence(who.clone(), at_rank, Some(at_rank)); Self::deposit_event(Event::::Proven { who, at_rank }); - Ok(Pays::No.into()) + Ok(()) } /// Introduce a new and unranked candidate (rank zero). @@ -423,7 +423,7 @@ pub mod pallet { origin: OriginFor, who: T::AccountId, to_rank: RankOf, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { match T::PromoteOrigin::try_origin(origin) { Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::::NoPermission), Err(origin) => ensure_root(origin)?, @@ -454,7 +454,7 @@ pub mod pallet { Self::deposit_event(Event::::Promoted { who, to_rank }); - Ok(Pays::No.into()) + Ok(()) } /// Stop tracking a prior member who is now not a ranked member of the collective.