difftreelog
refactor use collection-locality
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 /// The correponding reserve NFT of a token ID130 #[pallet::storage]131 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]132 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<133 Hasher1 = Twox64Concat,134 Key1 = CollectionId,135 Hasher2 = Twox64Concat,136 Key2 = TokenId,137 Value = staging_xcm::v3::AssetInstance,138 QueryKind = OptionQuery,139 >;140141 #[pallet::pallet]142 pub struct Pallet<T>(_);143144 #[pallet::call]145 impl<T: Config> Pallet<T> {146 #[pallet::call_index(0)]147 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]148 pub fn force_register_foreign_asset(149 origin: OriginFor<T>,150 reserve_location: Box<MultiLocation>,151 name: CollectionName,152 token_prefix: CollectionTokenPrefix,153 mode: ForeignCollectionMode,154 ) -> DispatchResult {155 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;156157 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {158 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());159 }160161 let foreign_collection_owner = Self::pallet_account();162163 let description: CollectionDescription = "Foreign Assets Collection"164 .encode_utf16()165 .collect::<Vec<_>>()166 .try_into()167 .expect("description length < max description length; qed");168169 let payer = None;170 let is_special_collection = true;171 let collection_id = T::CollectionDispatch::create_raw(172 foreign_collection_owner,173 payer,174 is_special_collection,175 CreateCollectionData {176 name,177 token_prefix,178 description,179 mode: mode.into(),180181 properties: vec![Property {182 key: Self::reserve_location_property_key(),183 value: reserve_location184 .encode()185 .try_into()186 .expect("multilocation is less than 32k; qed"),187 }]188 .try_into()189 .expect("just one property can always be stored; qed"),190191 token_property_permissions: vec![PropertyKeyPermission {192 key: Self::reserve_asset_instance_property_key(),193 permission: PropertyPermission {194 mutable: false,195 collection_admin: true,196 token_owner: false,197 },198 }]199 .try_into()200 .expect("just one property permission can always be stored; qed"),201 ..Default::default()202 },203 )?;204205 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);206 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);207208 Self::deposit_event(Event::<T>::ForeignAssetRegistered {209 asset_id: collection_id,210 reserve_location,211 });212213 Ok(())214 }215 }216}217218impl<T: Config> Pallet<T> {219 fn pallet_account() -> T::CrossAccountId {220 let owner: T::AccountId = T::PalletId::get().into_account_truncating();221 T::CrossAccountId::from_sub(owner)222 }223224 fn reserve_location_property_key() -> PropertyKey {225 b"reserve-location"226 .to_vec()227 .try_into()228 .expect("key length < max property key length; qed")229 }230231 fn reserve_asset_instance_property_key() -> PropertyKey {232 b"reserve-asset-instance"233 .to_vec()234 .try_into()235 .expect("key length < max property key length; qed")236 }237238 /// Converts a multilocation to a local collection on Unique Network.239 ///240 /// The multilocation corresponds to a local collection if:241 /// * It is `Here` location that corresponds to the native token of this parachain.242 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.243 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds244 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,245 /// otherwise `None` is returned.246 ///247 /// If the multilocation doesn't match the patterns listed above,248 /// or the `<Collection ID>` points to a foreign collection,249 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.250 fn local_asset_location_to_collection(asset_location: &MultiLocation) -> Option<CollectionId> {251 let self_location = T::SelfLocation::get();252253 if *asset_location == Here.into() || *asset_location == self_location {254 Some(NATIVE_FUNGIBLE_COLLECTION_ID)255 } else if asset_location.parents == self_location.parents {256 match asset_location257 .interior258 .match_and_split(&self_location.interior)259 {260 Some(GeneralIndex(collection_id)) => {261 let collection_id = CollectionId((*collection_id).try_into().ok()?);262263 Self::collection_to_foreign_reserve_location(collection_id)264 .is_none()265 .then_some(collection_id)266 }267 _ => None,268 }269 } else {270 None271 }272 }273274 /// Converts an asset ID to a Unique Network's collection (either foreign or a local one).275 ///276 /// The function will check if the asset's reserve location has the corresponding277 /// foreign collection on Unique Network, and will return the collection ID if found.278 ///279 /// If no corresponding foreign collection is found, the function will check280 /// if the asset's reserve location corresponds to a local collection.281 /// If the local collection is found, its ID is returned.282 ///283 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.284 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionId, XcmError> {285 let AssetId::Concrete(asset_reserve_location) = asset_id else {286 return Err(XcmExecutorError::AssetNotHandled.into());287 };288289 Self::foreign_reserve_location_to_collection(asset_reserve_location)290 .or_else(|| Self::local_asset_location_to_collection(asset_reserve_location))291 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())292 }293294 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.295 ///296 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:297 /// `AssetInstance::Index(<token ID>)`.298 ///299 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,300 /// the `AssetNotFound` error will be returned.301 fn local_asset_instance_to_token_id(302 asset_instance: &AssetInstance,303 ) -> Result<TokenId, XcmError> {304 match asset_instance {305 AssetInstance::Index(token_id) => Ok(TokenId(306 (*token_id)307 .try_into()308 .map_err(|_| XcmError::AssetNotFound)?,309 )),310 _ => Err(XcmError::AssetNotFound),311 }312 }313314 /// Obtains the token ID of the `asset_instance` in the collection.315 ///316 /// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection317 /// and the item hasn't yet been created on this blockchain.318 ///319 /// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.320 ///321 /// If the `asset_instance` is a part of a local collection,322 /// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.323 fn asset_instance_to_token_id(324 collection_id: CollectionId,325 asset_instance: &AssetInstance,326 ) -> Result<Option<TokenId>, XcmError> {327 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {328 Ok(Self::foreign_reserve_asset_instance_to_token_id(329 collection_id,330 asset_instance,331 ))332 } else {333 Self::local_asset_instance_to_token_id(asset_instance).map(Some)334 }335 }336337 /// Creates a foreign item in the the collection.338 fn create_foreign_asset_instance(339 xcm_ext: &dyn XcmExtensions<T>,340 collection_id: CollectionId,341 asset_instance: &AssetInstance,342 to: T::CrossAccountId,343 ) -> DispatchResult {344 let asset_instance_encoded = asset_instance.encode();345346 let derivative_token_id = xcm_ext.create_item(347 &Self::pallet_account(),348 to,349 CreateItemData::NFT(CreateNftData {350 properties: vec![Property {351 key: Self::reserve_asset_instance_property_key(),352 value: asset_instance_encoded353 .try_into()354 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),355 }]356 .try_into()357 .expect("just one property can always be stored; qed"),358 }),359 &ZeroBudget,360 )?;361362 <ForeignReserveAssetInstanceToTokenId<T>>::insert(363 collection_id,364 asset_instance,365 derivative_token_id,366 );367368 <TokenIdToForeignReserveAssetInstance<T>>::insert(369 collection_id,370 derivative_token_id,371 asset_instance,372 );373374 Ok(())375 }376377 /// Deposits an asset instance to the `to` account.378 ///379 /// Either transfers an existing item from the pallet's account380 /// or creates a foreign item.381 fn deposit_asset_instance(382 xcm_ext: &dyn XcmExtensions<T>,383 collection_id: CollectionId,384 asset_instance: &AssetInstance,385 to: T::CrossAccountId,386 ) -> XcmResult {387 let deposit_result = if let Some(token_id) =388 Self::asset_instance_to_token_id(collection_id, asset_instance)?389 {390 let depositor = &Self::pallet_account();391 let from = depositor;392 let amount = 1;393394 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)395 } else {396 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)397 };398399 deposit_result400 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))401 }402403 /// Withdraws an asset instance from the `from` account.404 ///405 /// Transfers the asset instance to the pallet's account.406 fn withdraw_asset_instance(407 xcm_ext: &dyn XcmExtensions<T>,408 collection_id: CollectionId,409 asset_instance: &AssetInstance,410 from: T::CrossAccountId,411 ) -> XcmResult {412 let token_id = Self::asset_instance_to_token_id(collection_id, asset_instance)?413 .ok_or(XcmError::AssetNotFound)?;414415 let depositor = &from;416 let to = Self::pallet_account();417 let amount = 1;418 xcm_ext419 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)420 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;421422 Ok(())423 }424}425426impl<T: Config> TransactAsset for Pallet<T> {427 fn can_check_in(428 _origin: &MultiLocation,429 _what: &MultiAsset,430 _context: &XcmContext,431 ) -> XcmResult {432 Err(XcmError::Unimplemented)433 }434435 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}436437 fn can_check_out(438 _dest: &MultiLocation,439 _what: &MultiAsset,440 _context: &XcmContext,441 ) -> XcmResult {442 Err(XcmError::Unimplemented)443 }444445 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}446447 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {448 let to = T::LocationToAccountId::convert_location(to)449 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;450451 let collection_id = Self::asset_to_collection(&what.id)?;452 let dispatch =453 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;454455 let collection = dispatch.as_dyn();456 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;457458 match what.fun {459 Fungibility::Fungible(amount) => xcm_ext460 .create_item(461 &Self::pallet_account(),462 to,463 CreateItemData::Fungible(CreateFungibleData { value: amount }),464 &ZeroBudget,465 )466 .map(|_| ())467 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),468469 Fungibility::NonFungible(asset_instance) => {470 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)471 }472 }473 }474475 fn withdraw_asset(476 what: &MultiAsset,477 from: &MultiLocation,478 _maybe_context: Option<&XcmContext>,479 ) -> Result<staging_xcm_executor::Assets, XcmError> {480 let from = T::LocationToAccountId::convert_location(from)481 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;482483 let collection_id = Self::asset_to_collection(&what.id)?;484 let dispatch =485 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;486487 let collection = dispatch.as_dyn();488 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;489490 match what.fun {491 Fungibility::Fungible(amount) => xcm_ext492 .burn_item(from, TokenId::default(), amount)493 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,494495 Fungibility::NonFungible(asset_instance) => {496 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;497 }498 }499500 Ok(what.clone().into())501 }502503 fn internal_transfer_asset(504 what: &MultiAsset,505 from: &MultiLocation,506 to: &MultiLocation,507 _context: &XcmContext,508 ) -> Result<staging_xcm_executor::Assets, XcmError> {509 let from = T::LocationToAccountId::convert_location(from)510 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;511512 let to = T::LocationToAccountId::convert_location(to)513 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;514515 let collection_id = Self::asset_to_collection(&what.id)?;516517 let dispatch =518 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;519 let collection = dispatch.as_dyn();520 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;521522 let depositor = &from;523524 let token_id;525 let amount;526 let map_error: fn(DispatchError) -> XcmError;527528 match what.fun {529 Fungibility::Fungible(fungible_amount) => {530 token_id = TokenId::default();531 amount = fungible_amount;532 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");533 }534535 Fungibility::NonFungible(asset_instance) => {536 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?537 .ok_or(XcmError::AssetNotFound)?;538539 amount = 1;540 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")541 }542 }543544 xcm_ext545 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)546 .map_err(map_error)?;547548 Ok(what.clone().into())549 }550}551552pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);553impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>554 for CurrencyIdConvert<T>555{556 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {557 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {558 Some(T::SelfLocation::get())559 } else {560 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {561 T::SelfLocation::get()562 .pushed_with_interior(GeneralIndex(collection_id.0.into()))563 .ok()564 })565 }566 }567}568569pub use frame_support::{570 traits::{571 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,572 },573 weights::{WeightToFee, WeightToFeePolynomial},574};575576#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]577pub enum ForeignCollectionMode {578 NFT,579 Fungible(u8),580}581582impl From<ForeignCollectionMode> for CollectionMode {583 fn from(value: ForeignCollectionMode) -> Self {584 match value {585 ForeignCollectionMode::NFT => Self::NFT,586 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),587 }588 }589}590591pub struct FreeForAll;592593impl WeightTrader for FreeForAll {594 fn new() -> Self {595 Self596 }597598 fn buy_weight(599 &mut self,600 weight: Weight,601 payment: Assets,602 _xcm: &XcmContext,603 ) -> Result<Assets, XcmError> {604 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);605 Ok(payment)606 }607}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 asset_id: CollectionId,103 reserve_location: Box<MultiLocation>,104 },105 }106107 /// The corresponding collections of reserve locations.108 #[pallet::storage]109 #[pallet::getter(fn foreign_reserve_location_to_collection)]110 pub type ForeignReserveLocationToCollection<T: Config> =111 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;112113 /// The corresponding reserve location of collections.114 #[pallet::storage]115 #[pallet::getter(fn collection_to_foreign_reserve_location)]116 pub type CollectionToForeignReserveLocation<T: Config> =117 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, 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 reserve_location: Box<MultiLocation>,153 name: CollectionName,154 token_prefix: CollectionTokenPrefix,155 mode: ForeignCollectionMode,156 ) -> DispatchResult {157 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {160 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());161 }162163 let foreign_collection_owner = Self::pallet_account();164165 let description: CollectionDescription = "Foreign Assets Collection"166 .encode_utf16()167 .collect::<Vec<_>>()168 .try_into()169 .expect("description length < max description length; qed");170171 let payer = None;172 let is_special_collection = true;173 let collection_id = T::CollectionDispatch::create_raw(174 foreign_collection_owner,175 payer,176 is_special_collection,177 CreateCollectionData {178 name,179 token_prefix,180 description,181 mode: mode.into(),182183 properties: vec![Property {184 key: Self::reserve_location_property_key(),185 value: reserve_location186 .encode()187 .try_into()188 .expect("multilocation is less than 32k; qed"),189 }]190 .try_into()191 .expect("just one property can always be stored; qed"),192193 token_property_permissions: vec![PropertyKeyPermission {194 key: Self::reserve_asset_instance_property_key(),195 permission: PropertyPermission {196 mutable: false,197 collection_admin: true,198 token_owner: false,199 },200 }]201 .try_into()202 .expect("just one property permission can always be stored; qed"),203 ..Default::default()204 },205 )?;206207 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);208 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);209210 Self::deposit_event(Event::<T>::ForeignAssetRegistered {211 asset_id: collection_id,212 reserve_location,213 });214215 Ok(())216 }217 }218}219220impl<T: Config> Pallet<T> {221 fn pallet_account() -> T::CrossAccountId {222 let owner: T::AccountId = T::PalletId::get().into_account_truncating();223 T::CrossAccountId::from_sub(owner)224 }225226 fn reserve_location_property_key() -> PropertyKey {227 b"reserve-location"228 .to_vec()229 .try_into()230 .expect("key length < max property key length; qed")231 }232233 fn reserve_asset_instance_property_key() -> PropertyKey {234 b"reserve-asset-instance"235 .to_vec()236 .try_into()237 .expect("key length < max property key length; qed")238 }239240 /// Converts a multilocation to a local collection on Unique Network.241 ///242 /// The multilocation corresponds to a local collection if:243 /// * It is `Here` location that corresponds to the native token of this parachain.244 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.245 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds246 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,247 /// otherwise `None` is returned.248 ///249 /// If the multilocation doesn't match the patterns listed above,250 /// or the `<Collection ID>` points to a foreign collection,251 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.252 fn local_asset_location_to_collection(253 asset_location: &MultiLocation,254 ) -> Option<CollectionLocality> {255 let self_location = T::SelfLocation::get();256257 if *asset_location == Here.into() || *asset_location == self_location {258 Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID))259 } else if asset_location.parents == self_location.parents {260 match asset_location261 .interior262 .match_and_split(&self_location.interior)263 {264 Some(GeneralIndex(collection_id)) => {265 let collection_id = CollectionId((*collection_id).try_into().ok()?);266267 Self::collection_to_foreign_reserve_location(collection_id)268 .is_none()269 .then_some(CollectionLocality::Local(collection_id))270 }271 _ => None,272 }273 } else {274 None275 }276 }277278 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).279 ///280 /// The function will check if the asset's reserve location has the corresponding281 /// foreign collection on Unique Network,282 /// and will return the "foreign" locality containing the collection ID if found.283 ///284 /// If no corresponding foreign collection is found, the function will check285 /// if the asset's reserve location corresponds to a local collection.286 /// If the local collection is found, the "local" locality with the collection ID is returned.287 ///288 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.289 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {290 let AssetId::Concrete(asset_reserve_location) = asset_id else {291 return Err(XcmExecutorError::AssetNotHandled.into());292 };293294 Self::foreign_reserve_location_to_collection(asset_reserve_location)295 .map(CollectionLocality::Foreign)296 .or_else(|| Self::local_asset_location_to_collection(asset_reserve_location))297 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())298 }299300 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.301 ///302 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:303 /// `AssetInstance::Index(<token ID>)`.304 ///305 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,306 /// `None` will be returned.307 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {308 match asset_instance {309 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),310 _ => None,311 }312 }313314 /// Obtains the token ID of the `asset_instance` in the collection.315 fn asset_instance_to_token_id(316 collection_locality: CollectionLocality,317 asset_instance: &AssetInstance,318 ) -> Option<TokenId> {319 match collection_locality {320 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),321 CollectionLocality::Foreign(collection_id) => {322 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)323 }324 }325 }326327 /// Creates a foreign item in the the collection.328 fn create_foreign_asset_instance(329 xcm_ext: &dyn XcmExtensions<T>,330 collection_id: CollectionId,331 asset_instance: &AssetInstance,332 to: T::CrossAccountId,333 ) -> DispatchResult {334 let asset_instance_encoded = asset_instance.encode();335336 let derivative_token_id = xcm_ext.create_item(337 &Self::pallet_account(),338 to,339 CreateItemData::NFT(CreateNftData {340 properties: vec![Property {341 key: Self::reserve_asset_instance_property_key(),342 value: asset_instance_encoded343 .try_into()344 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),345 }]346 .try_into()347 .expect("just one property can always be stored; qed"),348 }),349 &ZeroBudget,350 )?;351352 <ForeignReserveAssetInstanceToTokenId<T>>::insert(353 collection_id,354 asset_instance,355 derivative_token_id,356 );357358 <TokenIdToForeignReserveAssetInstance<T>>::insert(359 collection_id,360 derivative_token_id,361 asset_instance,362 );363364 Ok(())365 }366367 /// Deposits an asset instance to the `to` account.368 ///369 /// Either transfers an existing item from the pallet's account370 /// or creates a foreign item.371 fn deposit_asset_instance(372 xcm_ext: &dyn XcmExtensions<T>,373 collection_locality: CollectionLocality,374 asset_instance: &AssetInstance,375 to: T::CrossAccountId,376 ) -> XcmResult {377 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);378379 let deposit_result = match (collection_locality, token_id) {380 (_, Some(token_id)) => {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 }387 (CollectionLocality::Foreign(collection_id), None) => {388 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)389 }390 (CollectionLocality::Local(_), None) => {391 return Err(XcmError::AssetNotFound);392 }393 };394395 deposit_result396 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))397 }398399 /// Withdraws an asset instance from the `from` account.400 ///401 /// Transfers the asset instance to the pallet's account.402 fn withdraw_asset_instance(403 xcm_ext: &dyn XcmExtensions<T>,404 collection_locality: CollectionLocality,405 asset_instance: &AssetInstance,406 from: T::CrossAccountId,407 ) -> XcmResult {408 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)409 .ok_or(XcmError::AssetNotFound)?;410411 let depositor = &from;412 let to = Self::pallet_account();413 let amount = 1;414 xcm_ext415 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)416 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;417418 Ok(())419 }420}421422impl<T: Config> TransactAsset for Pallet<T> {423 fn can_check_in(424 _origin: &MultiLocation,425 _what: &MultiAsset,426 _context: &XcmContext,427 ) -> XcmResult {428 Err(XcmError::Unimplemented)429 }430431 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}432433 fn can_check_out(434 _dest: &MultiLocation,435 _what: &MultiAsset,436 _context: &XcmContext,437 ) -> XcmResult {438 Err(XcmError::Unimplemented)439 }440441 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}442443 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {444 let to = T::LocationToAccountId::convert_location(to)445 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;446447 let collection_locality = Self::asset_to_collection(&what.id)?;448 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)449 .map_err(|_| XcmError::AssetNotFound)?;450451 let collection = dispatch.as_dyn();452 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;453454 match what.fun {455 Fungibility::Fungible(amount) => xcm_ext456 .create_item(457 &Self::pallet_account(),458 to,459 CreateItemData::Fungible(CreateFungibleData { value: amount }),460 &ZeroBudget,461 )462 .map(|_| ())463 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),464465 Fungibility::NonFungible(asset_instance) => {466 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)467 }468 }469 }470471 fn withdraw_asset(472 what: &MultiAsset,473 from: &MultiLocation,474 _maybe_context: Option<&XcmContext>,475 ) -> Result<staging_xcm_executor::Assets, XcmError> {476 let from = T::LocationToAccountId::convert_location(from)477 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;478479 let collection_locality = Self::asset_to_collection(&what.id)?;480 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)481 .map_err(|_| XcmError::AssetNotFound)?;482483 let collection = dispatch.as_dyn();484 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;485486 match what.fun {487 Fungibility::Fungible(amount) => xcm_ext488 .burn_item(from, TokenId::default(), amount)489 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,490491 Fungibility::NonFungible(asset_instance) => {492 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;493 }494 }495496 Ok(what.clone().into())497 }498499 fn internal_transfer_asset(500 what: &MultiAsset,501 from: &MultiLocation,502 to: &MultiLocation,503 _context: &XcmContext,504 ) -> Result<staging_xcm_executor::Assets, XcmError> {505 let from = T::LocationToAccountId::convert_location(from)506 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;507508 let to = T::LocationToAccountId::convert_location(to)509 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;510511 let collection_locality = Self::asset_to_collection(&what.id)?;512513 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)514 .map_err(|_| XcmError::AssetNotFound)?;515 let collection = dispatch.as_dyn();516 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;517518 let depositor = &from;519520 let token_id;521 let amount;522 let map_error: fn(DispatchError) -> XcmError;523524 match what.fun {525 Fungibility::Fungible(fungible_amount) => {526 token_id = TokenId::default();527 amount = fungible_amount;528 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");529 }530531 Fungibility::NonFungible(asset_instance) => {532 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)533 .ok_or(XcmError::AssetNotFound)?;534535 amount = 1;536 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")537 }538 }539540 xcm_ext541 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)542 .map_err(map_error)?;543544 Ok(what.clone().into())545 }546}547548#[derive(Clone, Copy)]549pub enum CollectionLocality {550 Local(CollectionId),551 Foreign(CollectionId),552}553554impl Deref for CollectionLocality {555 type Target = CollectionId;556557 fn deref(&self) -> &Self::Target {558 match self {559 Self::Local(id) => id,560 Self::Foreign(id) => id,561 }562 }563}564565pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);566impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>567 for CurrencyIdConvert<T>568{569 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {570 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {571 Some(T::SelfLocation::get())572 } else {573 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {574 T::SelfLocation::get()575 .pushed_with_interior(GeneralIndex(collection_id.0.into()))576 .ok()577 })578 }579 }580}581582#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]583pub enum ForeignCollectionMode {584 NFT,585 Fungible(u8),586}587588impl From<ForeignCollectionMode> for CollectionMode {589 fn from(value: ForeignCollectionMode) -> Self {590 match value {591 ForeignCollectionMode::NFT => Self::NFT,592 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),593 }594 }595}596597pub struct FreeForAll;598599impl WeightTrader for FreeForAll {600 fn new() -> Self {601 Self602 }603604 fn buy_weight(605 &mut self,606 weight: Weight,607 payment: Assets,608 _xcm: &XcmContext,609 ) -> Result<Assets, XcmError> {610 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);611 Ok(payment)612 }613}