pallet-asset-conversion: Swap Credit (#1677)

Introduces a swap implementation that allows the exchange of a credit
(aka Negative Imbalance) of one asset for a credit of another asset.

This is particularly useful when a credit swap is required but may not
have sufficient value to meet the ED constraint, hence cannot be
deposited to temp account before. An example use case is when XCM fees
are paid using an asset held in the XCM executor registry and has to be
swapped for native currency.

Additional Updates:
- encapsulates the existing `Swap` trait impl within a transactional
context, since partial storage mutation is possible when an error
occurs;
- supplied `Currency` and `Assets` impls must be implemented over the
same `Balance` type, the `AssetBalance` generic type is dropped. This
helps to avoid numerous type conversion and overflow cases. If those
types are different it should be handled outside of the pallet;
- `Box` asset kind on a pallet level, unbox on a runtime level - here
[why](https://substrate.stackexchange.com/questions/10039/boxed-argument-of-a-dispatchable/10103#10103);
- `path` uses `Vec` now, instead of `BoundedVec` since it is never used
in PoV;
- removes the `Transfer` event due to it's redundancy with the events
emitted by `fungible/s` implementations;
- modifies the `SwapExecuted` event type;

related issue: 
- https://github.com/paritytech/polkadot-sdk/issues/105

related PRs:
- (required for) https://github.com/paritytech/polkadot-sdk/pull/1845
- (caused) https://github.com/paritytech/polkadot-sdk/pull/1717

// DONE make the pallet work only with `fungibles` trait and make it
free from the concept of a `native` asset -
https://github.com/paritytech/polkadot-sdk/issues/1842

---------

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
Muharem
2023-12-19 17:31:18 +01:00
committed by GitHub
parent 84d6342cd2
commit 5ce04514eb
20 changed files with 3932 additions and 647 deletions
@@ -21,7 +21,6 @@ use super::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_support::{
assert_ok,
storage::bounded_vec::BoundedVec,
traits::{
fungible::{Inspect as InspectFungible, Mutate as MutateFungible, Unbalanced},
fungibles::{Create, Inspect, Mutate},
@@ -49,7 +48,7 @@ where
fn create_asset<T: Config>(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
{
@@ -70,7 +69,7 @@ fn create_asset_and_pool<T: Config>(
asset2: &T::MultiAssetId,
) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
T::PoolAssetId: Into<u32>,
@@ -80,8 +79,8 @@ where
assert_ok!(AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone()
Box::new(asset1.clone()),
Box::new(asset2.clone())
));
let lp_token = get_lp_token_id::<T>();
@@ -99,7 +98,6 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
benchmarks! {
where_clause {
where
T::AssetBalance: From<u128> + Into<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Balance: From<u128> + Into<u128>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
@@ -110,7 +108,7 @@ benchmarks! {
let asset1 = T::MultiAssetIdConverter::get_native();
let asset2 = T::BenchmarkHelper::multiasset_id(0);
let (caller, _) = create_asset::<T>(&asset2);
}: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()))
verify {
let lp_token = get_lp_token_id::<T>();
let pool_id = (asset1.clone(), asset2.clone());
@@ -128,7 +126,7 @@ benchmarks! {
let (lp_token, caller, _) = create_asset_and_pool::<T>(&asset1, &asset2);
let ed: u128 = T::Currency::minimum_balance().into();
let add_amount = 1000 + ed;
}: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone(), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone())
verify {
let pool_id = (asset1.clone(), asset2.clone());
let lp_minted = AssetConversion::<T>::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into();
@@ -157,8 +155,8 @@ benchmarks! {
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
add_amount.into(),
1000.into(),
0.into(),
@@ -166,7 +164,7 @@ benchmarks! {
caller.clone(),
)?;
let total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
}: _(SystemOrigin::Signed(caller.clone()), asset1, asset2, remove_lp_amount.into(), 0.into(), 0.into(), caller.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1), Box::new(asset2), remove_lp_amount.into(), 0.into(), 0.into(), caller.clone())
verify {
let new_total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
assert_eq!(
@@ -185,8 +183,8 @@ benchmarks! {
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset1.clone(),
Box::new(native.clone()),
Box::new(asset1.clone()),
(100 * ed).into(),
200.into(),
0.into(),
@@ -199,29 +197,45 @@ benchmarks! {
// if we only allow the native-asset pools, then the worst case scenario would be to swap
// asset1-native-asset2
if !T::AllowMultiAssetPools::get() {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset2.clone(),
Box::new(native.clone()),
Box::new(asset2.clone()),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![asset1.clone(), native.clone(), asset2.clone()];
path = vec![
Box::new(asset1.clone()),
Box::new(native.clone()),
Box::new(asset2.clone())
];
swap_amount = 100.into();
} else {
let asset3 = T::BenchmarkHelper::multiasset_id(3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
)?;
let (_, _) = create_asset::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
200.into(),
2000.into(),
0.into(),
@@ -230,19 +244,22 @@ benchmarks! {
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset2.clone(),
asset3.clone(),
Box::new(asset2.clone()),
Box::new(asset3.clone()),
2000.into(),
2000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()];
path = vec![
Box::new(native.clone()),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
Box::new(asset3.clone())
];
swap_amount = ed.into();
}
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let native_balance = T::Currency::balance(&caller);
let asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller);
}: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone(), false)
@@ -266,8 +283,8 @@ benchmarks! {
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset1.clone(),
Box::new(native.clone()),
Box::new(asset1.clone()),
(1000 * ed).into(),
500.into(),
0.into(),
@@ -279,28 +296,44 @@ benchmarks! {
// if we only allow the native-asset pools, then the worst case scenario would be to swap
// asset1-native-asset2
if !T::AllowMultiAssetPools::get() {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset2.clone(),
Box::new(native.clone()),
Box::new(asset2.clone()),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![asset1.clone(), native.clone(), asset2.clone()];
path = vec![
Box::new(asset1.clone()),
Box::new(native.clone()),
Box::new(asset2.clone())
];
} else {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
)?;
let asset3 = T::BenchmarkHelper::multiasset_id(3);
let (_, _) = create_asset::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
2000.into(),
2000.into(),
0.into(),
@@ -309,18 +342,22 @@ benchmarks! {
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset2.clone(),
asset3.clone(),
Box::new(asset2.clone()),
Box::new(asset3.clone()),
2000.into(),
2000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()];
path = vec![
Box::new(native.clone()),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
Box::new(asset3.clone())
];
}
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller);
let asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller);
}: _(SystemOrigin::Signed(caller.clone()), path.clone(), 100.into(), (1000 * ed).into(), caller.clone(), false)
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -154,7 +154,6 @@ ord_parameter_types! {
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type AssetBalance = <Self as pallet_balances::Config>::Balance;
type AssetId = u32;
type PoolAssetId = u32;
type Assets = Assets;
@@ -169,7 +168,7 @@ impl Config for Test {
type MaxSwapPathLength = ConstU32<4>;
type MintMinLiquidity = ConstU128<100>; // 100 is good enough when the main currency has 12 decimals.
type Balance = u128;
type Balance = <Self as pallet_balances::Config>::Balance;
type HigherPrecisionBalance = sp_core::U256;
type MultiAssetId = NativeOrAssetId<u32>;
@@ -0,0 +1,210 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Traits and implementations for swap between the various asset classes.
use super::*;
/// Trait for providing methods to swap between the various asset classes.
pub trait Swap<AccountId> {
/// Measure units of the asset classes for swapping.
type Balance: Balance;
/// Kind of assets that are going to be swapped.
type MultiAssetId;
/// Returns the upper limit on the length of the swap path.
fn max_path_len() -> u32;
/// Swap exactly `amount_in` of asset `path[0]` for asset `path[last]`.
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
/// the amount desired.
///
/// Withdraws the `path[0]` asset from `sender`, deposits the `path[last]` asset to `send_to`,
/// respecting `keep_alive`.
///
/// If successful, returns the amount of `path[last]` acquired for the `amount_in`.
///
/// This operation is expected to be atomic.
fn swap_exact_tokens_for_tokens(
sender: AccountId,
path: Vec<Self::MultiAssetId>,
amount_in: Self::Balance,
amount_out_min: Option<Self::Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[last]`. If an
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
/// too costly.
///
/// Withdraws `path[0]` asset from `sender`, deposits `path[last]` asset to `send_to`,
/// respecting `keep_alive`.
///
/// If successful returns the amount of the `path[0]` taken to provide `path[last]`.
///
/// This operation is expected to be atomic.
fn swap_tokens_for_exact_tokens(
sender: AccountId,
path: Vec<Self::MultiAssetId>,
amount_out: Self::Balance,
amount_in_max: Option<Self::Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait providing methods to swap between the various asset classes.
pub trait SwapCredit<AccountId> {
/// Measure units of the asset classes for swapping.
type Balance: Balance;
/// Kind of assets that are going to be swapped.
type MultiAssetId;
/// Credit implying a negative imbalance in the system that can be placed into an account or
/// alter the total supply.
type Credit;
/// Returns the upper limit on the length of the swap path.
fn max_path_len() -> u32;
/// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` is
/// provided and the swap can't achieve at least this amount, an error is returned.
///
/// On a successful swap, the function returns the `credit_out` of `path[last]` obtained from
/// the `credit_in`. On failure, it returns an `Err` containing the original `credit_in` and the
/// associated error code.
///
/// This operation is expected to be atomic.
fn swap_exact_tokens_for_tokens(
path: Vec<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out_min: Option<Self::Balance>,
) -> Result<Self::Credit, (Self::Credit, DispatchError)>;
/// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of
/// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target
/// `amount_out`, or an error will occur.
///
/// On success, the function returns a (`credit_out`, `credit_change`) tuple, where `credit_out`
/// represents the acquired amount of the `path[last]` asset, and `credit_change` is the
/// remaining portion from the `credit_in`. On failure, an `Err` with the initial `credit_in`
/// and error code is returned.
///
/// This operation is expected to be atomic.
fn swap_tokens_for_exact_tokens(
path: Vec<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out: Self::Balance,
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>;
}
impl<T: Config> Swap<T::AccountId> for Pallet<T> {
type Balance = T::Balance;
type MultiAssetId = T::MultiAssetId;
fn max_path_len() -> u32 {
T::MaxSwapPathLength::get()
}
fn swap_exact_tokens_for_tokens(
sender: T::AccountId,
path: Vec<Self::MultiAssetId>,
amount_in: Self::Balance,
amount_out_min: Option<Self::Balance>,
send_to: T::AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
let amount_out = with_storage_layer(|| {
Self::do_swap_exact_tokens_for_tokens(
sender,
path,
amount_in,
amount_out_min,
send_to,
keep_alive,
)
})?;
Ok(amount_out.into())
}
fn swap_tokens_for_exact_tokens(
sender: T::AccountId,
path: Vec<Self::MultiAssetId>,
amount_out: Self::Balance,
amount_in_max: Option<Self::Balance>,
send_to: T::AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
let amount_in = with_storage_layer(|| {
Self::do_swap_tokens_for_exact_tokens(
sender,
path,
amount_out,
amount_in_max,
send_to,
keep_alive,
)
})?;
Ok(amount_in.into())
}
}
impl<T: Config> SwapCredit<T::AccountId> for Pallet<T> {
type Balance = T::Balance;
type MultiAssetId = T::MultiAssetId;
type Credit = Credit<T>;
fn max_path_len() -> u32 {
T::MaxSwapPathLength::get()
}
fn swap_exact_tokens_for_tokens(
path: Vec<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out_min: Option<Self::Balance>,
) -> Result<Self::Credit, (Self::Credit, DispatchError)> {
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
let res = Self::do_swap_exact_credit_tokens_for_tokens(path, credit_in, amount_out_min);
match &res {
Ok(_) => TransactionOutcome::Commit(Ok(res)),
// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
// `From<DispatchError>` bound of the `with_transaction` function.
Err(_) => TransactionOutcome::Rollback(Ok(res)),
}
})
// should never map an error since `with_transaction` above never returns it.
.map_err(|_| (Self::Credit::native_zero(), DispatchError::Corruption))?
}
fn swap_tokens_for_exact_tokens(
path: Vec<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out: Self::Balance,
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> {
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
let res = Self::do_swap_credit_tokens_for_exact_tokens(path, credit_in, amount_out);
match &res {
Ok(_) => TransactionOutcome::Commit(Ok(res)),
// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
// `From<DispatchError>` bound of the `with_transaction` function.
Err(_) => TransactionOutcome::Rollback(Ok(res)),
}
})
// should never map an error since `with_transaction` above never returns it.
.map_err(|_| (Self::Credit::native_zero(), DispatchError::Corruption))?
}
}
File diff suppressed because it is too large Load Diff
+106 -37
View File
@@ -27,6 +27,16 @@ use sp_std::{cmp::Ordering, marker::PhantomData};
/// migration.
pub(super) type PoolIdOf<T> = (<T as Config>::MultiAssetId, <T as Config>::MultiAssetId);
/// Represents a swap path with associated asset amounts indicating how much of the asset needs to
/// be deposited to get the following asset's amount withdrawn (this is inclusive of fees).
///
/// Example:
/// Given path [(asset1, amount_in), (asset2, amount_out2), (asset3, amount_out3)], can be resolved:
/// 1. `asset(asset1, amount_in)` take from `user` and move to the pool(asset1, asset2);
/// 2. `asset(asset2, amount_out2)` transfer from pool(asset1, asset2) to pool(asset2, asset3);
/// 3. `asset(asset3, amount_out3)` move from pool(asset2, asset3) to `user`.
pub(super) type BalancePath<T> = Vec<(<T as Config>::MultiAssetId, <T as Config>::Balance)>;
/// Stores the lp_token asset id a particular pool has been assigned.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
pub struct PoolInfo<PoolAssetId> {
@@ -83,43 +93,6 @@ where
}
}
/// Trait for providing methods to swap between the various asset classes.
pub trait Swap<AccountId, Balance, MultiAssetId> {
/// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`.
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
/// the amount desired.
///
/// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`,
/// respecting `keep_alive`.
///
/// If successful, returns the amount of `path[1]` acquired for the `amount_in`.
fn swap_exact_tokens_for_tokens(
sender: AccountId,
path: Vec<MultiAssetId>,
amount_in: Balance,
amount_out_min: Option<Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Balance, DispatchError>;
/// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
/// too costly.
///
/// Withdraws `path[0]` asset from `sender`, deposits `path[1]` asset to `send_to`,
/// respecting `keep_alive`.
///
/// If successful returns the amount of the `path[0]` taken to provide `path[1]`.
fn swap_tokens_for_exact_tokens(
sender: AccountId,
path: Vec<MultiAssetId>,
amount_out: Balance,
amount_in_max: Option<Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Balance, DispatchError>;
}
/// An implementation of MultiAssetId that can be either Native or an asset.
#[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, Copy, Debug)]
pub enum NativeOrAssetId<AssetId>
@@ -186,3 +159,99 @@ impl<AssetId: Ord + Clone> MultiAssetIdConverter<NativeOrAssetId<AssetId>, Asset
}
}
}
/// Credit of [Config::Currency].
///
/// Implies a negative imbalance in the system that can be placed into an account or alter the total
/// supply.
pub type NativeCredit<T> =
CreditFungible<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
/// Credit (aka negative imbalance) of [Config::Assets].
///
/// Implies a negative imbalance in the system that can be placed into an account or alter the total
/// supply.
pub type AssetCredit<T> =
CreditFungibles<<T as frame_system::Config>::AccountId, <T as Config>::Assets>;
/// Credit that can be either [`NativeCredit`] or [`AssetCredit`].
///
/// Implies a negative imbalance in the system that can be placed into an account or alter the total
/// supply.
#[derive(RuntimeDebug, Eq, PartialEq)]
pub enum Credit<T: Config> {
/// Native credit.
Native(NativeCredit<T>),
/// Asset credit.
Asset(AssetCredit<T>),
}
impl<T: Config> From<NativeCredit<T>> for Credit<T> {
fn from(value: NativeCredit<T>) -> Self {
Credit::Native(value)
}
}
impl<T: Config> From<AssetCredit<T>> for Credit<T> {
fn from(value: AssetCredit<T>) -> Self {
Credit::Asset(value)
}
}
impl<T: Config> TryInto<NativeCredit<T>> for Credit<T> {
type Error = ();
fn try_into(self) -> Result<NativeCredit<T>, ()> {
match self {
Credit::Native(c) => Ok(c),
_ => Err(()),
}
}
}
impl<T: Config> TryInto<AssetCredit<T>> for Credit<T> {
type Error = ();
fn try_into(self) -> Result<AssetCredit<T>, ()> {
match self {
Credit::Asset(c) => Ok(c),
_ => Err(()),
}
}
}
impl<T: Config> Credit<T> {
/// Create zero native credit.
pub fn native_zero() -> Self {
NativeCredit::<T>::zero().into()
}
/// Amount of `self`.
pub fn peek(&self) -> T::Balance {
match self {
Credit::Native(c) => c.peek(),
Credit::Asset(c) => c.peek(),
}
}
/// Asset class of `self`.
pub fn asset(&self) -> T::MultiAssetId {
match self {
Credit::Native(_) => T::MultiAssetIdConverter::get_native(),
Credit::Asset(c) => c.asset().into(),
}
}
/// Consume `self` and return two independent instances; the first is guaranteed to be at most
/// `amount` and the second will be the remainder.
pub fn split(self, amount: T::Balance) -> (Self, Self) {
match self {
Credit::Native(c) => {
let (left, right) = c.split(amount);
(left.into(), right.into())
},
Credit::Asset(c) => {
let (left, right) = c.split(amount);
(left.into(), right.into())
},
}
}
}