mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 00:31:07 +00:00
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:
committed by
Robert Habermeier
parent
f9e224e7b8
commit
dcd77a147c
@@ -61,14 +61,184 @@ pub enum UpdateBalanceOutcome {
|
||||
AccountKilled,
|
||||
}
|
||||
|
||||
pub trait ArithmeticType {
|
||||
type Type: SimpleArithmetic + As<usize> + As<u64> + Codec + Copy + MaybeSerializeDebug + Default;
|
||||
/// Simple trait designed for hooking into a transaction payment.
|
||||
///
|
||||
/// It operates over a single generic `AccountId` type.
|
||||
pub trait MakePayment<AccountId> {
|
||||
/// Make transaction payment from `who` for an extrinsic of encoded length
|
||||
/// `encoded_len` bytes. Return `Ok` iff the payment was successful.
|
||||
fn make_payment(who: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl<T> MakePayment<T> for () {
|
||||
fn make_payment(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
/// Handler for when some currency "account" decreased in balance for
|
||||
/// some reason.
|
||||
///
|
||||
/// The only reason at present for an increase would be for validator rewards, but
|
||||
/// there may be other reasons in the future or for other chains.
|
||||
///
|
||||
/// Reasons for decreases include:
|
||||
///
|
||||
/// - Someone got slashed.
|
||||
/// - Someone paid for a transaction to be included.
|
||||
pub trait OnUnbalanced<Imbalance> {
|
||||
/// Handler for some imbalance. Infallible.
|
||||
fn on_unbalanced(amount: Imbalance);
|
||||
}
|
||||
|
||||
impl<Imbalance: Drop> OnUnbalanced<Imbalance> for () {
|
||||
fn on_unbalanced(amount: Imbalance) { drop(amount); }
|
||||
}
|
||||
|
||||
/// Simple boolean for whether an account needs to be kept in existence.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ExistenceRequirement {
|
||||
/// Operation must not result in the account going out of existence.
|
||||
KeepAlive,
|
||||
/// Operation may result in account going out of existence.
|
||||
AllowDeath,
|
||||
}
|
||||
|
||||
/// A trait for a not-quite Linear Type that tracks an imbalance.
|
||||
///
|
||||
/// Functions that alter account balances return an object of this trait to
|
||||
/// express how much account balances have been altered in aggregate. If
|
||||
/// dropped, the currency system will take some default steps to deal with
|
||||
/// the imbalance (`balances` module simply reduces or increases its
|
||||
/// total issuance). Your module should generally handle it in some way,
|
||||
/// good practice is to do so in a configurable manner using an
|
||||
/// `OnUnbalanced` type for each situation in which your module needs to
|
||||
/// handle an imbalance.
|
||||
///
|
||||
/// Imbalances can either be Positive (funds were added somewhere without
|
||||
/// being subtracted elsewhere - e.g. a reward) or Negative (funds deducted
|
||||
/// somewhere without an equal and opposite addition - e.g. a slash or
|
||||
/// system fee payment).
|
||||
///
|
||||
/// Since they are unsigned, the actual type is always Positive or Negative.
|
||||
/// The trait makes no distinction except to define the `Opposite` type.
|
||||
///
|
||||
/// New instances of zero value can be created (`zero`) and destroyed
|
||||
/// (`drop_zero`).
|
||||
///
|
||||
/// Existing instances can be `split` and merged either consuming `self` with
|
||||
/// `merge` or mutating `self` with `subsume`. If the target is an `Option`,
|
||||
/// then `maybe_merge` and `maybe_subsume` might work better. Instances can
|
||||
/// also be `offset` with an `Opposite` that is less than or equal to in value.
|
||||
///
|
||||
/// You can always retrieve the raw balance value using `peek`.
|
||||
#[must_use]
|
||||
pub trait Imbalance<Balance>: Sized {
|
||||
/// The oppositely imbalanced type. They come in pairs.
|
||||
type Opposite: Imbalance<Balance>;
|
||||
|
||||
/// The zero imbalance. Can be destroyed with `drop_zero`.
|
||||
fn zero() -> Self;
|
||||
|
||||
/// Drop an instance cleanly. Only works if its `value()` is zero.
|
||||
fn drop_zero(self) -> Result<(), Self>;
|
||||
|
||||
/// Consume `self` and return two independent instances; the first
|
||||
/// is guaranteed to be at most `amount` and the second will be the remainder.
|
||||
fn split(self, amount: Balance) -> (Self, Self);
|
||||
|
||||
/// Consume `self` and an `other` to return a new instance that combines
|
||||
/// both.
|
||||
fn merge(self, other: Self) -> Self;
|
||||
|
||||
/// Consume `self` and maybe an `other` to return a new instance that combines
|
||||
/// both.
|
||||
fn maybe_merge(self, other: Option<Self>) -> Self {
|
||||
if let Some(o) = other {
|
||||
self.merge(o)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume an `other` to mutate `self` into a new instance that combines
|
||||
/// both.
|
||||
fn subsume(&mut self, other: Self);
|
||||
|
||||
/// Maybe consume an `other` to mutate `self` into a new instance that combines
|
||||
/// both.
|
||||
fn maybe_subsume(&mut self, other: Option<Self>) {
|
||||
if let Some(o) = other {
|
||||
self.subsume(o)
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume self and along with an opposite counterpart to return
|
||||
/// a combined result.
|
||||
///
|
||||
/// Returns `Ok` along with a new instance of `Self` if this instance has a
|
||||
/// greater value than the `other`. Otherwise returns `Err` with an instance of
|
||||
/// the `Opposite`. In both cases the value represents the combination of `self`
|
||||
/// and `other`.
|
||||
fn offset(self, other: Self::Opposite) -> Result<Self, Self::Opposite>;
|
||||
|
||||
/// The raw value of self.
|
||||
fn peek(&self) -> Balance;
|
||||
}
|
||||
|
||||
/// Either a positive or a negative imbalance.
|
||||
pub enum SignedImbalance<B, P: Imbalance<B>>{
|
||||
/// A positive imbalance (funds have been created but none destroyed).
|
||||
Positive(P),
|
||||
/// A negative imbalance (funds have been destroyed but none created).
|
||||
Negative(P::Opposite),
|
||||
}
|
||||
|
||||
impl<
|
||||
P: Imbalance<B, Opposite=N>,
|
||||
N: Imbalance<B, Opposite=P>,
|
||||
B: SimpleArithmetic + As<usize> + As<u64> + Codec + Copy + MaybeSerializeDebug + Default,
|
||||
> SignedImbalance<B, P> {
|
||||
pub fn zero() -> Self {
|
||||
SignedImbalance::Positive(P::zero())
|
||||
}
|
||||
|
||||
pub fn drop_zero(self) -> Result<(), Self> {
|
||||
match self {
|
||||
SignedImbalance::Positive(x) => x.drop_zero().map_err(SignedImbalance::Positive),
|
||||
SignedImbalance::Negative(x) => x.drop_zero().map_err(SignedImbalance::Negative),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume `self` and an `other` to return a new instance that combines
|
||||
/// both.
|
||||
pub fn merge(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(SignedImbalance::Positive(one), SignedImbalance::Positive(other)) =>
|
||||
SignedImbalance::Positive(one.merge(other)),
|
||||
(SignedImbalance::Negative(one), SignedImbalance::Negative(other)) =>
|
||||
SignedImbalance::Negative(one.merge(other)),
|
||||
(SignedImbalance::Positive(one), SignedImbalance::Negative(other)) =>
|
||||
if one.peek() > other.peek() {
|
||||
SignedImbalance::Positive(one.offset(other).ok().unwrap_or_else(P::zero))
|
||||
} else {
|
||||
SignedImbalance::Negative(other.offset(one).ok().unwrap_or_else(N::zero))
|
||||
},
|
||||
(one, other) => other.merge(one),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstraction over a fungible assets system.
|
||||
pub trait Currency<AccountId> {
|
||||
/// The balance of an account.
|
||||
type Balance;
|
||||
type Balance: SimpleArithmetic + As<usize> + As<u64> + Codec + Copy + MaybeSerializeDebug + Default;
|
||||
|
||||
/// The opaque token type for an imbalance. This is returned by unbalanced operations
|
||||
/// and must be dealt with. It may be dropped but cannot be cloned.
|
||||
type PositiveImbalance: Imbalance<Self::Balance>;
|
||||
|
||||
/// The opaque token type for an imbalance. This is returned by unbalanced operations
|
||||
/// and must be dealt with. It may be dropped but cannot be cloned.
|
||||
type NegativeImbalance: Imbalance<Self::Balance>;
|
||||
|
||||
// PUBLIC IMMUTABLES
|
||||
|
||||
@@ -117,30 +287,83 @@ pub trait Currency<AccountId> {
|
||||
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
|
||||
fn reserved_balance(who: &AccountId) -> Self::Balance;
|
||||
|
||||
// PUBLIC MUTABLES (DANGEROUS)
|
||||
/// Returns `Ok` iff the account is able to make a withdrawal of the given amount
|
||||
/// for the given reason. Basically, it's just a dry-run of `withdraw`.
|
||||
///
|
||||
/// `Err(...)` with the reason why not otherwise.
|
||||
fn ensure_can_withdraw(
|
||||
who: &AccountId,
|
||||
_amount: Self::Balance,
|
||||
reason: WithdrawReason,
|
||||
new_balance: Self::Balance,
|
||||
) -> result::Result<(), &'static str>;
|
||||
|
||||
/// Deducts up to `value` from the combined balance of `who`, preferring to deduct from the
|
||||
// PUBLIC MUTABLES (DANGEROUS)
|
||||
|
||||
/// Transfer some liquid free balance to another staker.
|
||||
///
|
||||
/// This is a very high-level function. It will ensure all appropriate fees are paid
|
||||
/// and no imbalance in the system remains.
|
||||
fn transfer(
|
||||
source: &AccountId,
|
||||
dest: &AccountId,
|
||||
value: Self::Balance,
|
||||
) -> result::Result<(), &'static str>;
|
||||
|
||||
/// Deducts up to `value` from the combined balance of `who`, preferring to deduct from the
|
||||
/// free balance. This function cannot fail.
|
||||
///
|
||||
/// The resulting imbalance is the first item of the tuple returned.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
fn slash(who: &AccountId, value: Self::Balance) -> Option<Self::Balance>;
|
||||
/// then a non-zero second item will be returned.
|
||||
fn slash(
|
||||
who: &AccountId,
|
||||
value: Self::Balance
|
||||
) -> (Self::NegativeImbalance, Self::Balance);
|
||||
|
||||
/// Adds up to `value` to the free balance of `who`.
|
||||
/// Mints `value` to the free balance of `who`.
|
||||
///
|
||||
/// If `who` doesn't exist, nothing is done and an Err returned.
|
||||
fn reward(who: &AccountId, value: Self::Balance) -> result::Result<(), &'static str>;
|
||||
fn deposit_into_existing(
|
||||
who: &AccountId,
|
||||
value: Self::Balance
|
||||
) -> result::Result<Self::PositiveImbalance, &'static str>;
|
||||
|
||||
/// Adds up to `value` to the free balance of `who`.
|
||||
/// Removes some free balance from `who` account for `reason` if possible. If `liveness` is `KeepAlive`,
|
||||
/// then no less than `ExistentialDeposit` must be left remaining.
|
||||
///
|
||||
/// If `who` doesn't exist, it is created
|
||||
///
|
||||
/// Returns if the account was successfully updated or update has led to killing of the account.
|
||||
///
|
||||
/// NOTE: This assumes that the total stake remains unchanged after this operation.
|
||||
fn increase_free_balance_creating(who: &AccountId, value: Self::Balance) -> UpdateBalanceOutcome;
|
||||
/// This checks any locks, vesting and liquidity requirements. If the removal is not possible, then it
|
||||
/// returns `Err`.
|
||||
fn withdraw(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
reason: WithdrawReason,
|
||||
liveness: ExistenceRequirement,
|
||||
) -> result::Result<Self::NegativeImbalance, &'static str>;
|
||||
|
||||
/// Moves `value` from balance to reserved balance.
|
||||
/// Adds up to `value` to the free balance of `who`. If `who` doesn't exist, it is created
|
||||
///
|
||||
/// Infallible.
|
||||
fn deposit_creating(
|
||||
who: &AccountId,
|
||||
value: Self::Balance,
|
||||
) -> Self::PositiveImbalance;
|
||||
|
||||
/// Ensure an account's free balance equals some value; this will create the account
|
||||
/// if needed.
|
||||
///
|
||||
/// Returns a signed imbalance and status to indicate if the account was successfully updated or update
|
||||
/// has led to killing of the account.
|
||||
fn ensure_free_balance_is(
|
||||
who: &AccountId,
|
||||
balance: Self::Balance,
|
||||
) -> (
|
||||
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
|
||||
UpdateBalanceOutcome,
|
||||
);
|
||||
|
||||
/// Moves `value` from balance to reserved balance.
|
||||
///
|
||||
/// If the free balance is lower than `value`, then no funds will be moved and an `Err` will
|
||||
/// be returned to notify of this. This is different behaviour to `unreserve`.
|
||||
@@ -149,27 +372,31 @@ pub trait Currency<AccountId> {
|
||||
/// Moves up to `value` from reserved balance to balance. This function cannot fail.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
/// then non-zero will be returned.
|
||||
///
|
||||
/// NOTE: This is different to `reserve`.
|
||||
fn unreserve(who: &AccountId, value: Self::Balance) -> Option<Self::Balance>;
|
||||
fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance;
|
||||
|
||||
/// Deducts up to `value` from reserved balance of `who`. This function cannot fail.
|
||||
///
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
|
||||
fn slash_reserved(who: &AccountId, value: Self::Balance) -> Option<Self::Balance>;
|
||||
/// then non-zero second item will be returned.
|
||||
fn slash_reserved(
|
||||
who: &AccountId,
|
||||
value: Self::Balance
|
||||
) -> (Self::NegativeImbalance, Self::Balance);
|
||||
|
||||
/// Moves up to `value` from reserved balance of account `slashed` to free balance of account
|
||||
/// `beneficiary`. `beneficiary` must exist for this to succeed. If it does not, `Err` will be
|
||||
/// returned.
|
||||
///
|
||||
/// As much funds up to `value` will be moved as possible. If this is less than `value`, then
|
||||
/// `Ok(Some(remaining))` will be returned. Full completion is given by `Ok(None)`.
|
||||
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
|
||||
/// then `Ok(non_zero)` will be returned.
|
||||
fn repatriate_reserved(
|
||||
slashed: &AccountId,
|
||||
beneficiary: &AccountId,
|
||||
value: Self::Balance
|
||||
) -> result::Result<Option<Self::Balance>, &'static str>;
|
||||
) -> result::Result<Self::Balance, &'static str>;
|
||||
}
|
||||
|
||||
/// An identifier for a lock. Used for disambiguating different locks so that
|
||||
@@ -207,25 +434,6 @@ pub trait LockableCurrency<AccountId>: Currency<AccountId> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Charge bytes fee trait
|
||||
pub trait ChargeBytesFee<AccountId> {
|
||||
/// Charge fees from `transactor` for an extrinsic (transaction) of encoded length
|
||||
/// `encoded_len` bytes. Return Ok if the payment was successful.
|
||||
fn charge_base_bytes_fee(transactor: &AccountId, encoded_len: usize) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
/// Charge fee trait
|
||||
pub trait ChargeFee<AccountId>: ChargeBytesFee<AccountId> {
|
||||
/// The type of fee amount.
|
||||
type Amount;
|
||||
|
||||
/// Charge `amount` of fees from `transactor`. Return Ok iff the payment was successful.
|
||||
fn charge_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
|
||||
/// Refund `amount` of previous charged fees from `transactor`. Return Ok if the refund was successful.
|
||||
fn refund_fee(transactor: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
bitmask! {
|
||||
/// Reasons for moving funds out of an account.
|
||||
#[derive(Encode, Decode)]
|
||||
@@ -240,39 +448,7 @@ bitmask! {
|
||||
Transfer = 0b00000010,
|
||||
/// In order to reserve some funds for a later return or repatriation
|
||||
Reserve = 0b00000100,
|
||||
/// In order to pay some other (higher-level) fees.
|
||||
Fee = 0b00001000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Transfer fungible asset trait
|
||||
pub trait TransferAsset<AccountId> {
|
||||
/// The type of asset amount.
|
||||
type Amount;
|
||||
|
||||
/// Transfer asset from `from` account to `to` account with `amount` of asset.
|
||||
fn transfer(from: &AccountId, to: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
|
||||
/// Remove asset from `who` account by deducting `amount` in the account balances.
|
||||
fn withdraw(who: &AccountId, amount: Self::Amount, reason: WithdrawReason) -> Result<(), &'static str>;
|
||||
|
||||
/// Add asset to `who` account by increasing `amount` in the account balances.
|
||||
fn deposit(who: &AccountId, amount: Self::Amount) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl<T> ChargeBytesFee<T> for () {
|
||||
fn charge_base_bytes_fee(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
impl<T> ChargeFee<T> for () {
|
||||
type Amount = ();
|
||||
|
||||
fn charge_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn refund_fee(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
impl<T> TransferAsset<T> for () {
|
||||
type Amount = ();
|
||||
|
||||
fn transfer(_: &T, _: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
fn withdraw(_: &T, _: Self::Amount, _: WithdrawReason) -> Result<(), &'static str> { Ok(()) }
|
||||
fn deposit(_: &T, _: Self::Amount) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user