From 0bcafdc24894b4274e7e580ecec90a0618639844 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Wed, 8 Nov 2023 12:17:16 +0530 Subject: [PATCH 01/13] validator checks before assigning role --- Cargo.lock | 6 ++ pallets/roles/Cargo.toml | 18 +++- pallets/roles/src/lib.rs | 46 +++++------ pallets/roles/src/mock.rs | 163 +++++++++++++++++++++++++++++++++++-- pallets/roles/src/tests.rs | 58 +++++++------ 5 files changed, 229 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e0ec86ef..706b22215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9059,10 +9059,14 @@ name = "pallet-roles" version = "0.5.0" dependencies = [ "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", "hex-literal 0.3.4", "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-timestamp", "parity-scale-codec", "scale-info", "serde", @@ -9070,6 +9074,7 @@ dependencies = [ "sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "sp-io 23.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "sp-runtime 24.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "sp-staking", "sp-std 8.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "tangle-primitives", ] @@ -9125,6 +9130,7 @@ dependencies = [ "pallet-authorship", "pallet-session", "parity-scale-codec", + "rand_chacha 0.2.2", "scale-info", "serde", "sp-application-crypto 23.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", diff --git a/pallets/roles/Cargo.toml b/pallets/roles/Cargo.toml index a7741be0c..5c41cc474 100644 --- a/pallets/roles/Cargo.toml +++ b/pallets/roles/Cargo.toml @@ -17,11 +17,16 @@ serde = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } +pallet-staking = { workspace = true } +pallet-session = { workspace = true } +pallet-timestamp = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } +sp-staking = { workspace = true } tangle-primitives = {workspace = true, default-features = false } frame-benchmarking = { workspace = true, optional = true } +frame-election-provider-support = { workspace = true } [dev-dependencies] hex-literal = { workspace = true } @@ -40,8 +45,13 @@ std = [ "sp-runtime/std", "sp-std/std", "sp-io/std", + "sp-staking/std", "pallet-balances/std", - "tangle-primitives/std" + "pallet-staking/std", + "pallet-session/std", + "pallet-timestamp/std", + "tangle-primitives/std", + "frame-election-provider-support/std", ] try-runtime = ["frame-support/try-runtime"] @@ -50,5 +60,9 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-balances/runtime-benchmarks" + "sp-staking/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", ] diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index dc90a9c07..7f0174eba 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -21,9 +21,10 @@ use codec::MaxEncodedLen; use frame_support::{ ensure, - traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced}, + traits::{Currency, Get, LockIdentifier, LockableCurrency}, CloneNoBound, EqNoBound, PalletId, PartialEqNoBound, RuntimeDebugNoBound, }; + pub use pallet::*; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -71,12 +72,9 @@ impl RoleStakingLedger { } } -pub type CurrencyOf = ::Currency; +pub type CurrencyOf = ::Currency; pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; + as Currency<::AccountId>>::Balance; const ROLES_STAKING_ID: LockIdentifier = *b"rstaking"; @@ -92,30 +90,10 @@ pub mod pallet { /// Configuration trait. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_staking::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type Currency: LockableCurrency< - Self::AccountId, - Moment = BlockNumberFor, - Balance = Self::CurrencyBalance, - >; - /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to - /// `From`. - type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned - + codec::FullCodec - + Copy - + MaybeSerializeDeserialize - + sp_std::fmt::Debug - + Default - + From - + TypeInfo - + MaxEncodedLen; - - /// Handler for the unbalanced reduction when slashing a staker. - type Slash: OnUnbalanced>; - type WeightInfo: WeightInfo; #[pallet::constant] @@ -139,6 +117,8 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// Not a validator. + NotValidator, /// Role has already been assigned to provided validator. RoleAlreadyAssigned, /// No role assigned to provided validator. @@ -188,6 +168,12 @@ pub mod pallet { role: RoleType, ) -> DispatchResult { let stash_account = ensure_signed(origin)?; + // Ensure stash account is a validator. + ensure!( + pallet_staking::Validators::::contains_key(&stash_account), + Error::::NotValidator + ); + // Check if role is already assigned. ensure!( !AccountRolesMapping::::contains_key(&stash_account), @@ -233,6 +219,12 @@ pub mod pallet { #[pallet::call_index(1)] pub fn clear_role(origin: OriginFor, role: RoleType) -> DispatchResult { let stash_account = ensure_signed(origin)?; + // Ensure stash account is a validator. + ensure!( + pallet_staking::Validators::::contains_key(&stash_account), + Error::::NotValidator + ); + // check if role is assigned. ensure!( Self::is_validator(stash_account.clone(), role.clone()), diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs index f4cc2c38c..f659803b1 100644 --- a/pallets/roles/src/mock.rs +++ b/pallets/roles/src/mock.rs @@ -17,14 +17,19 @@ use super::*; use crate as pallet_roles; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU128, ConstU32, ConstU64, Everything}, + traits::{ConstU128, ConstU32, ConstU64, Everything, Hooks}, }; - +use pallet_session::TestSessionHandler; use sp_core::H256; -use sp_runtime::{traits::IdentityLookup, BuildStorage}; -pub type AccountId = u128; +use sp_runtime::{ + testing::{Header, UintAuthorityId}, + traits::IdentityLookup, + BuildStorage, Perbill, +}; +pub type AccountId = u64; pub type Balance = u128; impl frame_system::Config for Runtime { @@ -69,20 +74,116 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = (); } +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +impl pallet_session::historical::Config for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +sp_runtime::impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: UintAuthorityId, + } +} + +impl From for MockSessionKeys { + fn from(dummy: UintAuthorityId) -> Self { + Self { dummy } + } +} + +pub struct MockSessionManager; + +impl pallet_session::SessionManager for MockSessionManager { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(idx: sp_staking::SessionIndex) -> Option> { + if idx == 0 || idx == 1 { + Some(vec![1, 2, 3, 4]) + } else if idx == 2 { + Some(vec![1, 2, 3, 4]) + } else { + None + } + } +} + parameter_types! { - pub const RolesPalletId: PalletId = PalletId(*b"py/roles"); + pub const Period: u64 = 1; + pub const Offset: u64 = 0; } -impl Config for Runtime { +impl pallet_session::Config for Runtime { + type SessionManager = MockSessionManager; + type Keys = MockSessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = TestSessionHandler; type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type WeightInfo = (); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; type Currency = Balances; type CurrencyBalance = ::Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = (); + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type AdminOrigin = frame_system::EnsureRoot; + type BondingDuration = (); + type SessionInterface = Self; + type EraPayout = (); + type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = onchain::OnChainExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type EventListeners = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub const RolesPalletId: PalletId = PalletId(*b"py/roles"); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; type PalletId = RolesPalletId; type WeightInfo = (); } -type Block = frame_system::mocking::MockBlock; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; construct_runtime!( pub enum Runtime @@ -90,6 +191,8 @@ construct_runtime!( System: frame_system, Balances: pallet_balances, Roles: pallet_roles, + Session: pallet_session, + Staking: pallet_staking, } ); @@ -116,14 +219,56 @@ pub fn assert_events(mut expected: Vec) { // This function basically just builds a genesis storage key/value store according to // our desired mockup. -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); // We use default for brevity, but you can configure as desired if needed. - pallet_balances::GenesisConfig:: { balances: vec![(10, 10000), (20, 10000)] } + let balances: Vec<_> = authorities.iter().map(|i| (*i, 20_000_u128)).collect(); + + pallet_balances::GenesisConfig:: { balances } .assimilate_storage(&mut t) .unwrap(); + let session_keys: Vec<_> = authorities + .iter() + .map(|id| (*id, *id, MockSessionKeys { dummy: UintAuthorityId(*id) })) + .collect(); + + pallet_session::GenesisConfig:: { keys: session_keys } + .assimilate_storage(&mut t) + .unwrap(); + + let stakers: Vec<_> = authorities + .iter() + .map(|authority| { + ( + *authority, + *authority, + 10_000_u128, + pallet_staking::StakerStatus::::Validator, + ) + }) + .collect(); + + let staking_config = pallet_staking::GenesisConfig:: { + stakers, + validator_count: 4, + force_era: pallet_staking::Forcing::ForceNew, + minimum_validator_count: 0, + max_validator_count: Some(5), + max_nominator_count: Some(5), + invulnerables: vec![], + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + >::on_initialize(1); + }); + ext } diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index 38abccadd..be44cec9f 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -15,78 +15,88 @@ // along with Tangle. If not, see . #![cfg(test)] use super::*; -use frame_support::assert_ok; +use frame_support::{assert_err, assert_ok}; use mock::*; use tangle_primitives::jobs::ValidatorOffence; #[test] fn test_assign_role() { - new_test_ext().execute_with(|| { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); assert_events(vec![ - RuntimeEvent::Roles(crate::Event::Bonded { account: 10, amount: 5000 }), - RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 10, role: RoleType::Tss }), + RuntimeEvent::Roles(crate::Event::Bonded { account: 1, amount: 5000 }), + RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 1, role: RoleType::Tss }), ]); // Lets verify role assigned to account. - assert_eq!(Roles::account_role(10), Some(RoleType::Tss)); + assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); // Verify ledger mapping - assert_eq!(Roles::ledger(10), Some(RoleStakingLedger { stash: 10, total_locked: 5000 })); + assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total_locked: 5000 })); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(10), 5000); + assert_eq!(Balances::usable_balance(1), 5000); }); } #[test] fn test_clear_role() { - new_test_ext().execute_with(|| { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(10), 5000); + assert_eq!(Balances::usable_balance(1), 5000); // Now lets clear the role - assert_ok!(Roles::clear_role(RuntimeOrigin::signed(10), RoleType::Tss)); + assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); assert_events(vec![ - RuntimeEvent::Roles(crate::Event::Unbonded { account: 10, amount: 5000 }), - RuntimeEvent::Roles(crate::Event::RoleRemoved { account: 10, role: RoleType::Tss }), + RuntimeEvent::Roles(crate::Event::Unbonded { account: 1, amount: 5000 }), + RuntimeEvent::Roles(crate::Event::RoleRemoved { account: 1, role: RoleType::Tss }), ]); // Role should be removed from account role mappings. - assert_eq!(Roles::account_role(10), None); + assert_eq!(Roles::account_role(1), None); // Ledger should be removed from ledger mappings. - assert_eq!(Roles::ledger(10), None); + assert_eq!(Roles::ledger(1), None); // Verify total usable balance of the account. Since we have cleared the role, we should // have 10000 tokens usable. - assert_eq!(Balances::usable_balance(10), 10000); + assert_eq!(Balances::usable_balance(1), 10000); }); } -// test slashing #[test] fn test_slash_validator() { - new_test_ext().execute_with(|| { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(10), 5000); + assert_eq!(Balances::usable_balance(1), 5000); // Now lets slash the account for being Inactive. - assert_ok!(Roles::slash_validator(10, ValidatorOffence::Inactivity)); + assert_ok!(Roles::slash_validator(1, ValidatorOffence::Inactivity)); assert_events(vec![RuntimeEvent::Roles(crate::Event::Slashed { - account: 10, + account: 1, amount: 1000, })]); // should be updated in ledger - assert_eq!(Roles::ledger(10), Some(RoleStakingLedger { stash: 10, total_locked: 4000 })); + assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total_locked: 4000 })); + }); +} + +#[test] +fn test_assign_role_should_fail_if_not_validator() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + // we will use account 5 which is not a validator + assert_err!( + Roles::assign_role(RuntimeOrigin::signed(5), 5000, RoleType::Tss), + Error::::NotValidator + ); }); } From 4aeb4dc99897d2f97518cd528ab29b8187ff2942 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 18:28:23 +0530 Subject: [PATCH 02/13] block unbond call from pallet-staking and allow unbonding through roles-pallet --- pallets/roles/src/impls.rs | 34 ++--------- pallets/roles/src/lib.rs | 104 ++++++++++++++++++++++------------ pallets/roles/src/tests.rs | 12 ++-- standalone/runtime/src/lib.rs | 8 +++ 4 files changed, 85 insertions(+), 73 deletions(-) diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs index 28554650d..e465d58fb 100644 --- a/pallets/roles/src/impls.rs +++ b/pallets/roles/src/impls.rs @@ -15,7 +15,6 @@ // along with Tangle. If not, see . use super::*; -use frame_support::{pallet_prelude::DispatchResult, traits::WithdrawReasons}; use sp_runtime::Saturating; use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler}; @@ -73,7 +72,7 @@ impl Pallet { /// The total amount of the balance that can be slashed. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. - Self::ledger(&stash).map(|l| l.total_locked).unwrap_or_default() + Self::ledger(&stash).map(|l| l.total).unwrap_or_default() } /// Slash the given amount from the stash account. @@ -85,9 +84,9 @@ impl Pallet { address: T::AccountId, slash_amount: T::CurrencyBalance, ) -> sp_runtime::DispatchResult { - let mut ledger = Self::ledger(&address).ok_or(Error::::InvalidStashController)?; + let mut ledger = Self::ledger(&address).ok_or(Error::::AccountNotPaired)?; let (_imbalance, _missing) = T::Currency::slash(&address, slash_amount.into()); - ledger.total_locked = ledger.total_locked.saturating_sub(slash_amount.into()); + ledger.total = ledger.total.saturating_sub(slash_amount.into()); Self::update_ledger(&address, &ledger); Self::deposit_event(Event::Slashed { account: address, amount: slash_amount }); Ok(()) @@ -102,36 +101,11 @@ impl Pallet { /// # Note /// This function will set a lock on the stash account. pub(crate) fn update_ledger(staker: &T::AccountId, ledger: &RoleStakingLedger) { - T::Currency::set_lock( - ROLES_STAKING_ID, - &ledger.stash, - ledger.total_locked, - WithdrawReasons::all(), - ); >::insert(staker, ledger); } /// Kill the stash account and remove all related information. - pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult { + pub(crate) fn kill_stash(stash: &T::AccountId) { >::remove(&stash); - Ok(()) - } - - /// Unbond the stash account. - /// - /// # Parameters - /// - `ledger`: The ledger of the stash account. - /// - /// # Note - /// This function will remove the lock on the stash account. - pub(super) fn unbond(ledger: &RoleStakingLedger) -> DispatchResult { - let stash = ledger.stash.clone(); - if ledger.total_locked > T::Currency::minimum_balance() { - // Remove the lock. - T::Currency::remove_lock(ROLES_STAKING_ID, &stash); - // Kill the stash and related information. - Self::kill_stash(&stash)?; - } - Ok(()) } } diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 7f0174eba..69f7708a5 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -21,7 +21,7 @@ use codec::MaxEncodedLen; use frame_support::{ ensure, - traits::{Currency, Get, LockIdentifier, LockableCurrency}, + traits::{Currency, Get}, CloneNoBound, EqNoBound, PalletId, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -54,21 +54,21 @@ pub use weights::WeightInfo; pub struct RoleStakingLedger { /// The stash account whose balance is actually locked and at stake. pub stash: T::AccountId, - /// The total amount of the stash's balance that we are currently accounting for. - /// It's just `active` plus all the `unlocking` balances. + /// The total amount of the stash's balance that is re-staked for selected services + /// This re-staked balance we are currently accounting for new slashing conditions. #[codec(compact)] - pub total_locked: BalanceOf, + pub total: BalanceOf, } impl RoleStakingLedger { /// Initializes the default object using the given `validator`. pub fn default_from(stash: T::AccountId) -> Self { - Self { stash, total_locked: Zero::zero() } + Self { stash, total: Zero::zero() } } /// Returns `true` if the stash account has no funds at all. pub fn is_empty(&self) -> bool { - self.total_locked.is_zero() + self.total.is_zero() } } @@ -76,8 +76,6 @@ pub type CurrencyOf = ::Currency; pub type BalanceOf = as Currency<::AccountId>>::Balance; -const ROLES_STAKING_ID: LockIdentifier = *b"rstaking"; - #[frame_support::pallet] pub mod pallet { use super::*; @@ -125,10 +123,12 @@ pub mod pallet { RoleNotAssigned, /// Insufficient bond to become a validator. InsufficientBond, - /// Stash controller account already added to Ledger - AlreadyPaired, - /// Stash controller account not found in ledger - InvalidStashController, + /// Insufficient bond to re stake. + InsufficientReStakingBond, + /// Stash controller account already added to Roles Ledger + AccountAlreadyPaired, + /// Stash controller account not found in Roles Ledger. + AccountNotPaired, } /// Map from all "controller" accounts to the info regarding the staking. @@ -142,10 +142,10 @@ pub mod pallet { /// Mapping of resource to bridge index pub type AccountRolesMapping = StorageMap<_, Blake2_256, T::AccountId, RoleType>; - /// The minimum active bond to become and maintain the role. + /// The minimum re staking bond to become and maintain the role. #[pallet::storage] #[pallet::getter(fn min_active_bond)] - pub(super) type MinActiveBond = StorageValue<_, BalanceOf, ValueQuery>; + pub(super) type MinReStakingBond = StorageValue<_, BalanceOf, ValueQuery>; /// Assigns a role to the validator. /// @@ -164,8 +164,8 @@ pub mod pallet { #[pallet::call_index(0)] pub fn assign_role( origin: OriginFor, - #[pallet::compact] bond_value: BalanceOf, role: RoleType, + #[pallet::compact] re_stake: BalanceOf, ) -> DispatchResult { let stash_account = ensure_signed(origin)?; // Ensure stash account is a validator. @@ -180,26 +180,21 @@ pub mod pallet { Error::::RoleAlreadyAssigned ); - // Check if stash account is already paired. - ensure!(!>::contains_key(&stash_account), Error::::AlreadyPaired); + // Check if stash account is already paired/ re-staked. + ensure!(!>::contains_key(&stash_account), Error::::AccountAlreadyPaired); - // Check if min active bond is met. - let min_active_bond = MinActiveBond::::get(); - ensure!(bond_value > min_active_bond.into(), Error::::InsufficientBond); + // Validate re-staking bond, should be greater than min re-staking bond requirement. + let min_re_staking_bond = MinReStakingBond::::get(); + ensure!(re_stake >= min_re_staking_bond, Error::::InsufficientReStakingBond); - // Bond with stash account. - let stash_balance = T::Currency::free_balance(&stash_account); - let value = bond_value.min(stash_balance); + // Validate re-staking bond, should be less than active staked bond. + let staking_ledger = pallet_staking::Ledger::::get(&stash_account).unwrap(); + ensure!(staking_ledger.active > re_stake, Error::::InsufficientBond); // Update ledger. - let item = RoleStakingLedger { stash: stash_account.clone(), total_locked: value }; + let item = RoleStakingLedger { stash: stash_account.clone(), total: re_stake }; Self::update_ledger(&stash_account, &item); - Self::deposit_event(Event::::Bonded { - account: stash_account.clone(), - amount: value, - }); - // Add role mapping for the stash account. AccountRolesMapping::::insert(&stash_account, role); Self::deposit_event(Event::::RoleAssigned { account: stash_account.clone(), role }); @@ -234,19 +229,54 @@ pub mod pallet { // On successful removal of services, remove the role from the mapping. // Issue link for reference : https://github.com/webb-tools/tangle/issues/292 - // Unbound locked funds. - let ledger = Self::ledger(&stash_account).ok_or(Error::::InvalidStashController)?; - Self::unbond(&ledger)?; - Self::deposit_event(Event::::Unbonded { - account: ledger.stash, - amount: ledger.total_locked, - }); - // Remove role from the mapping. AccountRolesMapping::::remove(&stash_account); + // Remove stash account related info. + Self::kill_stash(&stash_account); + Self::deposit_event(Event::::RoleRemoved { account: stash_account, role }); Ok(()) } + + /// Unbound funds from the stash account. + /// This will allow validator to unbound and later withdraw funds. + /// If you have opted for any of the roles, please submit `clear_role` extrinsic to opt out + /// Once opted out of all services and your role is cleared, you can unbound and withdraw + /// funds. + /// + /// # Parameters + /// + /// - `origin`: Origin of the transaction. + /// - `amount`: Amount of funds to unbound. + /// + /// This function will return error if + /// - Stash account is not a validator. + /// - Insufficient funds to unbound. + /// + #[pallet::weight({0})] + #[pallet::call_index(2)] + pub fn unbound_funds( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let stash_account = ensure_signed(origin.clone())?; + // Ensure stash account is a validator. + ensure!( + pallet_staking::Validators::::contains_key(&stash_account), + Error::::NotValidator + ); + + // Ensure no role is assigned to the validator and is eligible to unbound. + ensure!( + !AccountRolesMapping::::contains_key(&stash_account), + Error::::RoleAlreadyAssigned + ); + + // Unbound funds. + let _ = pallet_staking::Pallet::::unbond(origin, amount); + + Ok(()) + } } } diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index be44cec9f..e1734589f 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -23,7 +23,7 @@ use tangle_primitives::jobs::ValidatorOffence; fn test_assign_role() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); assert_events(vec![ RuntimeEvent::Roles(crate::Event::Bonded { account: 1, amount: 5000 }), @@ -33,7 +33,7 @@ fn test_assign_role() { // Lets verify role assigned to account. assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); // Verify ledger mapping - assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total_locked: 5000 })); + assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 5000 })); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. assert_eq!(Balances::usable_balance(1), 5000); @@ -44,7 +44,7 @@ fn test_assign_role() { fn test_clear_role() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. assert_eq!(Balances::usable_balance(1), 5000); @@ -73,7 +73,7 @@ fn test_clear_role() { fn test_slash_validator() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), 5000, RoleType::Tss)); + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should // have 5000 tokens usable. assert_eq!(Balances::usable_balance(1), 5000); @@ -86,7 +86,7 @@ fn test_slash_validator() { amount: 1000, })]); // should be updated in ledger - assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total_locked: 4000 })); + assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 4000 })); }); } @@ -95,7 +95,7 @@ fn test_assign_role_should_fail_if_not_validator() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // we will use account 5 which is not a validator assert_err!( - Roles::assign_role(RuntimeOrigin::signed(5), 5000, RoleType::Tss), + Roles::assign_role(RuntimeOrigin::signed(5), RoleType::Tss, 5000), Error::::NotValidator ); }); diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 85911cbce..fd9604aea 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -1098,6 +1098,14 @@ impl Contains for BaseFilter { return false } + let is_stake_unbound_call = + matches!(call, RuntimeCall::Staking(pallet_staking::Call::unbond{..})); + + if is_stake_unbound_call { + // no unbond call + return false + } + let democracy_related = matches!( call, // Filter democracy proposals creation From f222d291abbee673fff33b7e49aeef222f53ee15 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 18:42:59 +0530 Subject: [PATCH 03/13] update errors --- pallets/roles/src/lib.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 69f7708a5..66bfc4017 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -118,12 +118,12 @@ pub mod pallet { /// Not a validator. NotValidator, /// Role has already been assigned to provided validator. - RoleAlreadyAssigned, + HasRoleAssigned, /// No role assigned to provided validator. - RoleNotAssigned, - /// Insufficient bond to become a validator. - InsufficientBond, - /// Insufficient bond to re stake. + NoRoleAssigned, + /// Invalid Re-staking amount, should not exceed total staked amount. + InvalidReStakingBond, + /// Re staking amount should be greater than minimum re-staking bond to become and maintain the role. InsufficientReStakingBond, /// Stash controller account already added to Roles Ledger AccountAlreadyPaired, @@ -177,7 +177,7 @@ pub mod pallet { // Check if role is already assigned. ensure!( !AccountRolesMapping::::contains_key(&stash_account), - Error::::RoleAlreadyAssigned + Error::::HasRoleAssigned ); // Check if stash account is already paired/ re-staked. @@ -187,9 +187,9 @@ pub mod pallet { let min_re_staking_bond = MinReStakingBond::::get(); ensure!(re_stake >= min_re_staking_bond, Error::::InsufficientReStakingBond); - // Validate re-staking bond, should be less than active staked bond. + // Validate re-staking bond, should not exceed active staked bond. let staking_ledger = pallet_staking::Ledger::::get(&stash_account).unwrap(); - ensure!(staking_ledger.active > re_stake, Error::::InsufficientBond); + ensure!(staking_ledger.active > re_stake, Error::::InvalidReStakingBond); // Update ledger. let item = RoleStakingLedger { stash: stash_account.clone(), total: re_stake }; @@ -223,7 +223,7 @@ pub mod pallet { // check if role is assigned. ensure!( Self::is_validator(stash_account.clone(), role.clone()), - Error::::RoleNotAssigned + Error::::NoRoleAssigned ); // TODO: Call jobs manager to remove the services. // On successful removal of services, remove the role from the mapping. @@ -252,7 +252,7 @@ pub mod pallet { /// /// This function will return error if /// - Stash account is not a validator. - /// - Insufficient funds to unbound. + /// - If there is any role assigned to the validator. /// #[pallet::weight({0})] #[pallet::call_index(2)] @@ -270,7 +270,7 @@ pub mod pallet { // Ensure no role is assigned to the validator and is eligible to unbound. ensure!( !AccountRolesMapping::::contains_key(&stash_account), - Error::::RoleAlreadyAssigned + Error::::HasRoleAssigned ); // Unbound funds. From e4efef4472845a27c7ab2e53a8932ecb45999dc2 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 19:02:31 +0530 Subject: [PATCH 04/13] update docs --- pallets/roles/src/lib.rs | 33 +++++++++++++-------------------- standalone/runtime/src/lib.rs | 2 +- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 66bfc4017..dcc0f1ea4 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -123,7 +123,7 @@ pub mod pallet { NoRoleAssigned, /// Invalid Re-staking amount, should not exceed total staked amount. InvalidReStakingBond, - /// Re staking amount should be greater than minimum re-staking bond to become and maintain the role. + /// Re staking amount should be greater than minimum re-staking bond requirement. InsufficientReStakingBond, /// Stash controller account already added to Roles Ledger AccountAlreadyPaired, @@ -152,12 +152,13 @@ pub mod pallet { /// # Parameters /// /// - `origin`: Origin of the transaction. - /// - `bond_value`: Amount of funds to bond. /// - `role`: Role to assign to the validator. + /// - `re_stake`: Amount of funds you want to re-stake. /// /// This function will return error if + /// - Account is not a validator account. /// - Role is already assigned to the validator. - /// - Min active bond is not met. + /// - Min re-staking bond is not met. #[pallet::call] impl Pallet { #[pallet::weight({0})] @@ -209,7 +210,9 @@ pub mod pallet { /// - `role`: Role to remove from the validator. /// /// This function will return error if + /// - Account is not a validator account. /// - Role is not assigned to the validator. + /// - All the jobs are not completed. #[pallet::weight({0})] #[pallet::call_index(1)] pub fn clear_role(origin: OriginFor, role: RoleType) -> DispatchResult { @@ -240,10 +243,10 @@ pub mod pallet { } /// Unbound funds from the stash account. - /// This will allow validator to unbound and later withdraw funds. + /// This will allow user to unbound and later withdraw funds. /// If you have opted for any of the roles, please submit `clear_role` extrinsic to opt out - /// Once opted out of all services and your role is cleared, you can unbound and withdraw - /// funds. + /// of all the services. Once your role is cleared, you can unbound + /// and withdraw funds. /// /// # Parameters /// @@ -251,8 +254,7 @@ pub mod pallet { /// - `amount`: Amount of funds to unbound. /// /// This function will return error if - /// - Stash account is not a validator. - /// - If there is any role assigned to the validator. + /// - If there is any active role assigned to the user. /// #[pallet::weight({0})] #[pallet::call_index(2)] @@ -260,18 +262,9 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { - let stash_account = ensure_signed(origin.clone())?; - // Ensure stash account is a validator. - ensure!( - pallet_staking::Validators::::contains_key(&stash_account), - Error::::NotValidator - ); - - // Ensure no role is assigned to the validator and is eligible to unbound. - ensure!( - !AccountRolesMapping::::contains_key(&stash_account), - Error::::HasRoleAssigned - ); + let account = ensure_signed(origin.clone())?; + // Ensure no role is assigned to the account and is eligible to unbound. + ensure!(!AccountRolesMapping::::contains_key(&account), Error::::HasRoleAssigned); // Unbound funds. let _ = pallet_staking::Pallet::::unbond(origin, amount); diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index fd9604aea..27e9ff404 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -1099,7 +1099,7 @@ impl Contains for BaseFilter { } let is_stake_unbound_call = - matches!(call, RuntimeCall::Staking(pallet_staking::Call::unbond{..})); + matches!(call, RuntimeCall::Staking(pallet_staking::Call::unbond { .. })); if is_stake_unbound_call { // no unbond call From d623e12f55201d9d9286da880adfc8cd5a28a7a8 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 19:42:29 +0530 Subject: [PATCH 05/13] add new tests --- pallets/roles/src/mock.rs | 19 +++++++- pallets/roles/src/tests.rs | 92 ++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 20 deletions(-) diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs index f659803b1..c2d1fe877 100644 --- a/pallets/roles/src/mock.rs +++ b/pallets/roles/src/mock.rs @@ -20,7 +20,7 @@ use crate as pallet_roles; use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU128, ConstU32, ConstU64, Everything, Hooks}, + traits::{ConstU128, ConstU32, ConstU64, Contains, Hooks}, }; use pallet_session::TestSessionHandler; use sp_core::H256; @@ -51,7 +51,7 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = Everything; + type BaseCallFilter = BaseFilter; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); @@ -86,6 +86,21 @@ impl pallet_session::historical::Config for Runtime { type FullIdentificationOf = pallet_staking::ExposureOf; } +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + let is_stake_unbound_call = + matches!(call, RuntimeCall::Staking(pallet_staking::Call::unbond { .. })); + + if is_stake_unbound_call { + // no unbond call + return false + } + + true + } +} + sp_runtime::impl_opaque_keys! { pub struct MockSessionKeys { pub dummy: UintAuthorityId, diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index e1734589f..949598ce1 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -25,18 +25,15 @@ fn test_assign_role() { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); - assert_events(vec![ - RuntimeEvent::Roles(crate::Event::Bonded { account: 1, amount: 5000 }), - RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 1, role: RoleType::Tss }), - ]); + assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned { + account: 1, + role: RoleType::Tss, + })]); // Lets verify role assigned to account. assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); // Verify ledger mapping assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 5000 })); - // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should - // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(1), 5000); }); } @@ -45,27 +42,20 @@ fn test_clear_role() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); - // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should - // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(1), 5000); // Now lets clear the role assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); - assert_events(vec![ - RuntimeEvent::Roles(crate::Event::Unbonded { account: 1, amount: 5000 }), - RuntimeEvent::Roles(crate::Event::RoleRemoved { account: 1, role: RoleType::Tss }), - ]); + assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleRemoved { + account: 1, + role: RoleType::Tss, + })]); // Role should be removed from account role mappings. assert_eq!(Roles::account_role(1), None); // Ledger should be removed from ledger mappings. assert_eq!(Roles::ledger(1), None); - - // Verify total usable balance of the account. Since we have cleared the role, we should - // have 10000 tokens usable. - assert_eq!(Balances::usable_balance(1), 10000); }); } @@ -100,3 +90,69 @@ fn test_assign_role_should_fail_if_not_validator() { ); }); } + +#[test] +fn test_unbound_funds_should_work() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens + // for providing TSS services. + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + + // Lets verify role is assigned to account. + assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); + + // Lets clear the role. + assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); + + // Role should be removed from account role mappings. + assert_eq!(Roles::account_role(1), None); + + // unbound funds. + assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000)); + + assert_events(vec![RuntimeEvent::Staking(pallet_staking::Event::Unbonded { + stash: 1, + amount: 5000, + })]); + + // Get pallet staking ledger mapping. + let staking_ledger = pallet_staking::Ledger::::get(1).unwrap(); + // Since we we have unbounded 5000 tokens, we should have 5000 tokens in staking ledger. + assert_eq!(staking_ledger.active, 5000); + }); +} + +// Test unbound should fail if role is assigned to account. +#[test] +fn test_unbound_funds_should_fail_if_role_assigned() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens + // for providing TSS services. + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + + // Lets verify role is assigned to account. + assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); + + // Lets try to unbound funds. + assert_err!( + Roles::unbound_funds(RuntimeOrigin::signed(1), 5000), + Error::::HasRoleAssigned + ); + }); +} + +// Test unbound should work if no role assigned to account. +#[test] +fn test_unbound_funds_should_work_if_no_role_assigned() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + // Initially validator account has staked 10_000 tokens. + + // Since validator has not opted for any roles, he should be able to unbound his funds. + assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000)); + + assert_events(vec![RuntimeEvent::Staking(pallet_staking::Event::Unbonded { + stash: 1, + amount: 5000, + })]); + }); +} From a0fea1d4bf0564b3c2bf1e1854921a5041759bbe Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 20:22:19 +0530 Subject: [PATCH 06/13] remove slashing test --- pallets/roles/src/tests.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index 949598ce1..fcd96b4e5 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -17,7 +17,6 @@ use super::*; use frame_support::{assert_err, assert_ok}; use mock::*; -use tangle_primitives::jobs::ValidatorOffence; #[test] fn test_assign_role() { @@ -59,27 +58,6 @@ fn test_clear_role() { }); } -#[test] -fn test_slash_validator() { - new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); - // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should - // have 5000 tokens usable. - assert_eq!(Balances::usable_balance(1), 5000); - - // Now lets slash the account for being Inactive. - assert_ok!(Roles::slash_validator(1, ValidatorOffence::Inactivity)); - - assert_events(vec![RuntimeEvent::Roles(crate::Event::Slashed { - account: 1, - amount: 1000, - })]); - // should be updated in ledger - assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 4000 })); - }); -} - #[test] fn test_assign_role_should_fail_if_not_validator() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { From e0d83399e52305ef37e9b117903df6c096093b13 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 21:17:52 +0530 Subject: [PATCH 07/13] Option to choose re-stake value (Full or custom) --- pallets/roles/src/lib.rs | 20 +++++++++---- pallets/roles/src/tests.rs | 53 +++++++++++++++++++++++++++++++---- primitives/src/types/roles.rs | 10 +++++++ 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index dcc0f1ea4..a4e232fe1 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -30,7 +30,10 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{codec, traits::Zero}; use sp_std::{convert::TryInto, prelude::*, vec}; -use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler}; +use tangle_primitives::{ + roles::{ReStakingOption, RoleType}, + traits::roles::RolesHandler, +}; mod impls; #[cfg(test)] pub(crate) mod mock; @@ -166,7 +169,7 @@ pub mod pallet { pub fn assign_role( origin: OriginFor, role: RoleType, - #[pallet::compact] re_stake: BalanceOf, + re_stake: ReStakingOption, ) -> DispatchResult { let stash_account = ensure_signed(origin)?; // Ensure stash account is a validator. @@ -184,16 +187,21 @@ pub mod pallet { // Check if stash account is already paired/ re-staked. ensure!(!>::contains_key(&stash_account), Error::::AccountAlreadyPaired); + let staking_ledger = pallet_staking::Ledger::::get(&stash_account).unwrap(); + let re_stake_amount = match re_stake { + ReStakingOption::Full => staking_ledger.active, + ReStakingOption::Custom(x) => x.into(), + }; + // Validate re-staking bond, should be greater than min re-staking bond requirement. let min_re_staking_bond = MinReStakingBond::::get(); - ensure!(re_stake >= min_re_staking_bond, Error::::InsufficientReStakingBond); + ensure!(re_stake_amount >= min_re_staking_bond, Error::::InsufficientReStakingBond); // Validate re-staking bond, should not exceed active staked bond. - let staking_ledger = pallet_staking::Ledger::::get(&stash_account).unwrap(); - ensure!(staking_ledger.active > re_stake, Error::::InvalidReStakingBond); + ensure!(staking_ledger.active >= re_stake_amount, Error::::InvalidReStakingBond); // Update ledger. - let item = RoleStakingLedger { stash: stash_account.clone(), total: re_stake }; + let item = RoleStakingLedger { stash: stash_account.clone(), total: re_stake_amount }; Self::update_ledger(&stash_account, &item); // Add role mapping for the stash account. diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index fcd96b4e5..f66576194 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -22,7 +22,11 @@ use mock::*; fn test_assign_role() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + assert_ok!(Roles::assign_role( + RuntimeOrigin::signed(1), + RoleType::Tss, + ReStakingOption::Custom(5000) + )); assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 1, @@ -36,11 +40,38 @@ fn test_assign_role() { }); } +// Test that we can assign role with full staking option. +#[test] +fn test_assign_role_with_full_staking_option() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens + assert_ok!(Roles::assign_role( + RuntimeOrigin::signed(1), + RoleType::Tss, + ReStakingOption::Full + )); + + assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned { + account: 1, + role: RoleType::Tss, + })]); + + // Lets verify role assigned to account. + assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); + // Verify ledger mapping + assert_eq!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 10000 })); + }); +} + #[test] fn test_clear_role() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + assert_ok!(Roles::assign_role( + RuntimeOrigin::signed(1), + RoleType::Tss, + ReStakingOption::Custom(5000) + )); // Now lets clear the role assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); @@ -63,7 +94,11 @@ fn test_assign_role_should_fail_if_not_validator() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // we will use account 5 which is not a validator assert_err!( - Roles::assign_role(RuntimeOrigin::signed(5), RoleType::Tss, 5000), + Roles::assign_role( + RuntimeOrigin::signed(5), + RoleType::Tss, + ReStakingOption::Custom(5000) + ), Error::::NotValidator ); }); @@ -74,7 +109,11 @@ fn test_unbound_funds_should_work() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens // for providing TSS services. - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + assert_ok!(Roles::assign_role( + RuntimeOrigin::signed(1), + RoleType::Tss, + ReStakingOption::Custom(5000) + )); // Lets verify role is assigned to account. assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); @@ -106,7 +145,11 @@ fn test_unbound_funds_should_fail_if_role_assigned() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens // for providing TSS services. - assert_ok!(Roles::assign_role(RuntimeOrigin::signed(1), RoleType::Tss, 5000)); + assert_ok!(Roles::assign_role( + RuntimeOrigin::signed(1), + RoleType::Tss, + ReStakingOption::Custom(5000) + )); // Lets verify role is assigned to account. assert_eq!(Roles::account_role(1), Some(RoleType::Tss)); diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs index 3d4c9842a..7104633ee 100644 --- a/primitives/src/types/roles.rs +++ b/primitives/src/types/roles.rs @@ -46,3 +46,13 @@ impl From for RoleType { } } } + + + /// Role type to be used in the system. + #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] + pub enum ReStakingOption { + // Re-stake all the staked funds for selected role. + Full, + // Re-stake only the given amount of funds for selected role. + Custom(u64) + } From 5d737c2c8c3a76433fd77fbff945c2d578e0d7c9 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 21:23:16 +0530 Subject: [PATCH 08/13] handle unwrap --- pallets/roles/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index a4e232fe1..10f4007c0 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -187,7 +187,8 @@ pub mod pallet { // Check if stash account is already paired/ re-staked. ensure!(!>::contains_key(&stash_account), Error::::AccountAlreadyPaired); - let staking_ledger = pallet_staking::Ledger::::get(&stash_account).unwrap(); + let staking_ledger = + pallet_staking::Ledger::::get(&stash_account).ok_or(Error::::NotValidator)?; let re_stake_amount = match re_stake { ReStakingOption::Full => staking_ledger.active, ReStakingOption::Custom(x) => x.into(), From 9fd4deeec0605391fa1c7ca100c477dc180447d6 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 21:24:40 +0530 Subject: [PATCH 09/13] cargo fmt --- primitives/src/types/roles.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs index 7104633ee..e69212299 100644 --- a/primitives/src/types/roles.rs +++ b/primitives/src/types/roles.rs @@ -47,12 +47,11 @@ impl From for RoleType { } } - - /// Role type to be used in the system. - #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] - pub enum ReStakingOption { +/// Role type to be used in the system. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum ReStakingOption { // Re-stake all the staked funds for selected role. Full, // Re-stake only the given amount of funds for selected role. - Custom(u64) - } + Custom(u64), +} From 09de121eea7a194a29f469c8b61c0dcb86ab4023 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 22:25:50 +0530 Subject: [PATCH 10/13] block chill and withdraw-unbond calls --- pallets/roles/src/lib.rs | 52 +++++++++++++++++++++++++++++++++-- pallets/roles/src/mock.rs | 13 +++++++++ standalone/runtime/src/lib.rs | 18 +++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 10f4007c0..30327705b 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -120,7 +120,7 @@ pub mod pallet { pub enum Error { /// Not a validator. NotValidator, - /// Role has already been assigned to provided validator. + /// Validator has active role assigned HasRoleAssigned, /// No role assigned to provided validator. NoRoleAssigned, @@ -251,6 +251,30 @@ pub mod pallet { Ok(()) } + /// Declare no desire to either validate or nominate. + /// + /// If you have opted for any of the roles, please submit `clear_role` extrinsic to opt out + /// of all the services. Once your role is cleared, your request will be processed. + /// + /// # Parameters + /// + /// - `origin`: Origin of the transaction. + /// + /// This function will return error if + /// - Account is not a validator account. + /// - Role is assigned to the validator. + #[pallet::weight({0})] + #[pallet::call_index(2)] + pub fn chill(origin: OriginFor) -> DispatchResult { + let account = ensure_signed(origin.clone())?; + // Ensure no role is assigned to the account before chilling. + ensure!(!AccountRolesMapping::::contains_key(&account), Error::::HasRoleAssigned); + + // chill + let _ = pallet_staking::Pallet::::chill(origin); + Ok(()) + } + /// Unbound funds from the stash account. /// This will allow user to unbound and later withdraw funds. /// If you have opted for any of the roles, please submit `clear_role` extrinsic to opt out @@ -266,7 +290,7 @@ pub mod pallet { /// - If there is any active role assigned to the user. /// #[pallet::weight({0})] - #[pallet::call_index(2)] + #[pallet::call_index(3)] pub fn unbound_funds( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -280,5 +304,29 @@ pub mod pallet { Ok(()) } + + /// Withdraw unbound funds after un-bonding period has passed. + /// + /// # Parameters + /// + /// - `origin`: Origin of the transaction. + /// + /// This function will return error if + /// - If there is any active role assigned to the user. + #[pallet::weight({0})] + #[pallet::call_index(4)] + pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + let stash_account = ensure_signed(origin.clone())?; + // Ensure no role is assigned to the account and is eligible to withdraw. + ensure!( + !AccountRolesMapping::::contains_key(&stash_account), + Error::::HasRoleAssigned + ); + + // Withdraw unbound funds. + let _ = pallet_staking::Pallet::::withdraw_unbonded(origin, 0); + + Ok(()) + } } } diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs index c2d1fe877..ae4141095 100644 --- a/pallets/roles/src/mock.rs +++ b/pallets/roles/src/mock.rs @@ -97,6 +97,19 @@ impl Contains for BaseFilter { return false } + // no chill call + if matches!(call, RuntimeCall::Staking(pallet_staking::Call::chill { .. })) { + return false + } + + // no withdraw_unbonded call + let is_stake_withdraw_call = + matches!(call, RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. })); + + if is_stake_withdraw_call { + return false + } + true } } diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 27e9ff404..82616bf14 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -1097,7 +1097,8 @@ impl Contains for BaseFilter { // no paused call return false } - + // Following staking pallet calls will be blocked and will be allowed to execute + // through role pallet. let is_stake_unbound_call = matches!(call, RuntimeCall::Staking(pallet_staking::Call::unbond { .. })); @@ -1106,6 +1107,21 @@ impl Contains for BaseFilter { return false } + // no chill call + if matches!(call, RuntimeCall::Staking(pallet_staking::Call::chill { .. })) { + return false + } + + // no withdraw_unbonded call + let is_stake_withdraw_call = matches!( + call, + RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. }) + ); + + if is_stake_withdraw_call{ + return false + } + let democracy_related = matches!( call, // Filter democracy proposals creation From cd75483f0d4e847f3460cc10ae35a6282be13393 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 22:30:04 +0530 Subject: [PATCH 11/13] update events --- pallets/roles/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 30327705b..e32ff9cba 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -108,10 +108,6 @@ pub mod pallet { RoleAssigned { account: T::AccountId, role: RoleType }, /// Removed validator from role. RoleRemoved { account: T::AccountId, role: RoleType }, - /// Funds bonded to become a validator. - Bonded { account: T::AccountId, amount: BalanceOf }, - /// Funds unbonded to stop being a validator. - Unbonded { account: T::AccountId, amount: BalanceOf }, /// Slashed validator. Slashed { account: T::AccountId, amount: BalanceOf }, } From 2dfe702478f9bcc865759202b1155b3ef4e20e35 Mon Sep 17 00:00:00 2001 From: salman01zp Date: Tue, 14 Nov 2023 22:30:44 +0530 Subject: [PATCH 12/13] cargo fmt --all --- standalone/runtime/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 82616bf14..887db12f4 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -1113,12 +1113,10 @@ impl Contains for BaseFilter { } // no withdraw_unbonded call - let is_stake_withdraw_call = matches!( - call, - RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. }) - ); + let is_stake_withdraw_call = + matches!(call, RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. })); - if is_stake_withdraw_call{ + if is_stake_withdraw_call { return false } From 3651aa3116a27a0d20db85264ab03c2d8a34341d Mon Sep 17 00:00:00 2001 From: salman01zp Date: Wed, 15 Nov 2023 14:33:30 +0530 Subject: [PATCH 13/13] convert DispatchErrorWithPostInfo to DispatchError --- pallets/roles/src/lib.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index e32ff9cba..ad643a145 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -267,8 +267,7 @@ pub mod pallet { ensure!(!AccountRolesMapping::::contains_key(&account), Error::::HasRoleAssigned); // chill - let _ = pallet_staking::Pallet::::chill(origin); - Ok(()) + pallet_staking::Pallet::::chill(origin) } /// Unbound funds from the stash account. @@ -296,9 +295,11 @@ pub mod pallet { ensure!(!AccountRolesMapping::::contains_key(&account), Error::::HasRoleAssigned); // Unbound funds. - let _ = pallet_staking::Pallet::::unbond(origin, amount); - - Ok(()) + let res = pallet_staking::Pallet::::unbond(origin, amount); + match res { + Ok(_) => Ok(()), + Err(dispatch_post_info) => Err(dispatch_post_info.error), + } } /// Withdraw unbound funds after un-bonding period has passed. @@ -320,9 +321,11 @@ pub mod pallet { ); // Withdraw unbound funds. - let _ = pallet_staking::Pallet::::withdraw_unbonded(origin, 0); - - Ok(()) + let res = pallet_staking::Pallet::::withdraw_unbonded(origin, 0); + match res { + Ok(_) => Ok(()), + Err(dispatch_post_info) => Err(dispatch_post_info.error), + } } } }