Skip to content

Commit 39a5245

Browse files
Limit the authority to adjust nomination pool deposits (#10399)
Up until this point, when EDs of chains using nomination pools were reduced, the subsequent reward account funds in exces could be claimed by anyone, despite the fact that they had been typically provided at the beginning by the pool owner. We therefore limit access to these funds only to the pool owner and the (optional) root account when EDs get reduced. The restriction does not apply to the increase in EDs, as these imply that funds are transferred into the pool rather than out of it. Fixes paritytech-secops/srlabs_findings#413 --------- Signed-off-by: Andrei Trandafir <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent e733978 commit 39a5245

File tree

3 files changed

+125
-6
lines changed

3 files changed

+125
-6
lines changed

prdoc/pr_10399.prdoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
title: Limit the authority to adjust nomination pool deposits
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
Up until this point, when EDs of chains using nomination pools were reduced, the subsequent reward account funds in exces could be claimed by anyone, despite the fact that they had been typically provided at the beginning by the pool owner. We therefore limit access to these funds only to the pool owner and the (optional) root account when EDs get reduced. The restriction does not apply to the increase in EDs, as these imply that funds are transferred into the pool rather than out of it.
6+
crates:
7+
- name: pallet-nomination-pools
8+
bump: major

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3719,8 +3719,16 @@ impl<T: Config> Pallet<T> {
37193719
Self::freeze_pool_deposit(reward_acc)?;
37203720

37213721
if pre_frozen_balance > min_balance {
3722+
// Ensure the caller is the depositor or the root.
3723+
ensure!(
3724+
who == bonded_pool.roles.depositor ||
3725+
bonded_pool.roles.root.as_ref().map_or(false, |root| &who == root),
3726+
Error::<T>::DoesNotHavePermission
3727+
);
3728+
37223729
// Transfer excess back to depositor.
37233730
let excess = pre_frozen_balance.saturating_sub(min_balance);
3731+
37243732
T::Currency::transfer(reward_acc, &who, excess, Preservation::Preserve)?;
37253733
Self::deposit_event(Event::<T>::MinBalanceExcessAdjusted {
37263734
pool_id: pool,

substrate/frame/nomination-pools/src/tests.rs

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
use super::*;
1919
use crate::{mock::*, Event};
20-
use frame_support::{assert_err, assert_noop, assert_ok};
20+
use frame_support::{assert_err, assert_noop, assert_ok, hypothetically};
2121
use pallet_balances::Event as BEvent;
2222
use sp_runtime::{
2323
bounded_btree_map,
@@ -420,7 +420,7 @@ mod reward_pool {
420420
// clear events
421421
pool_events_since_last_call();
422422

423-
// Then: Anyone can permissionlessly can adjust ED deposit.
423+
// Then: Anyone can permissionlessly adjust ED deposit upwards.
424424

425425
// make sure caller has enough funds..
426426
assert_ok!(Currency::mint_into(&99, 100));
@@ -445,12 +445,17 @@ mod reward_pool {
445445
// When: ED is decreased and reward account has excess ED frozen
446446
ExistentialDeposit::set(5);
447447

448+
let bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
449+
let owner = bonded_pool.roles.depositor;
450+
451+
assert_eq!(owner, 10);
452+
448453
// And:: adjust ED deposit is called
449-
let pre_balance = Currency::free_balance(&100);
450-
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(100), 1));
454+
let pre_balance = Currency::free_balance(&owner);
455+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(owner), 1));
451456

452-
// Then: excess ED is claimed by the caller
453-
assert_eq!(Currency::free_balance(&100), pre_balance + 45);
457+
// Then: excess ED is claimed by the pool depositor
458+
assert_eq!(Currency::free_balance(&owner), pre_balance + 45);
454459

455460
assert_eq!(
456461
pool_events_since_last_call(),
@@ -459,6 +464,104 @@ mod reward_pool {
459464
});
460465
}
461466

467+
#[test]
468+
fn pool_owner_can_adjust_deposit_downards() {
469+
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
470+
// Given: a nomination pool with no reward deficit
471+
472+
// Set an initial ED and adjust to there as a baseline.
473+
let ed_baseline = 50;
474+
let ed_delta = 20;
475+
476+
ExistentialDeposit::set(ed_baseline);
477+
478+
// Pool some rewards and check the imbalance.
479+
deposit_rewards(50);
480+
assert_eq!(reward_imbalance(1), Surplus(0));
481+
482+
let bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
483+
let owner = bonded_pool.roles.depositor;
484+
let root = bonded_pool.roles.root.unwrap();
485+
486+
assert_eq!(owner, 10);
487+
assert_eq!(root, 900);
488+
489+
Currency::set_balance(&owner, 100);
490+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(owner), 1));
491+
492+
hypothetically!({
493+
// When the ED is adjusted downards (decreased)
494+
Currency::set_balance(&owner, 100);
495+
ExistentialDeposit::set(ed_baseline - ed_delta);
496+
497+
// Then a standard account cannot adjust the pool deposit downards
498+
Currency::set_balance(&70, 100);
499+
assert_err!(
500+
Pools::adjust_pool_deposit(RuntimeOrigin::signed(70), 1),
501+
Error::<T>::DoesNotHavePermission
502+
);
503+
504+
// And the pool owner can adjust deposit downards
505+
let pre_balance = Currency::free_balance(&owner);
506+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(owner), 1));
507+
assert_eq!(reward_imbalance(1), Surplus(0));
508+
509+
// And the pool owner's balance increases by the ED difference.
510+
assert_eq!(Currency::free_balance(&owner), pre_balance + ed_delta);
511+
});
512+
513+
// When the ED is adjusted downards (decreased)
514+
Currency::set_balance(&root, 100);
515+
ExistentialDeposit::set(ed_baseline - ed_delta * 2);
516+
517+
// Then a standard account cannot adjust the pool deposit downards
518+
Currency::set_balance(&7, 100);
519+
assert_err!(
520+
Pools::adjust_pool_deposit(RuntimeOrigin::signed(7), 1),
521+
Error::<T>::DoesNotHavePermission
522+
);
523+
524+
// And the root can also adjust the deposit downwards
525+
let pre_balance = Currency::free_balance(&root);
526+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(root), 1));
527+
assert_eq!(reward_imbalance(1), Surplus(0));
528+
529+
// And the root's balance increases by the ED difference.
530+
assert_eq!(Currency::free_balance(&root), pre_balance + ed_delta * 2);
531+
});
532+
}
533+
534+
#[test]
535+
fn anyone_can_adjust_deposit_upwards() {
536+
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
537+
// Given: a nomination pool with no reward deficit
538+
539+
// Set an initial ED and adjust to there as a baseline.
540+
let ed_baseline = 20;
541+
let ed_delta = 40;
542+
ExistentialDeposit::set(ed_baseline);
543+
544+
// Pool some rewards and check the imbalance.
545+
deposit_rewards(50);
546+
assert_eq!(reward_imbalance(1), Surplus(0));
547+
548+
Currency::set_balance(&10, 100);
549+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1));
550+
551+
// When the ED increases
552+
ExistentialDeposit::set(ed_baseline + ed_delta);
553+
554+
// Then anyone can adjust the pool deposit upwards if they have enough funds.
555+
Currency::set_balance(&70, 100);
556+
let pre_balance = Currency::free_balance(&70);
557+
assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(70), 1));
558+
559+
// And the caller's balance decreases by the ED difference.
560+
assert_eq!(Currency::free_balance(&70), pre_balance - ed_delta);
561+
assert_eq!(reward_imbalance(1), Surplus(0));
562+
});
563+
}
564+
462565
#[test]
463566
fn topping_up_does_not_work_for_pools_with_no_deficit() {
464567
ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {

0 commit comments

Comments
 (0)