mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 11:07:56 +00:00
staking: Proportional ledger slashing (#10982)
* staking: Proportional ledger slashing * Some comment cleanup * Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fix benchmarks * FMT * Try fill in all staking configs * round of feedback and imp from kian * demonstrate per_thing usage * Update some tests * FMT * Test that era offset works correctly * Update mocks * Remove unnescary docs * Remove unlock_era * Update frame/staking/src/lib.rs * Adjust tests to account for only remove when < ED * Remove stale TODOs * Remove dupe test Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: kianenigma <kian@parity.io>
This commit is contained in:
@@ -2104,7 +2104,7 @@ fn reward_validator_slashing_validator_does_not_overflow() {
|
||||
&[Perbill::from_percent(100)],
|
||||
);
|
||||
|
||||
assert_eq!(Balances::total_balance(&11), stake - 1);
|
||||
assert_eq!(Balances::total_balance(&11), stake);
|
||||
assert_eq!(Balances::total_balance(&2), 1);
|
||||
})
|
||||
}
|
||||
@@ -4854,3 +4854,216 @@ fn force_apply_min_commission_works() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ledger_slash_works() {
|
||||
let c = |era, value| UnlockChunk::<Balance> { era, value };
|
||||
// Given
|
||||
let mut ledger = StakingLedger::<Test> {
|
||||
stash: 123,
|
||||
total: 10,
|
||||
active: 10,
|
||||
unlocking: bounded_vec![],
|
||||
claimed_rewards: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(BondingDuration::get(), 3);
|
||||
|
||||
// When we slash a ledger with no unlocking chunks
|
||||
assert_eq!(ledger.slash(5, 1, 0), 5);
|
||||
// Then
|
||||
assert_eq!(ledger.total, 5);
|
||||
assert_eq!(ledger.active, 5);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 5);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
|
||||
|
||||
// When we slash a ledger with no unlocking chunks and the slash amount is greater then the
|
||||
// total
|
||||
assert_eq!(ledger.slash(11, 1, 0), 5);
|
||||
// Then
|
||||
assert_eq!(ledger.total, 0);
|
||||
assert_eq!(ledger.active, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
|
||||
|
||||
// Given
|
||||
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)];
|
||||
ledger.total = 2 * 10;
|
||||
ledger.active = 0;
|
||||
// When all the chunks overlap with the slash eras
|
||||
assert_eq!(ledger.slash(20, 0, 0), 20);
|
||||
// Then
|
||||
assert_eq!(ledger.unlocking, vec![]);
|
||||
assert_eq!(ledger.total, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)]));
|
||||
|
||||
// Given
|
||||
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
|
||||
ledger.total = 4 * 100;
|
||||
ledger.active = 0;
|
||||
// When the first 2 chunks don't overlap with the affected range of unlock eras.
|
||||
assert_eq!(ledger.slash(140, 0, 2), 140);
|
||||
// Then
|
||||
assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]);
|
||||
assert_eq!(ledger.total, 4 * 100 - 140);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)]));
|
||||
|
||||
// Given
|
||||
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
|
||||
ledger.active = 500;
|
||||
// 900
|
||||
ledger.total = 40 + 10 + 100 + 250 + 500;
|
||||
// When we have a partial slash that touches all chunks
|
||||
assert_eq!(ledger.slash(900 / 2, 0, 0), 450);
|
||||
// Then
|
||||
assert_eq!(ledger.active, 500 / 2);
|
||||
assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]);
|
||||
assert_eq!(ledger.total, 900 / 2);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
|
||||
assert_eq!(
|
||||
LedgerSlashPerEra::get().1,
|
||||
BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)])
|
||||
);
|
||||
|
||||
// slash 1/4th with not chunk.
|
||||
ledger.unlocking = bounded_vec![];
|
||||
ledger.active = 500;
|
||||
ledger.total = 500;
|
||||
// When we have a partial slash that touches all chunks
|
||||
assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4);
|
||||
// Then
|
||||
assert_eq!(ledger.active, 3 * 500 / 4);
|
||||
assert_eq!(ledger.unlocking, vec![]);
|
||||
assert_eq!(ledger.total, ledger.active);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
|
||||
|
||||
// Given we have the same as above,
|
||||
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
|
||||
ledger.active = 500;
|
||||
ledger.total = 40 + 10 + 100 + 250 + 500; // 900
|
||||
assert_eq!(ledger.total, 900);
|
||||
// When we have a higher min balance
|
||||
assert_eq!(
|
||||
ledger.slash(
|
||||
900 / 2,
|
||||
25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to
|
||||
* get swept */
|
||||
0
|
||||
),
|
||||
475
|
||||
);
|
||||
let dust = (10 / 2) + (40 / 2);
|
||||
assert_eq!(ledger.active, 500 / 2);
|
||||
assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]);
|
||||
assert_eq!(ledger.total, 900 / 2 - dust);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
|
||||
assert_eq!(
|
||||
LedgerSlashPerEra::get().1,
|
||||
BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)])
|
||||
);
|
||||
|
||||
// Given
|
||||
// slash order --------------------NA--------2----------0----------1----
|
||||
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
|
||||
ledger.active = 500;
|
||||
ledger.total = 40 + 10 + 100 + 250 + 500; // 900
|
||||
assert_eq!(
|
||||
ledger.slash(
|
||||
500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2
|
||||
0,
|
||||
2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and
|
||||
* ledge.active. This will cause the affected to go to zero, and then we will
|
||||
* start slashing older chunks */
|
||||
),
|
||||
500 + 250 + 10 + 100 / 2
|
||||
);
|
||||
// Then
|
||||
assert_eq!(ledger.active, 0);
|
||||
assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]);
|
||||
assert_eq!(ledger.total, 90);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)]));
|
||||
|
||||
// Given
|
||||
// iteration order------------------NA---------2----------0----------1----
|
||||
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
|
||||
ledger.active = 100;
|
||||
ledger.total = 5 * 100;
|
||||
// When
|
||||
assert_eq!(
|
||||
ledger.slash(
|
||||
351, // active + era 6 + era 7 + era 5 / 2 + 1
|
||||
50, // min balance - everything slashed below 50 will get dusted
|
||||
2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and
|
||||
* ledge.active. This will cause the affected to go to zero, and then we will
|
||||
* start slashing older chunks */
|
||||
),
|
||||
400
|
||||
);
|
||||
// Then
|
||||
assert_eq!(ledger.active, 0);
|
||||
assert_eq!(ledger.unlocking, vec![c(4, 100)]);
|
||||
assert_eq!(ledger.total, 100);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)]));
|
||||
|
||||
// Tests for saturating arithmetic
|
||||
|
||||
// Given
|
||||
let slash = u64::MAX as Balance * 2;
|
||||
let value = slash
|
||||
- (9 * 4) // The value of the other parts of ledger that will get slashed
|
||||
+ 1;
|
||||
|
||||
ledger.active = 10;
|
||||
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)];
|
||||
ledger.total = value + 40;
|
||||
// When
|
||||
let slash_amount = ledger.slash(slash, 0, 0);
|
||||
assert_eq_error_rate!(slash_amount, slash, 5);
|
||||
// Then
|
||||
assert_eq!(ledger.active, 0); // slash of 9
|
||||
assert_eq!(ledger.unlocking, vec![]);
|
||||
assert_eq!(ledger.total, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)]));
|
||||
|
||||
// Given
|
||||
let slash = u64::MAX as Balance * 2;
|
||||
let value = u64::MAX as Balance * 2;
|
||||
let unit = 100;
|
||||
// slash * value that will saturate
|
||||
assert!(slash.checked_mul(value).is_none());
|
||||
// but slash * unit won't.
|
||||
assert!(slash.checked_mul(unit).is_some());
|
||||
ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)];
|
||||
//--------------------------------------note value^^^
|
||||
ledger.active = unit;
|
||||
ledger.total = unit * 4 + value;
|
||||
// When
|
||||
assert_eq!(ledger.slash(slash, 0, 0), slash - 43);
|
||||
// Then
|
||||
// The amount slashed out of `unit`
|
||||
let affected_balance = value + unit * 4;
|
||||
let ratio = Perquintill::from_rational(slash, affected_balance);
|
||||
// `unit` after the slash is applied
|
||||
let unit_slashed = {
|
||||
let unit_slash = ratio * unit;
|
||||
unit - unit_slash
|
||||
};
|
||||
let value_slashed = {
|
||||
let value_slash = ratio * value;
|
||||
value - value_slash
|
||||
};
|
||||
assert_eq!(ledger.active, unit_slashed);
|
||||
assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]);
|
||||
assert_eq!(ledger.total, value_slashed);
|
||||
assert_eq!(LedgerSlashPerEra::get().0, 0);
|
||||
assert_eq!(
|
||||
LedgerSlashPerEra::get().1,
|
||||
BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user