Skip to content

Commit 27b9822

Browse files
Prevent dust in staking by disallowing cheap bond_extra (#7718)
* prevent bond_extra to cause staking actve lower than ed * prevent bond_extra to cause staking actve lower than ed * Check in post conditions. * check rebond as well. * also change withdraw_unbonded. * Fix build * change check format. * Apply suggestions from code review Co-authored-by: Shawn Tabrizi <[email protected]> Co-authored-by: Shawn Tabrizi <[email protected]>
1 parent b618267 commit 27b9822

File tree

3 files changed

+100
-3
lines changed

3 files changed

+100
-3
lines changed

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,11 +1474,13 @@ decl_module! {
14741474
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
14751475

14761476
let stash_balance = T::Currency::free_balance(&stash);
1477-
14781477
if let Some(extra) = stash_balance.checked_sub(&ledger.total) {
14791478
let extra = extra.min(max_additional);
14801479
ledger.total += extra;
14811480
ledger.active += extra;
1481+
// last check: the new active amount of ledger must be more than ED.
1482+
ensure!(ledger.active >= T::Currency::minimum_balance(), Error::<T>::InsufficientValue);
1483+
14821484
Self::deposit_event(RawEvent::Bonded(stash, extra));
14831485
Self::update_ledger(&controller, &ledger);
14841486
}
@@ -1586,7 +1588,7 @@ decl_module! {
15861588
ledger = ledger.consolidate_unlocked(current_era)
15871589
}
15881590

1589-
let post_info_weight = if ledger.unlocking.is_empty() && ledger.active.is_zero() {
1591+
let post_info_weight = if ledger.unlocking.is_empty() && ledger.active <= T::Currency::minimum_balance() {
15901592
// This account must have called `unbond()` with some value that caused the active
15911593
// portion to fall below existential deposit + will have no more unlocking chunks
15921594
// left. We can now safely remove all staking-related information.
@@ -1973,6 +1975,9 @@ decl_module! {
19731975
ensure!(!ledger.unlocking.is_empty(), Error::<T>::NoUnlockChunk);
19741976

19751977
let ledger = ledger.rebond(value);
1978+
// last check: the new active amount of ledger must be more than ED.
1979+
ensure!(ledger.active >= T::Currency::minimum_balance(), Error::<T>::InsufficientValue);
1980+
19761981
Self::update_ledger(&controller, &ledger);
19771982
Ok(Some(
19781983
35 * WEIGHT_PER_MICROS

src/mock.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,18 @@ fn assert_is_stash(acc: AccountId) {
579579
fn assert_ledger_consistent(ctrl: AccountId) {
580580
// ensures ledger.total == ledger.active + sum(ledger.unlocking).
581581
let ledger = Staking::ledger(ctrl).expect("Not a controller.");
582-
let real_total: Balance = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
582+
let real_total: Balance = ledger
583+
.unlocking
584+
.iter()
585+
.fold(ledger.active, |a, c| a + c.value);
583586
assert_eq!(real_total, ledger.total);
587+
assert!(
588+
ledger.active >= Balances::minimum_balance() || ledger.active == 0,
589+
"{}: active ledger amount ({}) must be greater than ED {}",
590+
ctrl,
591+
ledger.active,
592+
Balances::minimum_balance()
593+
);
584594
}
585595

586596
pub(crate) fn bond_validator(stash: AccountId, ctrl: AccountId, val: Balance) {

src/tests.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4696,3 +4696,85 @@ fn payout_to_any_account_works() {
46964696
assert!(Balances::free_balance(42) > 0);
46974697
})
46984698
}
4699+
4700+
#[test]
4701+
fn cannot_bond_extra_to_lower_than_ed() {
4702+
ExtBuilder::default()
4703+
.existential_deposit(10)
4704+
.build_and_execute(|| {
4705+
// stash must have more balance than bonded for this to work.
4706+
assert_eq!(Balances::free_balance(&21), 512_000);
4707+
4708+
// initial stuff.
4709+
assert_eq!(
4710+
Staking::ledger(&20).unwrap(),
4711+
StakingLedger {
4712+
stash: 21,
4713+
total: 1000,
4714+
active: 1000,
4715+
unlocking: vec![],
4716+
claimed_rewards: vec![]
4717+
}
4718+
);
4719+
4720+
// unbond all of it.
4721+
assert_ok!(Staking::unbond(Origin::signed(20), 1000));
4722+
assert_eq!(
4723+
Staking::ledger(&20).unwrap(),
4724+
StakingLedger {
4725+
stash: 21,
4726+
total: 1000,
4727+
active: 0,
4728+
unlocking: vec![UnlockChunk { value: 1000, era: 3 }],
4729+
claimed_rewards: vec![]
4730+
}
4731+
);
4732+
4733+
// now bond a wee bit more
4734+
assert_noop!(
4735+
Staking::bond_extra(Origin::signed(21), 5),
4736+
Error::<Test>::InsufficientValue,
4737+
);
4738+
})
4739+
}
4740+
4741+
#[test]
4742+
fn cannot_rebond_to_lower_than_ed() {
4743+
ExtBuilder::default()
4744+
.existential_deposit(10)
4745+
.build_and_execute(|| {
4746+
// stash must have more balance than bonded for this to work.
4747+
assert_eq!(Balances::free_balance(&21), 512_000);
4748+
4749+
// initial stuff.
4750+
assert_eq!(
4751+
Staking::ledger(&20).unwrap(),
4752+
StakingLedger {
4753+
stash: 21,
4754+
total: 1000,
4755+
active: 1000,
4756+
unlocking: vec![],
4757+
claimed_rewards: vec![]
4758+
}
4759+
);
4760+
4761+
// unbond all of it.
4762+
assert_ok!(Staking::unbond(Origin::signed(20), 1000));
4763+
assert_eq!(
4764+
Staking::ledger(&20).unwrap(),
4765+
StakingLedger {
4766+
stash: 21,
4767+
total: 1000,
4768+
active: 0,
4769+
unlocking: vec![UnlockChunk { value: 1000, era: 3 }],
4770+
claimed_rewards: vec![]
4771+
}
4772+
);
4773+
4774+
// now bond a wee bit more
4775+
assert_noop!(
4776+
Staking::rebond(Origin::signed(20), 5),
4777+
Error::<Test>::InsufficientValue,
4778+
);
4779+
})
4780+
}

0 commit comments

Comments
 (0)