Skip to content

Commit f820dc0

Browse files
authored
[NPoS] Fix for Reward Deficit in the pool (#1255)
closes #158. partially addresses #226. Instead of fragile calculation of current balance by looking at `free balance - ED`, Nomination Pool now freezes ED in the pool reward account to restrict an account from going below minimum balance. This also has a nice side effect that if ED changes, we know how much is the imbalance in ED frozen in the pool and the current required ED. A pool operator can diligently top up the pool with the deficit in ED or vice versa, withdraw the excess they transferred to the pool. ## Notable changes - New call `adjust_pool_deposit`: Allows to top up the deficit or withdraw the excess deposited funds to the pool. - Uses Fungible trait (instead of Currency trait). Since NP was not doing any locking/reserving previously, no migration is needed for this. - One time migration of freezing ED from each of the existing pools (not very PoV friendly but fine for relay chain).
1 parent 0691c91 commit f820dc0

File tree

13 files changed

+2355
-1833
lines changed

13 files changed

+2355
-1833
lines changed

polkadot/runtime/westend/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,9 @@ impl pallet_balances::Config for Runtime {
292292
type ReserveIdentifier = [u8; 8];
293293
type WeightInfo = weights::pallet_balances::WeightInfo<Runtime>;
294294
type RuntimeHoldReason = RuntimeHoldReason;
295-
type FreezeIdentifier = ();
295+
type FreezeIdentifier = RuntimeFreezeReason;
296+
type MaxFreezes = ConstU32<1>;
296297
type MaxHolds = ConstU32<1>;
297-
type MaxFreezes = ConstU32<0>;
298298
}
299299

300300
parameter_types! {
@@ -1311,6 +1311,7 @@ impl pallet_nomination_pools::Config for Runtime {
13111311
type RuntimeEvent = RuntimeEvent;
13121312
type WeightInfo = weights::pallet_nomination_pools::WeightInfo<Self>;
13131313
type Currency = Balances;
1314+
type RuntimeFreezeReason = RuntimeFreezeReason;
13141315
type RewardCounter = FixedU128;
13151316
type BalanceToU256 = BalanceToU256;
13161317
type U256ToBalance = U256ToBalance;
@@ -1398,7 +1399,7 @@ construct_runtime! {
13981399
VoterList: pallet_bags_list::<Instance1>::{Pallet, Call, Storage, Event<T>} = 25,
13991400

14001401
// Nomination pools for staking.
1401-
NominationPools: pallet_nomination_pools::{Pallet, Call, Storage, Event<T>, Config<T>} = 29,
1402+
NominationPools: pallet_nomination_pools::{Pallet, Call, Storage, Event<T>, Config<T>, FreezeReason} = 29,
14021403

14031404
// Fast unstake pallet: extension to staking.
14041405
FastUnstake: pallet_fast_unstake = 30,
@@ -1505,6 +1506,7 @@ pub mod migrations {
15051506
UpgradeSessionKeys,
15061507
parachains_configuration::migration::v9::MigrateToV9<Runtime>,
15071508
paras_registrar::migration::VersionCheckedMigrateToV1<Runtime, ()>,
1509+
pallet_nomination_pools::migration::versioned_migrations::V5toV6<Runtime>,
15081510
pallet_referenda::migration::v1::MigrateV0ToV1<Runtime, ()>,
15091511
);
15101512
}

polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs

Lines changed: 431 additions & 411 deletions
Large diffs are not rendered by default.

prdoc/pr_1255.prdoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Schema: Parity PR Documentation Schema (prdoc)
2+
# See doc at https://github.com/paritytech/prdoc
3+
4+
title: Fix for Reward Deficit in the pool
5+
6+
doc:
7+
- audience: Core Dev
8+
description: Instead of fragile calculation of current balance by looking at free balance - ED, Nomination Pool now freezes ED in the pool reward account to restrict an account from going below minimum balance. This also has a nice side effect that if ED changes, we know how much is the imbalance in ED frozen in the pool and the current required ED. A pool operator can diligently top up the pool with the deficit in ED or vice versa, withdraw the excess they transferred to the pool.
9+
notes:
10+
- Introduces new call `adjust_pool_deposit` that allows to top up the deficit or withdraw the excess deposit for the pool.
11+
- Switch to using Fungible trait from Currency trait.
12+
13+
migrations:
14+
db: []
15+
16+
runtime:
17+
- { pallet: "pallet-nomination-pools", description: "One time migration of freezing ED from each of the existing pools."}
18+
19+
crates:
20+
- name: pallet-nomination-pools
21+
22+
host_functions: []

substrate/bin/node/runtime/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,8 @@ impl pallet_balances::Config for Runtime {
521521
type ExistentialDeposit = ExistentialDeposit;
522522
type AccountStore = frame_system::Pallet<Runtime>;
523523
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
524-
type FreezeIdentifier = ();
525-
type MaxFreezes = ();
524+
type FreezeIdentifier = RuntimeFreezeReason;
525+
type MaxFreezes = ConstU32<1>;
526526
type RuntimeHoldReason = RuntimeHoldReason;
527527
type MaxHolds = ConstU32<2>;
528528
}
@@ -882,6 +882,7 @@ impl pallet_nomination_pools::Config for Runtime {
882882
type WeightInfo = ();
883883
type RuntimeEvent = RuntimeEvent;
884884
type Currency = Balances;
885+
type RuntimeFreezeReason = RuntimeFreezeReason;
885886
type RewardCounter = FixedU128;
886887
type BalanceToU256 = BalanceToU256;
887888
type U256ToBalance = U256ToBalance;

substrate/frame/nomination-pools/benchmarking/src/lib.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ use frame_benchmarking::v1::{account, whitelist_account};
2727
use frame_election_provider_support::SortedListProvider;
2828
use frame_support::{
2929
assert_ok, ensure,
30-
traits::{Currency, Get},
30+
traits::{
31+
fungible::{Inspect, Mutate, Unbalanced},
32+
Get,
33+
},
3134
};
3235
use frame_system::RawOrigin as RuntimeOrigin;
3336
use pallet_nomination_pools::{
@@ -67,7 +70,7 @@ fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
6770
balance: BalanceOf<T>,
6871
) -> T::AccountId {
6972
let user = account(string, n, USER_SEED);
70-
T::Currency::make_free_balance_be(&user, balance);
73+
T::Currency::set_balance(&user, balance);
7174
user
7275
}
7376

@@ -148,8 +151,7 @@ impl<T: Config> ListScenario<T> {
148151
);
149152

150153
// Burn the entire issuance.
151-
let i = CurrencyOf::<T>::burn(CurrencyOf::<T>::total_issuance());
152-
sp_std::mem::forget(i);
154+
CurrencyOf::<T>::set_total_issuance(Zero::zero());
153155

154156
// Create accounts with the origin weight
155157
let (pool_creator1, pool_origin1) =
@@ -206,7 +208,7 @@ impl<T: Config> ListScenario<T> {
206208

207209
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
208210
self.origin1_member = Some(joiner.clone());
209-
CurrencyOf::<T>::make_free_balance_be(&joiner, amount * 2u32.into());
211+
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
210212

211213
let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
212214

@@ -254,7 +256,7 @@ frame_benchmarking::benchmarks! {
254256
whitelist_account!(joiner);
255257
}: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1)
256258
verify {
257-
assert_eq!(CurrencyOf::<T>::free_balance(&joiner), joiner_free - max_additional);
259+
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
258260
assert_eq!(
259261
T::Staking::active_stake(&scenario.origin1).unwrap(),
260262
scenario.dest_weight
@@ -289,7 +291,7 @@ frame_benchmarking::benchmarks! {
289291
// transfer exactly `extra` to the depositor of the src pool (1),
290292
let reward_account1 = Pools::<T>::create_reward_account(1);
291293
assert!(extra >= CurrencyOf::<T>::minimum_balance());
292-
CurrencyOf::<T>::deposit_creating(&reward_account1, extra);
294+
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
293295

294296
}: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards)
295297
verify {
@@ -309,27 +311,27 @@ frame_benchmarking::benchmarks! {
309311
let reward_account = Pools::<T>::create_reward_account(1);
310312

311313
// Send funds to the reward account of the pool
312-
CurrencyOf::<T>::make_free_balance_be(&reward_account, ed + origin_weight);
314+
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
313315

314316
// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
315317
// behalf.
316318
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll);
317319

318320
// Sanity check
319321
assert_eq!(
320-
CurrencyOf::<T>::free_balance(&depositor),
322+
CurrencyOf::<T>::balance(&depositor),
321323
origin_weight
322324
);
323325

324326
whitelist_account!(depositor);
325327
}:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone())
326328
verify {
327329
assert_eq!(
328-
CurrencyOf::<T>::free_balance(&depositor),
330+
CurrencyOf::<T>::balance(&depositor),
329331
origin_weight + commission * origin_weight
330332
);
331333
assert_eq!(
332-
CurrencyOf::<T>::free_balance(&reward_account),
334+
CurrencyOf::<T>::balance(&reward_account),
333335
ed + commission * origin_weight
334336
);
335337
}
@@ -383,7 +385,7 @@ frame_benchmarking::benchmarks! {
383385
T::Staking::active_stake(&pool_account).unwrap(),
384386
min_create_bond + min_join_bond
385387
);
386-
assert_eq!(CurrencyOf::<T>::free_balance(&joiner), min_join_bond);
388+
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
387389

388390
// Unbond the new member
389391
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
@@ -403,7 +405,7 @@ frame_benchmarking::benchmarks! {
403405
}: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s)
404406
verify {
405407
// The joiners funds didn't change
406-
assert_eq!(CurrencyOf::<T>::free_balance(&joiner), min_join_bond);
408+
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
407409
// The unlocking chunk was removed
408410
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
409411
}
@@ -426,7 +428,7 @@ frame_benchmarking::benchmarks! {
426428
T::Staking::active_stake(&pool_account).unwrap(),
427429
min_create_bond + min_join_bond
428430
);
429-
assert_eq!(CurrencyOf::<T>::free_balance(&joiner), min_join_bond);
431+
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
430432

431433
// Unbond the new member
432434
pallet_staking::CurrentEra::<T>::put(0);
@@ -447,8 +449,7 @@ frame_benchmarking::benchmarks! {
447449
}: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s)
448450
verify {
449451
assert_eq!(
450-
CurrencyOf::<T>::free_balance(&joiner),
451-
min_join_bond * 2u32.into()
452+
CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into()
452453
);
453454
// The unlocking chunk was removed
454455
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
@@ -485,7 +486,7 @@ frame_benchmarking::benchmarks! {
485486
Zero::zero()
486487
);
487488
assert_eq!(
488-
CurrencyOf::<T>::free_balance(&pool_account),
489+
CurrencyOf::<T>::balance(&pool_account),
489490
min_create_bond
490491
);
491492
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
@@ -515,7 +516,7 @@ frame_benchmarking::benchmarks! {
515516

516517
// Funds where transferred back correctly
517518
assert_eq!(
518-
CurrencyOf::<T>::free_balance(&depositor),
519+
CurrencyOf::<T>::balance(&depositor),
519520
// gets bond back + rewards collecting when unbonding
520521
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
521522
);
@@ -527,7 +528,7 @@ frame_benchmarking::benchmarks! {
527528
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
528529

529530
// Give the depositor some balance to bond
530-
CurrencyOf::<T>::make_free_balance_be(&depositor, min_create_bond * 2u32.into());
531+
CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
531532

532533
// Make sure no Pools exist at a pre-condition for our verify checks
533534
assert_eq!(RewardPools::<T>::count(), 0);
@@ -782,7 +783,7 @@ frame_benchmarking::benchmarks! {
782783
let ed = CurrencyOf::<T>::minimum_balance();
783784
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
784785
let reward_account = Pools::<T>::create_reward_account(1);
785-
CurrencyOf::<T>::make_free_balance_be(&reward_account, ed + origin_weight);
786+
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
786787

787788
// member claims a payout to make some commission available.
788789
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer).into());
@@ -791,15 +792,29 @@ frame_benchmarking::benchmarks! {
791792
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into())
792793
verify {
793794
assert_eq!(
794-
CurrencyOf::<T>::free_balance(&depositor),
795+
CurrencyOf::<T>::balance(&depositor),
795796
origin_weight + commission * origin_weight
796797
);
797798
assert_eq!(
798-
CurrencyOf::<T>::free_balance(&reward_account),
799+
CurrencyOf::<T>::balance(&reward_account),
799800
ed + commission * origin_weight
800801
);
801802
}
802803

804+
adjust_pool_deposit {
805+
// Create a pool
806+
let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
807+
808+
// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
809+
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
810+
assert!(&Pools::<T>::check_ed_imbalance().is_err());
811+
812+
whitelist_account!(depositor);
813+
}:_(RuntimeOrigin::Signed(depositor), 1)
814+
verify {
815+
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
816+
}
817+
803818
impl_benchmark_test_suite!(
804819
Pallet,
805820
crate::mock::new_test_ext(),

substrate/frame/nomination-pools/benchmarking/src/mock.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ impl pallet_balances::Config for Runtime {
7474
type ExistentialDeposit = ExistentialDeposit;
7575
type AccountStore = System;
7676
type WeightInfo = ();
77-
type FreezeIdentifier = ();
78-
type MaxFreezes = ();
77+
type FreezeIdentifier = RuntimeFreezeReason;
78+
type MaxFreezes = ConstU32<1>;
7979
type RuntimeHoldReason = ();
8080
type MaxHolds = ();
8181
}
@@ -160,6 +160,7 @@ impl pallet_nomination_pools::Config for Runtime {
160160
type RuntimeEvent = RuntimeEvent;
161161
type WeightInfo = ();
162162
type Currency = Balances;
163+
type RuntimeFreezeReason = RuntimeFreezeReason;
163164
type RewardCounter = FixedU128;
164165
type BalanceToU256 = BalanceToU256;
165166
type U256ToBalance = U256ToBalance;
@@ -183,7 +184,7 @@ frame_support::construct_runtime!(
183184
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
184185
Staking: pallet_staking::{Pallet, Call, Config<T>, Storage, Event<T>},
185186
VoterList: pallet_bags_list::<Instance1>::{Pallet, Call, Storage, Event<T>},
186-
Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event<T>},
187+
Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event<T>, FreezeReason},
187188
}
188189
);
189190

0 commit comments

Comments
 (0)