diff --git a/Cargo.lock b/Cargo.lock
index 0e0e47f955ad0..2dd8bc64b5dcc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20945,6 +20945,7 @@ dependencies = [
"log",
"pallet-balances",
"pallet-message-queue",
+ "pallet-nfts",
"pallet-uniques",
"pallet-xcm",
"parity-scale-codec",
diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs
index e3f9104096384..31581caff226c 100644
--- a/polkadot/xcm/xcm-builder/src/lib.rs
+++ b/polkadot/xcm/xcm-builder/src/lib.rs
@@ -78,6 +78,12 @@ pub use nonfungibles_adapter::{
NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter,
};
+mod nonfungibles_v2_adapter;
+pub use nonfungibles_v2_adapter::{
+ MultiLocationCollectionId, NonFungiblesV2Adapter, NonFungiblesV2MutateAdapter,
+ NonFungiblesV2TransferAdapter,
+};
+
mod weight;
pub use weight::{
FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds,
diff --git a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs
index 6cf5980df0e9e..fa2747dea1cb2 100644
--- a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs
+++ b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
-//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM.
+//! Adapters to work with `frame_support::traits::tokens::nonfungibles` through XCM.
use crate::{AssetChecking, MintLocation};
use frame_support::{
diff --git a/polkadot/xcm/xcm-builder/src/nonfungibles_v2_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungibles_v2_adapter.rs
new file mode 100644
index 0000000000000..66b61f8be6f0f
--- /dev/null
+++ b/polkadot/xcm/xcm-builder/src/nonfungibles_v2_adapter.rs
@@ -0,0 +1,452 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Polkadot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Polkadot. If not, see .
+
+//! Adapters to work with [`frame_support::traits::tokens::nonfungibles_v2`] through XCM.
+use crate::{AssetChecking, MintLocation};
+use frame_support::{
+ ensure,
+ traits::{tokens::nonfungibles_v2, Get, Incrementable},
+};
+use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+use sp_std::{marker::PhantomData, prelude::*, result};
+use xcm::latest::prelude::*;
+use xcm_executor::traits::{
+ ConvertLocation, Error as MatchError, MatchesNonFungibles, TransactAsset,
+};
+
+const LOG_TARGET: &str = "xcm::nonfungibles_v2_adapter";
+/// Adapter for transferring non-fungible tokens (NFTs) using [`nonfungibles_v2`].
+///
+/// This adapter facilitates the transfer of NFTs between different locations.
+pub struct NonFungiblesV2TransferAdapter(
+ PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
+);
+impl<
+ Assets: nonfungibles_v2::Transfer,
+ Matcher: MatchesNonFungibles,
+ AccountIdConverter: ConvertLocation,
+ AccountId: Clone, // can't get away without it since `nonfungibles_v2` is generic over it.
+ > TransactAsset for NonFungiblesV2TransferAdapter
+{
+ fn transfer_asset(
+ what: &MultiAsset,
+ from: &MultiLocation,
+ to: &MultiLocation,
+ context: &XcmContext,
+ ) -> result::Result {
+ log::trace!(
+ target: LOG_TARGET,
+ "transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}",
+ what,
+ from,
+ to,
+ context,
+ );
+ // Check we handle this asset.
+ let (class, instance) = Matcher::matches_nonfungibles(what)?;
+ let destination = AccountIdConverter::convert_location(to)
+ .ok_or(MatchError::AccountIdConversionFailed)?;
+ Assets::transfer(&class, &instance, &destination)
+ .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
+ Ok(what.clone().into())
+ }
+}
+
+/// Adapter for mutating non-fungible tokens (NFTs) using [`nonfungibles_v2`].
+///
+/// This adapter provides functions to withdraw, deposit, check in and check out non fungibles.
+pub struct NonFungiblesV2MutateAdapter<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+>(
+ PhantomData<(
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ )>,
+)
+where
+ ItemConfig: Default;
+
+impl<
+ Assets: nonfungibles_v2::Mutate,
+ Matcher: MatchesNonFungibles,
+ AccountIdConverter: ConvertLocation,
+ AccountId: Clone + Eq, // can't get away without it since `nonfungibles_v2` is generic over it.
+ CheckAsset: AssetChecking,
+ CheckingAccount: Get>,
+ ItemConfig: Default,
+ >
+ NonFungiblesV2MutateAdapter<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >
+{
+ fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
+ ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
+ Ok(())
+ }
+ fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
+ if let Some(checking_account) = CheckingAccount::get() {
+ // This is an asset whose teleports we track.
+ let owner = Assets::owner(&class, &instance);
+ ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
+ ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
+ }
+ Ok(())
+ }
+ fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
+ if let Some(checking_account) = CheckingAccount::get() {
+ let ok = Assets::mint_into(
+ &class,
+ &instance,
+ &checking_account,
+ &ItemConfig::default(),
+ true,
+ )
+ .is_ok();
+ debug_assert!(ok, "`mint_into` cannot generally fail; qed");
+ }
+ }
+ fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
+ let ok = Assets::burn(&class, &instance, None).is_ok();
+ debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
+ }
+}
+
+impl<
+ Assets: nonfungibles_v2::Mutate,
+ Matcher: MatchesNonFungibles,
+ AccountIdConverter: ConvertLocation,
+ AccountId: Clone + Eq, // can't get away without it since `nonfungibles_v2` is generic over it.
+ CheckAsset: AssetChecking,
+ CheckingAccount: Get>,
+ ItemConfig: Default,
+ > TransactAsset
+ for NonFungiblesV2MutateAdapter<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >
+{
+ fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
+ log::trace!(
+ target: LOG_TARGET,
+ "can_check_in origin: {:?}, what: {:?}, context: {:?}",
+ _origin,
+ what,
+ context,
+ );
+ // Check we handle this asset.
+ let (class, instance) = Matcher::matches_nonfungibles(what)?;
+ match CheckAsset::asset_checking(&class) {
+ // We track this asset's teleports to ensure no more come in than have gone out.
+ Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
+ // We track this asset's teleports to ensure no more go out than have come in.
+ Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
+ _ => Ok(()),
+ }
+ }
+
+ fn check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
+ log::trace!(
+ target: LOG_TARGET,
+ "check_in origin: {:?}, what: {:?}, context: {:?}",
+ _origin,
+ what,
+ context,
+ );
+ if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
+ match CheckAsset::asset_checking(&class) {
+ // We track this asset's teleports to ensure no more come in than have gone out.
+ Some(MintLocation::Local) => Self::reduce_checked(class, instance),
+ // We track this asset's teleports to ensure no more go out than have come in.
+ Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
+ _ => (),
+ }
+ }
+ }
+
+ fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
+ log::trace!(
+ target: LOG_TARGET,
+ "can_check_out dest: {:?}, what: {:?}, context: {:?}",
+ _dest,
+ what,
+ context,
+ );
+ // Check we handle this asset.
+ let (class, instance) = Matcher::matches_nonfungibles(what)?;
+ match CheckAsset::asset_checking(&class) {
+ // We track this asset's teleports to ensure no more come in than have gone out.
+ Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
+ // We track this asset's teleports to ensure no more go out than have come in.
+ Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
+ _ => Ok(()),
+ }
+ }
+
+ fn check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
+ log::trace!(
+ target: LOG_TARGET,
+ "check_out dest: {:?}, what: {:?}, context: {:?}",
+ _dest,
+ what,
+ context,
+ );
+ if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
+ match CheckAsset::asset_checking(&class) {
+ // We track this asset's teleports to ensure no more come in than have gone out.
+ Some(MintLocation::Local) => Self::accrue_checked(class, instance),
+ // We track this asset's teleports to ensure no more go out than have come in.
+ Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
+ _ => (),
+ }
+ }
+ }
+
+ fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
+ log::trace!(
+ target: LOG_TARGET,
+ "deposit_asset what: {:?}, who: {:?}, context: {:?}",
+ what,
+ who,
+ context,
+ );
+ // Check we handle this asset.
+ let (class, instance) = Matcher::matches_nonfungibles(what)?;
+ let who = AccountIdConverter::convert_location(who)
+ .ok_or(MatchError::AccountIdConversionFailed)?;
+
+ Assets::mint_into(&class, &instance, &who, &ItemConfig::default(), true)
+ .map_err(|e| XcmError::FailedToTransactAsset(e.into()))
+ }
+
+ fn withdraw_asset(
+ what: &MultiAsset,
+ who: &MultiLocation,
+ maybe_context: Option<&XcmContext>,
+ ) -> result::Result {
+ log::trace!(
+ target: LOG_TARGET,
+ "withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}",
+ what,
+ who,
+ maybe_context,
+ );
+ // Check we handle this asset.
+ let who = AccountIdConverter::convert_location(who)
+ .ok_or(MatchError::AccountIdConversionFailed)?;
+ let (class, instance) = Matcher::matches_nonfungibles(what)?;
+ Assets::burn(&class, &instance, Some(&who))
+ .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
+ Ok(what.clone().into())
+ }
+}
+
+/// Adapter for handling non-fungible tokens (NFTs) using [`nonfungibles_v2`].
+///
+/// This adapter combines the functionalities of both the [`NonFungiblesV2TransferAdapter`] and [`NonFungiblesV2MutateAdapter`] adapters,
+/// allowing handling NFTs in various scenarios.
+/// For detailed information on the functions, refer to [`TransactAsset`].
+pub struct NonFungiblesV2Adapter<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+>(
+ PhantomData<(
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ )>,
+)
+where
+ ItemConfig: Default;
+impl<
+ Assets: nonfungibles_v2::Mutate + nonfungibles_v2::Transfer,
+ Matcher: MatchesNonFungibles,
+ AccountIdConverter: ConvertLocation,
+ AccountId: Clone + Eq, // can't get away without it since `nonfungibles_v2` is generic over it.
+ CheckAsset: AssetChecking,
+ CheckingAccount: Get>,
+ ItemConfig: Default,
+ > TransactAsset
+ for NonFungiblesV2Adapter<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >
+{
+ fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::can_check_in(origin, what, context)
+ }
+
+ fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::check_in(origin, what, context)
+ }
+
+ fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::can_check_out(dest, what, context)
+ }
+
+ fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::check_out(dest, what, context)
+ }
+
+ fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::deposit_asset(what, who, context)
+ }
+
+ fn withdraw_asset(
+ what: &MultiAsset,
+ who: &MultiLocation,
+ maybe_context: Option<&XcmContext>,
+ ) -> result::Result {
+ NonFungiblesV2MutateAdapter::<
+ Assets,
+ Matcher,
+ AccountIdConverter,
+ AccountId,
+ CheckAsset,
+ CheckingAccount,
+ ItemConfig,
+ >::withdraw_asset(what, who, maybe_context)
+ }
+
+ fn transfer_asset(
+ what: &MultiAsset,
+ from: &MultiLocation,
+ to: &MultiLocation,
+ context: &XcmContext,
+ ) -> result::Result {
+ NonFungiblesV2TransferAdapter::::transfer_asset(
+ what, from, to, context,
+ )
+ }
+}
+
+#[derive(
+ Copy, Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo, MaxEncodedLen,
+)]
+/// Represents a collection ID based on a MultiLocation.
+///
+/// This structure provides a way to map a MultiLocation to a collection ID,
+/// which is useful for describing collections that do not follow an incremental pattern.
+pub struct MultiLocationCollectionId(MultiLocation);
+impl MultiLocationCollectionId {
+ /// Consume `self` and return the inner MultiLocation.
+ pub fn into_inner(self) -> MultiLocation {
+ self.0
+ }
+
+ /// Return a reference to the inner MultiLocation.
+ pub fn inner(&self) -> &MultiLocation {
+ &self.0
+ }
+}
+
+impl Incrementable for MultiLocationCollectionId {
+ fn increment(&self) -> Option {
+ None
+ }
+
+ fn initial_value() -> Option {
+ None
+ }
+}
+
+impl From for MultiLocationCollectionId {
+ fn from(value: MultiLocation) -> Self {
+ MultiLocationCollectionId(value)
+ }
+}
+
+impl From for MultiLocation {
+ fn from(value: MultiLocationCollectionId) -> MultiLocation {
+ value.into_inner()
+ }
+}
diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml
index e29c1c0d1f2ce..52f4aef1ced22 100644
--- a/polkadot/xcm/xcm-simulator/example/Cargo.toml
+++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml
@@ -16,6 +16,7 @@ frame-support = { path = "../../../../substrate/frame/support" }
pallet-balances = { path = "../../../../substrate/frame/balances" }
pallet-message-queue = { path = "../../../../substrate/frame/message-queue" }
pallet-uniques = { path = "../../../../substrate/frame/uniques" }
+pallet-nfts = { path = "../../../../substrate/frame/nfts" }
sp-std = { path = "../../../../substrate/primitives/std" }
sp-core = { path = "../../../../substrate/primitives/core" }
sp-runtime = { path = "../../../../substrate/primitives/runtime" }
diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs
index 85b8ad1c5cb7b..fa0efffff94ec 100644
--- a/polkadot/xcm/xcm-simulator/example/src/lib.rs
+++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs
@@ -24,6 +24,7 @@ use xcm_executor::traits::ConvertLocation;
use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt};
pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]);
+pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]);
pub const INITIAL_BALANCE: u128 = 1_000_000_000;
decl_test_parachain! {
@@ -68,27 +69,27 @@ decl_test_network! {
pub fn parent_account_id() -> parachain::AccountId {
let location = (Parent,);
- parachain::LocationToAccountId::convert_location(&location.into()).unwrap()
+ parachain::SovereignAccountOf::convert_location(&location.into()).unwrap()
}
pub fn child_account_id(para: u32) -> relay_chain::AccountId {
let location = (Parachain(para),);
- relay_chain::LocationToAccountId::convert_location(&location.into()).unwrap()
+ relay_chain::SovereignAccountOf::convert_location(&location.into()).unwrap()
}
pub fn child_account_account_id(para: u32, who: sp_runtime::AccountId32) -> relay_chain::AccountId {
let location = (Parachain(para), AccountId32 { network: None, id: who.into() });
- relay_chain::LocationToAccountId::convert_location(&location.into()).unwrap()
+ relay_chain::SovereignAccountOf::convert_location(&location.into()).unwrap()
}
pub fn sibling_account_account_id(para: u32, who: sp_runtime::AccountId32) -> parachain::AccountId {
let location = (Parent, Parachain(para), AccountId32 { network: None, id: who.into() });
- parachain::LocationToAccountId::convert_location(&location.into()).unwrap()
+ parachain::SovereignAccountOf::convert_location(&location.into()).unwrap()
}
pub fn parent_account_account_id(who: sp_runtime::AccountId32) -> parachain::AccountId {
let location = (Parent, AccountId32 { network: None, id: who.into() });
- parachain::LocationToAccountId::convert_location(&location.into()).unwrap()
+ parachain::SovereignAccountOf::convert_location(&location.into()).unwrap()
}
pub fn para_ext(para_id: u32) -> sp_io::TestExternalities {
@@ -140,11 +141,15 @@ pub type ParachainPalletXcm = pallet_xcm::Pallet;
#[cfg(test)]
mod tests {
+ use crate::parachain::Balance;
+
use super::*;
use codec::Encode;
use frame_support::{assert_ok, weights::Weight};
+ use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings};
use xcm::latest::QueryResponseInfo;
+ use xcm_builder::MultiLocationCollectionId;
use xcm_simulator::TestExt;
// Helper function for forming buy execution message
@@ -152,6 +157,15 @@ mod tests {
BuyExecution { fees: fees.into(), weight_limit: Unlimited }
}
+ // helper function for CollectionConfig
+ fn get_collection_config() -> CollectionConfig {
+ CollectionConfig {
+ settings: CollectionSettings::all_enabled(),
+ max_supply: None,
+ mint_settings: MintSettings::default(),
+ }
+ }
+
#[test]
fn remote_account_ids_work() {
child_account_account_id(1, ALICE);
@@ -364,7 +378,7 @@ mod tests {
#[test]
fn teleport_nft() {
MockNet::reset();
-
+ let collection_id = MultiLocation { parents: 1, interior: X1(GeneralIndex(1)) }.into();
Relay::execute_with(|| {
// Mint the NFT (1, 69) and give it to our "parachain#1 alias".
assert_ok!(relay_chain::Uniques::mint(
@@ -381,17 +395,18 @@ mod tests {
);
});
ParaA::execute_with(|| {
- assert_ok!(parachain::ForeignUniques::force_create(
- parachain::RuntimeOrigin::root(),
- (Parent, GeneralIndex(1)).into(),
+ use pallet_nfts::Event;
+
+ assert_ok!(parachain::ForeignNfts::do_create_collection(
+ collection_id,
+ ALICE,
ALICE,
- false,
+ get_collection_config(),
+ 1000u128,
+ Event::ForceCreated { collection: collection_id, owner: ALICE },
));
- assert_eq!(
- parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()),
- None,
- );
- assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0);
+
+ assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000);
// IRL Alice would probably just execute this locally on the Relay-chain, but we can't
// easily do that here since we only send between chains.
@@ -411,11 +426,8 @@ mod tests {
assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message));
});
ParaA::execute_with(|| {
- assert_eq!(
- parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()),
- Some(ALICE),
- );
- assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000);
+ assert_eq!(parachain::ForeignNfts::owner(collection_id, 69u32.into()), Some(ALICE),);
+ assert_eq!(parachain::Balances::reserved_balance(&ALICE), 2000);
});
Relay::execute_with(|| {
assert_eq!(relay_chain::Uniques::owner(1, 69), None);
@@ -429,9 +441,8 @@ mod tests {
/// Asserts that the parachain accounts are updated as expected.
#[test]
fn reserve_asset_transfer_nft() {
- sp_tracing::init_for_tests();
MockNet::reset();
-
+ let collection_id = MultiLocation { parents: 1, interior: X1(GeneralIndex(2)) }.into();
Relay::execute_with(|| {
assert_ok!(relay_chain::Uniques::force_create(
relay_chain::RuntimeOrigin::root(),
@@ -451,17 +462,18 @@ mod tests {
);
});
ParaA::execute_with(|| {
- assert_ok!(parachain::ForeignUniques::force_create(
- parachain::RuntimeOrigin::root(),
- (Parent, GeneralIndex(2)).into(),
+ use pallet_nfts::Event;
+
+ assert_ok!(parachain::ForeignNfts::do_create_collection(
+ collection_id,
+ ALICE,
ALICE,
- false,
+ get_collection_config(),
+ 1000u128,
+ Event::ForceCreated { collection: collection_id, owner: ALICE },
));
- assert_eq!(
- parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()),
- None,
- );
- assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0);
+ assert_eq!(parachain::ForeignNfts::owner(collection_id, 69u32.into()), None,);
+ assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000);
let message = Xcm(vec![
WithdrawAsset((GeneralIndex(2), 69u32).into()),
@@ -479,12 +491,8 @@ mod tests {
assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message));
});
ParaA::execute_with(|| {
- log::debug!(target: "xcm-exceutor", "Hello");
- assert_eq!(
- parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()),
- Some(ALICE),
- );
- assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000);
+ assert_eq!(parachain::ForeignNfts::owner(collection_id, 69u32.into()), Some(ALICE),);
+ assert_eq!(parachain::Balances::reserved_balance(&ALICE), 2000);
});
Relay::execute_with(|| {
@@ -492,70 +500,116 @@ mod tests {
});
}
+
/// Scenario:
- /// The relay-chain creates an asset class on a parachain and then Alice transfers her NFT into
- /// that parachain's sovereign account, who then mints a trustless-backed-derivative locally.
- ///
- /// Asserts that the parachain accounts are updated as expected.
+ /// Alice on Parachain A transfers an NFT into a parachain B's sovereign account.
+ /// Parachain B then mints a derivative NFT locally. Alice then transfers the derivative
+ /// NFT to Bob on Parachain B. Bob then withdraws the derivative NFT on Parachain B and
+ /// transfers the NFT to himself on Parachain A. The derivative is burned on Parachain B
+ /// and the original is transfered to Bob on Parachain A.
#[test]
- fn reserve_asset_class_create_and_reserve_transfer() {
+ fn two_way_reserve_asset_transfer_nft() {
+ use pallet_nfts::Event;
MockNet::reset();
-
- Relay::execute_with(|| {
- assert_ok!(relay_chain::Uniques::force_create(
- relay_chain::RuntimeOrigin::root(),
- 2,
+ let collection_id = MultiLocation { parents: 0, interior: X1(GeneralIndex(2)) }.into();
+ ParaA::execute_with(|| {
+ assert_ok!(parachain::ForeignNfts::do_create_collection(
+ collection_id,
ALICE,
- false
+ ALICE,
+ get_collection_config(),
+ 1000,
+ Event::ForceCreated { collection: collection_id, owner: ALICE },
));
- assert_ok!(relay_chain::Uniques::mint(
- relay_chain::RuntimeOrigin::signed(ALICE),
- 2,
- 69,
- child_account_account_id(1, ALICE)
+
+ assert_ok!(parachain::ForeignNfts::mint(
+ parachain::RuntimeOrigin::signed(ALICE),
+ collection_id,
+ Index(69),
+ sibling_account_account_id(2, ALICE),
+ None
));
+
assert_eq!(
- relay_chain::Uniques::owner(2, 69),
- Some(child_account_account_id(1, ALICE))
+ parachain::ForeignNfts::owner(collection_id, Index(69)),
+ Some(sibling_account_account_id(2, ALICE))
);
-
- let message = Xcm(vec![Transact {
- origin_kind: OriginKind::Xcm,
- require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024),
- call: parachain::RuntimeCall::from(
- pallet_uniques::Call::::create {
- collection: (Parent, 2u64).into(),
- admin: parent_account_id(),
- },
- )
- .encode()
- .into(),
- }]);
- // Send creation.
- assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message));
});
- ParaA::execute_with(|| {
- // Then transfer
+
+ let foreign_collection_id =
+ MultiLocation { parents: 1, interior: X2(Parachain(1), GeneralIndex(2)) }.into();
+
+ ParaB::execute_with(|| {
+ assert_ok!(parachain::ForeignNfts::do_create_collection(
+ foreign_collection_id,
+ ALICE,
+ ALICE,
+ get_collection_config(),
+ 1000,
+ Event::ForceCreated { collection: foreign_collection_id, owner: ALICE },
+ ));
+
let message = Xcm(vec![
WithdrawAsset((GeneralIndex(2), 69u32).into()),
DepositReserveAsset {
assets: AllCounted(1).into(),
- dest: Parachain(1).into(),
+ dest: (Parent, Parachain(2)).into(),
xcm: Xcm(vec![DepositAsset {
assets: AllCounted(1).into(),
beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(),
}]),
},
]);
+ // Send transfer
+
let alice = AccountId32 { id: ALICE.into(), network: None };
- assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message));
+ assert_ok!(ParachainPalletXcm::send_xcm(alice, (Parent, Parachain(1)), message));
});
- ParaA::execute_with(|| {
- assert_eq!(parachain::Balances::reserved_balance(&parent_account_id()), 1000);
+
+ ParaB::execute_with(|| {
assert_eq!(
- parachain::ForeignUniques::collection_owner((Parent, 2u64).into()),
- Some(parent_account_id())
+ parachain::ForeignNfts::owner(foreign_collection_id, Index(69)),
+ Some(ALICE)
);
+
+ let message = Xcm(vec![TransferAsset {
+ assets: (foreign_collection_id, 69u32).into(),
+ beneficiary: (AccountId32 { id: BOB.into(), network: None },).into(),
+ }]);
+
+ assert_ok!(ParachainPalletXcm::execute(
+ parachain::RuntimeOrigin::signed(ALICE),
+ Box::new(VersionedXcm::V3(message)),
+ Weight::MAX
+ ));
+
+ assert_eq!(parachain::ForeignNfts::owner(foreign_collection_id, Index(69)), Some(BOB));
+ });
+
+ ParaB::execute_with(|| {
+ let message: Xcm<_> = Xcm(vec![
+ WithdrawAsset((foreign_collection_id, 69u32).into()),
+ InitiateReserveWithdraw {
+ assets: AllCounted(1).into(),
+ reserve: (Parent, Parachain(1)).into(),
+ xcm: Xcm(vec![DepositAsset {
+ assets: AllCounted(1).into(),
+ beneficiary: (AccountId32 { id: BOB.into(), network: None },).into(),
+ }]),
+ },
+ ]);
+
+ assert_ok!(ParachainPalletXcm::execute(
+ parachain::RuntimeOrigin::signed(BOB),
+ Box::new(VersionedXcm::V3(message)),
+ Weight::MAX
+ ));
+
+ assert_eq!(parachain::ForeignNfts::owner(foreign_collection_id, Index(69)), None);
+ });
+
+ ParaA::execute_with(|| {
+ assert_eq!(parachain::ForeignNfts::owner(collection_id, Index(69)), Some(BOB));
});
}
diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain.rs b/polkadot/xcm/xcm-simulator/example/src/parachain.rs
index 9c4cbe8fcb2d8..95f6210158efd 100644
--- a/polkadot/xcm/xcm-simulator/example/src/parachain.rs
+++ b/polkadot/xcm/xcm-simulator/example/src/parachain.rs
@@ -25,10 +25,10 @@ use frame_support::{
};
use frame_system::EnsureRoot;
-use sp_core::{ConstU32, H256};
+use sp_core::{ConstU32, ConstU64, H256};
use sp_runtime::{
- traits::{Get, Hash, IdentityLookup},
- AccountId32,
+ traits::{Get, Hash, IdentityLookup, Verify},
+ AccountId32, MultiSignature,
};
use sp_std::prelude::*;
@@ -41,7 +41,7 @@ use xcm::{latest::prelude::*, VersionedXcm};
use xcm_builder::{
Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId,
CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds,
- IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset,
+ IsConcrete, MultiLocationCollectionId, NativeAsset, NoChecking, NonFungiblesV2Adapter, ParentIsPreset,
SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
SovereignSignedViaLocation,
};
@@ -50,10 +50,13 @@ use xcm_executor::{
Config, XcmExecutor,
};
+use pallet_nfts::{ItemConfig, PalletFeatures};
+
pub type SovereignAccountOf = (
SiblingParachainConvertsVia,
AccountId32Aliases,
ParentIsPreset,
+ Account32Hash<(), AccountId>,
);
pub type AccountId = AccountId32;
@@ -111,59 +114,26 @@ impl pallet_balances::Config for Runtime {
type MaxFreezes = ConstU32<0>;
}
-#[cfg(feature = "runtime-benchmarks")]
-pub struct UniquesHelper;
-#[cfg(feature = "runtime-benchmarks")]
-impl pallet_uniques::BenchmarkHelper for UniquesHelper {
- fn collection(i: u16) -> MultiLocation {
- GeneralIndex(i as u128).into()
- }
- fn item(i: u16) -> AssetInstance {
- AssetInstance::Index(i as u128)
- }
-}
-
-impl pallet_uniques::Config for Runtime {
- type RuntimeEvent = RuntimeEvent;
- type CollectionId = MultiLocation;
- type ItemId = AssetInstance;
- type Currency = Balances;
- type CreateOrigin = ForeignCreators;
- type ForceOrigin = frame_system::EnsureRoot;
- type CollectionDeposit = frame_support::traits::ConstU128<1_000>;
- type ItemDeposit = frame_support::traits::ConstU128<1_000>;
- type MetadataDepositBase = frame_support::traits::ConstU128<1_000>;
- type AttributeDepositBase = frame_support::traits::ConstU128<1_000>;
- type DepositPerByte = frame_support::traits::ConstU128<1>;
- type StringLimit = ConstU32<64>;
- type KeyLimit = ConstU32<64>;
- type ValueLimit = ConstU32<128>;
- type Locker = ();
- type WeightInfo = ();
- #[cfg(feature = "runtime-benchmarks")]
- type Helper = UniquesHelper;
-}
-
// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins
// which are locations containing the class location.
-pub struct ForeignCreators;
-impl EnsureOriginWithArg for ForeignCreators {
+pub struct ForeignNftCreators;
+impl EnsureOriginWithArg for ForeignNftCreators {
type Success = AccountId;
fn try_origin(
o: RuntimeOrigin,
- a: &MultiLocation,
+ a: &MultiLocationCollectionId,
) -> sp_std::result::Result {
let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?;
- if !a.starts_with(&origin_location) {
+ if !a.inner().starts_with(&origin_location) {
return Err(o)
}
SovereignAccountOf::convert_location(&origin_location).ok_or(o)
}
#[cfg(feature = "runtime-benchmarks")]
- fn try_successful_origin(a: &MultiLocation) -> Result {
- Ok(pallet_xcm::Origin::Xcm(a.clone()).into())
+ fn try_successful_origin(a: &MultiLocationCollectionId) -> Result {
+ Ok(pallet_xcm::Origin::Xcm(a.clone().into()).into())
}
}
@@ -178,15 +148,9 @@ parameter_types! {
pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into();
}
-pub type LocationToAccountId = (
- ParentIsPreset,
- SiblingParachainConvertsVia,
- AccountId32Aliases,
- Account32Hash<(), AccountId>,
-);
pub type XcmOriginToCallOrigin = (
- SovereignSignedViaLocation,
+ SovereignSignedViaLocation,
SignedAccountId32AsNative,
XcmPassthrough,
);
@@ -200,14 +164,15 @@ parameter_types! {
}
pub type LocalAssetTransactor = (
- XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>,
- NonFungiblesAdapter<
- ForeignUniques,
- ConvertedConcreteId,
+ XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>,
+ NonFungiblesV2Adapter<
+ ForeignNfts,
+ ConvertedConcreteId,
SovereignAccountOf,
AccountId,
NoChecking,
(),
+ ItemConfig,
>,
);
@@ -437,7 +402,7 @@ impl pallet_xcm::Config for Runtime {
type Currency = Balances;
type CurrencyMatcher = ();
type TrustedLockers = TrustedLockers;
- type SovereignAccountOf = LocationToAccountId;
+ type SovereignAccountOf = SovereignAccountOf;
type MaxLockers = ConstU32<8>;
type MaxRemoteLockConsumers = ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
@@ -447,6 +412,42 @@ impl pallet_xcm::Config for Runtime {
type AdminOrigin = EnsureRoot;
}
+parameter_types! {
+ pub storage Features: PalletFeatures = PalletFeatures::all_enabled();
+}
+
+pub type Signature = MultiSignature;
+pub type AccountPublic = ::Signer;
+
+impl pallet_nfts::Config for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type CollectionId = MultiLocationCollectionId;
+ type ItemId = AssetInstance;
+ type Currency = Balances;
+ type CreateOrigin = ForeignNftCreators;
+ type ForceOrigin = frame_system::EnsureRoot;
+ type CollectionDeposit = frame_support::traits::ConstU128<1_000>;
+ type ItemDeposit = frame_support::traits::ConstU128<1_000>;
+ type MetadataDepositBase = frame_support::traits::ConstU128<1_000>;
+ type AttributeDepositBase = frame_support::traits::ConstU128<1_000>;
+ type DepositPerByte = frame_support::traits::ConstU128<1>;
+ type StringLimit = ConstU32<64>;
+ type KeyLimit = ConstU32<64>;
+ type ValueLimit = ConstU32<128>;
+ type ApprovalsLimit = ConstU32<10>;
+ type ItemAttributesApprovalsLimit = ConstU32<2>;
+ type MaxTips = ConstU32<10>;
+ type MaxDeadlineDuration = ConstU64<10000>;
+ type MaxAttributesPerCall = ConstU32<2>;
+ type Features = Features;
+ type OffchainSignature = Signature;
+ type OffchainPublic = AccountPublic;
+ type Locker = ();
+ type WeightInfo = ();
+ #[cfg(feature = "runtime-benchmarks")]
+ type Helper = ();
+}
+
type Block = frame_system::mocking::MockBlock;
construct_runtime!(
@@ -456,6 +457,6 @@ construct_runtime!(
Balances: pallet_balances::{Pallet, Call, Storage, Config, Event},
MsgQueue: mock_msg_queue::{Pallet, Storage, Event},
PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin},
- ForeignUniques: pallet_uniques::{Pallet, Call, Storage, Event},
+ ForeignNfts: pallet_nfts::{Pallet, Call, Storage, Event},
}
);
diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs
index 6f0e92dc91b9a..8e19ae322866f 100644
--- a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs
+++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs
@@ -132,18 +132,18 @@ parameter_types! {
pub UnitWeightCost: u64 = 1_000;
}
-pub type LocationToAccountId = (
+pub type SovereignAccountOf = (
ChildParachainConvertsVia,
AccountId32Aliases,
Account32Hash<(), AccountId>,
);
pub type LocalAssetTransactor = (
- XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>,
+ XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>,
NonFungiblesAdapter<
Uniques,
ConvertedConcreteId, JustTry>,
- LocationToAccountId,
+ SovereignAccountOf,
AccountId,
NoChecking,
(),
@@ -151,7 +151,7 @@ pub type LocalAssetTransactor = (
);
type LocalOriginConverter = (
- SovereignSignedViaLocation,
+ SovereignSignedViaLocation,
ChildParachainAsNative,
SignedAccountId32AsNative,
ChildSystemParachainAsSuperuser,
@@ -222,7 +222,7 @@ impl pallet_xcm::Config for Runtime {
type Currency = Balances;
type CurrencyMatcher = IsConcrete;
type TrustedLockers = ();
- type SovereignAccountOf = LocationToAccountId;
+ type SovereignAccountOf = SovereignAccountOf;
type MaxLockers = ConstU32<8>;
type MaxRemoteLockConsumers = ConstU32<0>;
type RemoteLockConsumerIdentifier = ();