Skip to content
This repository was archived by the owner on May 22, 2023. It is now read-only.

Commit 486a88b

Browse files
xlcshawntabrizigavofyorkshaunxw
authored andcommitted
Named reserve (paritytech#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>
1 parent 78e74f8 commit 486a88b

41 files changed

Lines changed: 536 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/node/runtime/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,13 @@ parameter_types! {
375375
// For weight estimation, we assume that the most locks on an individual account will be 50.
376376
// This number may need to be adjusted in the future if this assumption no longer holds true.
377377
pub const MaxLocks: u32 = 50;
378+
pub const MaxReserves: u32 = 50;
378379
}
379380

380381
impl pallet_balances::Config for Runtime {
381382
type MaxLocks = MaxLocks;
383+
type MaxReserves = MaxReserves;
384+
type ReserveIdentifier = [u8; 8];
382385
type Balance = Balance;
383386
type DustRemoval = ();
384387
type Event = Event;

frame/assets/src/mock.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ impl pallet_balances::Config for Test {
8080
type AccountStore = System;
8181
type WeightInfo = ();
8282
type MaxLocks = ();
83+
type MaxReserves = ();
84+
type ReserveIdentifier = [u8; 8];
8385
}
8486

8587
parameter_types! {

frame/atomic-swap/src/tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ parameter_types! {
6060
}
6161
impl pallet_balances::Config for Test {
6262
type MaxLocks = ();
63+
type MaxReserves = ();
64+
type ReserveIdentifier = [u8; 8];
6365
type Balance = u64;
6466
type DustRemoval = ();
6567
type Event = Event;

frame/babe/src/mock.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ parameter_types! {
155155

156156
impl pallet_balances::Config for Test {
157157
type MaxLocks = ();
158+
type MaxReserves = ();
159+
type ReserveIdentifier = [u8; 8];
158160
type Balance = u128;
159161
type DustRemoval = ();
160162
type Event = Event;

frame/balances/src/lib.rs

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
//! - [`Currency`](frame_support::traits::Currency): Functions for dealing with a
7474
//! fungible assets system.
7575
//! - [`ReservableCurrency`](frame_support::traits::ReservableCurrency):
76+
//! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency):
7677
//! Functions for dealing with assets that can be reserved from an account.
7778
//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for
7879
//! dealing with accounts that allow liquidity restrictions.
@@ -163,9 +164,9 @@ use frame_support::{
163164
traits::{
164165
Currency, OnUnbalanced, TryDrop, StoredMap, MaxEncodedLen,
165166
WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
166-
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
167-
ExistenceRequirement::AllowDeath,
168-
tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status}
167+
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::{AllowDeath, KeepAlive},
168+
NamedReservableCurrency,
169+
tokens::{fungible, DepositConsequence, WithdrawConsequence, BalanceStatus as Status},
169170
}
170171
};
171172
#[cfg(feature = "std")]
@@ -214,6 +215,12 @@ pub mod pallet {
214215
/// The maximum number of locks that should exist on an account.
215216
/// Not strictly enforced, but used for weight estimation.
216217
type MaxLocks: Get<u32>;
218+
219+
/// The maximum number of named reserves that can exist on an account.
220+
type MaxReserves: Get<u32>;
221+
222+
/// The id type for named reserves.
223+
type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
217224
}
218225

219226
#[pallet::pallet]
@@ -409,6 +416,8 @@ pub mod pallet {
409416
ExistingVestingSchedule,
410417
/// Beneficiary account must pre-exist
411418
DeadAccount,
419+
/// Number of named reserves exceed MaxReserves
420+
TooManyReserves,
412421
}
413422

414423
/// The total units issued in the system.
@@ -444,6 +453,17 @@ pub mod pallet {
444453
ConstU32<300_000>,
445454
>;
446455

456+
/// Named reserves on some account balances.
457+
#[pallet::storage]
458+
#[pallet::getter(fn reserves)]
459+
pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
460+
_,
461+
Blake2_128Concat,
462+
T::AccountId,
463+
BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
464+
ValueQuery
465+
>;
466+
447467
/// Storage version of the pallet.
448468
///
449469
/// This is set to v2.0.0 for new networks.
@@ -560,6 +580,15 @@ pub struct BalanceLock<Balance> {
560580
pub reasons: Reasons,
561581
}
562582

583+
/// Store named reserved balance.
584+
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)]
585+
pub struct ReserveData<ReserveIdentifier, Balance> {
586+
/// The identifier for the named reserve.
587+
pub id: ReserveIdentifier,
588+
/// The amount of the named reserve.
589+
pub amount: Balance,
590+
}
591+
563592
/// All balance information for an account.
564593
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen)]
565594
pub struct AccountData<Balance> {
@@ -575,6 +604,7 @@ pub struct AccountData<Balance> {
575604
///
576605
/// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens
577606
/// that are still 'owned' by the account holder, but which are suspendable.
607+
/// This includes named reserve and unnamed reserve.
578608
pub reserved: Balance,
579609
/// The amount that `free` may not drop below when withdrawing for *anything except transaction
580610
/// fee payment*.
@@ -1648,6 +1678,195 @@ impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I>
16481678
}
16491679
}
16501680

1681+
impl<T: Config<I>, I: 'static> NamedReservableCurrency<T::AccountId> for Pallet<T, I> where
1682+
T::Balance: MaybeSerializeDeserialize + Debug
1683+
{
1684+
type ReserveIdentifier = T::ReserveIdentifier;
1685+
1686+
fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance {
1687+
let reserves = Self::reserves(who);
1688+
reserves
1689+
.binary_search_by_key(id, |data| data.id)
1690+
.map(|index| reserves[index].amount)
1691+
.unwrap_or_default()
1692+
}
1693+
1694+
/// Move `value` from the free balance from `who` to a named reserve balance.
1695+
///
1696+
/// Is a no-op if value to be reserved is zero.
1697+
fn reserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> DispatchResult {
1698+
if value.is_zero() { return Ok(()) }
1699+
1700+
Reserves::<T, I>::try_mutate(who, |reserves| -> DispatchResult {
1701+
match reserves.binary_search_by_key(id, |data| data.id) {
1702+
Ok(index) => {
1703+
// this add can't overflow but just to be defensive.
1704+
reserves[index].amount = reserves[index].amount.saturating_add(value);
1705+
},
1706+
Err(index) => {
1707+
reserves.try_insert(index, ReserveData {
1708+
id: id.clone(),
1709+
amount: value
1710+
}).map_err(|_| Error::<T, I>::TooManyReserves)?;
1711+
},
1712+
};
1713+
<Self as ReservableCurrency<_>>::reserve(who, value)?;
1714+
Ok(())
1715+
})
1716+
}
1717+
1718+
/// Unreserve some funds, returning any amount that was unable to be unreserved.
1719+
///
1720+
/// Is a no-op if the value to be unreserved is zero.
1721+
fn unreserve_named(id: &Self::ReserveIdentifier, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
1722+
if value.is_zero() { return Zero::zero() }
1723+
1724+
Reserves::<T, I>::mutate_exists(who, |maybe_reserves| -> Self::Balance {
1725+
if let Some(reserves) = maybe_reserves.as_mut() {
1726+
match reserves.binary_search_by_key(id, |data| data.id) {
1727+
Ok(index) => {
1728+
let to_change = cmp::min(reserves[index].amount, value);
1729+
1730+
let remain = <Self as ReservableCurrency<_>>::unreserve(who, to_change);
1731+
1732+
// remain should always be zero but just to be defensive here
1733+
let actual = to_change.saturating_sub(remain);
1734+
1735+
// `actual <= to_change` and `to_change <= amount`; qed;
1736+
reserves[index].amount -= actual;
1737+
1738+
if reserves[index].amount.is_zero() {
1739+
if reserves.len() == 1 {
1740+
// no more named reserves
1741+
*maybe_reserves = None;
1742+
} else {
1743+
// remove this named reserve
1744+
reserves.remove(index);
1745+
}
1746+
}
1747+
1748+
value - actual
1749+
},
1750+
Err(_) => {
1751+
value
1752+
},
1753+
}
1754+
} else {
1755+
value
1756+
}
1757+
})
1758+
}
1759+
1760+
/// Slash from reserved balance, returning the negative imbalance created,
1761+
/// and any amount that was unable to be slashed.
1762+
///
1763+
/// Is a no-op if the value to be slashed is zero.
1764+
fn slash_reserved_named(
1765+
id: &Self::ReserveIdentifier,
1766+
who: &T::AccountId,
1767+
value: Self::Balance
1768+
) -> (Self::NegativeImbalance, Self::Balance) {
1769+
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
1770+
1771+
Reserves::<T, I>::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) {
1772+
match reserves.binary_search_by_key(id, |data| data.id) {
1773+
Ok(index) => {
1774+
let to_change = cmp::min(reserves[index].amount, value);
1775+
1776+
let (imb, remain) = <Self as ReservableCurrency<_>>::slash_reserved(who, to_change);
1777+
1778+
// remain should always be zero but just to be defensive here
1779+
let actual = to_change.saturating_sub(remain);
1780+
1781+
// `actual <= to_change` and `to_change <= amount`; qed;
1782+
reserves[index].amount -= actual;
1783+
1784+
(imb, value - actual)
1785+
},
1786+
Err(_) => {
1787+
(NegativeImbalance::zero(), value)
1788+
},
1789+
}
1790+
})
1791+
}
1792+
1793+
/// Move the reserved balance of one account into the balance of another, according to `status`.
1794+
/// If `status` is `Reserved`, the balance will be reserved with given `id`.
1795+
///
1796+
/// Is a no-op if:
1797+
/// - the value to be moved is zero; or
1798+
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
1799+
fn repatriate_reserved_named(
1800+
id: &Self::ReserveIdentifier,
1801+
slashed: &T::AccountId,
1802+
beneficiary: &T::AccountId,
1803+
value: Self::Balance,
1804+
status: Status,
1805+
) -> Result<Self::Balance, DispatchError> {
1806+
if value.is_zero() { return Ok(Zero::zero()) }
1807+
1808+
if slashed == beneficiary {
1809+
return match status {
1810+
Status::Free => Ok(Self::unreserve_named(id, slashed, value)),
1811+
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))),
1812+
};
1813+
}
1814+
1815+
Reserves::<T, I>::try_mutate(slashed, |reserves| -> Result<Self::Balance, DispatchError> {
1816+
match reserves.binary_search_by_key(id, |data| data.id) {
1817+
Ok(index) => {
1818+
let to_change = cmp::min(reserves[index].amount, value);
1819+
1820+
let actual = if status == Status::Reserved {
1821+
// make it the reserved under same identifier
1822+
Reserves::<T, I>::try_mutate(beneficiary, |reserves| -> Result<T::Balance, DispatchError> {
1823+
match reserves.binary_search_by_key(id, |data| data.id) {
1824+
Ok(index) => {
1825+
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
1826+
1827+
// remain should always be zero but just to be defensive here
1828+
let actual = to_change.saturating_sub(remain);
1829+
1830+
// this add can't overflow but just to be defensive.
1831+
reserves[index].amount = reserves[index].amount.saturating_add(actual);
1832+
1833+
Ok(actual)
1834+
},
1835+
Err(index) => {
1836+
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
1837+
1838+
// remain should always be zero but just to be defensive here
1839+
let actual = to_change.saturating_sub(remain);
1840+
1841+
reserves.try_insert(index, ReserveData {
1842+
id: id.clone(),
1843+
amount: actual
1844+
}).map_err(|_| Error::<T, I>::TooManyReserves)?;
1845+
1846+
Ok(actual)
1847+
},
1848+
}
1849+
})?
1850+
} else {
1851+
let remain = <Self as ReservableCurrency<_>>::repatriate_reserved(slashed, beneficiary, to_change, status)?;
1852+
1853+
// remain should always be zero but just to be defensive here
1854+
to_change.saturating_sub(remain)
1855+
};
1856+
1857+
// `actual <= to_change` and `to_change <= amount`; qed;
1858+
reserves[index].amount -= actual;
1859+
1860+
Ok(value - actual)
1861+
},
1862+
Err(_) => {
1863+
Ok(value)
1864+
},
1865+
}
1866+
})
1867+
}
1868+
}
1869+
16511870
impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
16521871
where
16531872
T::Balance: MaybeSerializeDeserialize + Debug

0 commit comments

Comments
 (0)