UnionOf types for merged fungible and fungibles implementations (#2033)

Introduces `UnionOf` types, crafted to merge `fungible` and `fungibles`
implementations or two `fungibles` implementations into a single type
implementing `fungibles`.

This also addresses an issue where `ItemOf` initiates a double drop for
an imbalance type, leading to inaccurate total issuance accounting.

Find the application of these types in this PR -
[link](https://github.com/paritytech/polkadot-sdk/pull/2031), places in
code -
[1](https://github.com/paritytech/polkadot-sdk/blob/4ec7496fa2632385b08fae860fcf28a523a7b5de/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs#L327),
[2](https://github.com/paritytech/polkadot-sdk/blob/4ec7496fa2632385b08fae860fcf28a523a7b5de/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs#L343).

---------

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: joepetrowski <joe@parity.io>
This commit is contained in:
Muharem
2023-12-19 13:51:05 +01:00
committed by GitHub
parent 81156d03ce
commit 0b74812ce8
15 changed files with 2353 additions and 37 deletions
+14 -2
View File
@@ -1648,8 +1648,20 @@ pub mod pallet {
T::AssetAccountDeposit::get()
}
fn touch(asset: T::AssetId, who: T::AccountId, depositor: T::AccountId) -> DispatchResult {
Self::do_touch(asset, who, depositor, false)
fn should_touch(asset: T::AssetId, who: &T::AccountId) -> bool {
match Asset::<T, I>::get(&asset) {
Some(info) if info.is_sufficient => false,
Some(_) => !Account::<T, I>::contains_key(asset, who),
_ => true,
}
}
fn touch(
asset: T::AssetId,
who: &T::AccountId,
depositor: &T::AccountId,
) -> DispatchResult {
Self::do_touch(asset, who.clone(), depositor.clone(), false)
}
}
+2
View File
@@ -28,6 +28,8 @@ use pallet_balances::Error as BalancesError;
use sp_io::storage;
use sp_runtime::{traits::ConvertInto, TokenError};
mod sets;
fn asset_ids() -> Vec<u32> {
let mut s: Vec<_> = Assets::asset_ids().collect();
s.sort();
+346
View File
@@ -0,0 +1,346 @@
// 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.
//! Tests for [`ItemOf`], [`fungible::UnionOf`] and [`fungibles::UnionOf`] set types.
use super::*;
use frame_support::{
parameter_types,
traits::{
fungible,
fungible::ItemOf,
fungibles,
tokens::{
fungibles::{
Balanced as FungiblesBalanced, Create as FungiblesCreate,
Inspect as FungiblesInspect, Mutate as FungiblesMutate,
},
Fortitude, Precision, Preservation,
},
},
};
use sp_runtime::{traits::ConvertToValue, Either};
const FIRST_ASSET: u32 = 0;
const UNKNOWN_ASSET: u32 = 10;
parameter_types! {
pub const LeftAsset: Either<(), u32> = Either::Left(());
pub const RightAsset: Either<u32, ()> = Either::Right(());
pub const RightUnitAsset: Either<(), ()> = Either::Right(());
}
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
/// single asset class from [`T`] identified by [`FIRST_ASSET`].
type FirstFungible<T> = ItemOf<T, frame_support::traits::ConstU32<{ FIRST_ASSET }>, u64>;
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
/// single asset class from [`T`] identified by [`UNKNOWN_ASSET`].
type UnknownFungible<T> = ItemOf<T, frame_support::traits::ConstU32<{ UNKNOWN_ASSET }>, u64>;
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
/// the [`FirstFungible`] from the left.
type LeftFungible<T> = fungible::UnionOf<FirstFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
/// the [`LeftFungible`] from the right.
type RightFungible<T> =
fungible::UnionOf<UnknownFungible<T>, LeftFungible<T>, ConvertToValue<RightUnitAsset>, (), u64>;
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
/// the [`RightFungible`] from the left.
type LeftFungibles<T> = fungibles::UnionOf<RightFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
/// the [`LeftFungibles`] from the right.
///
/// By using this type, we can navigate through each branch of [`fungible::UnionOf`],
/// [`fungibles::UnionOf`], and [`ItemOf`] to access the underlying `fungibles::*`
/// implementation provided by the pallet.
type First<T> = fungibles::UnionOf<T, LeftFungibles<T>, ConvertToValue<RightAsset>, (), u64>;
#[test]
fn deposit_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1 = 0;
let account1 = 1;
let account2 = 2;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::deposit((), &account2, 50, Precision::Exact).unwrap();
assert_eq!(First::<Assets>::balance((), &account2), 50);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(imb.peek(), 50);
let (imb1, imb2) = imb.split(30);
assert_eq!(imb1.peek(), 30);
assert_eq!(imb2.peek(), 20);
drop(imb2);
assert_eq!(First::<Assets>::total_issuance(()), 120);
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
assert_eq!(First::<Assets>::balance((), &account1), 70);
assert_eq!(First::<Assets>::balance((), &account2), 50);
assert_eq!(First::<Assets>::total_issuance(()), 120);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn issue_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1: u32 = 0;
let account1: u64 = 1;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_eq!(First::<Assets>::balance((), &account1), 100);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::issue((), 100);
assert_eq!(First::<Assets>::total_issuance(()), 200);
assert_eq!(imb.peek(), 100);
let (imb1, imb2) = imb.split(30);
assert_eq!(imb1.peek(), 30);
assert_eq!(imb2.peek(), 70);
drop(imb2);
assert_eq!(First::<Assets>::total_issuance(()), 130);
assert!(First::<Assets>::resolve(&account1, imb1).is_ok());
assert_eq!(First::<Assets>::balance((), &account1), 130);
assert_eq!(First::<Assets>::total_issuance(()), 130);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn pair_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1: u32 = 0;
let account1: u64 = 1;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_eq!(First::<Assets>::balance((), &account1), 100);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let (debt, credit) = First::<Assets>::pair((), 100);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(debt.peek(), 100);
assert_eq!(credit.peek(), 100);
let (debt1, debt2) = debt.split(30);
assert_eq!(debt1.peek(), 30);
assert_eq!(debt2.peek(), 70);
drop(debt2);
assert_eq!(First::<Assets>::total_issuance(()), 170);
assert!(First::<Assets>::settle(&account1, debt1, Preservation::Preserve).is_ok());
assert_eq!(First::<Assets>::balance((), &account1), 70);
assert_eq!(First::<Assets>::total_issuance(()), 170);
let (credit1, credit2) = credit.split(40);
assert_eq!(credit1.peek(), 40);
assert_eq!(credit2.peek(), 60);
drop(credit2);
assert_eq!(First::<Assets>::total_issuance(()), 110);
assert!(First::<Assets>::resolve(&account1, credit1).is_ok());
assert_eq!(First::<Assets>::balance((), &account1), 110);
assert_eq!(First::<Assets>::total_issuance(()), 110);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn rescind_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1: u32 = 0;
let account1: u64 = 1;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::rescind((), 20);
assert_eq!(First::<Assets>::total_issuance(()), 80);
assert_eq!(imb.peek(), 20);
let (imb1, imb2) = imb.split(15);
assert_eq!(imb1.peek(), 15);
assert_eq!(imb2.peek(), 5);
drop(imb2);
assert_eq!(First::<Assets>::total_issuance(()), 85);
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
assert_eq!(First::<Assets>::balance((), &account1), 85);
assert_eq!(First::<Assets>::total_issuance(()), 85);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn resolve_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1: u32 = 0;
let account1: u64 = 1;
let account2: u64 = 2;
let ed = 11;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_eq!(First::<Assets>::balance((), &account1), 100);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::issue((), 100);
assert_eq!(First::<Assets>::total_issuance(()), 200);
assert_eq!(imb.peek(), 100);
let (imb1, imb2) = imb.split(10);
assert_eq!(imb1.peek(), 10);
assert_eq!(imb2.peek(), 90);
assert_eq!(First::<Assets>::total_issuance(()), 200);
// ed requirements not met.
let imb1 = First::<Assets>::resolve(&account2, imb1).unwrap_err();
assert_eq!(imb1.peek(), 10);
drop(imb1);
assert_eq!(First::<Assets>::total_issuance(()), 190);
assert_eq!(First::<Assets>::balance((), &account2), 0);
// resolve to new account `2`.
assert_ok!(First::<Assets>::resolve(&account2, imb2));
assert_eq!(First::<Assets>::total_issuance(()), 190);
assert_eq!(First::<Assets>::balance((), &account2), 90);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn settle_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1: u32 = 0;
let account1: u64 = 1;
let account2: u64 = 2;
let ed = 11;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_ok!(Assets::mint_into(asset1, &account2, 100));
assert_eq!(First::<Assets>::balance((), &account2), 100);
assert_eq!(First::<Assets>::total_issuance(()), 200);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::rescind((), 100);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(imb.peek(), 100);
let (imb1, imb2) = imb.split(10);
assert_eq!(imb1.peek(), 10);
assert_eq!(imb2.peek(), 90);
assert_eq!(First::<Assets>::total_issuance(()), 100);
// ed requirements not met.
let imb2 = First::<Assets>::settle(&account2, imb2, Preservation::Preserve).unwrap_err();
assert_eq!(imb2.peek(), 90);
drop(imb2);
assert_eq!(First::<Assets>::total_issuance(()), 190);
assert_eq!(First::<Assets>::balance((), &account2), 100);
// settle to account `1`.
assert_ok!(First::<Assets>::settle(&account2, imb1, Preservation::Preserve));
assert_eq!(First::<Assets>::total_issuance(()), 190);
assert_eq!(First::<Assets>::balance((), &account2), 90);
let imb = First::<Assets>::rescind((), 85);
assert_eq!(First::<Assets>::total_issuance(()), 105);
assert_eq!(imb.peek(), 85);
// settle to account `1` and expect some dust.
let imb = First::<Assets>::settle(&account2, imb, Preservation::Expendable).unwrap();
assert_eq!(imb.peek(), 5);
assert_eq!(First::<Assets>::total_issuance(()), 105);
assert_eq!(First::<Assets>::balance((), &account2), 0);
drop(imb);
assert_eq!(First::<Assets>::total_issuance(()), 100);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}
#[test]
fn withdraw_from_set_types_works() {
new_test_ext().execute_with(|| {
let asset1 = 0;
let account1 = 1;
let account2 = 2;
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
assert_ok!(Assets::mint_into(asset1, &account1, 100));
assert_ok!(Assets::mint_into(asset1, &account2, 100));
assert_eq!(First::<Assets>::total_issuance(()), 200);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
let imb = First::<Assets>::withdraw(
(),
&account2,
50,
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)
.unwrap();
assert_eq!(First::<Assets>::balance((), &account2), 50);
assert_eq!(First::<Assets>::total_issuance(()), 200);
assert_eq!(imb.peek(), 50);
drop(imb);
assert_eq!(First::<Assets>::total_issuance(()), 150);
assert_eq!(First::<Assets>::balance((), &account2), 50);
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
});
}