mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 04:41:02 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -721,6 +721,8 @@ mod tests {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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! {
|
||||
|
||||
Reference in New Issue
Block a user