difftreelog
fix rfts are unsupported in XCM
in: master
2 files changed
pallets/foreign-assets/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/foreign-assets/src/benchmarking.rs
+++ b/pallets/foreign-assets/src/benchmarking.rs
@@ -18,12 +18,12 @@
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
-use pallet_common::benchmarking::create_u16_data;
+use pallet_common::benchmarking::{create_data, create_u16_data};
use sp_std::vec;
use staging_xcm::prelude::*;
-use up_data_structs::{CollectionMode, MAX_COLLECTION_NAME_LENGTH};
+use up_data_structs::{MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH};
-use super::{Call, Config, Pallet};
+use super::{Call, Config, ForeignCollectionMode, Pallet};
#[benchmarks]
mod benchmarks {
@@ -35,10 +35,11 @@
let location =
MultiLocation::from(X3(Parachain(1000), PalletInstance(42), GeneralIndex(1)));
let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
- let mode = CollectionMode::NFT;
+ let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
+ let mode = ForeignCollectionMode::NFT;
#[extrinsic_call]
- _(RawOrigin::Root, location, name, mode);
+ _(RawOrigin::Root, location, name, token_prefix, mode);
Ok(())
}
pallets/foreign-assets/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Foreign Assets18//!19//! ## Overview20//!21//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.2223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};27use frame_system::pallet_prelude::*;28use pallet_common::{29 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,30};31use sp_runtime::traits::AccountIdConversion;32use sp_std::{vec, vec::Vec};33use staging_xcm::{34 opaque::latest::{prelude::XcmError, Weight},35 v3::{prelude::*, MultiAsset, XcmContext},36};37use staging_xcm_executor::{38 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},39 Assets,40};41use up_data_structs::{42 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,43 CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,44};4546pub mod weights;4748#[cfg(feature = "runtime-benchmarks")]49mod benchmarking;5051pub use module::*;52pub use weights::WeightInfo;5354#[frame_support::pallet]55pub mod module {56 use up_data_structs::{57 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,58 };5960 use super::*;6162 #[pallet::config]63 pub trait Config:64 frame_system::Config65 + pallet_common::Config66 + pallet_fungible::Config67 + pallet_balances::Config68 {69 /// The overarching event type.70 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7172 /// Origin for force registering of a foreign asset.73 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7475 /// The ID of the foreign assets pallet.76 type PalletId: Get<PalletId>;7778 /// Self-location of this parachain.79 type SelfLocation: Get<MultiLocation>;8081 /// The converter from a MultiLocation to a CrossAccountId.82 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8384 /// Weight information for the extrinsics in this module.85 type WeightInfo: WeightInfo;86 }8788 #[pallet::error]89 pub enum Error<T> {90 /// The foreign asset is already registered91 ForeignAssetAlreadyRegistered,92 }9394 #[pallet::event]95 #[pallet::generate_deposit(fn deposit_event)]96 pub enum Event<T: Config> {97 /// The foreign asset registered.98 ForeignAssetRegistered {99 asset_id: CollectionId,100 reserve_location: MultiLocation,101 },102 }103104 /// The corresponding collections of reserve locations.105 #[pallet::storage]106 #[pallet::getter(fn foreign_reserve_location_to_collection)]107 pub type ForeignReserveLocationToCollection<T: Config> =108 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;109110 /// The corresponding reserve location of collections.111 #[pallet::storage]112 #[pallet::getter(fn collection_to_foreign_reserve_location)]113 pub type CollectionToForeignReserveLocation<T: Config> =114 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;115116 /// The correponding NFT token id of reserve NFTs117 #[pallet::storage]118 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]119 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<120 Hasher1 = Twox64Concat,121 Key1 = CollectionId,122 Hasher2 = Twox64Concat,123 Key2 = staging_xcm::v3::AssetInstance,124 Value = TokenId,125 QueryKind = OptionQuery,126 >;127128 #[pallet::pallet]129 pub struct Pallet<T>(_);130131 #[pallet::call]132 impl<T: Config> Pallet<T> {133 #[pallet::call_index(0)]134 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]135 pub fn force_register_foreign_asset(136 origin: OriginFor<T>,137 reserve_location: MultiLocation,138 name: CollectionName,139 mode: CollectionMode,140 ) -> DispatchResult {141 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;142143 if <ForeignReserveLocationToCollection<T>>::contains_key(reserve_location) {144 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());145 }146147 let foreign_collection_owner = Self::pallet_account();148149 let description: CollectionDescription = "Foreign Assets Collection"150 .encode_utf16()151 .collect::<Vec<_>>()152 .try_into()153 .expect("description length < max description length; qed");154155 let payer = None;156 let is_special_collection = true;157 let collection_id = T::CollectionDispatch::create_internal(158 foreign_collection_owner,159 payer,160 is_special_collection,161 CreateCollectionData {162 name,163 description,164 mode,165166 properties: vec![Property {167 key: Self::reserve_location_property_key(),168 value: reserve_location169 .encode()170 .try_into()171 .expect("multilocation is less than 32k; qed"),172 }]173 .try_into()174 .expect("just one property can always be stored; qed"),175176 token_property_permissions: vec![PropertyKeyPermission {177 key: Self::reserve_asset_instance_property_key(),178 permission: PropertyPermission {179 mutable: false,180 collection_admin: true,181 token_owner: false,182 },183 }]184 .try_into()185 .expect("just one property permission can always be stored; qed"),186 ..Default::default()187 },188 )?;189190 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);191 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);192193 Self::deposit_event(Event::<T>::ForeignAssetRegistered {194 asset_id: collection_id,195 reserve_location,196 });197198 Ok(())199 }200 }201}202203impl<T: Config> Pallet<T> {204 fn pallet_account() -> T::CrossAccountId {205 let owner: T::AccountId = T::PalletId::get().into_account_truncating();206 T::CrossAccountId::from_sub(owner)207 }208209 fn reserve_location_property_key() -> PropertyKey {210 b"reserve-location"211 .to_vec()212 .try_into()213 .expect("key length < max property key length; qed")214 }215216 fn reserve_asset_instance_property_key() -> PropertyKey {217 b"reserve-asset-instance"218 .to_vec()219 .try_into()220 .expect("key length < max property key length; qed")221 }222223 /// Converts a multilocation to the Unique Network's local collection224 /// (i.e. the collection originally created on the Unique Network's parachain).225 ///226 /// The multilocation corresponds to a Unique Network's collection if:227 /// * It is `Here` location that corresponds to the native token of this parachain.228 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.229 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds230 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,231 /// otherwise the `AssetIdConversionFailed` error will be returned.232 ///233 /// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,234 /// identifying that the given multilocation doesn't correspond to a local collection.235 fn native_asset_location_to_collection(236 asset_location: &MultiLocation,237 ) -> Result<Option<CollectionId>, XcmError> {238 let self_location = T::SelfLocation::get();239240 if *asset_location == Here.into() {241 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))242 } else if *asset_location == self_location {243 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))244 } else if asset_location.parents == self_location.parents {245 match asset_location246 .interior247 .match_and_split(&self_location.interior)248 {249 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(250 (*collection_id)251 .try_into()252 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,253 ))),254 _ => Ok(None),255 }256 } else {257 Ok(None)258 }259 }260261 /// Converts a multiasset to a Unique Network's collection (either local or the foreign one).262 ///263 /// The function will try to convert the multiasset's reserve location264 /// to the Unique Network's local collection.265 ///266 /// If the multilocation doesn't correspond to a local collection,267 /// the function will check if the reserve location has the corresponding268 /// derivative Unique Network's collection, and will return the said collection ID if found.269 ///270 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.271 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {272 let AssetId::Concrete(asset_reserve_location) = asset.id else {273 return Err(XcmExecutorError::AssetNotHandled.into());274 };275276 Self::native_asset_location_to_collection(&asset_reserve_location)?277 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))278 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())279 }280281 /// Converts an XCM asset instance to the Unique Network's token ID.282 ///283 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:284 /// `AssetInstance::Index(<token ID>)`.285 ///286 /// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,287 /// the `AssetNotFound` error will be returned.288 fn native_asset_instance_to_token_id(289 asset_instance: &AssetInstance,290 ) -> Result<TokenId, XcmError> {291 match asset_instance {292 AssetInstance::Index(token_id) => Ok(TokenId(293 (*token_id)294 .try_into()295 .map_err(|_| XcmError::AssetNotFound)?,296 )),297 _ => Err(XcmError::AssetNotFound),298 }299 }300301 /// Obtains the token ID of the `asset_instance` in the collection.302 ///303 /// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection304 /// and the item hasn't yet been created on this blockchain.305 ///306 /// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.307 ///308 /// If the `asset_instance` is a part of a local collection,309 /// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.310 fn asset_instance_to_token_id(311 collection_id: CollectionId,312 asset_instance: &AssetInstance,313 ) -> Result<Option<TokenId>, XcmError> {314 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {315 Ok(Self::foreign_reserve_asset_instance_to_token_id(316 collection_id,317 asset_instance,318 ))319 } else {320 Self::native_asset_instance_to_token_id(asset_instance).map(Some)321 }322 }323324 /// Creates a foreign item in the the collection.325 fn create_foreign_asset_instance(326 xcm_ext: &dyn XcmExtensions<T>,327 collection_id: CollectionId,328 asset_instance: &AssetInstance,329 to: T::CrossAccountId,330 ) -> DispatchResult {331 let asset_instance_encoded = asset_instance.encode();332333 let derivative_token_id = xcm_ext.create_item(334 &Self::pallet_account(),335 to,336 CreateItemData::NFT(CreateNftData {337 properties: vec![Property {338 key: Self::reserve_asset_instance_property_key(),339 value: asset_instance_encoded340 .try_into()341 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),342 }]343 .try_into()344 .expect("just one property can always be stored; qed"),345 }),346 &ZeroBudget,347 )?;348349 <ForeignReserveAssetInstanceToTokenId<T>>::insert(350 collection_id,351 asset_instance,352 derivative_token_id,353 );354355 Ok(())356 }357358 /// Deposits an asset instance to the `to` account.359 ///360 /// Either transfers an existing item from the pallet's account361 /// or creates a foreign item.362 fn deposit_asset_instance(363 xcm_ext: &dyn XcmExtensions<T>,364 collection_id: CollectionId,365 asset_instance: &AssetInstance,366 to: T::CrossAccountId,367 ) -> XcmResult {368 let deposit_result = if let Some(token_id) =369 Self::asset_instance_to_token_id(collection_id, asset_instance)?370 {371 let depositor = &Self::pallet_account();372 let from = depositor;373 let amount = 1;374375 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)376 } else {377 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)378 };379380 deposit_result381 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))382 }383384 /// Withdraws an asset instance from the `from` account.385 ///386 /// Transfers the asset instance to the pallet's account.387 ///388 /// Won't withdraw the instance if it has children.389 fn withdraw_asset_instance(390 xcm_ext: &dyn XcmExtensions<T>,391 collection_id: CollectionId,392 asset_instance: &AssetInstance,393 from: T::CrossAccountId,394 ) -> XcmResult {395 let token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?396 .ok_or(XcmError::AssetNotFound)?;397398 if xcm_ext.token_has_children(token_id) {399 return Err(XcmError::Unimplemented);400 }401402 let depositor = &from;403 let to = Self::pallet_account();404 let amount = 1;405 xcm_ext406 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)407 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;408409 Ok(())410 }411}412413impl<T: Config> TransactAsset for Pallet<T> {414 fn can_check_in(415 _origin: &MultiLocation,416 _what: &MultiAsset,417 _context: &XcmContext,418 ) -> XcmResult {419 Err(XcmError::Unimplemented)420 }421422 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}423424 fn can_check_out(425 _dest: &MultiLocation,426 _what: &MultiAsset,427 _context: &XcmContext,428 ) -> XcmResult {429 Err(XcmError::Unimplemented)430 }431432 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}433434 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {435 let to = T::LocationToAccountId::convert_location(to)436 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;437438 let collection_id = Self::multiasset_to_collection(what)?;439 let dispatch =440 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;441442 let collection = dispatch.as_dyn();443 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;444445 match what.fun {446 Fungibility::Fungible(amount) => xcm_ext447 .create_item(448 &Self::pallet_account(),449 to,450 CreateItemData::Fungible(CreateFungibleData { value: amount }),451 &ZeroBudget,452 )453 .map(|_| ())454 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),455456 Fungibility::NonFungible(asset_instance) => {457 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)458 }459 }460 }461462 fn withdraw_asset(463 what: &MultiAsset,464 from: &MultiLocation,465 _maybe_context: Option<&XcmContext>,466 ) -> Result<staging_xcm_executor::Assets, XcmError> {467 let from = T::LocationToAccountId::convert_location(from)468 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;469470 let collection_id = Self::multiasset_to_collection(what)?;471 let dispatch =472 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;473474 let collection = dispatch.as_dyn();475 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;476477 match what.fun {478 Fungibility::Fungible(amount) => xcm_ext479 .burn_item(from, TokenId::default(), amount)480 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,481482 Fungibility::NonFungible(asset_instance) => {483 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;484 }485 }486487 Ok(what.clone().into())488 }489490 fn internal_transfer_asset(491 what: &MultiAsset,492 from: &MultiLocation,493 to: &MultiLocation,494 _context: &XcmContext,495 ) -> Result<staging_xcm_executor::Assets, XcmError> {496 let from = T::LocationToAccountId::convert_location(from)497 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;498499 let to = T::LocationToAccountId::convert_location(to)500 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;501502 let collection_id = Self::multiasset_to_collection(what)?;503504 let dispatch =505 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;506 let collection = dispatch.as_dyn();507 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;508509 let depositor = &from;510511 let token_id;512 let amount;513 let map_error: fn(DispatchError) -> XcmError;514515 match what.fun {516 Fungibility::Fungible(fungible_amount) => {517 token_id = TokenId::default();518 amount = fungible_amount;519 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");520 }521522 Fungibility::NonFungible(asset_instance) => {523 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?524 .ok_or(XcmError::AssetNotFound)?;525526 amount = 1;527 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")528 }529 }530531 xcm_ext532 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)533 .map_err(map_error)?;534535 Ok(what.clone().into())536 }537}538539pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);540impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>541 for CurrencyIdConvert<T>542{543 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {544 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {545 Some(Here.into())546 } else {547 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {548 T::SelfLocation::get()549 .pushed_with_interior(GeneralIndex(collection_id.0.into()))550 .ok()551 })552 }553 }554}555556pub use frame_support::{557 traits::{558 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,559 },560 weights::{WeightToFee, WeightToFeePolynomial},561};562563pub struct FreeForAll;564565impl WeightTrader for FreeForAll {566 fn new() -> Self {567 Self568 }569570 fn buy_weight(571 &mut self,572 weight: Weight,573 payment: Assets,574 _xcm: &XcmContext,575 ) -> Result<Assets, XcmError> {576 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);577 Ok(payment)578 }579}