difftreelog
fix native_asset_location_to_collection
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 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::{boxed::Box, 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, CollectionTokenPrefix,43 CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,44 TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455#[frame_support::pallet]56pub mod module {57 use up_data_structs::{58 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,59 };6061 use super::*;6263 #[pallet::config]64 pub trait Config:65 frame_system::Config66 + pallet_common::Config67 + pallet_fungible::Config68 + pallet_balances::Config69 {70 /// The overarching event type.71 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7273 /// Origin for force registering of a foreign asset.74 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7576 /// The ID of the foreign assets pallet.77 type PalletId: Get<PalletId>;7879 /// Self-location of this parachain.80 type SelfLocation: Get<MultiLocation>;8182 /// The converter from a MultiLocation to a CrossAccountId.83 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8485 /// Weight information for the extrinsics in this module.86 type WeightInfo: WeightInfo;87 }8889 #[pallet::error]90 pub enum Error<T> {91 /// The foreign asset is already registered.92 ForeignAssetAlreadyRegistered,93 }9495 #[pallet::event]96 #[pallet::generate_deposit(fn deposit_event)]97 pub enum Event<T: Config> {98 /// The foreign asset registered.99 ForeignAssetRegistered {100 asset_id: CollectionId,101 reserve_location: Box<MultiLocation>,102 },103 }104105 /// The corresponding collections of reserve locations.106 #[pallet::storage]107 #[pallet::getter(fn foreign_reserve_location_to_collection)]108 pub type ForeignReserveLocationToCollection<T: Config> =109 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;110111 /// The corresponding reserve location of collections.112 #[pallet::storage]113 #[pallet::getter(fn collection_to_foreign_reserve_location)]114 pub type CollectionToForeignReserveLocation<T: Config> =115 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;116117 /// The correponding NFT token id of reserve NFTs118 #[pallet::storage]119 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]120 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<121 Hasher1 = Twox64Concat,122 Key1 = CollectionId,123 Hasher2 = Twox64Concat,124 Key2 = staging_xcm::v3::AssetInstance,125 Value = TokenId,126 QueryKind = OptionQuery,127 >;128129 #[pallet::pallet]130 pub struct Pallet<T>(_);131132 #[pallet::call]133 impl<T: Config> Pallet<T> {134 #[pallet::call_index(0)]135 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]136 pub fn force_register_foreign_asset(137 origin: OriginFor<T>,138 reserve_location: Box<MultiLocation>,139 name: CollectionName,140 token_prefix: CollectionTokenPrefix,141 mode: ForeignCollectionMode,142 ) -> DispatchResult {143 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;144145 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {146 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());147 }148149 let foreign_collection_owner = Self::pallet_account();150151 let description: CollectionDescription = "Foreign Assets Collection"152 .encode_utf16()153 .collect::<Vec<_>>()154 .try_into()155 .expect("description length < max description length; qed");156157 let payer = None;158 let is_special_collection = true;159 let collection_id = T::CollectionDispatch::create_raw(160 foreign_collection_owner,161 payer,162 is_special_collection,163 CreateCollectionData {164 name,165 token_prefix,166 description,167 mode: mode.into(),168169 properties: vec![Property {170 key: Self::reserve_location_property_key(),171 value: reserve_location172 .encode()173 .try_into()174 .expect("multilocation is less than 32k; qed"),175 }]176 .try_into()177 .expect("just one property can always be stored; qed"),178179 token_property_permissions: vec![PropertyKeyPermission {180 key: Self::reserve_asset_instance_property_key(),181 permission: PropertyPermission {182 mutable: false,183 collection_admin: true,184 token_owner: false,185 },186 }]187 .try_into()188 .expect("just one property permission can always be stored; qed"),189 ..Default::default()190 },191 )?;192193 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);194 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);195196 Self::deposit_event(Event::<T>::ForeignAssetRegistered {197 asset_id: collection_id,198 reserve_location,199 });200201 Ok(())202 }203 }204}205206impl<T: Config> Pallet<T> {207 fn pallet_account() -> T::CrossAccountId {208 let owner: T::AccountId = T::PalletId::get().into_account_truncating();209 T::CrossAccountId::from_sub(owner)210 }211212 fn reserve_location_property_key() -> PropertyKey {213 b"reserve-location"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 fn reserve_asset_instance_property_key() -> PropertyKey {220 b"reserve-asset-instance"221 .to_vec()222 .try_into()223 .expect("key length < max property key length; qed")224 }225226 /// Converts a multilocation to the Unique Network's local collection227 /// (i.e. the collection originally created on the Unique Network's parachain).228 ///229 /// The multilocation corresponds to a Unique Network's collection if:230 /// * It is `Here` location that corresponds to the native token of this parachain.231 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.232 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds233 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,234 /// otherwise the `AssetIdConversionFailed` error will be returned.235 ///236 /// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,237 /// identifying that the given multilocation doesn't correspond to a local collection.238 fn native_asset_location_to_collection(239 asset_location: &MultiLocation,240 ) -> Result<Option<CollectionId>, XcmError> {241 let self_location = T::SelfLocation::get();242243 if *asset_location == Here.into() || *asset_location == self_location {244 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))245 } else if asset_location.parents == self_location.parents {246 match asset_location247 .interior248 .match_and_split(&self_location.interior)249 {250 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(251 (*collection_id)252 .try_into()253 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,254 ))),255 _ => Ok(None),256 }257 } else {258 Ok(None)259 }260 }261262 /// Converts a multiasset to a Unique Network's collection (either local or a foreign one).263 ///264 /// The function will try to convert the multiasset's reserve location265 /// to the Unique Network's local collection.266 ///267 /// If the multilocation doesn't correspond to a local collection,268 /// the function will check if the reserve location has the corresponding269 /// derivative Unique Network's collection, and will return the said collection ID if found.270 ///271 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.272 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {273 let AssetId::Concrete(asset_reserve_location) = asset.id else {274 return Err(XcmExecutorError::AssetNotHandled.into());275 };276277 Self::native_asset_location_to_collection(&asset_reserve_location)?278 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))279 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())280 }281282 /// Converts an XCM asset instance to the Unique Network's token ID.283 ///284 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:285 /// `AssetInstance::Index(<token ID>)`.286 ///287 /// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,288 /// the `AssetNotFound` error will be returned.289 fn native_asset_instance_to_token_id(290 asset_instance: &AssetInstance,291 ) -> Result<TokenId, XcmError> {292 match asset_instance {293 AssetInstance::Index(token_id) => Ok(TokenId(294 (*token_id)295 .try_into()296 .map_err(|_| XcmError::AssetNotFound)?,297 )),298 _ => Err(XcmError::AssetNotFound),299 }300 }301302 /// Obtains the token ID of the `asset_instance` in the collection.303 ///304 /// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection305 /// and the item hasn't yet been created on this blockchain.306 ///307 /// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.308 ///309 /// If the `asset_instance` is a part of a local collection,310 /// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.311 fn asset_instance_to_token_id(312 collection_id: CollectionId,313 asset_instance: &AssetInstance,314 ) -> Result<Option<TokenId>, XcmError> {315 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {316 Ok(Self::foreign_reserve_asset_instance_to_token_id(317 collection_id,318 asset_instance,319 ))320 } else {321 Self::native_asset_instance_to_token_id(asset_instance).map(Some)322 }323 }324325 /// Creates a foreign item in the the collection.326 fn create_foreign_asset_instance(327 xcm_ext: &dyn XcmExtensions<T>,328 collection_id: CollectionId,329 asset_instance: &AssetInstance,330 to: T::CrossAccountId,331 ) -> DispatchResult {332 let asset_instance_encoded = asset_instance.encode();333334 let derivative_token_id = xcm_ext.create_item(335 &Self::pallet_account(),336 to,337 CreateItemData::NFT(CreateNftData {338 properties: vec![Property {339 key: Self::reserve_asset_instance_property_key(),340 value: asset_instance_encoded341 .try_into()342 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),343 }]344 .try_into()345 .expect("just one property can always be stored; qed"),346 }),347 &ZeroBudget,348 )?;349350 <ForeignReserveAssetInstanceToTokenId<T>>::insert(351 collection_id,352 asset_instance,353 derivative_token_id,354 );355356 Ok(())357 }358359 /// Deposits an asset instance to the `to` account.360 ///361 /// Either transfers an existing item from the pallet's account362 /// or creates a foreign item.363 fn deposit_asset_instance(364 xcm_ext: &dyn XcmExtensions<T>,365 collection_id: CollectionId,366 asset_instance: &AssetInstance,367 to: T::CrossAccountId,368 ) -> XcmResult {369 let deposit_result = if let Some(token_id) =370 Self::asset_instance_to_token_id(collection_id, asset_instance)?371 {372 let depositor = &Self::pallet_account();373 let from = depositor;374 let amount = 1;375376 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)377 } else {378 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)379 };380381 deposit_result382 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))383 }384385 /// Withdraws an asset instance from the `from` account.386 ///387 /// Transfers the asset instance to the pallet's account.388 fn withdraw_asset_instance(389 xcm_ext: &dyn XcmExtensions<T>,390 collection_id: CollectionId,391 asset_instance: &AssetInstance,392 from: T::CrossAccountId,393 ) -> XcmResult {394 let token_id = Self::asset_instance_to_token_id(collection_id, asset_instance)?395 .ok_or(XcmError::AssetNotFound)?;396397 let depositor = &from;398 let to = Self::pallet_account();399 let amount = 1;400 xcm_ext401 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)402 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;403404 Ok(())405 }406}407408impl<T: Config> TransactAsset for Pallet<T> {409 fn can_check_in(410 _origin: &MultiLocation,411 _what: &MultiAsset,412 _context: &XcmContext,413 ) -> XcmResult {414 Err(XcmError::Unimplemented)415 }416417 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}418419 fn can_check_out(420 _dest: &MultiLocation,421 _what: &MultiAsset,422 _context: &XcmContext,423 ) -> XcmResult {424 Err(XcmError::Unimplemented)425 }426427 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}428429 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {430 let to = T::LocationToAccountId::convert_location(to)431 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;432433 let collection_id = Self::multiasset_to_collection(what)?;434 let dispatch =435 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;436437 let collection = dispatch.as_dyn();438 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;439440 match what.fun {441 Fungibility::Fungible(amount) => xcm_ext442 .create_item(443 &Self::pallet_account(),444 to,445 CreateItemData::Fungible(CreateFungibleData { value: amount }),446 &ZeroBudget,447 )448 .map(|_| ())449 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),450451 Fungibility::NonFungible(asset_instance) => {452 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)453 }454 }455 }456457 fn withdraw_asset(458 what: &MultiAsset,459 from: &MultiLocation,460 _maybe_context: Option<&XcmContext>,461 ) -> Result<staging_xcm_executor::Assets, XcmError> {462 let from = T::LocationToAccountId::convert_location(from)463 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;464465 let collection_id = Self::multiasset_to_collection(what)?;466 let dispatch =467 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;468469 let collection = dispatch.as_dyn();470 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;471472 match what.fun {473 Fungibility::Fungible(amount) => xcm_ext474 .burn_item(from, TokenId::default(), amount)475 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,476477 Fungibility::NonFungible(asset_instance) => {478 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;479 }480 }481482 Ok(what.clone().into())483 }484485 fn internal_transfer_asset(486 what: &MultiAsset,487 from: &MultiLocation,488 to: &MultiLocation,489 _context: &XcmContext,490 ) -> Result<staging_xcm_executor::Assets, XcmError> {491 let from = T::LocationToAccountId::convert_location(from)492 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;493494 let to = T::LocationToAccountId::convert_location(to)495 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;496497 let collection_id = Self::multiasset_to_collection(what)?;498499 let dispatch =500 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;501 let collection = dispatch.as_dyn();502 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;503504 let depositor = &from;505506 let token_id;507 let amount;508 let map_error: fn(DispatchError) -> XcmError;509510 match what.fun {511 Fungibility::Fungible(fungible_amount) => {512 token_id = TokenId::default();513 amount = fungible_amount;514 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");515 }516517 Fungibility::NonFungible(asset_instance) => {518 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?519 .ok_or(XcmError::AssetNotFound)?;520521 amount = 1;522 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")523 }524 }525526 xcm_ext527 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)528 .map_err(map_error)?;529530 Ok(what.clone().into())531 }532}533534pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);535impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>536 for CurrencyIdConvert<T>537{538 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {539 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {540 Some(T::SelfLocation::get())541 } else {542 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {543 T::SelfLocation::get()544 .pushed_with_interior(GeneralIndex(collection_id.0.into()))545 .ok()546 })547 }548 }549}550551pub use frame_support::{552 traits::{553 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,554 },555 weights::{WeightToFee, WeightToFeePolynomial},556};557558#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]559pub enum ForeignCollectionMode {560 NFT,561 Fungible(u8),562}563564impl From<ForeignCollectionMode> for CollectionMode {565 fn from(value: ForeignCollectionMode) -> Self {566 match value {567 ForeignCollectionMode::NFT => Self::NFT,568 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),569 }570 }571}572573pub struct FreeForAll;574575impl WeightTrader for FreeForAll {576 fn new() -> Self {577 Self578 }579580 fn buy_weight(581 &mut self,582 weight: Weight,583 payment: Assets,584 _xcm: &XcmContext,585 ) -> Result<Assets, XcmError> {586 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);587 Ok(payment)588 }589}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 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::{boxed::Box, 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, CollectionTokenPrefix,43 CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,44 TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455#[frame_support::pallet]56pub mod module {57 use up_data_structs::{58 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,59 };6061 use super::*;6263 #[pallet::config]64 pub trait Config:65 frame_system::Config66 + pallet_common::Config67 + pallet_fungible::Config68 + pallet_balances::Config69 {70 /// The overarching event type.71 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7273 /// Origin for force registering of a foreign asset.74 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7576 /// The ID of the foreign assets pallet.77 type PalletId: Get<PalletId>;7879 /// Self-location of this parachain.80 type SelfLocation: Get<MultiLocation>;8182 /// The converter from a MultiLocation to a CrossAccountId.83 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8485 /// Weight information for the extrinsics in this module.86 type WeightInfo: WeightInfo;87 }8889 #[pallet::error]90 pub enum Error<T> {91 /// The foreign asset is already registered.92 ForeignAssetAlreadyRegistered,93 }9495 #[pallet::event]96 #[pallet::generate_deposit(fn deposit_event)]97 pub enum Event<T: Config> {98 /// The foreign asset registered.99 ForeignAssetRegistered {100 asset_id: CollectionId,101 reserve_location: Box<MultiLocation>,102 },103 }104105 /// The corresponding collections of reserve locations.106 #[pallet::storage]107 #[pallet::getter(fn foreign_reserve_location_to_collection)]108 pub type ForeignReserveLocationToCollection<T: Config> =109 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;110111 /// The corresponding reserve location of collections.112 #[pallet::storage]113 #[pallet::getter(fn collection_to_foreign_reserve_location)]114 pub type CollectionToForeignReserveLocation<T: Config> =115 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;116117 /// The correponding NFT token id of reserve NFTs118 #[pallet::storage]119 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]120 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<121 Hasher1 = Twox64Concat,122 Key1 = CollectionId,123 Hasher2 = Twox64Concat,124 Key2 = staging_xcm::v3::AssetInstance,125 Value = TokenId,126 QueryKind = OptionQuery,127 >;128129 #[pallet::pallet]130 pub struct Pallet<T>(_);131132 #[pallet::call]133 impl<T: Config> Pallet<T> {134 #[pallet::call_index(0)]135 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]136 pub fn force_register_foreign_asset(137 origin: OriginFor<T>,138 reserve_location: Box<MultiLocation>,139 name: CollectionName,140 token_prefix: CollectionTokenPrefix,141 mode: ForeignCollectionMode,142 ) -> DispatchResult {143 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;144145 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {146 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());147 }148149 let foreign_collection_owner = Self::pallet_account();150151 let description: CollectionDescription = "Foreign Assets Collection"152 .encode_utf16()153 .collect::<Vec<_>>()154 .try_into()155 .expect("description length < max description length; qed");156157 let payer = None;158 let is_special_collection = true;159 let collection_id = T::CollectionDispatch::create_raw(160 foreign_collection_owner,161 payer,162 is_special_collection,163 CreateCollectionData {164 name,165 token_prefix,166 description,167 mode: mode.into(),168169 properties: vec![Property {170 key: Self::reserve_location_property_key(),171 value: reserve_location172 .encode()173 .try_into()174 .expect("multilocation is less than 32k; qed"),175 }]176 .try_into()177 .expect("just one property can always be stored; qed"),178179 token_property_permissions: vec![PropertyKeyPermission {180 key: Self::reserve_asset_instance_property_key(),181 permission: PropertyPermission {182 mutable: false,183 collection_admin: true,184 token_owner: false,185 },186 }]187 .try_into()188 .expect("just one property permission can always be stored; qed"),189 ..Default::default()190 },191 )?;192193 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);194 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);195196 Self::deposit_event(Event::<T>::ForeignAssetRegistered {197 asset_id: collection_id,198 reserve_location,199 });200201 Ok(())202 }203 }204}205206impl<T: Config> Pallet<T> {207 fn pallet_account() -> T::CrossAccountId {208 let owner: T::AccountId = T::PalletId::get().into_account_truncating();209 T::CrossAccountId::from_sub(owner)210 }211212 fn reserve_location_property_key() -> PropertyKey {213 b"reserve-location"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 fn reserve_asset_instance_property_key() -> PropertyKey {220 b"reserve-asset-instance"221 .to_vec()222 .try_into()223 .expect("key length < max property key length; qed")224 }225226 /// Converts a multilocation to the Unique Network's local collection227 /// (i.e. the collection originally created on the Unique Network's parachain).228 ///229 /// The multilocation corresponds to a Unique Network's collection if:230 /// * It is `Here` location that corresponds to the native token of this parachain.231 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.232 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds233 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,234 /// otherwise the `AssetIdConversionFailed` error will be returned.235 ///236 /// If the multilocation doesn't match the patterns listed above,237 /// or the `<Collection ID>` points to a foreign collection,238 /// the function returns `Ok(None)`, identifying that the given multilocation doesn't correspond to a local collection.239 fn native_asset_location_to_collection(240 asset_location: &MultiLocation,241 ) -> Result<Option<CollectionId>, XcmError> {242 let self_location = T::SelfLocation::get();243244 if *asset_location == Here.into() || *asset_location == self_location {245 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))246 } else if asset_location.parents == self_location.parents {247 match asset_location248 .interior249 .match_and_split(&self_location.interior)250 {251 Some(GeneralIndex(collection_id)) => {252 let collection_id = CollectionId(253 (*collection_id)254 .try_into()255 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,256 );257258 if Self::collection_to_foreign_reserve_location(collection_id).is_some() {259 Ok(None)260 } else {261 Ok(Some(collection_id))262 }263 }264 _ => Ok(None),265 }266 } else {267 Ok(None)268 }269 }270271 /// Converts a multiasset to a Unique Network's collection (either local or a foreign one).272 ///273 /// The function will try to convert the multiasset's reserve location274 /// to the Unique Network's local collection.275 ///276 /// If the multilocation doesn't correspond to a local collection,277 /// the function will check if the reserve location has the corresponding278 /// derivative Unique Network's collection, and will return the said collection ID if found.279 ///280 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.281 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {282 let AssetId::Concrete(asset_reserve_location) = asset.id else {283 return Err(XcmExecutorError::AssetNotHandled.into());284 };285286 Self::native_asset_location_to_collection(&asset_reserve_location)?287 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))288 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())289 }290291 /// Converts an XCM asset instance to the Unique Network's token ID.292 ///293 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:294 /// `AssetInstance::Index(<token ID>)`.295 ///296 /// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,297 /// the `AssetNotFound` error will be returned.298 fn native_asset_instance_to_token_id(299 asset_instance: &AssetInstance,300 ) -> Result<TokenId, XcmError> {301 match asset_instance {302 AssetInstance::Index(token_id) => Ok(TokenId(303 (*token_id)304 .try_into()305 .map_err(|_| XcmError::AssetNotFound)?,306 )),307 _ => Err(XcmError::AssetNotFound),308 }309 }310311 /// Obtains the token ID of the `asset_instance` in the collection.312 ///313 /// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection314 /// and the item hasn't yet been created on this blockchain.315 ///316 /// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.317 ///318 /// If the `asset_instance` is a part of a local collection,319 /// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.320 fn asset_instance_to_token_id(321 collection_id: CollectionId,322 asset_instance: &AssetInstance,323 ) -> Result<Option<TokenId>, XcmError> {324 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {325 Ok(Self::foreign_reserve_asset_instance_to_token_id(326 collection_id,327 asset_instance,328 ))329 } else {330 Self::native_asset_instance_to_token_id(asset_instance).map(Some)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 Ok(())366 }367368 /// Deposits an asset instance to the `to` account.369 ///370 /// Either transfers an existing item from the pallet's account371 /// or creates a foreign item.372 fn deposit_asset_instance(373 xcm_ext: &dyn XcmExtensions<T>,374 collection_id: CollectionId,375 asset_instance: &AssetInstance,376 to: T::CrossAccountId,377 ) -> XcmResult {378 let deposit_result = if let Some(token_id) =379 Self::asset_instance_to_token_id(collection_id, asset_instance)?380 {381 let depositor = &Self::pallet_account();382 let from = depositor;383 let amount = 1;384385 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)386 } else {387 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)388 };389390 deposit_result391 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))392 }393394 /// Withdraws an asset instance from the `from` account.395 ///396 /// Transfers the asset instance to the pallet's account.397 fn withdraw_asset_instance(398 xcm_ext: &dyn XcmExtensions<T>,399 collection_id: CollectionId,400 asset_instance: &AssetInstance,401 from: T::CrossAccountId,402 ) -> XcmResult {403 let token_id = Self::asset_instance_to_token_id(collection_id, asset_instance)?404 .ok_or(XcmError::AssetNotFound)?;405406 let depositor = &from;407 let to = Self::pallet_account();408 let amount = 1;409 xcm_ext410 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)411 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;412413 Ok(())414 }415}416417impl<T: Config> TransactAsset for Pallet<T> {418 fn can_check_in(419 _origin: &MultiLocation,420 _what: &MultiAsset,421 _context: &XcmContext,422 ) -> XcmResult {423 Err(XcmError::Unimplemented)424 }425426 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}427428 fn can_check_out(429 _dest: &MultiLocation,430 _what: &MultiAsset,431 _context: &XcmContext,432 ) -> XcmResult {433 Err(XcmError::Unimplemented)434 }435436 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}437438 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {439 let to = T::LocationToAccountId::convert_location(to)440 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;441442 let collection_id = Self::multiasset_to_collection(what)?;443 let dispatch =444 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;445446 let collection = dispatch.as_dyn();447 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;448449 match what.fun {450 Fungibility::Fungible(amount) => xcm_ext451 .create_item(452 &Self::pallet_account(),453 to,454 CreateItemData::Fungible(CreateFungibleData { value: amount }),455 &ZeroBudget,456 )457 .map(|_| ())458 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),459460 Fungibility::NonFungible(asset_instance) => {461 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)462 }463 }464 }465466 fn withdraw_asset(467 what: &MultiAsset,468 from: &MultiLocation,469 _maybe_context: Option<&XcmContext>,470 ) -> Result<staging_xcm_executor::Assets, XcmError> {471 let from = T::LocationToAccountId::convert_location(from)472 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;473474 let collection_id = Self::multiasset_to_collection(what)?;475 let dispatch =476 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;477478 let collection = dispatch.as_dyn();479 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;480481 match what.fun {482 Fungibility::Fungible(amount) => xcm_ext483 .burn_item(from, TokenId::default(), amount)484 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,485486 Fungibility::NonFungible(asset_instance) => {487 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;488 }489 }490491 Ok(what.clone().into())492 }493494 fn internal_transfer_asset(495 what: &MultiAsset,496 from: &MultiLocation,497 to: &MultiLocation,498 _context: &XcmContext,499 ) -> Result<staging_xcm_executor::Assets, XcmError> {500 let from = T::LocationToAccountId::convert_location(from)501 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;502503 let to = T::LocationToAccountId::convert_location(to)504 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;505506 let collection_id = Self::multiasset_to_collection(what)?;507508 let dispatch =509 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;510 let collection = dispatch.as_dyn();511 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;512513 let depositor = &from;514515 let token_id;516 let amount;517 let map_error: fn(DispatchError) -> XcmError;518519 match what.fun {520 Fungibility::Fungible(fungible_amount) => {521 token_id = TokenId::default();522 amount = fungible_amount;523 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");524 }525526 Fungibility::NonFungible(asset_instance) => {527 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?528 .ok_or(XcmError::AssetNotFound)?;529530 amount = 1;531 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")532 }533 }534535 xcm_ext536 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)537 .map_err(map_error)?;538539 Ok(what.clone().into())540 }541}542543pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);544impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>545 for CurrencyIdConvert<T>546{547 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {548 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {549 Some(T::SelfLocation::get())550 } else {551 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {552 T::SelfLocation::get()553 .pushed_with_interior(GeneralIndex(collection_id.0.into()))554 .ok()555 })556 }557 }558}559560pub use frame_support::{561 traits::{562 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,563 },564 weights::{WeightToFee, WeightToFeePolynomial},565};566567#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]568pub enum ForeignCollectionMode {569 NFT,570 Fungible(u8),571}572573impl From<ForeignCollectionMode> for CollectionMode {574 fn from(value: ForeignCollectionMode) -> Self {575 match value {576 ForeignCollectionMode::NFT => Self::NFT,577 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),578 }579 }580}581582pub struct FreeForAll;583584impl WeightTrader for FreeForAll {585 fn new() -> Self {586 Self587 }588589 fn buy_weight(590 &mut self,591 weight: Weight,592 payment: Assets,593 _xcm: &XcmContext,594 ) -> Result<Assets, XcmError> {595 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);596 Ok(payment)597 }598}