Skip to content

Commit

Permalink
[XNFT PoC] xtokens non-fungible multiassets support (open-web3-stack#966
Browse files Browse the repository at this point in the history
)

* feat: xtokens non-fungible multiassets support PoC

* test: nft/fee related tests for multiasset transfer

* fix: do_transfer_multiassets zero fee issue
  • Loading branch information
mrshiposha authored Nov 21, 2023
1 parent 8f59b50 commit e196931
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 7 deletions.
25 changes: 18 additions & 7 deletions xtokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,20 +520,31 @@ pub mod module {
T::MultiLocationsFilter::contains(&dest),
Error::<T>::NotSupportedMultiLocation
);

// Fee payment can only be made by using the non-zero amount of fungibles
ensure!(
matches!(fee.fun, Fungibility::Fungible(x) if !x.is_zero()),
Error::<T>::InvalidAsset
);

let origin_location = T::AccountIdToMultiLocation::convert(who.clone());

let mut non_fee_reserve: Option<MultiLocation> = None;
let asset_len = assets.len();
for i in 0..asset_len {
let asset = assets.get(i).ok_or(Error::<T>::AssetIndexNonExistent)?;
ensure!(
matches!(asset.fun, Fungibility::Fungible(x) if !x.is_zero()),
Error::<T>::InvalidAsset
);

match asset.fun {
Fungibility::Fungible(x) => ensure!(!x.is_zero(), Error::<T>::InvalidAsset),
Fungibility::NonFungible(AssetInstance::Undefined) => return Err(Error::<T>::InvalidAsset.into()),
_ => {}
}

// `assets` includes fee, the reserve location is decided by non fee asset
if (fee != *asset && non_fee_reserve.is_none()) || asset_len == 1 {
if non_fee_reserve.is_none() && asset.id != fee.id {
non_fee_reserve = T::ReserveProvider::reserve(asset);
}

// make sure all non fee assets share the same reserve
if non_fee_reserve.is_some() {
ensure!(
Expand All @@ -544,7 +555,7 @@ pub mod module {
}

let fee_reserve = T::ReserveProvider::reserve(&fee);
if fee_reserve != non_fee_reserve {
if asset_len > 1 && fee_reserve != non_fee_reserve {
// Current only support `ToReserve` with relay-chain asset as fee. other case
// like `NonReserve` or `SelfReserve` with relay-chain fee is not support.
ensure!(non_fee_reserve == dest.chain_part(), Error::<T>::InvalidAsset);
Expand Down Expand Up @@ -610,7 +621,7 @@ pub mod module {
origin_location,
assets.clone(),
fee.clone(),
non_fee_reserve,
fee_reserve,
&dest,
None,
dest_weight_limit,
Expand Down
97 changes: 97 additions & 0 deletions xtokens/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1721,3 +1721,100 @@ fn send_with_insufficient_weight_limit() {
assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 0);
});
}

#[test]
fn send_multiasset_with_zero_fee_should_yield_an_error() {
TestNet::reset();

let asset_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
ParaA::execute_with(|| {
assert_noop!(
ParaXTokens::transfer_multiasset_with_fee(
Some(ALICE).into(),
Box::new((asset_id, 100).into()),
Box::new((asset_id, Fungibility::Fungible(0)).into()),
Box::new(
MultiLocation::new(
1,
X2(
Parachain(2),
Junction::AccountId32 {
network: None,
id: BOB.into()
},
)
)
.into()
),
WeightLimit::Unlimited,
),
Error::<para::Runtime>::InvalidAsset
);
});
}

#[test]
fn send_undefined_nft_should_yield_an_error() {
TestNet::reset();

let fee_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
let nft_id: AssetId = X1(Junction::GeneralIndex(42)).into();

ParaA::execute_with(|| {
assert_noop!(
ParaXTokens::transfer_multiasset_with_fee(
Some(ALICE).into(),
Box::new((nft_id, Undefined).into()),
Box::new((fee_id, 100).into()),
Box::new(
MultiLocation::new(
1,
X2(
Parachain(2),
Junction::AccountId32 {
network: None,
id: BOB.into()
},
)
)
.into()
),
WeightLimit::Unlimited,
),
Error::<para::Runtime>::InvalidAsset
);
});
}

#[test]
fn nfts_cannot_be_fee_assets() {
TestNet::reset();

let asset_id: AssetId = X1(Junction::from(BoundedVec::try_from(b"A".to_vec()).unwrap())).into();
let nft_id: AssetId = X1(Junction::GeneralIndex(42)).into();

ParaA::execute_with(|| {
assert_noop!(
ParaXTokens::transfer_multiasset_with_fee(
Some(ALICE).into(),
Box::new((asset_id, 100).into()),
Box::new((nft_id, Index(1)).into()),
Box::new(
MultiLocation::new(
1,
X2(
Parachain(2),
Junction::AccountId32 {
network: None,
id: BOB.into()
},
)
)
.into()
),
WeightLimit::Unlimited,
),
Error::<para::Runtime>::InvalidAsset
);
});
}

0 comments on commit e196931

Please sign in to comment.