diff --git a/currencies/src/lib.rs b/currencies/src/lib.rs index 90de7d62e..b60c2573f 100644 --- a/currencies/src/lib.rs +++ b/currencies/src/lib.rs @@ -44,7 +44,8 @@ use frame_support::{ pallet_prelude::*, traits::{ Currency as PalletCurrency, ExistenceRequirement, Get, Imbalance, LockableCurrency as PalletLockableCurrency, - ReservableCurrency as PalletReservableCurrency, WithdrawReasons, + NamedReservableCurrency as PalletNamedReservableCurrency, ReservableCurrency as PalletReservableCurrency, + WithdrawReasons, }, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; @@ -53,6 +54,7 @@ use orml_traits::{ currency::TransferAll, BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, + NamedBasicReservableCurrency, NamedMultiReservableCurrency, }; use orml_utilities::with_transaction_result; use sp_runtime::{ @@ -78,17 +80,22 @@ pub mod module { <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; pub(crate) type AmountOf = <::MultiCurrency as MultiCurrencyExtended<::AccountId>>::Amount; + pub(crate) type ReserveIdentifierOf = <::MultiCurrency as NamedMultiReservableCurrency< + ::AccountId, + >>::ReserveIdentifier; #[pallet::config] pub trait Config: frame_system::Config { type MultiCurrency: TransferAll + MultiCurrencyExtended + MultiLockableCurrency - + MultiReservableCurrency; + + MultiReservableCurrency + + NamedMultiReservableCurrency; type NativeCurrency: BasicCurrencyExtended, Amount = AmountOf> + BasicLockableCurrency> - + BasicReservableCurrency>; + + BasicReservableCurrency> + + NamedBasicReservableCurrency, Balance = BalanceOf>; #[pallet::constant] type GetNativeCurrencyId: Get>; @@ -368,6 +375,76 @@ impl MultiReservableCurrency for Pallet { } } +impl NamedMultiReservableCurrency for Pallet { + type ReserveIdentifier = ReserveIdentifierOf; + + fn slash_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if currency_id == T::GetNativeCurrencyId::get() { + T::NativeCurrency::slash_reserved_named(id, who, value) + } else { + T::MultiCurrency::slash_reserved_named(id, currency_id, who, value) + } + } + + fn reserved_balance_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + ) -> Self::Balance { + if currency_id == T::GetNativeCurrencyId::get() { + T::NativeCurrency::reserved_balance_named(id, who) + } else { + T::MultiCurrency::reserved_balance_named(id, currency_id, who) + } + } + + fn reserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if currency_id == T::GetNativeCurrencyId::get() { + T::NativeCurrency::reserve_named(id, who, value) + } else { + T::MultiCurrency::reserve_named(id, currency_id, who, value) + } + } + + fn unreserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if currency_id == T::GetNativeCurrencyId::get() { + T::NativeCurrency::unreserve_named(id, who, value) + } else { + T::MultiCurrency::unreserve_named(id, currency_id, who, value) + } + } + + fn repatriate_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> result::Result { + if currency_id == T::GetNativeCurrencyId::get() { + T::NativeCurrency::repatriate_reserved_named(id, slashed, beneficiary, value, status) + } else { + T::MultiCurrency::repatriate_reserved_named(id, currency_id, slashed, beneficiary, value, status) + } + } +} + pub struct Currency(marker::PhantomData, marker::PhantomData); impl BasicCurrency for Currency @@ -491,6 +568,50 @@ where } } +impl NamedBasicReservableCurrency> for Currency +where + T: Config, + GetCurrencyId: Get>, +{ + fn slash_reserved_named(id: &ReserveIdentifierOf, who: &T::AccountId, value: Self::Balance) -> Self::Balance { + as NamedMultiReservableCurrency>::slash_reserved_named( + id, + GetCurrencyId::get(), + who, + value, + ) + } + + fn reserved_balance_named(id: &ReserveIdentifierOf, who: &T::AccountId) -> Self::Balance { + as NamedMultiReservableCurrency>::reserved_balance_named(id, GetCurrencyId::get(), who) + } + + fn reserve_named(id: &ReserveIdentifierOf, who: &T::AccountId, value: Self::Balance) -> DispatchResult { + as NamedMultiReservableCurrency>::reserve_named(id, GetCurrencyId::get(), who, value) + } + + fn unreserve_named(id: &ReserveIdentifierOf, who: &T::AccountId, value: Self::Balance) -> Self::Balance { + as NamedMultiReservableCurrency>::unreserve_named(id, GetCurrencyId::get(), who, value) + } + + fn repatriate_reserved_named( + id: &ReserveIdentifierOf, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> result::Result { + as NamedMultiReservableCurrency>::repatriate_reserved_named( + id, + GetCurrencyId::get(), + slashed, + beneficiary, + value, + status, + ) + } +} + pub type NativeCurrencyOf = Currency::GetNativeCurrencyId>; /// Adapt other currency traits implementation to `BasicCurrency`. @@ -653,6 +774,41 @@ where } } +// Adapt `frame_support::traits::NamedReservableCurrency` +impl + NamedBasicReservableCurrency for BasicCurrencyAdapter +where + Currency: PalletNamedReservableCurrency, + T: Config, +{ + fn slash_reserved_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> Self::Balance { + let (_, gap) = Currency::slash_reserved_named(id, who, value); + gap + } + + fn reserved_balance_named(id: &ReserveIdentifier, who: &AccountId) -> Self::Balance { + Currency::reserved_balance_named(id, who) + } + + fn reserve_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> DispatchResult { + Currency::reserve_named(id, who, value) + } + + fn unreserve_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> Self::Balance { + Currency::unreserve_named(id, who, value) + } + + fn repatriate_reserved_named( + id: &ReserveIdentifier, + slashed: &AccountId, + beneficiary: &AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> result::Result { + Currency::repatriate_reserved_named(id, slashed, beneficiary, value, status) + } +} + impl TransferAll for Pallet { fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { with_transaction_result(|| { diff --git a/traits/src/currency.rs b/traits/src/currency.rs index 1cc34437c..cd0c9d71e 100644 --- a/traits/src/currency.rs +++ b/traits/src/currency.rs @@ -212,8 +212,8 @@ pub trait NamedMultiReservableCurrency: MultiReservableCurrency: BasicCurrency { ) -> result::Result; } +/// A fungible single currency system where funds can be reserved from the user +/// with an identifier. +pub trait NamedBasicReservableCurrency: BasicReservableCurrency { + /// 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 excess will be + /// returned. + fn slash_reserved_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> 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: &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: &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: &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: &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: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> DispatchResult { + let current = Self::reserved_balance_named(id, who); + match current.cmp(&value) { + Ordering::Less => { + // we checked value > current + Self::reserve_named(id, who, value - current) + } + Ordering::Equal => Ok(()), + Ordering::Greater => { + // we always have enough balance to unreserve here + Self::unreserve_named(id, who, 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: &ReserveIdentifier, who: &AccountId) -> Self::Balance { + let value = Self::reserved_balance_named(id, who); + Self::unreserve_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: &ReserveIdentifier, who: &AccountId) -> Self::Balance { + let value = Self::reserved_balance_named(id, who); + Self::slash_reserved_named(id, who, value) + } + + /// 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: &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(|_| ()) + } +} + /// Handler for account which has dust, need to burn or recycle it pub trait OnDust { fn on_dust(who: &AccountId, currency_id: CurrencyId, amount: Balance); diff --git a/traits/src/lib.rs b/traits/src/lib.rs index f2642558b..15176626d 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -15,7 +15,7 @@ pub use auction::{Auction, AuctionHandler, AuctionInfo, OnNewBidResult}; pub use currency::{ BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, - NamedMultiReservableCurrency, OnDust, + NamedBasicReservableCurrency, NamedMultiReservableCurrency, OnDust, }; pub use data_provider::{DataFeeder, DataProvider, DataProviderExtended}; pub use get_by_key::GetByKey;