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 ) ]
565594pub 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+
16511870impl < T : Config < I > , I : ' static > LockableCurrency < T :: AccountId > for Pallet < T , I >
16521871where
16531872 T :: Balance : MaybeSerializeDeserialize + Debug
0 commit comments