Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Granular NFT traits and new XCM NFT types #4300

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e7003fd
feat(frame-support): add asset ops
mrshiposha Apr 25, 2024
6c6cf4b
feat(pallet-uniques): implement asset ops
mrshiposha Apr 25, 2024
a2099c0
feat(xcm): add unique instances adapters using asset ops
mrshiposha Apr 25, 2024
3f7c3a2
feat(rococo,westend): use xcm unique instances adapter
mrshiposha Apr 25, 2024
21323ff
Merge branch 'master' into feature/asset-ops-traits
mrshiposha Apr 26, 2024
2db1903
Merge branch 'master' into feature/asset-ops-traits
mrshiposha May 28, 2024
0016ec2
refactor: asset-ops related stuff, add Stash+Restore ops
mrshiposha Jun 11, 2024
17f54ab
refactor: unique instances XCM stuff
mrshiposha Jun 11, 2024
a725bbd
fix: add derives to NonFungibleAsset
mrshiposha Jun 11, 2024
b4893df
chore: remove unneeded
mrshiposha Jun 11, 2024
105ebfa
feat: add derives to id assignments
mrshiposha Jun 12, 2024
379d6cd
refactor(asset-ops): lifetimes in common strategies
mrshiposha Jun 13, 2024
d28ad75
refactor: xcm unique-instances derivatives traits
mrshiposha Jun 15, 2024
c45c7ca
refactor: id assignment
mrshiposha Jun 24, 2024
a5b1b1c
fix: remove unneeded imports
mrshiposha Jun 25, 2024
be99d8e
feat: add pallet-xnft
mrshiposha Jun 25, 2024
ae08d7e
Merge branch 'master' into feature/asset-ops-traits
mrshiposha Jun 27, 2024
6e06663
fix(xcm-builder): remove unneeded uses, add mod doc comment
mrshiposha Jun 27, 2024
aa2f827
fix(asset-ops): doc comment
mrshiposha Jun 27, 2024
79cae0a
fix: add pallet-xnft to workspace
mrshiposha Jun 27, 2024
4c7a0ca
fix: add Success type to all strategies
mrshiposha Jun 27, 2024
4585528
chore: cargo fmt
mrshiposha Jun 27, 2024
5324c5b
fix: impl StashStrategy,RestoreStrategy for WithOrigin
mrshiposha Jun 27, 2024
8f94c1d
fix: CI errors
mrshiposha Jul 3, 2024
23c36be
fix: remove unused imports
mrshiposha Jul 3, 2024
845149a
fix: clippy
mrshiposha Jul 3, 2024
f7e2c3e
fix: clippy
mrshiposha Jul 3, 2024
ce0b2e3
Merge branch 'master' into feature/asset-ops-traits
mrshiposha Jul 15, 2024
d30a218
Merge branch 'master' into feature/asset-ops-traits
mrshiposha Aug 5, 2024
d870d91
fix: cargo fmt, remove unneeded use
mrshiposha Aug 5, 2024
1a50904
fix: umbrella crate
mrshiposha Aug 5, 2024
cd24f76
refactor: replace pallet-xnft with pallet-derivatives
mrshiposha Sep 19, 2024
c1ff41e
refactor: simplify unique instances derivatives types
mrshiposha Sep 19, 2024
7bfb0fd
refactor: remove RegisterOnCreate/DeregisterOnDestroy
mrshiposha Sep 19, 2024
1b90a48
refactor: use result type in all registry's methods
mrshiposha Sep 19, 2024
15c0c53
feat: add AssetIdOf
mrshiposha Sep 19, 2024
a7e1809
refactor: use options to get original/derivative
mrshiposha Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ use testnet_parachains_constants::rococo::snowbridge::{
};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
unique_instances::RecreateableInstanceAdapter, AccountId32Aliases,
AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily,
EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter,
GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint,
NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset,
GlobalConsensusParachainConvertsFor, HashedDescription, InstancesOfClasses, IsConcrete,
LocalMint, NetworkExportTableItem, NoChecking, ParentAsSuperuser, ParentIsPreset,
RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter,
SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit,
Expand Down Expand Up @@ -149,19 +150,11 @@ pub type UniquesConvertedConcreteId =
assets_common::UniquesConvertedConcreteId<UniquesPalletLocation>;

/// Means for transacting unique assets.
pub type UniquesTransactor = NonFungiblesAdapter<
// Use this non-fungibles implementation:
Uniques,
// This adapter will handle any non-fungible asset from the uniques pallet.
UniquesConvertedConcreteId,
// Convert an XCM Location into a local account id:
LocationToAccountId,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
pub type UniquesTransactor = RecreateableInstanceAdapter<
AccountId,
// Does not check teleports.
NoChecking,
// The account to use for tracking teleports.
CheckingAccount,
LocationToAccountId,
InstancesOfClasses<UniquesConvertedConcreteId>,
Uniques,
>;

/// `AssetId`/`Balance` converter for `ForeignAssets`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice;
use sp_runtime::traits::{AccountIdConversion, ConvertInto};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
unique_instances::RecreateableInstanceAdapter, AccountId32Aliases,
AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal,
EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter,
GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint,
NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset,
GlobalConsensusParachainConvertsFor, HashedDescription, InstancesOfClasses, IsConcrete,
LocalMint, NetworkExportTableItem, NoChecking, ParentAsSuperuser, ParentIsPreset,
RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith,
StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents,
Expand Down Expand Up @@ -142,19 +143,11 @@ pub type UniquesConvertedConcreteId =
assets_common::UniquesConvertedConcreteId<UniquesPalletLocation>;

/// Means for transacting unique assets.
pub type UniquesTransactor = NonFungiblesAdapter<
// Use this non-fungibles implementation:
Uniques,
// This adapter will handle any non-fungible asset from the uniques pallet.
UniquesConvertedConcreteId,
// Convert an XCM Location into a local account id:
LocationToAccountId,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
pub type UniquesTransactor = RecreateableInstanceAdapter<
AccountId,
// Does not check teleports.
NoChecking,
// The account to use for tracking teleports.
CheckingAccount,
LocationToAccountId,
InstancesOfClasses<UniquesConvertedConcreteId>,
Uniques,
>;

/// `AssetId`/`Balance` converter for `ForeignAssets`.
Expand Down
24 changes: 23 additions & 1 deletion polkadot/xcm/xcm-builder/src/asset_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ use frame_support::traits::{Contains, Get};
use sp_runtime::traits::MaybeEquivalence;
use sp_std::{marker::PhantomData, prelude::*, result};
use xcm::latest::prelude::*;
use xcm_executor::traits::{Error as MatchError, MatchesFungibles, MatchesNonFungibles};
use xcm_executor::traits::{
Error as MatchError, MatchesFungibles, MatchesInstance, MatchesNonFungible, MatchesNonFungibles,
};

/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be
/// `TryFrom/TryInto<u128>`) into a `GeneralIndex` junction, prefixed by some `Location` value.
Expand Down Expand Up @@ -152,6 +154,26 @@ impl<
}
}

pub struct InstancesOfClasses<Matcher>(PhantomData<Matcher>);

impl<ClassId, InstanceId, Matcher: MatchesNonFungibles<ClassId, InstanceId>>
MatchesInstance<(ClassId, InstanceId)> for InstancesOfClasses<Matcher>
{
fn matches_instance(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
Matcher::matches_nonfungibles(a)
}
}

pub struct ClasslessInstances<Matcher>(PhantomData<Matcher>);

impl<InstanceId, Matcher: MatchesNonFungible<InstanceId>> MatchesInstance<InstanceId>
for ClasslessInstances<Matcher>
{
fn matches_instance(a: &Asset) -> result::Result<InstanceId, MatchError> {
Matcher::matches_nonfungible(a).ok_or(MatchError::AssetNotHandled)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 4 additions & 1 deletion polkadot/xcm/xcm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ mod asset_conversion;
#[allow(deprecated)]
pub use asset_conversion::ConvertedConcreteAssetId;
pub use asset_conversion::{
AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId,
AsPrefixedGeneralIndex, ClasslessInstances, ConvertedConcreteId, InstancesOfClasses,
MatchedConvertedConcreteId,
};

mod barriers;
Expand Down Expand Up @@ -92,6 +93,8 @@ pub use matches_token::IsConcrete;
mod matcher;
pub use matcher::{CreateMatcher, MatchXcm, Matcher};

pub mod unique_instances;

mod nonfungibles_adapter;
pub use nonfungibles_adapter::{
NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter,
Expand Down
172 changes: 172 additions & 0 deletions polkadot/xcm/xcm-builder/src/unique_instances/backed_derivative.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use super::LOG_TARGET;
use core::marker::PhantomData;
use frame_support::traits::{
tokens::asset_ops::{
common_asset_kinds::{Class, Instance},
common_strategies::{DeriveIdFrom, FromTo, Owned},
AssetDefinition, Create, Transfer,
},
Get,
};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesInstance, TransactAsset};

/// The status of a derivative instance.
pub enum DerivativeStatus<ClassId, InstanceId> {
/// The derivative can be deposited (created) in the given class.
DepositableIn(ClassId),

/// The derivative already exists and it has the given ID.
Exists(InstanceId),
}

/// The `BackedDerivativeInstanceAdapter` implements the `TransactAsset` for unique instances
/// (NFT-like entities).
///
/// The adapter uses the following asset operations:
/// * [`Create`] with the [`Owned`] strategy that uses the [`DeriveIdFrom`] id assignment.
/// * The [`DeriveIdFrom`] accepts the value of the `Id` type retrieved from the class [asset
/// definition](AssetDefinition)
/// (`ClassDef` generic parameter, represents a class-like entity such as a collection of NFTs).
/// * [`Transfer`] with [`FromTo`] strategy
///
/// This adapter assumes that a new asset can be created and an existing asset can be transferred.
/// Also, the adapter assumes that the asset can't be destroyed.
/// So, it transfers the asset to the `StashLocation` on withdrawal.
///
/// On deposit, the adapter consults the [`DerivativeStatus`] returned from the `Matcher`.
/// If the asset is depositable in a certain class, it will be created within that class.
/// Otherwise, if the asset exists, it will be transferred from the `StashLocation` to the
/// beneficiary.
///
/// Transfers work as expected, transferring the asset from the `from` location to the beneficiary.
///
/// This adapter is meant to be used in non-reserve locations where derivatives
/// can't be properly destroyed and then recreated.
///
/// For instance, an NFT engine on the chain (a pallet or a smart contract)
/// can be incapable of recreating an NFT with the same ID.
/// So, we can only create a derivative with a new ID.
/// In this context, if we burn a derivative on withdrawal:
/// 1. we could exhaust the ID space for derivatives
/// 2. if "burning" means transferring to a special address (like the zero address in EVM),
/// we also waste the active storage space for burned derivatives
///
/// To avoid that situation, the `StashLocation` is used to hold the withdrawn derivatives.
///
/// Also, this adapter can be used in the NFT engine that simply doesn't support burning NFTs.
pub struct BackedDerivativeInstanceAdapter<
mrshiposha marked this conversation as resolved.
Show resolved Hide resolved
AccountId,
AccountIdConverter,
Matcher,
ClassDef,
InstanceOps,
StashLocation,
>(PhantomData<(AccountId, AccountIdConverter, Matcher, ClassDef, InstanceOps, StashLocation)>);

impl<AccountId, AccountIdConverter, Matcher, ClassDef, InstanceOps, StashLocation> TransactAsset
for BackedDerivativeInstanceAdapter<
AccountId,
AccountIdConverter,
Matcher,
ClassDef,
InstanceOps,
StashLocation,
> where
AccountIdConverter: ConvertLocation<AccountId>,
Matcher: MatchesInstance<DerivativeStatus<ClassDef::Id, InstanceOps::Id>>,
ClassDef: AssetDefinition<Class>,
for<'a> InstanceOps: AssetDefinition<Instance>
+ Create<Instance, Owned<'a, DeriveIdFrom<'a, ClassDef::Id, InstanceOps::Id>, AccountId>>
+ Transfer<Instance, FromTo<'a, AccountId>>,
StashLocation: Get<Location>,
{
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
log::trace!(
target: LOG_TARGET,
"BackedDerivativeInstanceAdapter::deposit_asset what: {:?}, who: {:?}, context: {:?}",
what,
who,
context,
);

let derivative_status = Matcher::matches_instance(what)?;
let to = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;

let result = match derivative_status {
DerivativeStatus::DepositableIn(class_id) =>
InstanceOps::create(Owned::new(DeriveIdFrom::parent_id(&class_id), &to))
.map(|_id| ()),
DerivativeStatus::Exists(instance_id) => {
let from = AccountIdConverter::convert_location(&StashLocation::get())
.ok_or(MatchError::AccountIdConversionFailed)?;

InstanceOps::transfer(&instance_id, FromTo(&from, &to))
},
};

result.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
}

fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> Result<xcm_executor::AssetsInHolding, XcmError> {
log::trace!(
target: LOG_TARGET,
"BackedDerivativeInstanceAdapter::withdraw_asset what: {:?}, who: {:?}, context: {:?}",
what,
who,
maybe_context,
);

let derivative_status = Matcher::matches_instance(what)?;
let from = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;

if let DerivativeStatus::Exists(instance_id) = derivative_status {
let to = AccountIdConverter::convert_location(&StashLocation::get())
.ok_or(MatchError::AccountIdConversionFailed)?;

InstanceOps::transfer(&instance_id, FromTo(&from, &to))
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;

Ok(what.clone().into())
} else {
Err(XcmError::NotWithdrawable)
}
}

fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> Result<xcm_executor::AssetsInHolding, XcmError> {
log::trace!(
target: LOG_TARGET,
"BackedDerivativeInstanceAdapter::internal_transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}",
what,
from,
to,
context,
);

let derivative_status = Matcher::matches_instance(what)?;
let from = AccountIdConverter::convert_location(from)
.ok_or(MatchError::AccountIdConversionFailed)?;
let to = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;

if let DerivativeStatus::Exists(instance_id) = derivative_status {
InstanceOps::transfer(&instance_id, FromTo(&from, &to))
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;

Ok(what.clone().into())
} else {
Err(XcmError::NotWithdrawable)
}
}
}
35 changes: 35 additions & 0 deletions polkadot/xcm/xcm-builder/src/unique_instances/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use frame_support::traits::tokens::asset_ops::{
common_asset_kinds::Instance, common_strategies::FromTo, Transfer,
};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesInstance};

const LOG_TARGET: &str = "xcm::unique_instances";

pub mod backed_derivative;
pub mod recreateable;
pub mod transferable;

pub use backed_derivative::*;
pub use recreateable::*;
pub use transferable::*;

fn transfer_instance<
AccountId,
AccountIdConverter: ConvertLocation<AccountId>,
Matcher: MatchesInstance<InstanceTransfer::Id>,
InstanceTransfer: for<'a> Transfer<Instance, FromTo<'a, AccountId>>,
>(
what: &Asset,
from: &Location,
to: &Location,
) -> XcmResult {
let instance_id = Matcher::matches_instance(what)?;
let from =
AccountIdConverter::convert_location(from).ok_or(MatchError::AccountIdConversionFailed)?;
let to =
AccountIdConverter::convert_location(to).ok_or(MatchError::AccountIdConversionFailed)?;

InstanceTransfer::transfer(&instance_id, FromTo(&from, &to))
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
}
Loading
Loading