From 50cf239147a6f569e563bcadec6c7a1c5ad5c67e Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Wed, 1 Mar 2023 12:30:39 +0400 Subject: [PATCH] staking pallet: add bonding restriction --- bin/node/runtime/src/lib.rs | 1 + frame/babe/src/mock.rs | 1 + frame/beefy/src/mock.rs | 1 + frame/fast-unstake/src/mock.rs | 1 + frame/grandpa/src/mock.rs | 1 + .../nomination-pools/benchmarking/src/mock.rs | 1 + .../nomination-pools/test-staking/src/mock.rs | 1 + frame/offences/benchmarking/src/mock.rs | 1 + frame/root-offences/src/mock.rs | 1 + frame/session/benchmarking/src/mock.rs | 1 + frame/staking/src/lib.rs | 16 ++++++++++ frame/staking/src/mock.rs | 11 +++++++ frame/staking/src/pallet/mod.rs | 11 ++++++- frame/staking/src/tests.rs | 29 ++++++++++++++++++- 14 files changed, 75 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a869d309fa479..1d402f1f0aa25 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -585,6 +585,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; + type BondingRestriction = (); } impl pallet_fast_unstake::Config for Runtime { diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index b130677897883..2fbb24e98a256 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -204,6 +204,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl pallet_offences::Config for Test { diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index 74dba2a01b81d..52cb89c7516a2 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -233,6 +233,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl pallet_offences::Config for Test { diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index b20ba43ab3758..255c4e1fa17fe 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -156,6 +156,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } pub struct BalanceToU256; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 0948be0080889..290fa3ac237b8 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -208,6 +208,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl pallet_offences::Config for Test { diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 4e188ea7ef189..d27c9e78f3d10 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -119,6 +119,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } parameter_types! { diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 3d0ab2c6f35f3..a1779f96a212c 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -133,6 +133,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } parameter_types! { diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 233aa449d391c..c2e21b401d200 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -181,6 +181,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl pallet_im_online::Config for Test { diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index 0937c43d6e519..bfefa28beb1c4 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -195,6 +195,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl pallet_session::historical::Config for Test { diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 4c4accbbfac8f..1814675f6aece 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -183,6 +183,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = (); } impl crate::Config for Test {} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index d6345c2161f73..c424f324a6949 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -958,3 +958,19 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { type MaxValidators = frame_support::traits::ConstU32<100>; type MaxNominators = frame_support::traits::ConstU32<100>; } + +/// Means for checking if there is any external restriction on bonding with a specific account +/// +/// Allows for parts of the runtime that might implement other forms of fund locking to prevent +/// incompatible locking on a stash account which could lead to unsafe state. +pub trait BondingRestriction { + /// Determine if bonding is allowed with stash and controller combination + fn can_bond(stash: &AccountId) -> bool; +} + +// No restrictions +impl BondingRestriction for () { + fn can_bond(_stash: &AccountId) -> bool { + true + } +} \ No newline at end of file diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index e876940cb92d3..e09600a871da3 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -39,6 +39,7 @@ use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; +pub const RESTRICTED_ACCOUNT: AccountId = 55500; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; @@ -304,6 +305,7 @@ impl crate::pallet::pallet::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); + type BondingRestriction = AccountRestricted555; } pub(crate) type StakingCall = crate::Call; @@ -811,3 +813,12 @@ pub(crate) fn staking_events_since_last_call() -> Vec> { pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } + +pub struct AccountRestricted555; + +// Restrict stash account +impl BondingRestriction for AccountRestricted555 { + fn can_bond(stash: &AccountId) -> bool { + *stash != RESTRICTED_ACCOUNT + } +} \ No newline at end of file diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index a030538878241..23da3ab0f9451 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -44,7 +44,7 @@ mod impls; pub use impls::*; use crate::{ - slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, + slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, BondingRestriction, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, @@ -271,6 +271,9 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Interface for doing bonding restriction check + type BondingRestriction: BondingRestriction; } /// The ideal number of active validators. @@ -774,6 +777,8 @@ pub mod pallet { CommissionTooLow, /// Some bound is not met. BoundNotMet, + /// External restriction prevents bonding with given account + BondingRestricted, } #[pallet::hooks] @@ -867,6 +872,10 @@ pub mod pallet { return Err(Error::::AlreadyPaired.into()) } + if !T::BondingRestriction::can_bond(&stash) { + return Err(Error::::BondingRestricted.into()) + } + // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { return Err(Error::::InsufficientBond.into()) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 16bafae01e3c3..011f704ab2b23 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -20,7 +20,7 @@ use super::{ConfigOp, Event, *}; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_vec, + assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{Currency, Get, ReservableCurrency}, @@ -5825,3 +5825,30 @@ mod staking_interface { }); } } + +#[test] +fn bond_restriction_works() { + ExtBuilder::default().build_and_execute(|| { + start_session(2); + assert_err!( + Staking::bond( + RuntimeOrigin::signed(RESTRICTED_ACCOUNT), + 1, + 1000, + RewardDestination::Controller + ), + Error::::BondingRestricted + ); + + // put some money in account that we'll use. + let _ = Balances::make_free_balance_be(&(RESTRICTED_ACCOUNT + 1), 2000); + + // Using unrestricted account should not fail + assert_ok!(Staking::bond( + RuntimeOrigin::signed(RESTRICTED_ACCOUNT + 1), + 1, + 1000, + RewardDestination::Controller + )); + }); +} \ No newline at end of file