Introduce safe types for handling imbalances (#2048)

* Be a little safer with total issuance.

* PairT instead of _Pair

* Remove rev causing upset

* Remove fees stuff.

* Fix build (including tests)

* Update runtime, bump version

* Fix

* Handle gas refunds properly.

* Rename identifier

ala #2025

* Address grumbles

* New not-quite-linear-typing API

* Slimmer API

* More linear-type test fixes

* Fix tests

* Tidy

* Fix some grumbles

* Keep unchecked functions private

* Remove another less-than-safe currency function and ensure that
contracts module can never create cash.

* Address a few grumbles and fix tests
This commit is contained in:
Gav Wood
2019-03-20 14:07:28 +01:00
committed by Robert Habermeier
parent f9e224e7b8
commit dcd77a147c
49 changed files with 1108 additions and 1100 deletions
+30 -22
View File
@@ -6,8 +6,6 @@
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@@ -27,11 +25,11 @@ use parity_codec::{HasCompact, Encode, Decode};
use srml_support::{StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result};
use srml_support::{decl_module, decl_event, decl_storage, ensure};
use srml_support::traits::{
Currency, OnDilution, OnFreeBalanceZero, ArithmeticType,
LockIdentifier, LockableCurrency, WithdrawReasons
Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, WithdrawReasons,
OnUnbalanced, Imbalance
};
use session::OnSessionChange;
use primitives::{Perbill};
use primitives::Perbill;
use primitives::traits::{Zero, One, As, StaticLookup, Saturating, Bounded};
#[cfg(feature = "std")]
use primitives::{Serialize, Deserialize};
@@ -166,13 +164,14 @@ pub struct Exposure<AccountId, Balance: HasCompact> {
pub others: Vec<IndividualExposure<AccountId, Balance>>,
}
type BalanceOf<T> = <<T as Trait>::Currency as ArithmeticType>::Type;
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
type PositiveImbalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::PositiveImbalance;
type NegativeImbalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
pub trait Trait: system::Trait + session::Trait {
/// The staking balance.
type Currency:
ArithmeticType +
Currency<Self::AccountId, Balance=BalanceOf<Self>> +
Currency<Self::AccountId> +
LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
/// Some tokens minted.
@@ -180,6 +179,12 @@ pub trait Trait: system::Trait + session::Trait {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
/// Handler for the unbalanced reduction when slashing a staker.
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Handler for the unbalanced increment when rewarding a staker.
type Reward: OnUnbalanced<PositiveImbalanceOf<Self>>;
}
const STAKING_ID: LockIdentifier = *b"staking ";
@@ -540,7 +545,8 @@ impl<T: Trait> Module<T> {
let slash = slash.min(exposure.total);
// The amount we'll slash from the validator's stash directly.
let own_slash = exposure.own.min(slash);
let own_slash = own_slash - T::Currency::slash(v, own_slash).unwrap_or_default();
let (mut imbalance, missing) = T::Currency::slash(v, own_slash);
let own_slash = own_slash - missing;
// The amount remaining that we can't slash from the validator, that must be taken from the nominators.
let rest_slash = slash - own_slash;
if !rest_slash.is_zero() {
@@ -549,29 +555,29 @@ impl<T: Trait> Module<T> {
if !total.is_zero() {
let safe_mul_rational = |b| b * rest_slash / total;// FIXME #1572 avoid overflow
for i in exposure.others.iter() {
let _ = T::Currency::slash(&i.who, safe_mul_rational(i.value)); // best effort - not much that can be done on fail.
// best effort - not much that can be done on fail.
imbalance.subsume(T::Currency::slash(&i.who, safe_mul_rational(i.value)).0)
}
}
}
T::Slash::on_unbalanced(imbalance);
}
/// Actually make a payment to a staker. This uses the currency's reward function
/// to pay the right payee for the given staker account.
fn make_payout(who: &T::AccountId, amount: BalanceOf<T>) {
fn make_payout(who: &T::AccountId, amount: BalanceOf<T>) -> Option<PositiveImbalanceOf<T>> {
match Self::payee(who) {
RewardDestination::Controller => {
let _ = T::Currency::reward(&who, amount);
}
RewardDestination::Stash => {
let _ = Self::ledger(who).map(|l| T::Currency::reward(&l.stash, amount));
}
RewardDestination::Controller => T::Currency::deposit_into_existing(&who, amount).ok(),
RewardDestination::Stash => Self::ledger(who)
.and_then(|l| T::Currency::deposit_into_existing(&l.stash, amount).ok()),
RewardDestination::Staked =>
if let Some(mut l) = Self::ledger(who) {
Self::ledger(who).and_then(|mut l| {
l.active += amount;
l.total += amount;
let _ = T::Currency::reward(&l.stash, amount);
let r = T::Currency::deposit_into_existing(&l.stash, amount).ok();
Self::update_ledger(who, l);
},
r
}),
}
}
@@ -580,6 +586,7 @@ impl<T: Trait> Module<T> {
fn reward_validator(who: &T::AccountId, reward: BalanceOf<T>) {
let off_the_table = reward.min(Self::validators(who).validator_payment);
let reward = reward - off_the_table;
let mut imbalance = <PositiveImbalanceOf<T>>::zero();
let validator_cut = if reward.is_zero() {
Zero::zero()
} else {
@@ -587,11 +594,12 @@ impl<T: Trait> Module<T> {
let total = exposure.total.max(One::one());
let safe_mul_rational = |b| b * reward / total;// FIXME #1572: avoid overflow
for i in &exposure.others {
Self::make_payout(&i.who, safe_mul_rational(i.value));
imbalance.maybe_subsume(Self::make_payout(&i.who, safe_mul_rational(i.value)));
}
safe_mul_rational(exposure.own)
};
Self::make_payout(who, validator_cut + off_the_table);
imbalance.maybe_subsume(Self::make_payout(who, validator_cut + off_the_table));
T::Reward::on_unbalanced(imbalance);
}
/// Get the reward for the session, assuming it ends with this block.
+7
View File
@@ -58,6 +58,9 @@ impl balances::Trait for Test {
type OnFreeBalanceZero = Staking;
type OnNewAccount = ();
type Event = ();
type TransactionPayment = ();
type TransferPayment = ();
type DustRemoval = ();
}
impl session::Trait for Test {
type ConvertAccountIdToSessionKey = ConvertUintAuthorityId;
@@ -72,6 +75,8 @@ impl Trait for Test {
type Currency = balances::Module<Self>;
type OnRewardMinted = ();
type Event = ();
type Slash = ();
type Reward = ();
}
pub struct ExtBuilder {
@@ -193,6 +198,8 @@ impl ExtBuilder {
(40, balance_factor), (41, balance_factor * 40)
]
},
transaction_base_fee: 0,
transaction_byte_fee: 0,
existential_deposit: self.existential_deposit,
transfer_fee: 0,
creation_fee: 0,
+26 -28
View File
@@ -103,7 +103,7 @@ fn invulnerability_should_work() {
// Make account 10 invulnerable
assert_ok!(Staking::set_invulnerables(vec![10]));
// Give account 10 some funds
Balances::set_free_balance(&10, 70);
let _ = Balances::deposit_creating(&10, 69);
// There is no slash grace -- slash immediately.
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 10 has not been slashed
@@ -132,7 +132,7 @@ fn offline_should_slash_and_kick() {
// Test that an offline validator gets slashed and kicked
with_externalities(&mut ExtBuilder::default().build(), || {
// Give account 10 some balance
Balances::set_free_balance(&10, 1000);
let _ = Balances::deposit_creating(&10, 999);
// Confirm account 10 is a validator
assert!(<Validators<Test>>::exists(&10));
// Validators get slashed immediately
@@ -163,7 +163,7 @@ fn offline_grace_should_delay_slashing() {
// Tests that with grace, slashing is delayed
with_externalities(&mut ExtBuilder::default().build(), || {
// Initialize account 10 with balance
Balances::set_free_balance(&10, 70);
let _ = Balances::deposit_creating(&10, 69);
// Verify account 10 has balance
assert_eq!(Balances::free_balance(&10), 70);
@@ -204,8 +204,8 @@ fn max_unstake_threshold_works() {
with_externalities(&mut ExtBuilder::default().build(), || {
const MAX_UNSTAKE_THRESHOLD: u32 = 10;
// Two users with maximum possible balance
Balances::set_free_balance(&10, u64::max_value());
Balances::set_free_balance(&20, u64::max_value());
let _ = Balances::deposit_creating(&10, u64::max_value() - 1);
let _ = Balances::deposit_creating(&20, u64::max_value() - 1);
// Give them full exposer as a staker
<Stakers<Test>>::insert(&10, Exposure { total: u64::max_value(), own: u64::max_value(), others: vec![]});
@@ -253,9 +253,6 @@ fn max_unstake_threshold_works() {
fn slashing_does_not_cause_underflow() {
// Tests that slashing more than a user has does not underflow
with_externalities(&mut ExtBuilder::default().build(), || {
// One user with less than `max_value` will test underflow does not occur
Balances::set_free_balance(&10, 1);
// Verify initial conditions
assert_eq!(Balances::free_balance(&10), 1);
assert_eq!(Staking::offline_slash_grace(), 0);
@@ -449,7 +446,7 @@ fn staking_should_work() {
assert_eq!(Staking::bonding_duration(), 2);
// put some money in account that we'll use.
for i in 1..5 { Balances::set_free_balance(&i, 1000); }
for i in 1..5 { let _ = Balances::deposit_creating(&i, 1000); }
// bond one account pair and state interest in nomination.
// this is needed to keep 10 and 20 in the validator list with phragmen
@@ -628,9 +625,9 @@ fn nominating_and_rewards_should_work() {
// give the man some money
let initial_balance = 1000;
for i in 1..5 { Balances::set_free_balance(&i, initial_balance); }
Balances::set_free_balance(&10, initial_balance);
Balances::set_free_balance(&20, initial_balance);
for i in [1, 2, 3, 4, 5, 10, 20].iter() {
let _ = Balances::deposit_creating(i, initial_balance - Balances::total_balance(i));
}
// record their balances.
for i in 1..5 { assert_eq!(Balances::total_balance(&i), initial_balance); }
@@ -705,8 +702,9 @@ fn nominators_also_get_slashed() {
// give the man some money.
let initial_balance = 1000;
for i in 1..3 { Balances::set_free_balance(&i, initial_balance); }
Balances::set_free_balance(&10, initial_balance);
for i in [1, 2, 3, 10].iter() {
let _ = Balances::deposit_creating(i, initial_balance - Balances::total_balance(i));
}
// 2 will nominate for 10
let nominator_stake = 500;
@@ -843,7 +841,7 @@ fn cannot_transfer_staked_balance() {
assert_noop!(Balances::transfer(Origin::signed(11), 20, 1), "account liquidity restrictions prevent withdrawal");
// Give account 11 extra free balance
Balances::set_free_balance(&11, 10000);
let _ = Balances::deposit_creating(&11, 9999);
// Confirm that account 11 can now transfer some balance
assert_ok!(Balances::transfer(Origin::signed(11), 20, 1));
});
@@ -863,7 +861,7 @@ fn cannot_reserve_staked_balance() {
assert_noop!(Balances::reserve(&11, 1), "account liquidity restrictions prevent withdrawal");
// Give account 11 extra free balance
Balances::set_free_balance(&11, 10000);
let _ = Balances::deposit_creating(&11, 9990);
// Confirm account 11 can now reserve balance
assert_ok!(Balances::reserve(&11, 1));
});
@@ -1041,7 +1039,7 @@ fn bond_extra_works() {
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
// Give account 11 some large free balance greater than total
Balances::set_free_balance(&11, 1000000);
let _ = Balances::deposit_creating(&11, 999000);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(&11), 1000000);
@@ -1077,7 +1075,7 @@ fn bond_extra_and_withdraw_unbonded_works() {
assert_ok!(Staking::set_bonding_duration(2));
// Give account 11 some large free balance greater than total
Balances::set_free_balance(&11, 1000000);
let _ = Balances::deposit_creating(&11, 999000);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(&11), 1000000);
@@ -1175,8 +1173,8 @@ fn slot_stake_is_least_staked_validator_and_limits_maximum_punishment() {
assert_eq!(Staking::stakers(&20).total, 2000);
// Give the man some money.
Balances::set_free_balance(&10, 1000);
Balances::set_free_balance(&20, 1000);
let _ = Balances::deposit_creating(&10, 999);
let _ = Balances::deposit_creating(&20, 999);
// Confirm initial free balance.
assert_eq!(Balances::free_balance(&10), 1000);
@@ -1247,7 +1245,7 @@ fn on_free_balance_zero_stash_removes_validator() {
assert!(<Payee<Test>>::exists(&10));
// Reduce free_balance of controller to 0
Balances::set_free_balance(&10, 0);
Balances::slash(&10, u64::max_value());
// Check total balance of account 10
assert_eq!(Balances::total_balance(&10), 0);
@@ -1263,7 +1261,7 @@ fn on_free_balance_zero_stash_removes_validator() {
assert!(<Payee<Test>>::exists(&10));
// Reduce free_balance of stash to 0
Balances::set_free_balance(&11, 0);
Balances::slash(&11, u64::max_value());
// Check total balance of stash
assert_eq!(Balances::total_balance(&11), 0);
@@ -1306,7 +1304,7 @@ fn on_free_balance_zero_stash_removes_nominator() {
assert!(<Payee<Test>>::exists(&10));
// Reduce free_balance of controller to 0
Balances::set_free_balance(&10, 0);
Balances::slash(&10, u64::max_value());
// Check total balance of account 10
assert_eq!(Balances::total_balance(&10), 0);
@@ -1321,7 +1319,7 @@ fn on_free_balance_zero_stash_removes_nominator() {
assert!(<Payee<Test>>::exists(&10));
// Reduce free_balance of stash to 0
Balances::set_free_balance(&11, 0);
Balances::slash(&11, u64::max_value());
// Check total balance of stash
assert_eq!(Balances::total_balance(&11), 0);
@@ -1382,7 +1380,7 @@ fn phragmen_poc_works() {
// bond [2,1](A), [4,3](B), [6,5](C) as the 3 nominators
// Give all of them some balance to be able to bond properly.
for i in &[1, 3, 5] { Balances::set_free_balance(i, 50); }
for i in &[1, 3, 5] { let _ = Balances::deposit_creating(i, 50); }
// Linking names to the above test:
// 10 => X
// 20 => Y
@@ -1439,7 +1437,7 @@ fn phragmen_election_works() {
// bond [2,1](A), [4,3](B), as 2 nominators
// Give all of them some balance to be able to bond properly.
for i in &[1, 3] { Balances::set_free_balance(i, 50); }
for i in &[1, 3] { let _ = Balances::deposit_creating(i, 50); }
assert_ok!(Staking::bond(Origin::signed(1), 2, 5, RewardDestination::default()));
assert_ok!(Staking::nominate(Origin::signed(2), vec![10, 20]));
@@ -1499,7 +1497,7 @@ fn switching_roles() {
assert_eq!(Session::validators(), vec![20, 10]);
// put some money in account that we'll use.
for i in 1..7 { Balances::set_free_balance(&i, 5000); }
for i in 1..7 { let _ = Balances::deposit_creating(&i, 5000); }
// add 2 nominators
assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::default()));
@@ -1567,7 +1565,7 @@ fn wrong_vote_is_null() {
assert_eq!(Session::validators(), vec![40, 30, 20, 10]);
// put some money in account that we'll use.
for i in 1..3 { Balances::set_free_balance(&i, 5000); }
for i in 1..3 { let _ = Balances::deposit_creating(&i, 5000); }
// add 1 nominators
assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::default()));