Skip to content

Commit e196931

Browse files
authored
[XNFT PoC] xtokens non-fungible multiassets support (#966)
* feat: xtokens non-fungible multiassets support PoC * test: nft/fee related tests for multiasset transfer * fix: do_transfer_multiassets zero fee issue
1 parent 8f59b50 commit e196931

File tree

2 files changed

+115
-7
lines changed

2 files changed

+115
-7
lines changed

xtokens/src/lib.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -520,20 +520,31 @@ pub mod module {
520520
T::MultiLocationsFilter::contains(&dest),
521521
Error::<T>::NotSupportedMultiLocation
522522
);
523+
524+
// Fee payment can only be made by using the non-zero amount of fungibles
525+
ensure!(
526+
matches!(fee.fun, Fungibility::Fungible(x) if !x.is_zero()),
527+
Error::<T>::InvalidAsset
528+
);
529+
523530
let origin_location = T::AccountIdToMultiLocation::convert(who.clone());
524531

525532
let mut non_fee_reserve: Option<MultiLocation> = None;
526533
let asset_len = assets.len();
527534
for i in 0..asset_len {
528535
let asset = assets.get(i).ok_or(Error::<T>::AssetIndexNonExistent)?;
529-
ensure!(
530-
matches!(asset.fun, Fungibility::Fungible(x) if !x.is_zero()),
531-
Error::<T>::InvalidAsset
532-
);
536+
537+
match asset.fun {
538+
Fungibility::Fungible(x) => ensure!(!x.is_zero(), Error::<T>::InvalidAsset),
539+
Fungibility::NonFungible(AssetInstance::Undefined) => return Err(Error::<T>::InvalidAsset.into()),
540+
_ => {}
541+
}
542+
533543
// `assets` includes fee, the reserve location is decided by non fee asset
534-
if (fee != *asset && non_fee_reserve.is_none()) || asset_len == 1 {
544+
if non_fee_reserve.is_none() && asset.id != fee.id {
535545
non_fee_reserve = T::ReserveProvider::reserve(asset);
536546
}
547+
537548
// make sure all non fee assets share the same reserve
538549
if non_fee_reserve.is_some() {
539550
ensure!(
@@ -544,7 +555,7 @@ pub mod module {
544555
}
545556

546557
let fee_reserve = T::ReserveProvider::reserve(&fee);
547-
if fee_reserve != non_fee_reserve {
558+
if asset_len > 1 && fee_reserve != non_fee_reserve {
548559
// Current only support `ToReserve` with relay-chain asset as fee. other case
549560
// like `NonReserve` or `SelfReserve` with relay-chain fee is not support.
550561
ensure!(non_fee_reserve == dest.chain_part(), Error::<T>::InvalidAsset);
@@ -610,7 +621,7 @@ pub mod module {
610621
origin_location,
611622
assets.clone(),
612623
fee.clone(),
613-
non_fee_reserve,
624+
fee_reserve,
614625
&dest,
615626
None,
616627
dest_weight_limit,

xtokens/src/tests.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,3 +1721,100 @@ fn send_with_insufficient_weight_limit() {
17211721
assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 0);
17221722
});
17231723
}
1724+
1725+
#[test]
1726+
fn send_multiasset_with_zero_fee_should_yield_an_error() {
1727+
TestNet::reset();
1728+
1729+
let asset_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
1730+
ParaA::execute_with(|| {
1731+
assert_noop!(
1732+
ParaXTokens::transfer_multiasset_with_fee(
1733+
Some(ALICE).into(),
1734+
Box::new((asset_id, 100).into()),
1735+
Box::new((asset_id, Fungibility::Fungible(0)).into()),
1736+
Box::new(
1737+
MultiLocation::new(
1738+
1,
1739+
X2(
1740+
Parachain(2),
1741+
Junction::AccountId32 {
1742+
network: None,
1743+
id: BOB.into()
1744+
},
1745+
)
1746+
)
1747+
.into()
1748+
),
1749+
WeightLimit::Unlimited,
1750+
),
1751+
Error::<para::Runtime>::InvalidAsset
1752+
);
1753+
});
1754+
}
1755+
1756+
#[test]
1757+
fn send_undefined_nft_should_yield_an_error() {
1758+
TestNet::reset();
1759+
1760+
let fee_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
1761+
let nft_id: AssetId = X1(Junction::GeneralIndex(42)).into();
1762+
1763+
ParaA::execute_with(|| {
1764+
assert_noop!(
1765+
ParaXTokens::transfer_multiasset_with_fee(
1766+
Some(ALICE).into(),
1767+
Box::new((nft_id, Undefined).into()),
1768+
Box::new((fee_id, 100).into()),
1769+
Box::new(
1770+
MultiLocation::new(
1771+
1,
1772+
X2(
1773+
Parachain(2),
1774+
Junction::AccountId32 {
1775+
network: None,
1776+
id: BOB.into()
1777+
},
1778+
)
1779+
)
1780+
.into()
1781+
),
1782+
WeightLimit::Unlimited,
1783+
),
1784+
Error::<para::Runtime>::InvalidAsset
1785+
);
1786+
});
1787+
}
1788+
1789+
#[test]
1790+
fn nfts_cannot_be_fee_assets() {
1791+
TestNet::reset();
1792+
1793+
let asset_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
1794+
let nft_id: AssetId = X1(Junction::GeneralIndex(42)).into();
1795+
1796+
ParaA::execute_with(|| {
1797+
assert_noop!(
1798+
ParaXTokens::transfer_multiasset_with_fee(
1799+
Some(ALICE).into(),
1800+
Box::new((asset_id, 100).into()),
1801+
Box::new((nft_id, Index(1)).into()),
1802+
Box::new(
1803+
MultiLocation::new(
1804+
1,
1805+
X2(
1806+
Parachain(2),
1807+
Junction::AccountId32 {
1808+
network: None,
1809+
id: BOB.into()
1810+
},
1811+
)
1812+
)
1813+
.into()
1814+
),
1815+
WeightLimit::Unlimited,
1816+
),
1817+
Error::<para::Runtime>::InvalidAsset
1818+
);
1819+
});
1820+
}

0 commit comments

Comments
 (0)