diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 76af74d3b..e2982809f 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -304,6 +304,8 @@ pub mod pallet { NotNextSigner, ReshareNotInProgress, AlreadyConfirmed, + NoUnbodingWhenSigner, + NoUnbodingWhenNextSigner, } #[pallet::event] @@ -397,9 +399,56 @@ pub mod pallet { Ok(()) } - /// Wraps's substrate withdraw unbonded but clears extra state if fully unbonded #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::withdraw_unbonded())] + pub fn unbonded( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResultWithPostInfo { + let controller = ensure_signed(origin.clone())?; + let ledger = + pallet_staking::Pallet::::ledger(StakingAccount::Controller(controller.clone())) + .map_err(|_| Error::::NoThresholdKey)?; + + let validator_id = ::ValidatorId::try_from(ledger.stash) + .or(Err(Error::::InvalidValidatorId))?; + + ensure!(!Self::signers().contains(&validator_id), Error::::NoUnbodingWhenSigner); + ensure!( + !Self::next_signers().unwrap().next_signers.contains(&validator_id), + Error::::NoUnbodingWhenNextSigner + ); + + pallet_staking::Pallet::::unbond(origin, value)?; + + Ok(().into()) + } + + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::withdraw_unbonded())] + pub fn chill(origin: OriginFor) -> DispatchResultWithPostInfo { + let controller = ensure_signed(origin.clone())?; + let ledger = + pallet_staking::Pallet::::ledger(StakingAccount::Controller(controller.clone())) + .map_err(|_| Error::::NoThresholdKey)?; + + let validator_id = ::ValidatorId::try_from(ledger.stash) + .or(Err(Error::::InvalidValidatorId))?; + + ensure!(!Self::signers().contains(&validator_id), Error::::NoUnbodingWhenSigner); + ensure!( + !Self::next_signers().unwrap().next_signers.contains(&validator_id), + Error::::NoUnbodingWhenNextSigner + ); + + pallet_staking::Pallet::::chill(origin)?; + + Ok(().into()) + } + + /// Wraps's substrate withdraw unbonded but clears extra state if fully unbonded + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::withdraw_unbonded())] pub fn withdraw_unbonded( origin: OriginFor, num_slashing_spans: u32, @@ -412,6 +461,12 @@ pub mod pallet { let validator_id = ::ValidatorId::try_from(ledger.stash) .or(Err(Error::::InvalidValidatorId))?; + ensure!(!Self::signers().contains(&validator_id), Error::::NoUnbodingWhenSigner); + ensure!( + !Self::next_signers().unwrap().next_signers.contains(&validator_id), + Error::::NoUnbodingWhenNextSigner + ); + pallet_staking::Pallet::::withdraw_unbonded(origin, num_slashing_spans)?; // TODO: do not allow unbonding of validator if not enough validators https://github.com/entropyxyz/entropy-core/issues/942 if pallet_staking::Pallet::::bonded(&controller).is_none() { @@ -429,7 +484,7 @@ pub mod pallet { /// /// Note that - just like the original `validate()` extrinsic - the effects of this are /// only applied in the following era. - #[pallet::call_index(3)] + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::validate())] pub fn validate( origin: OriginFor, @@ -468,7 +523,7 @@ pub mod pallet { /// Let a validator declare if their kvdb is synced or not synced /// `synced`: State of validator's kvdb - #[pallet::call_index(4)] + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::declare_synced())] pub fn declare_synced(origin: OriginFor, synced: bool) -> DispatchResult { let who = ensure_signed(origin.clone())?; @@ -478,7 +533,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(5)] + #[pallet::call_index(7)] #[pallet::weight(({ ::WeightInfo::confirm_key_reshare_confirmed(MAX_SIGNERS as u32) .max(::WeightInfo::confirm_key_reshare_completed()) diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 372866da4..9eeb4cdab 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -396,7 +396,7 @@ impl pallet_parameters::Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap(); let pallet_balances = pallet_balances::GenesisConfig:: { - balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], + balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (7, 100), (8, 100)], }; let pallet_staking_extension = pallet_staking_extension::GenesisConfig:: { // (ValidatorID, (AccountId, X25519PublicKey, TssServerURL)) diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index a6639b9f5..f3956b1ff 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -243,6 +243,10 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { fn it_deletes_when_no_bond_left() { new_test_ext().execute_with(|| { Signers::::put(vec![5, 6]); + NextSigners::::put(NextSignerInfo { + next_signers: vec![7, 8], + confirmations: vec![], + }); start_active_era(1); assert_ok!(FrameStaking::bond( RuntimeOrigin::signed(2), @@ -311,6 +315,28 @@ fn it_deletes_when_no_bond_left() { assert_eq!(Staking::threshold_to_stash(3), None); // validator no longer synced assert_eq!(Staking::is_validator_synced(2), false); + + assert_ok!(FrameStaking::bond( + RuntimeOrigin::signed(7), + 100u64, + pallet_staking::RewardDestination::Account(1), + )); + + assert_noop!( + Staking::withdraw_unbonded(RuntimeOrigin::signed(7), 0), + Error::::NoUnbodingWhenNextSigner + ); + + assert_ok!(FrameStaking::bond( + RuntimeOrigin::signed(8), + 100u64, + pallet_staking::RewardDestination::Account(1), + )); + + assert_noop!( + Staking::withdraw_unbonded(RuntimeOrigin::signed(8), 0), + Error::::NoUnbodingWhenNextSigner + ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d5a469662..5985de516 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -318,6 +318,8 @@ impl Contains for BaseCallFilter { call, RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. }) | RuntimeCall::Staking(pallet_staking::Call::validate { .. }) + | RuntimeCall::Staking(pallet_staking::Call::unbond { .. }) + | RuntimeCall::Staking(pallet_staking::Call::chill { .. }) ); if is_paused || system_reject { // no paused call