difftreelog
fix take parents:0 multilocations into account
in: master
1 file changed
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 core::ops::Deref;2728use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36 opaque::latest::{prelude::XcmError, Weight},37 v3::{prelude::*, MultiAsset, XcmContext},38};39use staging_xcm_executor::{40 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41 Assets,42};43use up_data_structs::{44 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45 CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,46 TokenId,47};4849pub mod weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub use module::*;55pub use weights::WeightInfo;5657#[frame_support::pallet]58pub mod module {59 use up_data_structs::{60 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,61 };6263 use super::*;6465 #[pallet::config]66 pub trait Config:67 frame_system::Config68 + pallet_common::Config69 + pallet_fungible::Config70 + pallet_balances::Config71 {72 /// The overarching event type.73 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7475 /// Origin for force registering of a foreign asset.76 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7778 /// The ID of the foreign assets pallet.79 type PalletId: Get<PalletId>;8081 /// Self-location of this parachain.82 type SelfLocation: Get<MultiLocation>;8384 /// The converter from a MultiLocation to a CrossAccountId.85 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8687 /// Weight information for the extrinsics in this module.88 type WeightInfo: WeightInfo;89 }9091 #[pallet::error]92 pub enum Error<T> {93 /// The foreign asset is already registered.94 ForeignAssetAlreadyRegistered,95 }9697 #[pallet::event]98 #[pallet::generate_deposit(fn deposit_event)]99 pub enum Event<T: Config> {100 /// The foreign asset registered.101 ForeignAssetRegistered {102 collection_id: CollectionId,103 asset_id: Box<AssetId>,104 },105 }106107 /// The corresponding collections of foreign assets.108 #[pallet::storage]109 #[pallet::getter(fn foreign_asset_to_collection)]110 pub type ForeignAssetToCollection<T: Config> =111 StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;112113 /// The corresponding foreign assets of collections.114 #[pallet::storage]115 #[pallet::getter(fn collection_to_foreign_asset)]116 pub type CollectionToForeignAsset<T: Config> =117 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;118119 /// The correponding NFT token id of reserve NFTs120 #[pallet::storage]121 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]122 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<123 Hasher1 = Twox64Concat,124 Key1 = CollectionId,125 Hasher2 = Twox64Concat,126 Key2 = staging_xcm::v3::AssetInstance,127 Value = TokenId,128 QueryKind = OptionQuery,129 >;130131 /// The correponding reserve NFT of a token ID132 #[pallet::storage]133 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]134 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<135 Hasher1 = Twox64Concat,136 Key1 = CollectionId,137 Hasher2 = Twox64Concat,138 Key2 = TokenId,139 Value = staging_xcm::v3::AssetInstance,140 QueryKind = OptionQuery,141 >;142143 #[pallet::pallet]144 pub struct Pallet<T>(_);145146 #[pallet::call]147 impl<T: Config> Pallet<T> {148 #[pallet::call_index(0)]149 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]150 pub fn force_register_foreign_asset(151 origin: OriginFor<T>,152 asset_id: Box<AssetId>,153 name: CollectionName,154 token_prefix: CollectionTokenPrefix,155 mode: ForeignCollectionMode,156 ) -> DispatchResult {157 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159 ensure!(160 !<ForeignAssetToCollection<T>>::contains_key(*asset_id),161 <Error<T>>::ForeignAssetAlreadyRegistered,162 );163164 let foreign_collection_owner = Self::pallet_account();165166 let description: CollectionDescription = "Foreign Assets Collection"167 .encode_utf16()168 .collect::<Vec<_>>()169 .try_into()170 .expect("description length < max description length; qed");171172 let payer = None;173 let is_special_collection = true;174 let collection_id = T::CollectionDispatch::create_raw(175 foreign_collection_owner,176 payer,177 is_special_collection,178 CreateCollectionData {179 name,180 token_prefix,181 description,182 mode: mode.into(),183184 properties: vec![Property {185 key: Self::reserve_location_property_key(),186 value: asset_id187 .encode()188 .try_into()189 .expect("multilocation is less than 32k; qed"),190 }]191 .try_into()192 .expect("just one property can always be stored; qed"),193194 token_property_permissions: vec![PropertyKeyPermission {195 key: Self::reserve_asset_instance_property_key(),196 permission: PropertyPermission {197 mutable: false,198 collection_admin: true,199 token_owner: false,200 },201 }]202 .try_into()203 .expect("just one property permission can always be stored; qed"),204 ..Default::default()205 },206 )?;207208 <ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);209 <CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);210211 Self::deposit_event(Event::<T>::ForeignAssetRegistered {212 collection_id,213 asset_id,214 });215216 Ok(())217 }218 }219}220221impl<T: Config> Pallet<T> {222 fn pallet_account() -> T::CrossAccountId {223 let owner: T::AccountId = T::PalletId::get().into_account_truncating();224 T::CrossAccountId::from_sub(owner)225 }226227 fn reserve_location_property_key() -> PropertyKey {228 b"reserve-location"229 .to_vec()230 .try_into()231 .expect("key length < max property key length; qed")232 }233234 fn reserve_asset_instance_property_key() -> PropertyKey {235 b"reserve-asset-instance"236 .to_vec()237 .try_into()238 .expect("key length < max property key length; qed")239 }240241 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.242 ///243 /// The multilocation corresponds to a local collection if:244 /// * It is `Here` location that corresponds to the native token of this parachain.245 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.246 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds247 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,248 /// otherwise `None` is returned.249 ///250 /// If the multilocation doesn't match the patterns listed above,251 /// or the `<Collection ID>` points to a foreign collection,252 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.253 fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {254 let AssetId::Concrete(asset_location) = asset_id else {255 return None;256 };257258 let self_location = T::SelfLocation::get();259260 if *asset_location == Here.into() || *asset_location == self_location {261 Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID))262 } else if asset_location.parents == self_location.parents {263 match asset_location264 .interior265 .match_and_split(&self_location.interior)266 {267 Some(GeneralIndex(collection_id)) => {268 let collection_id = CollectionId((*collection_id).try_into().ok()?);269270 Self::collection_to_foreign_asset(collection_id)271 .is_none()272 .then_some(CollectionLocality::Local(collection_id))273 }274 _ => None,275 }276 } else {277 None278 }279 }280281 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).282 ///283 /// The function will check if the asset's reserve location has the corresponding284 /// foreign collection on Unique Network,285 /// and will return the "foreign" locality containing the collection ID if found.286 ///287 /// If no corresponding foreign collection is found, the function will check288 /// if the asset's reserve location corresponds to a local collection.289 /// If the local collection is found, the "local" locality with the collection ID is returned.290 ///291 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.292 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {293 Self::foreign_asset_to_collection(asset_id)294 .map(CollectionLocality::Foreign)295 .or_else(|| Self::local_asset_id_to_collection(asset_id))296 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())297 }298299 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.300 ///301 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:302 /// `AssetInstance::Index(<token ID>)`.303 ///304 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,305 /// `None` will be returned.306 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {307 match asset_instance {308 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),309 _ => None,310 }311 }312313 /// Obtains the token ID of the `asset_instance` in the collection.314 fn asset_instance_to_token_id(315 collection_locality: CollectionLocality,316 asset_instance: &AssetInstance,317 ) -> Option<TokenId> {318 match collection_locality {319 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),320 CollectionLocality::Foreign(collection_id) => {321 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)322 }323 }324 }325326 /// Creates a foreign item in the the collection.327 fn create_foreign_asset_instance(328 xcm_ext: &dyn XcmExtensions<T>,329 collection_id: CollectionId,330 asset_instance: &AssetInstance,331 to: T::CrossAccountId,332 ) -> DispatchResult {333 let asset_instance_encoded = asset_instance.encode();334335 let derivative_token_id = xcm_ext.create_item(336 &Self::pallet_account(),337 to,338 CreateItemData::NFT(CreateNftData {339 properties: vec![Property {340 key: Self::reserve_asset_instance_property_key(),341 value: asset_instance_encoded342 .try_into()343 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),344 }]345 .try_into()346 .expect("just one property can always be stored; qed"),347 }),348 &ZeroBudget,349 )?;350351 <ForeignReserveAssetInstanceToTokenId<T>>::insert(352 collection_id,353 asset_instance,354 derivative_token_id,355 );356357 <TokenIdToForeignReserveAssetInstance<T>>::insert(358 collection_id,359 derivative_token_id,360 asset_instance,361 );362363 Ok(())364 }365366 /// Deposits an asset instance to the `to` account.367 ///368 /// Either transfers an existing item from the pallet's account369 /// or creates a foreign item.370 fn deposit_asset_instance(371 xcm_ext: &dyn XcmExtensions<T>,372 collection_locality: CollectionLocality,373 asset_instance: &AssetInstance,374 to: T::CrossAccountId,375 ) -> XcmResult {376 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);377378 let deposit_result = match (collection_locality, token_id) {379 (_, Some(token_id)) => {380 let depositor = &Self::pallet_account();381 let from = depositor;382 let amount = 1;383384 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)385 }386 (CollectionLocality::Foreign(collection_id), None) => {387 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)388 }389 (CollectionLocality::Local(_), None) => {390 return Err(XcmError::AssetNotFound);391 }392 };393394 deposit_result395 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))396 }397398 /// Withdraws an asset instance from the `from` account.399 ///400 /// Transfers the asset instance to the pallet's account.401 fn withdraw_asset_instance(402 xcm_ext: &dyn XcmExtensions<T>,403 collection_locality: CollectionLocality,404 asset_instance: &AssetInstance,405 from: T::CrossAccountId,406 ) -> XcmResult {407 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)408 .ok_or(XcmError::AssetNotFound)?;409410 let depositor = &from;411 let to = Self::pallet_account();412 let amount = 1;413 xcm_ext414 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)415 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;416417 Ok(())418 }419}420421impl<T: Config> TransactAsset for Pallet<T> {422 fn can_check_in(423 _origin: &MultiLocation,424 _what: &MultiAsset,425 _context: &XcmContext,426 ) -> XcmResult {427 Err(XcmError::Unimplemented)428 }429430 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}431432 fn can_check_out(433 _dest: &MultiLocation,434 _what: &MultiAsset,435 _context: &XcmContext,436 ) -> XcmResult {437 Err(XcmError::Unimplemented)438 }439440 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}441442 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {443 let to = T::LocationToAccountId::convert_location(to)444 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;445446 let collection_locality = Self::asset_to_collection(&what.id)?;447 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)448 .map_err(|_| XcmError::AssetNotFound)?;449450 let collection = dispatch.as_dyn();451 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;452453 match what.fun {454 Fungibility::Fungible(amount) => xcm_ext455 .create_item(456 &Self::pallet_account(),457 to,458 CreateItemData::Fungible(CreateFungibleData { value: amount }),459 &ZeroBudget,460 )461 .map(|_| ())462 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),463464 Fungibility::NonFungible(asset_instance) => {465 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)466 }467 }468 }469470 fn withdraw_asset(471 what: &MultiAsset,472 from: &MultiLocation,473 _maybe_context: Option<&XcmContext>,474 ) -> Result<staging_xcm_executor::Assets, XcmError> {475 let from = T::LocationToAccountId::convert_location(from)476 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;477478 let collection_locality = Self::asset_to_collection(&what.id)?;479 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)480 .map_err(|_| XcmError::AssetNotFound)?;481482 let collection = dispatch.as_dyn();483 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;484485 match what.fun {486 Fungibility::Fungible(amount) => xcm_ext487 .burn_item(from, TokenId::default(), amount)488 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,489490 Fungibility::NonFungible(asset_instance) => {491 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;492 }493 }494495 Ok(what.clone().into())496 }497498 fn internal_transfer_asset(499 what: &MultiAsset,500 from: &MultiLocation,501 to: &MultiLocation,502 _context: &XcmContext,503 ) -> Result<staging_xcm_executor::Assets, XcmError> {504 let from = T::LocationToAccountId::convert_location(from)505 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;506507 let to = T::LocationToAccountId::convert_location(to)508 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;509510 let collection_locality = Self::asset_to_collection(&what.id)?;511512 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)513 .map_err(|_| XcmError::AssetNotFound)?;514 let collection = dispatch.as_dyn();515 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;516517 let depositor = &from;518519 let token_id;520 let amount;521 let map_error: fn(DispatchError) -> XcmError;522523 match what.fun {524 Fungibility::Fungible(fungible_amount) => {525 token_id = TokenId::default();526 amount = fungible_amount;527 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");528 }529530 Fungibility::NonFungible(asset_instance) => {531 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)532 .ok_or(XcmError::AssetNotFound)?;533534 amount = 1;535 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")536 }537 }538539 xcm_ext540 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)541 .map_err(map_error)?;542543 Ok(what.clone().into())544 }545}546547#[derive(Clone, Copy)]548pub enum CollectionLocality {549 Local(CollectionId),550 Foreign(CollectionId),551}552553impl Deref for CollectionLocality {554 type Target = CollectionId;555556 fn deref(&self) -> &Self::Target {557 match self {558 Self::Local(id) => id,559 Self::Foreign(id) => id,560 }561 }562}563564pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);565impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>566 for CurrencyIdConvert<T>567{568 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {569 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {570 Some(T::SelfLocation::get())571 } else {572 <Pallet<T>>::collection_to_foreign_asset(collection_id)573 .and_then(|asset_id| match asset_id {574 AssetId::Concrete(location) => Some(location),575 _ => None,576 })577 .or_else(|| {578 T::SelfLocation::get()579 .pushed_with_interior(GeneralIndex(collection_id.0.into()))580 .ok()581 })582 }583 }584}585586#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]587pub enum ForeignCollectionMode {588 NFT,589 Fungible(u8),590}591592impl From<ForeignCollectionMode> for CollectionMode {593 fn from(value: ForeignCollectionMode) -> Self {594 match value {595 ForeignCollectionMode::NFT => Self::NFT,596 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),597 }598 }599}600601pub struct FreeForAll;602603impl WeightTrader for FreeForAll {604 fn new() -> Self {605 Self606 }607608 fn buy_weight(609 &mut self,610 weight: Weight,611 payment: Assets,612 _xcm: &XcmContext,613 ) -> Result<Assets, XcmError> {614 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);615 Ok(payment)616 }617}1// 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 core::ops::Deref;2728use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36 opaque::latest::{prelude::XcmError, Weight},37 v3::{prelude::*, MultiAsset, XcmContext},38};39use staging_xcm_executor::{40 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41 Assets,42};43use up_data_structs::{44 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45 CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,46 TokenId,47};4849pub mod weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub use module::*;55pub use weights::WeightInfo;5657#[frame_support::pallet]58pub mod module {59 use up_data_structs::{60 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,61 };6263 use super::*;6465 #[pallet::config]66 pub trait Config:67 frame_system::Config68 + pallet_common::Config69 + pallet_fungible::Config70 + pallet_balances::Config71 {72 /// The overarching event type.73 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7475 /// Origin for force registering of a foreign asset.76 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7778 /// The ID of the foreign assets pallet.79 type PalletId: Get<PalletId>;8081 /// Self-location of this parachain.82 type SelfLocation: Get<MultiLocation>;8384 /// The converter from a MultiLocation to a CrossAccountId.85 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8687 /// Weight information for the extrinsics in this module.88 type WeightInfo: WeightInfo;89 }9091 #[pallet::error]92 pub enum Error<T> {93 /// The foreign asset is already registered.94 ForeignAssetAlreadyRegistered,95 }9697 #[pallet::event]98 #[pallet::generate_deposit(fn deposit_event)]99 pub enum Event<T: Config> {100 /// The foreign asset registered.101 ForeignAssetRegistered {102 collection_id: CollectionId,103 asset_id: Box<AssetId>,104 },105 }106107 /// The corresponding collections of foreign assets.108 #[pallet::storage]109 #[pallet::getter(fn foreign_asset_to_collection)]110 pub type ForeignAssetToCollection<T: Config> =111 StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;112113 /// The corresponding foreign assets of collections.114 #[pallet::storage]115 #[pallet::getter(fn collection_to_foreign_asset)]116 pub type CollectionToForeignAsset<T: Config> =117 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;118119 /// The correponding NFT token id of reserve NFTs120 #[pallet::storage]121 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]122 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<123 Hasher1 = Twox64Concat,124 Key1 = CollectionId,125 Hasher2 = Twox64Concat,126 Key2 = staging_xcm::v3::AssetInstance,127 Value = TokenId,128 QueryKind = OptionQuery,129 >;130131 /// The correponding reserve NFT of a token ID132 #[pallet::storage]133 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]134 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<135 Hasher1 = Twox64Concat,136 Key1 = CollectionId,137 Hasher2 = Twox64Concat,138 Key2 = TokenId,139 Value = staging_xcm::v3::AssetInstance,140 QueryKind = OptionQuery,141 >;142143 #[pallet::pallet]144 pub struct Pallet<T>(_);145146 #[pallet::call]147 impl<T: Config> Pallet<T> {148 #[pallet::call_index(0)]149 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]150 pub fn force_register_foreign_asset(151 origin: OriginFor<T>,152 asset_id: Box<AssetId>,153 name: CollectionName,154 token_prefix: CollectionTokenPrefix,155 mode: ForeignCollectionMode,156 ) -> DispatchResult {157 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159 ensure!(160 !<ForeignAssetToCollection<T>>::contains_key(*asset_id),161 <Error<T>>::ForeignAssetAlreadyRegistered,162 );163164 let foreign_collection_owner = Self::pallet_account();165166 let description: CollectionDescription = "Foreign Assets Collection"167 .encode_utf16()168 .collect::<Vec<_>>()169 .try_into()170 .expect("description length < max description length; qed");171172 let payer = None;173 let is_special_collection = true;174 let collection_id = T::CollectionDispatch::create_raw(175 foreign_collection_owner,176 payer,177 is_special_collection,178 CreateCollectionData {179 name,180 token_prefix,181 description,182 mode: mode.into(),183184 properties: vec![Property {185 key: Self::reserve_location_property_key(),186 value: asset_id187 .encode()188 .try_into()189 .expect("multilocation is less than 32k; qed"),190 }]191 .try_into()192 .expect("just one property can always be stored; qed"),193194 token_property_permissions: vec![PropertyKeyPermission {195 key: Self::reserve_asset_instance_property_key(),196 permission: PropertyPermission {197 mutable: false,198 collection_admin: true,199 token_owner: false,200 },201 }]202 .try_into()203 .expect("just one property permission can always be stored; qed"),204 ..Default::default()205 },206 )?;207208 <ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);209 <CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);210211 Self::deposit_event(Event::<T>::ForeignAssetRegistered {212 collection_id,213 asset_id,214 });215216 Ok(())217 }218 }219}220221impl<T: Config> Pallet<T> {222 fn pallet_account() -> T::CrossAccountId {223 let owner: T::AccountId = T::PalletId::get().into_account_truncating();224 T::CrossAccountId::from_sub(owner)225 }226227 fn reserve_location_property_key() -> PropertyKey {228 b"reserve-location"229 .to_vec()230 .try_into()231 .expect("key length < max property key length; qed")232 }233234 fn reserve_asset_instance_property_key() -> PropertyKey {235 b"reserve-asset-instance"236 .to_vec()237 .try_into()238 .expect("key length < max property key length; qed")239 }240241 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.242 ///243 /// The multilocation corresponds to a local collection if:244 /// * It is `Here` location that corresponds to the native token of this parachain.245 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.246 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds247 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,248 /// otherwise `None` is returned.249 /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.250 ///251 /// If the multilocation doesn't match the patterns listed above,252 /// or the `<Collection ID>` points to a foreign collection,253 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.254 fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {255 let AssetId::Concrete(asset_location) = asset_id else {256 return None;257 };258259 let self_location = T::SelfLocation::get();260261 if *asset_location == Here.into() || *asset_location == self_location {262 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));263 }264265 let collection_junction = if asset_location.parents == 0 {266 match &asset_location.interior {267 X1(junction) => junction,268 _ => return None,269 }270 } else if asset_location.parents == self_location.parents {271 asset_location272 .interior273 .match_and_split(&self_location.interior)?274 } else {275 return None;276 };277278 let GeneralIndex(collection_id) = collection_junction else {279 return None;280 };281282 let collection_id = CollectionId((*collection_id).try_into().ok()?);283284 Self::collection_to_foreign_asset(collection_id)285 .is_none()286 .then_some(CollectionLocality::Local(collection_id))287 }288289 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).290 ///291 /// The function will check if the asset's reserve location has the corresponding292 /// foreign collection on Unique Network,293 /// and will return the "foreign" locality containing the collection ID if found.294 ///295 /// If no corresponding foreign collection is found, the function will check296 /// if the asset's reserve location corresponds to a local collection.297 /// If the local collection is found, the "local" locality with the collection ID is returned.298 ///299 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.300 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {301 Self::foreign_asset_to_collection(asset_id)302 .map(CollectionLocality::Foreign)303 .or_else(|| Self::local_asset_id_to_collection(asset_id))304 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())305 }306307 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.308 ///309 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:310 /// `AssetInstance::Index(<token ID>)`.311 ///312 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,313 /// `None` will be returned.314 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {315 match asset_instance {316 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),317 _ => None,318 }319 }320321 /// Obtains the token ID of the `asset_instance` in the collection.322 fn asset_instance_to_token_id(323 collection_locality: CollectionLocality,324 asset_instance: &AssetInstance,325 ) -> Option<TokenId> {326 match collection_locality {327 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),328 CollectionLocality::Foreign(collection_id) => {329 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)330 }331 }332 }333334 /// Creates a foreign item in the the collection.335 fn create_foreign_asset_instance(336 xcm_ext: &dyn XcmExtensions<T>,337 collection_id: CollectionId,338 asset_instance: &AssetInstance,339 to: T::CrossAccountId,340 ) -> DispatchResult {341 let asset_instance_encoded = asset_instance.encode();342343 let derivative_token_id = xcm_ext.create_item(344 &Self::pallet_account(),345 to,346 CreateItemData::NFT(CreateNftData {347 properties: vec![Property {348 key: Self::reserve_asset_instance_property_key(),349 value: asset_instance_encoded350 .try_into()351 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),352 }]353 .try_into()354 .expect("just one property can always be stored; qed"),355 }),356 &ZeroBudget,357 )?;358359 <ForeignReserveAssetInstanceToTokenId<T>>::insert(360 collection_id,361 asset_instance,362 derivative_token_id,363 );364365 <TokenIdToForeignReserveAssetInstance<T>>::insert(366 collection_id,367 derivative_token_id,368 asset_instance,369 );370371 Ok(())372 }373374 /// Deposits an asset instance to the `to` account.375 ///376 /// Either transfers an existing item from the pallet's account377 /// or creates a foreign item.378 fn deposit_asset_instance(379 xcm_ext: &dyn XcmExtensions<T>,380 collection_locality: CollectionLocality,381 asset_instance: &AssetInstance,382 to: T::CrossAccountId,383 ) -> XcmResult {384 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);385386 let deposit_result = match (collection_locality, token_id) {387 (_, Some(token_id)) => {388 let depositor = &Self::pallet_account();389 let from = depositor;390 let amount = 1;391392 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)393 }394 (CollectionLocality::Foreign(collection_id), None) => {395 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)396 }397 (CollectionLocality::Local(_), None) => {398 return Err(XcmError::AssetNotFound);399 }400 };401402 deposit_result403 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))404 }405406 /// Withdraws an asset instance from the `from` account.407 ///408 /// Transfers the asset instance to the pallet's account.409 fn withdraw_asset_instance(410 xcm_ext: &dyn XcmExtensions<T>,411 collection_locality: CollectionLocality,412 asset_instance: &AssetInstance,413 from: T::CrossAccountId,414 ) -> XcmResult {415 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)416 .ok_or(XcmError::AssetNotFound)?;417418 let depositor = &from;419 let to = Self::pallet_account();420 let amount = 1;421 xcm_ext422 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)423 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;424425 Ok(())426 }427}428429impl<T: Config> TransactAsset for Pallet<T> {430 fn can_check_in(431 _origin: &MultiLocation,432 _what: &MultiAsset,433 _context: &XcmContext,434 ) -> XcmResult {435 Err(XcmError::Unimplemented)436 }437438 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}439440 fn can_check_out(441 _dest: &MultiLocation,442 _what: &MultiAsset,443 _context: &XcmContext,444 ) -> XcmResult {445 Err(XcmError::Unimplemented)446 }447448 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}449450 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {451 let to = T::LocationToAccountId::convert_location(to)452 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;453454 let collection_locality = Self::asset_to_collection(&what.id)?;455 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)456 .map_err(|_| XcmError::AssetNotFound)?;457458 let collection = dispatch.as_dyn();459 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;460461 match what.fun {462 Fungibility::Fungible(amount) => xcm_ext463 .create_item(464 &Self::pallet_account(),465 to,466 CreateItemData::Fungible(CreateFungibleData { value: amount }),467 &ZeroBudget,468 )469 .map(|_| ())470 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),471472 Fungibility::NonFungible(asset_instance) => {473 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)474 }475 }476 }477478 fn withdraw_asset(479 what: &MultiAsset,480 from: &MultiLocation,481 _maybe_context: Option<&XcmContext>,482 ) -> Result<staging_xcm_executor::Assets, XcmError> {483 let from = T::LocationToAccountId::convert_location(from)484 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;485486 let collection_locality = Self::asset_to_collection(&what.id)?;487 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)488 .map_err(|_| XcmError::AssetNotFound)?;489490 let collection = dispatch.as_dyn();491 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;492493 match what.fun {494 Fungibility::Fungible(amount) => xcm_ext495 .burn_item(from, TokenId::default(), amount)496 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,497498 Fungibility::NonFungible(asset_instance) => {499 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;500 }501 }502503 Ok(what.clone().into())504 }505506 fn internal_transfer_asset(507 what: &MultiAsset,508 from: &MultiLocation,509 to: &MultiLocation,510 _context: &XcmContext,511 ) -> Result<staging_xcm_executor::Assets, XcmError> {512 let from = T::LocationToAccountId::convert_location(from)513 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;514515 let to = T::LocationToAccountId::convert_location(to)516 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;517518 let collection_locality = Self::asset_to_collection(&what.id)?;519520 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)521 .map_err(|_| XcmError::AssetNotFound)?;522 let collection = dispatch.as_dyn();523 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;524525 let depositor = &from;526527 let token_id;528 let amount;529 let map_error: fn(DispatchError) -> XcmError;530531 match what.fun {532 Fungibility::Fungible(fungible_amount) => {533 token_id = TokenId::default();534 amount = fungible_amount;535 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");536 }537538 Fungibility::NonFungible(asset_instance) => {539 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)540 .ok_or(XcmError::AssetNotFound)?;541542 amount = 1;543 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")544 }545 }546547 xcm_ext548 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)549 .map_err(map_error)?;550551 Ok(what.clone().into())552 }553}554555#[derive(Clone, Copy)]556pub enum CollectionLocality {557 Local(CollectionId),558 Foreign(CollectionId),559}560561impl Deref for CollectionLocality {562 type Target = CollectionId;563564 fn deref(&self) -> &Self::Target {565 match self {566 Self::Local(id) => id,567 Self::Foreign(id) => id,568 }569 }570}571572pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);573impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>574 for CurrencyIdConvert<T>575{576 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {577 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {578 Some(T::SelfLocation::get())579 } else {580 <Pallet<T>>::collection_to_foreign_asset(collection_id)581 .and_then(|asset_id| match asset_id {582 AssetId::Concrete(location) => Some(location),583 _ => None,584 })585 .or_else(|| {586 T::SelfLocation::get()587 .pushed_with_interior(GeneralIndex(collection_id.0.into()))588 .ok()589 })590 }591 }592}593594#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]595pub enum ForeignCollectionMode {596 NFT,597 Fungible(u8),598}599600impl From<ForeignCollectionMode> for CollectionMode {601 fn from(value: ForeignCollectionMode) -> Self {602 match value {603 ForeignCollectionMode::NFT => Self::NFT,604 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),605 }606 }607}608609pub struct FreeForAll;610611impl WeightTrader for FreeForAll {612 fn new() -> Self {613 Self614 }615616 fn buy_weight(617 &mut self,618 weight: Weight,619 payment: Assets,620 _xcm: &XcmContext,621 ) -> Result<Assets, XcmError> {622 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);623 Ok(payment)624 }625}