Composite accounts (#4820)

* Basic account composition.

* Add try_mutate_exists

* De-duplicate

* Refactor away the UpdateBalanceOutcome

* Expunge final UpdateBalanceOutcome refs

* Refactor transfer

* Refactor reservable currency stuff.

* Test with the alternative setup.

* Fixes

* Test with both setups.

* Fixes

* Fix

* Fix macros

* Make indices opt-in

* Remove CreationFee, and make indices opt-in.

* Fix construct_runtime

* Fix last few bits

* Fix tests

* Update trait impls

* Don't hardcode the system event

* Make tests build and fix some stuff.

* Pointlessly bump runtime version

* Fix benchmark

* Another fix

* Whitespace

* Make indices module economically safe

* Migrations for indices.

* Fix

* Whilespace

* Trim defunct migrations

* Remove unused storage item

* More contains_key fixes

* Docs.

* Bump runtime

* Remove unneeded code

* Fix test

* Fix test

* Update frame/balances/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Fix ED logic

* Repatriate reserved logic

* Typo

* Fix typo

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Last few fixes

* Another fix

* Build fix

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Jaco Greeff <jacogr@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2020-02-14 00:47:51 +00:00
committed by GitHub
parent d3fa8c91af
commit 5b7512e2e4
79 changed files with 2459 additions and 2100 deletions
+142 -22
View File
@@ -28,6 +28,115 @@ use sp_runtime::{
};
use crate::dispatch::Parameter;
use crate::storage::StorageMap;
/// An abstraction of a value stored within storage, but possibly as part of a larger composite
/// item.
pub trait StoredMap<K, T> {
/// Get the item, or its default if it doesn't yet exist; we make no distinction between the
/// two.
fn get(k: &K) -> T;
/// Get whether the item takes up any storage. If this is `false`, then `get` will certainly
/// return the `T::default()`. If `true`, then there is no implication for `get` (i.e. it
/// may return any value, including the default).
///
/// NOTE: This may still be `true`, even after `remove` is called. This is the case where
/// a single storage entry is shared between multiple `StoredMap` items single, without
/// additional logic to enforce it, deletion of any one them doesn't automatically imply
/// deletion of them all.
fn is_explicit(k: &K) -> bool;
/// Mutate the item.
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R;
/// Mutate the item, removing or resetting to default value if it has been mutated to `None`.
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R;
/// Maybe mutate the item only if an `Ok` value is returned from `f`. Do nothing if an `Err` is
/// returned. It is removed or reset to default value if it has been mutated to `None`
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E>;
/// Set the item to something new.
fn insert(k: &K, t: T) { Self::mutate(k, |i| *i = t); }
/// Remove the item or otherwise replace it with its default value; we don't care which.
fn remove(k: &K);
}
/// A simple, generic one-parameter event notifier/handler.
pub trait Happened<T> {
/// The thing happened.
fn happened(t: &T);
}
/// A shim for placing around a storage item in order to use it as a `StoredValue`. Ideally this
/// wouldn't be needed as `StorageValue`s should blanket implement `StoredValue`s, however this
/// would break the ability to have custom impls of `StoredValue`. The other workaround is to
/// implement it directly in the macro.
///
/// This form has the advantage that two additional types are provides, `Created` and `Removed`,
/// which are both generic events that can be tied to handlers to do something in the case of being
/// about to create an account where one didn't previously exist (at all; not just where it used to
/// be the default value), or where the account is being removed or reset back to the default value
/// where previously it did exist (though may have been in a default state). This works well with
/// system module's `CallOnCreatedAccount` and `CallKillAccount`.
pub struct StorageMapShim<
S,
Created,
Removed,
K,
T
>(sp_std::marker::PhantomData<(S, Created, Removed, K, T)>);
impl<
S: StorageMap<K, T, Query=T>,
Created: Happened<K>,
Removed: Happened<K>,
K: FullCodec,
T: FullCodec
> StoredMap<K, T> for StorageMapShim<S, Created, Removed, K, T> {
fn get(k: &K) -> T { S::get(k) }
fn is_explicit(k: &K) -> bool { S::contains_key(k) }
fn insert(k: &K, t: T) {
S::insert(k, t);
if !S::contains_key(&k) {
Created::happened(k);
}
}
fn remove(k: &K) {
if S::contains_key(&k) {
Removed::happened(&k);
}
S::remove(k);
}
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R {
let r = S::mutate(k, f);
if !S::contains_key(&k) {
Created::happened(k);
}
r
}
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R {
let (existed, exists, r) = S::mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value);
(existed, maybe_value.is_some(), r)
});
if !existed && exists {
Created::happened(k);
} else if existed && !exists {
Removed::happened(k);
}
r
}
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
f(maybe_value).map(|v| (existed, maybe_value.is_some(), v))
}).map(|(existed, exists, v)| {
if !existed && exists {
Created::happened(k);
} else if existed && !exists {
Removed::happened(k);
}
v
})
}
}
/// Anything that can have a `::len()` method.
pub trait Len {
@@ -65,6 +174,25 @@ pub trait Contains<T: Ord> {
fn count() -> usize { Self::sorted_members().len() }
}
/// Determiner to say whether a given account is unused.
pub trait IsDeadAccount<AccountId> {
/// Is the given account dead?
fn is_dead_account(who: &AccountId) -> bool;
}
impl<AccountId> IsDeadAccount<AccountId> for () {
fn is_dead_account(_who: &AccountId) -> bool {
true
}
}
/// Handler for when a new account has been created.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnNewAccount<AccountId> {
/// A new account `who` has been registered.
fn on_new_account(who: &AccountId);
}
/// The account with the given id was reaped.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnReapAccount<AccountId> {
@@ -72,20 +200,6 @@ pub trait OnReapAccount<AccountId> {
fn on_reap_account(who: &AccountId);
}
/// Outcome of a balance update.
pub enum UpdateBalanceOutcome {
/// Account balance was simply updated.
Updated,
/// The update led to killing the account.
AccountKilled,
/// Free balance became zero as a result of this update.
FreeBalanceZero,
/// Reserved balance became zero as a result of this update.
ReservedBalanceZero,
/// The account started and ended non-existent.
StillDead,
}
/// A trait for finding the author of a block header based on the `PreRuntime` digests contained
/// within it.
pub trait FindAuthor<Author> {
@@ -494,10 +608,15 @@ pub trait Currency<AccountId> {
fn make_free_balance_be(
who: &AccountId,
balance: Self::Balance,
) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome,
);
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance>;
}
/// Status of funds.
pub enum BalanceStatus {
/// Funds are free, as corresponding to `free` item in Balances.
Free,
/// Funds are reserved, as corresponding to `reserved` item in Balances.
Reserved,
}
/// A currency where funds can be reserved from the user.
@@ -528,7 +647,6 @@ pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
fn reserved_balance(who: &AccountId) -> Self::Balance;
/// 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
@@ -547,16 +665,18 @@ pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
/// invoke `on_reserved_too_low` and could reap the account.
fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance;
/// Moves up to `value` from reserved balance of account `slashed` to free balance of account
/// Moves up to `value` from reserved balance of account `slashed` to balance of account
/// `beneficiary`. `beneficiary` must exist for this to succeed. If it does not, `Err` will be
/// returned.
/// returned. Funds will be placed in either the `free` balance or the `reserved` balance,
/// depending on the `status`.
///
/// 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
value: Self::Balance,
status: BalanceStatus,
) -> result::Result<Self::Balance, DispatchError>;
}