diff --git a/substrate/bin/node-template/runtime/src/lib.rs b/substrate/bin/node-template/runtime/src/lib.rs index b928f8d341..e51a190ae9 100644 --- a/substrate/bin/node-template/runtime/src/lib.rs +++ b/substrate/bin/node-template/runtime/src/lib.rs @@ -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. diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 97975c55e9..6c38bf41ec 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -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; diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 0b7aa33983..cf99eed703 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -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! { diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index cc2849f5bd..f41874a1ee 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -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; diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 40ee782e72..bd99531542 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -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; diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 04dacc7858..23c5cc97d0 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -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; + + /// The maximum number of named reserves that can exist on an account. + type MaxReserves: Get; + + /// 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, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec, 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 { pub reasons: Reasons, } +/// Store named reserved balance. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)] +pub struct ReserveData { + /// 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 { @@ -575,6 +604,7 @@ pub struct AccountData { /// /// 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, I: 'static> ReservableCurrency for Pallet } } +impl, I: 'static> NamedReservableCurrency for Pallet 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::::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::::TooManyReserves)?; + }, + }; + >::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::::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 = >::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::::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) = >::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 { + 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::::try_mutate(slashed, |reserves| -> Result { + 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::::try_mutate(beneficiary, |reserves| -> Result { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let remain = >::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 = >::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::::TooManyReserves)?; + + Ok(actual) + }, + } + })? + } else { + let remain = >::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, I: 'static> LockableCurrency for Pallet where T::Balance: MaybeSerializeDeserialize + Debug diff --git a/substrate/frame/balances/src/tests.rs b/substrate/frame/balances/src/tests.rs index 38a49df37b..9589fb2580 100644 --- a/substrate/frame/balances/src/tests.rs +++ b/substrate/frame/balances/src/tests.rs @@ -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::::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::::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); + }); + } } } diff --git a/substrate/frame/balances/src/tests_composite.rs b/substrate/frame/balances/src/tests_composite.rs index b4bdb13fbb..ff10607bce 100644 --- a/substrate/frame/balances/src/tests_composite.rs +++ b/substrate/frame/balances/src/tests_composite.rs @@ -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; type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/balances/src/tests_local.rs b/substrate/frame/balances/src/tests_local.rs index ac5adfd8d1..afa6876457 100644 --- a/substrate/frame/balances/src/tests_local.rs +++ b/substrate/frame/balances/src/tests_local.rs @@ -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, >; type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/balances/src/tests_reentrancy.rs b/substrate/frame/balances/src/tests_reentrancy.rs index 91ad51446c..a12da8f001 100644 --- a/substrate/frame/balances/src/tests_reentrancy.rs +++ b/substrate/frame/balances/src/tests_reentrancy.rs @@ -106,6 +106,7 @@ impl OnUnbalanced> 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, >; type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index e90b1f565a..04cc06ef64 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 6fdaecebd8..75ea8d9bd8 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index e8877e2774..1c68715d49 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -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; diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 2fb7927d98..830df099b5 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -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 = (); } diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index 9efe8c8260..ab2edfaac6 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1159,6 +1159,8 @@ mod tests { type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/elections/src/mock.rs b/substrate/frame/elections/src/mock.rs index 896fd40020..b5dd15ce81 100644 --- a/substrate/frame/elections/src/mock.rs +++ b/substrate/frame/elections/src/mock.rs @@ -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; diff --git a/substrate/frame/example/src/tests.rs b/substrate/frame/example/src/tests.rs index 496cd5701f..f4658c2807 100644 --- a/substrate/frame/example/src/tests.rs +++ b/substrate/frame/example/src/tests.rs @@ -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; diff --git a/substrate/frame/executive/src/lib.rs b/substrate/frame/executive/src/lib.rs index c630fb6399..593b8db92c 100644 --- a/substrate/frame/executive/src/lib.rs +++ b/substrate/frame/executive/src/lib.rs @@ -721,6 +721,8 @@ mod tests { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/gilt/src/mock.rs b/substrate/frame/gilt/src/mock.rs index f5c0d3a5aa..fb88851549 100644 --- a/substrate/frame/gilt/src/mock.rs +++ b/substrate/frame/gilt/src/mock.rs @@ -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! { diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 1ab28f7752..df55f6037e 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -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; diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 2bfad79640..262b3211b6 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -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! { diff --git a/substrate/frame/indices/src/mock.rs b/substrate/frame/indices/src/mock.rs index efaaa02124..bd9e9c33af 100644 --- a/substrate/frame/indices/src/mock.rs +++ b/substrate/frame/indices/src/mock.rs @@ -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; diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index b668fba859..07593c17e5 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/multisig/src/tests.rs b/substrate/frame/multisig/src/tests.rs index cf457f6db6..69f7cb17b0 100644 --- a/substrate/frame/multisig/src/tests.rs +++ b/substrate/frame/multisig/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/nicks/src/lib.rs b/substrate/frame/nicks/src/lib.rs index 4372fd326c..a76d4506f9 100644 --- a/substrate/frame/nicks/src/lib.rs +++ b/substrate/frame/nicks/src/lib.rs @@ -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 = (); diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index 9047120923..7230c1215a 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/proxy/src/tests.rs b/substrate/frame/proxy/src/tests.rs index fd632b91bb..a2cb00d0cc 100644 --- a/substrate/frame/proxy/src/tests.rs +++ b/substrate/frame/proxy/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 72dbc29fd7..6a0abab2bd 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -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; diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index 8f7acd3200..44a28234a2 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index cf2fa8a07c..87d1242812 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index aa46d40a14..5e156caa28 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/staking/fuzzer/src/mock.rs b/substrate/frame/staking/fuzzer/src/mock.rs index 11d810a26e..4ac1a10364 100644 --- a/substrate/frame/staking/fuzzer/src/mock.rs +++ b/substrate/frame/staking/fuzzer/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 4027ac1f67..8930a6bfd6 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -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 = (); diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 52def92ef9..96e1cece55 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -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}; diff --git a/substrate/frame/support/src/traits/tokens/currency.rs b/substrate/frame/support/src/traits/tokens/currency.rs index a18e0b6593..a00e99b0c4 100644 --- a/substrate/frame/support/src/traits/tokens/currency.rs +++ b/substrate/frame/support/src/traits/tokens/currency.rs @@ -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}; diff --git a/substrate/frame/support/src/traits/tokens/currency/reservable.rs b/substrate/frame/support/src/traits/tokens/currency/reservable.rs index 14ea1d3a16..17dee7a8ae 100644 --- a/substrate/frame/support/src/traits/tokens/currency/reservable.rs +++ b/substrate/frame/support/src/traits/tokens/currency/reservable.rs @@ -81,3 +81,116 @@ pub trait ReservableCurrency: Currency { status: BalanceStatus, ) -> Result; } + +pub trait NamedReservableCurrency: ReservableCurrency { + /// 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; + + /// 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(|_| ()) + } +} diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 3b11e105c6..6b144273ca 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/transaction-payment/src/lib.rs b/substrate/frame/transaction-payment/src/lib.rs index 3cf79caef7..2b1ad2db9a 100644 --- a/substrate/frame/transaction-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/src/lib.rs @@ -702,6 +702,8 @@ mod tests { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index cb6d4903a5..408f99f29e 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -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 = (); diff --git a/substrate/frame/uniques/src/mock.rs b/substrate/frame/uniques/src/mock.rs index 1040821d0d..336a262358 100644 --- a/substrate/frame/uniques/src/mock.rs +++ b/substrate/frame/uniques/src/mock.rs @@ -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! { diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 02b878e799..aa6bea8a27 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -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; diff --git a/substrate/frame/vesting/src/lib.rs b/substrate/frame/vesting/src/lib.rs index c8156e08c6..8c520b7158 100644 --- a/substrate/frame/vesting/src/lib.rs +++ b/substrate/frame/vesting/src/lib.rs @@ -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! {