Named reserve (#7778)

* add NamedReservableCurrency

* move currency related trait and types into a new file

* implement NamedReservableCurrency

* remove empty reserves

* Update frame/support/src/traits.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* fix build

* bump year

* add MaxReserves

* repatriate_reserved_named should put reserved fund into named reserved

* add tests

* add some docs

* fix warning

* Update lib.rs

* fix test

* fix test

* fix

* fix

* triggier CI

* Move NamedReservableCurrency.

* Use strongly bounded vec for reserves.

* Fix test.

* remove duplicated file

* trigger CI

* Make `ReserveIdentifier` assosicated type

* add helpers

* make ReserveIdentifier assosicated type

* fix

* update

* trigger CI

* Apply suggestions from code review

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* trigger CI

* Apply suggestions from code review

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Gavin Wood <i@gavwood.com>
Co-authored-by: Shaun Wang <spxwang@gmail.com>
This commit is contained in:
Xiliang Chen
2021-06-04 19:32:46 +12:00
committed by GitHub
parent 003738b1c5
commit 223529e4dd
42 changed files with 538 additions and 5 deletions
@@ -244,6 +244,8 @@ parameter_types! {
impl pallet_balances::Config for Runtime {
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
/// The type for recording an account's balance.
type Balance = Balance;
/// The ubiquitous event type.
+3
View File
@@ -375,10 +375,13 @@ parameter_types! {
// For weight estimation, we assume that the most locks on an individual account will be 50.
// This number may need to be adjusted in the future if this assumption no longer holds true.
pub const MaxLocks: u32 = 50;
pub const MaxReserves: u32 = 50;
}
impl pallet_balances::Config for Runtime {
type MaxLocks = MaxLocks;
type MaxReserves = MaxReserves;
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -80,6 +80,8 @@ impl pallet_balances::Config for Test {
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
parameter_types! {
+2
View File
@@ -60,6 +60,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -155,6 +155,8 @@ parameter_types! {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u128;
type DustRemoval = ();
type Event = Event;
+222 -3
View File
@@ -73,6 +73,7 @@
//! - [`Currency`](frame_support::traits::Currency): Functions for dealing with a
//! fungible assets system.
//! - [`ReservableCurrency`](frame_support::traits::ReservableCurrency):
//! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency):
//! Functions for dealing with assets that can be reserved from an account.
//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for
//! dealing with accounts that allow liquidity restrictions.
@@ -163,9 +164,9 @@ use frame_support::{
traits::{
Currency, OnUnbalanced, TryDrop, StoredMap, MaxEncodedLen,
WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
ExistenceRequirement::AllowDeath,
tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status}
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::{AllowDeath, KeepAlive},
NamedReservableCurrency,
tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status},
}
};
#[cfg(feature = "std")]
@@ -214,6 +215,12 @@ pub mod pallet {
/// The maximum number of locks that should exist on an account.
/// Not strictly enforced, but used for weight estimation.
type MaxLocks: Get<u32>;
/// The maximum number of named reserves that can exist on an account.
type MaxReserves: Get<u32>;
/// The id type for named reserves.
type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
}
#[pallet::pallet]
@@ -409,6 +416,8 @@ pub mod pallet {
ExistingVestingSchedule,
/// Beneficiary account must pre-exist
DeadAccount,
/// Number of named reserves exceed MaxReserves
TooManyReserves,
}
/// The total units issued in the system.
@@ -444,6 +453,17 @@ pub mod pallet {
ConstU32<300_000>,
>;
/// Named reserves on some account balances.
#[pallet::storage]
#[pallet::getter(fn reserves)]
pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
ValueQuery
>;
/// Storage version of the pallet.
///
/// This is set to v2.0.0 for new networks.
@@ -560,6 +580,15 @@ pub struct BalanceLock<Balance> {
pub reasons: Reasons,
}
/// Store named reserved balance.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)]
pub struct ReserveData<ReserveIdentifier, Balance> {
/// The identifier for the named reserve.
pub id: ReserveIdentifier,
/// The amount of the named reserve.
pub amount: Balance,
}
/// All balance information for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen)]
pub struct AccountData<Balance> {
@@ -575,6 +604,7 @@ pub struct AccountData<Balance> {
///
/// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens
/// that are still 'owned' by the account holder, but which are suspendable.
/// This includes named reserve and unnamed reserve.
pub reserved: Balance,
/// The amount that `free` may not drop below when withdrawing for *anything except transaction
/// fee payment*.
@@ -1648,6 +1678,195 @@ impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I>
}
}
impl<T: Config<I>, I: 'static> NamedReservableCurrency<T::AccountId> for Pallet<T, I> where
T::Balance: MaybeSerializeDeserialize + Debug
{
type ReserveIdentifier = T::ReserveIdentifier;
fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance {
let reserves = Self::reserves(who);
reserves
.binary_search_by_key(id, |data| data.id)
.map(|index| reserves[index].amount)
.unwrap_or_default()
}
/// Move `value` from the free balance from `who` to a named reserve balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() { return Ok(()) }
Reserves::<T, I>::try_mutate(who, |reserves| -> DispatchResult {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
// this add can't overflow but just to be defensive.
reserves[index].amount = reserves[index].amount.saturating_add(value);
},
Err(index) => {
reserves.try_insert(index, ReserveData {
id: id.clone(),
amount: value
}).map_err(|_| Error::<T, I>::TooManyReserves)?;
},
};
<Self as ReservableCurrency<_>>::reserve(who, value)?;
Ok(())
})
}
/// Unreserve some funds, returning any amount that was unable to be unreserved.
///
/// Is a no-op if the value to be unreserved is zero.
fn unreserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() { return Zero::zero() }
Reserves::<T, I>::mutate_exists(who, |maybe_reserves| -> Self::Balance {
if let Some(reserves) = maybe_reserves.as_mut() {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let remain = <Self as ReservableCurrency<_>>::unreserve(who, to_change);
// remain should always be zero but just to be defensive here
let actual = to_change.saturating_sub(remain);
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
if reserves[index].amount.is_zero() {
if reserves.len() == 1 {
// no more named reserves
*maybe_reserves = None;
} else {
// remove this named reserve
reserves.remove(index);
}
}
value - actual
},
Err(_) => {
value
},
}
} else {
value
}
})
}
/// Slash from reserved balance, returning the negative imbalance created,
/// and any amount that was unable to be slashed.
///
/// Is a no-op if the value to be slashed is zero.
fn slash_reserved_named(
id: &Self::ReserveIdentifier,
who: &T::AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
Reserves::<T, I>::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let (imb, remain) = <Self as ReservableCurrency<_>>::slash_reserved(who, to_change);
// remain should always be zero but just to be defensive here
let actual = to_change.saturating_sub(remain);
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
(imb, value - actual)
},
Err(_) => {
(NegativeImbalance::zero(), value)
},
}
})
}
/// Move the reserved balance of one account into the balance of another, according to `status`.
/// If `status` is `Reserved`, the balance will be reserved with given `id`.
///
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved_named(
id: &Self::ReserveIdentifier,
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok(Zero::zero()) }
if slashed == beneficiary {
return match status {
Status::Free => Ok(Self::unreserve_named(id, slashed, value)),
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))),
};
}
Reserves::<T, I>::try_mutate(slashed, |reserves| -> Result<Self::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let to_change = cmp::min(reserves[index].amount, value);
let actual = if status == Status::Reserved {
// make it the reserved under same identifier
Reserves::<T, I>::try_mutate(beneficiary, |reserves| -> Result<T::Balance, DispatchError> {
match reserves.binary_search_by_key(id, |data| data.id) {
Ok(index) => {
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
// remain should always be zero but just to be defensive here
let actual = to_change.saturating_sub(remain);
// this add can't overflow but just to be defensive.
reserves[index].amount = reserves[index].amount.saturating_add(actual);
Ok(actual)
},
Err(index) => {
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
// remain should always be zero but just to be defensive here
let actual = to_change.saturating_sub(remain);
reserves.try_insert(index, ReserveData {
id: id.clone(),
amount: actual
}).map_err(|_| Error::<T, I>::TooManyReserves)?;
Ok(actual)
},
}
})?
} else {
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
// remain should always be zero but just to be defensive here
to_change.saturating_sub(remain)
};
// `actual <= to_change` and `to_change <= amount`; qed;
reserves[index].amount -= actual;
Ok(value - actual)
},
Err(_) => {
Ok(value)
},
}
})
}
}
impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug
+118
View File
@@ -964,5 +964,123 @@ macro_rules! decl_tests {
assert_eq!(Balances::total_balance(&2), 100);
});
}
#[test]
fn named_reserve_should_work() {
<$ext_builder>::default().build().execute_with(|| {
let _ = Balances::deposit_creating(&1, 111);
let id_1 = [1u8; 8];
let id_2 = [2u8; 8];
let id_3 = [3u8; 8];
// reserve
assert_noop!(Balances::reserve_named(&id_1, &1, 112), Error::<Test, _>::InsufficientBalance);
assert_ok!(Balances::reserve_named(&id_1, &1, 12));
assert_eq!(Balances::reserved_balance(1), 12);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 12);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0);
assert_ok!(Balances::reserve_named(&id_1, &1, 2));
assert_eq!(Balances::reserved_balance(1), 14);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0);
assert_ok!(Balances::reserve_named(&id_2, &1, 23));
assert_eq!(Balances::reserved_balance(1), 37);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23);
assert_ok!(Balances::reserve(&1, 34));
assert_eq!(Balances::reserved_balance(1), 71);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23);
assert_eq!(Balances::total_balance(&1), 111);
assert_eq!(Balances::free_balance(1), 40);
assert_noop!(Balances::reserve_named(&id_3, &1, 2), Error::<Test, _>::TooManyReserves);
// unreserve
assert_eq!(Balances::unreserve_named(&id_1, &1, 10), 0);
assert_eq!(Balances::reserved_balance(1), 61);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 4);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23);
assert_eq!(Balances::unreserve_named(&id_1, &1, 5), 1);
assert_eq!(Balances::reserved_balance(1), 57);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23);
assert_eq!(Balances::unreserve_named(&id_2, &1, 3), 0);
assert_eq!(Balances::reserved_balance(1), 54);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20);
assert_eq!(Balances::total_balance(&1), 111);
assert_eq!(Balances::free_balance(1), 57);
// slash_reserved_named
assert_ok!(Balances::reserve_named(&id_1, &1, 10));
assert_eq!(Balances::slash_reserved_named(&id_1, &1, 25).1, 15);
assert_eq!(Balances::reserved_balance(1), 54);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20);
assert_eq!(Balances::total_balance(&1), 101);
assert_eq!(Balances::slash_reserved_named(&id_2, &1, 5).1, 0);
assert_eq!(Balances::reserved_balance(1), 49);
assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15);
assert_eq!(Balances::total_balance(&1), 96);
// repatriate_reserved_named
let _ = Balances::deposit_creating(&2, 100);
assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Status::Reserved).unwrap(), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5);
assert_eq!(Balances::reserved_balance_named(&id_2, &2), 10);
assert_eq!(Balances::reserved_balance(&2), 10);
assert_eq!(Balances::repatriate_reserved_named(&id_2, &2, &1, 11, Status::Reserved).unwrap(), 1);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15);
assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0);
assert_eq!(Balances::reserved_balance(&2), 0);
assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Status::Free).unwrap(), 0);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5);
assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0);
assert_eq!(Balances::free_balance(&2), 110);
// repatriate_reserved_named to self
assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 10, Status::Reserved).unwrap(), 5);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5);
assert_eq!(Balances::free_balance(&1), 47);
assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 15, Status::Free).unwrap(), 10);
assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0);
assert_eq!(Balances::free_balance(&1), 52);
});
}
}
}
@@ -87,6 +87,10 @@ impl pallet_transaction_payment::Config for Test {
type FeeMultiplierUpdate = ();
}
parameter_types! {
pub const MaxReserves: u32 = 2;
}
impl Config for Test {
type Balance = u64;
type DustRemoval = ();
@@ -94,6 +98,8 @@ impl Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Pallet<Test>;
type MaxLocks = ();
type MaxReserves = MaxReserves;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
@@ -90,6 +90,7 @@ impl pallet_transaction_payment::Config for Test {
}
parameter_types! {
pub const MaxLocks: u32 = 50;
pub const MaxReserves: u32 = 2;
}
impl Config for Test {
type Balance = u64;
@@ -103,6 +104,8 @@ impl Config for Test {
super::AccountData<u64>,
>;
type MaxLocks = MaxLocks;
type MaxReserves = MaxReserves;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
@@ -106,6 +106,7 @@ impl OnUnbalanced<NegativeImbalance<Test>> for OnDustRemoval {
}
parameter_types! {
pub const MaxLocks: u32 = 50;
pub const MaxReserves: u32 = 2;
}
impl Config for Test {
type Balance = u64;
@@ -119,6 +120,8 @@ impl Config for Test {
super::AccountData<u64>,
>;
type MaxLocks = MaxLocks;
type MaxReserves = MaxReserves;
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
+2
View File
@@ -88,6 +88,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -237,6 +237,8 @@ impl frame_system::Config for Test {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -123,6 +123,8 @@ parameter_types! {
pub const MaxLocks: u32 = 10;
}
impl pallet_balances::Config for Test {
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type MaxLocks = MaxLocks;
type Balance = u64;
type Event = Event;
@@ -237,6 +237,8 @@ impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
@@ -1159,6 +1159,8 @@ mod tests {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Pallet<Test>;
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
+2
View File
@@ -66,6 +66,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -83,6 +83,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -721,6 +721,8 @@ mod tests {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
+2
View File
@@ -85,6 +85,8 @@ impl pallet_balances::Config for Test {
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
parameter_types! {
+2
View File
@@ -150,6 +150,8 @@ parameter_types! {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u128;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -83,6 +83,8 @@ impl pallet_balances::Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
parameter_types! {
+2
View File
@@ -77,6 +77,8 @@ parameter_types! {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -87,6 +87,8 @@ parameter_types! {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -78,6 +78,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -304,6 +304,8 @@ mod tests {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
@@ -72,6 +72,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -80,6 +80,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -79,6 +79,8 @@ parameter_types! {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u128;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -84,6 +84,8 @@ impl frame_system::Config for Test {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
@@ -74,6 +74,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -96,6 +96,8 @@ impl frame_system::Config for Test {
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
@@ -71,6 +71,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type Event = Event;
type DustRemoval = ();
+2
View File
@@ -155,6 +155,8 @@ impl frame_system::Config for Test {
}
impl pallet_balances::Config for Test {
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type Event = Event;
type DustRemoval = ();
+2 -1
View File
@@ -23,7 +23,8 @@ pub mod tokens;
pub use tokens::fungible;
pub use tokens::fungibles;
pub use tokens::currency::{
Currency, LockIdentifier, LockableCurrency, ReservableCurrency, VestingSchedule,
Currency, LockIdentifier, LockableCurrency, ReservableCurrency, NamedReservableCurrency,
VestingSchedule,
};
pub use tokens::imbalance::{Imbalance, OnUnbalanced, SignedImbalance};
pub use tokens::{ExistenceRequirement, WithdrawReasons, BalanceStatus};
@@ -25,7 +25,7 @@ use super::imbalance::{Imbalance, SignedImbalance};
use frame_support::traits::MaxEncodedLen;
mod reservable;
pub use reservable::ReservableCurrency;
pub use reservable::{ReservableCurrency, NamedReservableCurrency};
mod lockable;
pub use lockable::{LockableCurrency, VestingSchedule, LockIdentifier};
@@ -81,3 +81,116 @@ pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
status: BalanceStatus,
) -> Result<Self::Balance, DispatchError>;
}
pub trait NamedReservableCurrency<AccountId>: ReservableCurrency<AccountId> {
/// An identifier for a reserve. Used for disambiguating different reserves so that
/// they can be individually replaced or removed.
type ReserveIdentifier;
/// 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 the reserve balance of `who`
/// is less than `value`, then a non-zero second item will be returned.
fn slash_reserved_named(
id: &Self::ReserveIdentifier,
who: &AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance);
/// The amount of the balance of a given account that is externally reserved; this can still get
/// slashed, but gets slashed last of all.
///
/// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens
/// that are still 'owned' by the account holder, but which are suspendable.
///
/// When this balance falls below the value of `ExistentialDeposit`, then this 'reserve account'
/// is deleted: specifically, `ReservedBalance`.
///
/// `system::AccountNonce` is also deleted if `FreeBalance` is also zero (it also gets
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
fn reserved_balance_named(id: &Self::ReserveIdentifier, 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
/// be returned to notify of this. This is different behavior than `unreserve`.
fn reserve_named(id: &Self::ReserveIdentifier, who: &AccountId, value: Self::Balance) -> DispatchResult;
/// Moves up to `value` from reserved balance to free balance. This function cannot fail.
///
/// As much funds up to `value` will be moved as possible. If the reserve balance of `who`
/// is less than `value`, then the remaining amount will be returned.
///
/// # NOTES
///
/// - This is different from `reserve`.
/// - If the remaining reserved balance is less than `ExistentialDeposit`, it will
/// invoke `on_reserved_too_low` and could reap the account.
fn unreserve_named(id: &Self::ReserveIdentifier, who: &AccountId, value: Self::Balance) -> Self::Balance;
/// 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. 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_named(
id: &Self::ReserveIdentifier,
slashed: &AccountId,
beneficiary: &AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> Result<Self::Balance, DispatchError>;
/// Ensure the reserved balance is equal to `value`.
///
/// This will reserve extra amount of current reserved balance is less than `value`.
/// And unreserve if current reserved balance is greater than `value`.
fn ensure_reserved_named(id: &Self::ReserveIdentifier, who: &AccountId, value: Self::Balance) -> DispatchResult {
let current = Self::reserved_balance_named(id, who);
if current > value {
// we always have enough balance to unreserve here
Self::unreserve_named(id, who, current - value);
Ok(())
} else if value > current {
// we checked value > current
Self::reserve_named(id, who, value - current)
} else { // current == value
Ok(())
}
}
/// Unreserve all the named reserved balances, returning unreserved amount.
///
/// Is a no-op if the value to be unreserved is zero.
fn unreserve_all_named(id: &Self::ReserveIdentifier, who: &AccountId) -> Self::Balance {
let value = Self::reserved_balance_named(id, who);
Self::slash_reserved_named(id, who, value);
value
}
/// Slash all the reserved balance, returning the negative imbalance created.
///
/// Is a no-op if the value to be slashed is zero.
fn slash_all_reserved_named(id: &Self::ReserveIdentifier, who: &AccountId) -> Self::NegativeImbalance {
let value = Self::reserved_balance_named(id, who);
Self::slash_reserved_named(id, who, value).0
}
/// Move all the named reserved balance of one account into the balance of another, according to `status`.
/// If `status` is `Reserved`, the balance will be reserved with given `id`.
///
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_all_reserved_named(
id: &Self::ReserveIdentifier,
slashed: &AccountId,
beneficiary: &AccountId,
status: BalanceStatus,
) -> DispatchResult {
let value = Self::reserved_balance_named(id, slashed);
Self::repatriate_reserved_named(id, slashed, beneficiary, value, status).map(|_| ())
}
}
+2
View File
@@ -87,6 +87,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
@@ -702,6 +702,8 @@ mod tests {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
+2
View File
@@ -83,6 +83,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type Event = Event;
type DustRemoval = ();
+3
View File
@@ -70,6 +70,7 @@ impl frame_system::Config for Test {
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const MaxReserves: u32 = 50;
}
impl pallet_balances::Config for Test {
@@ -80,6 +81,8 @@ impl pallet_balances::Config for Test {
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = MaxReserves;
type ReserveIdentifier = [u8; 8];
}
parameter_types! {
+2
View File
@@ -118,6 +118,8 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
+2
View File
@@ -473,6 +473,8 @@ mod tests {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type WeightInfo = ();
}
parameter_types! {