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/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 dc90a9c07..ad643a145 100644
--- a/pallets/roles/src/lib.rs
+++ b/pallets/roles/src/lib.rs
@@ -21,15 +21,19 @@
use codec::MaxEncodedLen;
use frame_support::{
ensure,
- traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced},
+ traits::{Currency, Get},
CloneNoBound, EqNoBound, PalletId, PartialEqNoBound, RuntimeDebugNoBound,
};
+
pub use pallet::*;
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;
@@ -53,32 +57,27 @@ 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()
}
}
-pub type CurrencyOf = ::Currency;
+pub type CurrencyOf = ::Currency;
pub type BalanceOf =
- <::Currency as Currency<::AccountId>>::Balance;
-type NegativeImbalanceOf = <::Currency as Currency<
- ::AccountId,
->>::NegativeImbalance;
-
-const ROLES_STAKING_ID: LockIdentifier = *b"rstaking";
+ as Currency<::AccountId>>::Balance;
#[frame_support::pallet]
pub mod pallet {
@@ -92,30 +91,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]
@@ -129,26 +108,26 @@ 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 },
}
#[pallet::error]
pub enum Error {
- /// Role has already been assigned to provided validator.
- RoleAlreadyAssigned,
+ /// Not a validator.
+ NotValidator,
+ /// Validator has active role assigned
+ HasRoleAssigned,
/// No role assigned to provided validator.
- RoleNotAssigned,
- /// Insufficient bond to become a validator.
- InsufficientBond,
- /// Stash controller account already added to Ledger
- AlreadyPaired,
- /// Stash controller account not found in ledger
- InvalidStashController,
+ NoRoleAssigned,
+ /// Invalid Re-staking amount, should not exceed total staked amount.
+ InvalidReStakingBond,
+ /// Re staking amount should be greater than minimum re-staking bond requirement.
+ 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.
@@ -162,58 +141,66 @@ 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.
///
/// # 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})]
#[pallet::call_index(0)]
pub fn assign_role(
origin: OriginFor,
- #[pallet::compact] bond_value: BalanceOf,
role: RoleType,
+ re_stake: ReStakingOption,
) -> 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),
- Error::::RoleAlreadyAssigned
+ Error::::HasRoleAssigned
);
- // 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);
+ 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(),
+ };
- // 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 greater than min re-staking bond requirement.
+ let min_re_staking_bond = MinReStakingBond::::get();
+ ensure!(re_stake_amount >= min_re_staking_bond, Error::::InsufficientReStakingBond);
+
+ // Validate re-staking bond, should not exceed active staked bond.
+ ensure!(staking_ledger.active >= re_stake_amount, Error::::InvalidReStakingBond);
// Update ledger.
- let item = RoleStakingLedger { stash: stash_account.clone(), total_locked: value };
+ let item = RoleStakingLedger { stash: stash_account.clone(), total: re_stake_amount };
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 });
@@ -228,33 +215,117 @@ 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 {
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()),
- Error::::RoleNotAssigned
+ Error::::NoRoleAssigned
);
// TODO: Call jobs manager to remove the services.
// 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(())
}
+
+ /// 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
+ pallet_staking::Pallet::::chill(origin)
+ }
+
+ /// 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
+ /// of all the services. Once 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
+ /// - If there is any active role assigned to the user.
+ ///
+ #[pallet::weight({0})]
+ #[pallet::call_index(3)]
+ pub fn unbound_funds(
+ origin: OriginFor,
+ #[pallet::compact] amount: BalanceOf,
+ ) -> DispatchResult {
+ 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 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.
+ ///
+ /// # 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 res = pallet_staking::Pallet::::withdraw_unbonded(origin, 0);
+ match res {
+ Ok(_) => Ok(()),
+ Err(dispatch_post_info) => Err(dispatch_post_info.error),
+ }
+ }
}
}
diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs
index f4cc2c38c..ae4141095 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, Contains, 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 {
@@ -46,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 = ();
@@ -69,20 +74,144 @@ 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;
+}
+
+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
+ }
+
+ // 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
+ }
+}
+
+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 +219,8 @@ construct_runtime!(
System: frame_system,
Balances: pallet_balances,
Roles: pallet_roles,
+ Session: pallet_session,
+ Staking: pallet_staking,
}
);
@@ -116,14 +247,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..f66576194 100644
--- a/pallets/roles/src/tests.rs
+++ b/pallets/roles/src/tests.rs
@@ -15,78 +15,165 @@
// 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),
+ RoleType::Tss,
+ ReStakingOption::Custom(5000)
+ ));
- assert_events(vec![
- RuntimeEvent::Roles(crate::Event::Bonded { account: 10, amount: 5000 }),
- RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 10, 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(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 }));
- // 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!(Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 5000 }));
+ });
+}
+
+// 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().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));
- // 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_ok!(Roles::assign_role(
+ RuntimeOrigin::signed(1),
+ RoleType::Tss,
+ ReStakingOption::Custom(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 }),
- ]);
+ 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(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);
+#[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),
+ RoleType::Tss,
+ ReStakingOption::Custom(5000)
+ ),
+ Error::::NotValidator
+ );
});
}
-// test slashing
#[test]
-fn test_slash_validator() {
- new_test_ext().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));
- // 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);
+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,
+ ReStakingOption::Custom(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,
+ ReStakingOption::Custom(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.
- // Now lets slash the account for being Inactive.
- assert_ok!(Roles::slash_validator(10, ValidatorOffence::Inactivity));
+ // 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::Roles(crate::Event::Slashed {
- account: 10,
- amount: 1000,
+ assert_events(vec![RuntimeEvent::Staking(pallet_staking::Event::Unbonded {
+ stash: 1,
+ amount: 5000,
})]);
- // should be updated in ledger
- assert_eq!(Roles::ledger(10), Some(RoleStakingLedger { stash: 10, total_locked: 4000 }));
});
}
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index 3d4c9842a..e69212299 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -46,3 +46,12 @@ 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),
+}
diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs
index 85911cbce..887db12f4 100644
--- a/standalone/runtime/src/lib.rs
+++ b/standalone/runtime/src/lib.rs
@@ -1097,6 +1097,28 @@ 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 { .. }));
+
+ if is_stake_unbound_call {
+ // no unbond call
+ 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,