Skip to content

Commit

Permalink
block unbond call from pallet-staking and allow unbonding through rol…
Browse files Browse the repository at this point in the history
…es-pallet
  • Loading branch information
salman01zp committed Nov 14, 2023
1 parent 0bcafdc commit 4aeb4dc
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 73 deletions.
34 changes: 4 additions & 30 deletions pallets/roles/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.

use super::*;
use frame_support::{pallet_prelude::DispatchResult, traits::WithdrawReasons};
use sp_runtime::Saturating;
use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler};

Expand Down Expand Up @@ -73,7 +72,7 @@ impl<T: Config> Pallet<T> {
/// The total amount of the balance that can be slashed.
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
// 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.
Expand All @@ -85,9 +84,9 @@ impl<T: Config> Pallet<T> {
address: T::AccountId,
slash_amount: T::CurrencyBalance,
) -> sp_runtime::DispatchResult {
let mut ledger = Self::ledger(&address).ok_or(Error::<T>::InvalidStashController)?;
let mut ledger = Self::ledger(&address).ok_or(Error::<T>::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(())
Expand All @@ -102,36 +101,11 @@ impl<T: Config> Pallet<T> {
/// # Note
/// This function will set a lock on the stash account.
pub(crate) fn update_ledger(staker: &T::AccountId, ledger: &RoleStakingLedger<T>) {
T::Currency::set_lock(
ROLES_STAKING_ID,
&ledger.stash,
ledger.total_locked,
WithdrawReasons::all(),
);
<Ledger<T>>::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) {
<Ledger<T>>::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<T>) -> 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(())
}
}
104 changes: 67 additions & 37 deletions pallets/roles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
use codec::MaxEncodedLen;
use frame_support::{
ensure,
traits::{Currency, Get, LockIdentifier, LockableCurrency},
traits::{Currency, Get},
CloneNoBound, EqNoBound, PalletId, PartialEqNoBound, RuntimeDebugNoBound,
};

Expand Down Expand Up @@ -54,30 +54,28 @@ pub use weights::WeightInfo;
pub struct RoleStakingLedger<T: Config> {
/// 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<T>,
pub total: BalanceOf<T>,
}

impl<T: Config> RoleStakingLedger<T> {
/// 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<T> = <T as pallet_staking::Config>::Currency;
pub type BalanceOf<T> =
<CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;

const ROLES_STAKING_ID: LockIdentifier = *b"rstaking";

#[frame_support::pallet]
pub mod pallet {
use super::*;
Expand Down Expand Up @@ -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.
Expand All @@ -142,10 +142,10 @@ pub mod pallet {
/// Mapping of resource to bridge index
pub type AccountRolesMapping<T: Config> = 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<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
pub(super) type MinReStakingBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

/// Assigns a role to the validator.
///
Expand All @@ -164,8 +164,8 @@ pub mod pallet {
#[pallet::call_index(0)]
pub fn assign_role(
origin: OriginFor<T>,
#[pallet::compact] bond_value: BalanceOf<T>,
role: RoleType,
#[pallet::compact] re_stake: BalanceOf<T>,
) -> DispatchResult {
let stash_account = ensure_signed(origin)?;
// Ensure stash account is a validator.
Expand All @@ -180,26 +180,21 @@ pub mod pallet {
Error::<T>::RoleAlreadyAssigned
);

// Check if stash account is already paired.
ensure!(!<Ledger<T>>::contains_key(&stash_account), Error::<T>::AlreadyPaired);
// Check if stash account is already paired/ re-staked.
ensure!(!<Ledger<T>>::contains_key(&stash_account), Error::<T>::AccountAlreadyPaired);

// Check if min active bond is met.
let min_active_bond = MinActiveBond::<T>::get();
ensure!(bond_value > min_active_bond.into(), Error::<T>::InsufficientBond);
// Validate re-staking bond, should be greater than min re-staking bond requirement.
let min_re_staking_bond = MinReStakingBond::<T>::get();
ensure!(re_stake >= min_re_staking_bond, Error::<T>::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::<T>::get(&stash_account).unwrap();
ensure!(staking_ledger.active > re_stake, Error::<T>::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::<T>::Bonded {
account: stash_account.clone(),
amount: value,
});

// Add role mapping for the stash account.
AccountRolesMapping::<T>::insert(&stash_account, role);
Self::deposit_event(Event::<T>::RoleAssigned { account: stash_account.clone(), role });
Expand Down Expand Up @@ -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::<T>::InvalidStashController)?;
Self::unbond(&ledger)?;
Self::deposit_event(Event::<T>::Unbonded {
account: ledger.stash,
amount: ledger.total_locked,
});

// Remove role from the mapping.
AccountRolesMapping::<T>::remove(&stash_account);
// Remove stash account related info.
Self::kill_stash(&stash_account);

Self::deposit_event(Event::<T>::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<T>,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResult {
let stash_account = ensure_signed(origin.clone())?;
// Ensure stash account is a validator.
ensure!(
pallet_staking::Validators::<T>::contains_key(&stash_account),
Error::<T>::NotValidator
);

// Ensure no role is assigned to the validator and is eligible to unbound.
ensure!(
!AccountRolesMapping::<T>::contains_key(&stash_account),
Error::<T>::RoleAlreadyAssigned
);

// Unbound funds.
let _ = pallet_staking::Pallet::<T>::unbond(origin, amount);

Ok(())
}
}
}
12 changes: 6 additions & 6 deletions pallets/roles/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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 }));
});
}

Expand All @@ -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::<Runtime>::NotValidator
);
});
Expand Down
8 changes: 8 additions & 0 deletions standalone/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,14 @@ impl Contains<RuntimeCall> 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
Expand Down

0 comments on commit 4aeb4dc

Please sign in to comment.