difftreelog
fix foreign flags
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::{29 dispatch::DispatchResult, pallet_prelude::*, storage_alias, traits::EnsureOrigin, PalletId,30};31use frame_system::pallet_prelude::*;32use pallet_common::{33 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,34};35use sp_runtime::traits::AccountIdConversion;36use sp_std::{boxed::Box, vec, vec::Vec};37use staging_xcm::{v4::prelude::*, VersionedAssetId};38use staging_xcm_executor::{39 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},40 AssetsInHolding,41};42use up_data_structs::{43 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,44 CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455/// Status of storage migration from an old XCM version to a new one.56#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]57pub enum MigrationStatus {58 V3ToV4(MigrationStatusV3ToV4),59}6061/// Status of storage migration from XCMv3 to XCMv4.62#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]63pub enum MigrationStatusV3ToV4 {64 /// The migration is completed.65 Done,6667 /// An asset is skipped during the migration68 /// due to its inconsistent state.69 SkippedInconsistentAssetData(staging_xcm::v3::AssetId),7071 /// An asset instance is skipped during the migration72 /// due to its inconsistent state.73 SkippedInconsistentAssetInstanceData {74 asset_id: staging_xcm::v3::AssetId,75 asset_instance: staging_xcm::v3::AssetInstance,76 },7778 /// An asset is skipped during the migration79 /// because it couldn't be converted to the new XCM version.80 SkippedNotConvertibleAssetId(staging_xcm::v3::AssetId),8182 /// An asset instance is skipped during the migration83 /// because it couldn't be converted to the new XCM version.84 SkippedNotConvertibleAssetInstance {85 asset_id: staging_xcm::v3::AssetId,86 asset_instance: staging_xcm::v3::AssetInstance,87 },88}8990#[frame_support::pallet]91pub mod module {92 use pallet_common::CollectionIssuer;93 use up_data_structs::CollectionDescription;9495 use super::*;9697 #[pallet::config]98 pub trait Config:99 frame_system::Config100 + pallet_common::Config101 + pallet_fungible::Config102 + pallet_balances::Config103 {104 /// The overarching event type.105 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;106107 /// Origin for force registering of a foreign asset.108 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;109110 /// The ID of the foreign assets pallet.111 type PalletId: Get<PalletId>;112113 /// Self-location of this parachain.114 type SelfLocation: Get<Location>;115116 /// The converter from a Location to a CrossAccountId.117 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;118119 /// Weight information for the extrinsics in this module.120 type WeightInfo: WeightInfo;121 }122123 #[pallet::error]124 pub enum Error<T> {125 /// The foreign asset is already registered.126 ForeignAssetAlreadyRegistered,127128 /// The given asset ID could not be converted into the current XCM version.129 BadForeignAssetId,130 }131132 #[pallet::event]133 #[pallet::generate_deposit(pub(crate) fn deposit_event)]134 pub enum Event<T: Config> {135 /// The foreign asset registered.136 ForeignAssetRegistered {137 collection_id: CollectionId,138 asset_id: Box<VersionedAssetId>,139 },140141 /// The migration status.142 MigrationStatus(MigrationStatus),143 }144145 /// The corresponding collections of foreign assets.146 #[pallet::storage]147 #[pallet::getter(fn foreign_asset_to_collection)]148 pub type ForeignAssetToCollection<T: Config> =149 StorageMap<_, Blake2_128Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>;150151 /// The corresponding foreign assets of collections.152 #[pallet::storage]153 #[pallet::getter(fn collection_to_foreign_asset)]154 pub type CollectionToForeignAsset<T: Config> =155 StorageMap<_, Blake2_128Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>;156157 /// The correponding NFT token id of reserve NFTs158 #[pallet::storage]159 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]160 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<161 Hasher1 = Blake2_128Concat,162 Key1 = CollectionId,163 Hasher2 = Blake2_128Concat,164 Key2 = staging_xcm::v4::AssetInstance,165 Value = TokenId,166 QueryKind = OptionQuery,167 >;168169 /// The correponding reserve NFT of a token ID170 #[pallet::storage]171 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]172 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<173 Hasher1 = Blake2_128Concat,174 Key1 = CollectionId,175 Hasher2 = Blake2_128Concat,176 Key2 = TokenId,177 Value = staging_xcm::v4::AssetInstance,178 QueryKind = OptionQuery,179 >;180181 const STORAGE_VERSION: StorageVersion = StorageVersion::new(staging_xcm::v4::VERSION as u16);182183 #[pallet::pallet]184 #[pallet::storage_version(STORAGE_VERSION)]185 pub struct Pallet<T>(_);186187 #[pallet::call]188 impl<T: Config> Pallet<T> {189 #[pallet::call_index(0)]190 #[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]191 pub fn force_register_foreign_asset(192 origin: OriginFor<T>,193 versioned_asset_id: Box<VersionedAssetId>,194 name: CollectionName,195 token_prefix: CollectionTokenPrefix,196 mode: ForeignCollectionMode,197 ) -> DispatchResult {198 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;199200 let asset_id: AssetId = versioned_asset_id201 .as_ref()202 .clone()203 .try_into()204 .map_err(|()| Error::<T>::BadForeignAssetId)?;205206 ensure!(207 !<ForeignAssetToCollection<T>>::contains_key(&asset_id),208 <Error<T>>::ForeignAssetAlreadyRegistered,209 );210211 let foreign_collection_owner = Self::pallet_account();212213 let description: CollectionDescription = "Foreign Assets Collection"214 .encode_utf16()215 .collect::<Vec<_>>()216 .try_into()217 .expect("description length < max description length; qed");218219 let collection_id = T::CollectionDispatch::create(220 foreign_collection_owner,221 CollectionIssuer::Internals,222 CreateCollectionData {223 name,224 token_prefix,225 description,226 mode: mode.into(),227 ..Default::default()228 },229 )?;230231 <ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);232 <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);233234 Self::deposit_event(Event::<T>::ForeignAssetRegistered {235 collection_id,236 asset_id: versioned_asset_id,237 });238239 Ok(())240 }241 }242243 #[pallet::hooks]244 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {245 fn on_runtime_upgrade() -> Weight {246 Self::migrate_v3_to_v4()247 }248 }249}250251mod v3_storage {252 use super::*;253254 #[storage_alias]255 pub type ForeignAssetToCollection<T: Config> =256 StorageMap<Pallet<T>, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;257258 #[storage_alias]259 pub type CollectionToForeignAsset<T: Config> =260 StorageMap<Pallet<T>, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;261262 #[storage_alias]263 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<264 Pallet<T>,265 Twox64Concat,266 CollectionId,267 Blake2_128Concat,268 staging_xcm::v3::AssetInstance,269 TokenId,270 OptionQuery,271 >;272273 #[storage_alias]274 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<275 Pallet<T>,276 Twox64Concat,277 CollectionId,278 Blake2_128Concat,279 TokenId,280 staging_xcm::v3::AssetInstance,281 OptionQuery,282 >;283}284285impl<T: Config> Pallet<T> {286 fn migrate_v3_to_v4() -> Weight {287 if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 {288 let put_version_weight = T::DbWeight::get().writes(1);289 let event_weight = T::DbWeight::get().writes(1);290 let collection_migration_weight = Self::migrate_collections();291292 StorageVersion::new(staging_xcm::v4::VERSION as u16).put::<Self>();293294 Self::deposit_event(Event::<T>::MigrationStatus(MigrationStatus::V3ToV4(295 MigrationStatusV3ToV4::Done,296 )));297298 put_version_weight299 .saturating_add(collection_migration_weight)300 .saturating_add(event_weight)301 } else {302 Weight::zero()303 }304 }305306 fn migrate_collections() -> Weight {307 use MigrationStatus::*;308 use MigrationStatusV3ToV4::*;309310 let mut weight = Weight::zero();311312 for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::<T>::drain() {313 let bwd_asset_id = v3_storage::CollectionToForeignAsset::<T>::take(collection_id);314 weight = weight.saturating_add(T::DbWeight::get().reads(2));315316 let Some(bwd_asset_id) = bwd_asset_id else {317 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(318 SkippedInconsistentAssetData(fwd_asset_id),319 )));320321 weight = weight.saturating_add(T::DbWeight::get().writes(1));322 continue;323 };324325 if fwd_asset_id != bwd_asset_id {326 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(327 SkippedInconsistentAssetData(fwd_asset_id),328 )));329330 weight = weight.saturating_add(T::DbWeight::get().writes(1));331 continue;332 }333334 let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else {335 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(336 SkippedNotConvertibleAssetId(fwd_asset_id),337 )));338339 weight = weight.saturating_add(T::DbWeight::get().writes(1));340 continue;341 };342343 <ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);344 <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);345 weight = weight.saturating_add(T::DbWeight::get().writes(2));346347 let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id);348 weight = weight.saturating_add(migrate_tokens_weight);349 }350351 weight352 }353354 fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight {355 use MigrationStatus::*;356 use MigrationStatusV3ToV4::*;357358 let mut weight = Weight::zero();359360 for (fwd_asset_instance, token_id) in361 v3_storage::ForeignReserveAssetInstanceToTokenId::<T>::drain_prefix(collection_id)362 {363 let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::<T>::take(364 collection_id,365 token_id,366 );367 weight = weight.saturating_add(T::DbWeight::get().reads(2));368369 let Some(bwd_asset_instance) = bwd_asset_instance else {370 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(371 SkippedInconsistentAssetInstanceData {372 asset_id: *asset_id,373 asset_instance: fwd_asset_instance,374 },375 )));376377 weight = weight.saturating_add(T::DbWeight::get().writes(1));378 continue;379 };380381 if fwd_asset_instance != bwd_asset_instance {382 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(383 SkippedInconsistentAssetInstanceData {384 asset_id: *asset_id,385 asset_instance: fwd_asset_instance,386 },387 )));388389 weight = weight.saturating_add(T::DbWeight::get().writes(1));390 continue;391 }392393 let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance)394 else {395 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(396 SkippedNotConvertibleAssetInstance {397 asset_id: *asset_id,398 asset_instance: fwd_asset_instance,399 },400 )));401402 weight = weight.saturating_add(T::DbWeight::get().writes(1));403 continue;404 };405406 <ForeignReserveAssetInstanceToTokenId<T>>::insert(407 collection_id,408 &asset_instance,409 token_id,410 );411 <TokenIdToForeignReserveAssetInstance<T>>::insert(412 collection_id,413 token_id,414 asset_instance,415 );416 weight = weight.saturating_add(T::DbWeight::get().writes(2));417 }418419 weight420 }421422 fn pallet_account() -> T::CrossAccountId {423 let owner: T::AccountId = T::PalletId::get().into_account_truncating();424 T::CrossAccountId::from_sub(owner)425 }426427 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.428 ///429 /// The multilocation corresponds to a local collection if:430 /// * It is `Here` location that corresponds to the native token of this parachain.431 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.432 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds433 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,434 /// otherwise `None` is returned.435 /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.436 ///437 /// If the multilocation doesn't match the patterns listed above,438 /// or the `<Collection ID>` points to a foreign collection,439 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.440 fn local_asset_id_to_collection(441 AssetId(asset_location): &AssetId,442 ) -> Option<CollectionLocality> {443 let self_location = T::SelfLocation::get();444445 if *asset_location == Here.into() || *asset_location == self_location {446 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));447 }448449 let prefix = if asset_location.parents == 0 {450 &Here451 } else if asset_location.parents == self_location.parents {452 &self_location.interior453 } else {454 return None;455 };456457 let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {458 return None;459 };460461 let collection_id = CollectionId((*collection_id).try_into().ok()?);462463 Self::collection_to_foreign_asset(collection_id)464 .is_none()465 .then_some(CollectionLocality::Local(collection_id))466 }467468 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).469 ///470 /// The function will check if the asset's reserve location has the corresponding471 /// foreign collection on Unique Network,472 /// and will return the "foreign" locality containing the collection ID if found.473 ///474 /// If no corresponding foreign collection is found, the function will check475 /// if the asset's reserve location corresponds to a local collection.476 /// If the local collection is found, the "local" locality with the collection ID is returned.477 ///478 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.479 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {480 Self::foreign_asset_to_collection(asset_id)481 .map(CollectionLocality::Foreign)482 .or_else(|| Self::local_asset_id_to_collection(asset_id))483 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())484 }485486 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.487 ///488 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:489 /// `AssetInstance::Index(<token ID>)`.490 ///491 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,492 /// `None` will be returned.493 ///494 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.495 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.496 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {497 match asset_instance {498 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),499 _ => None,500 }501 }502503 /// Obtains the token ID of the `asset_instance` in the collection.504 ///505 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.506 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.507 fn asset_instance_to_token_id(508 collection_locality: CollectionLocality,509 asset_instance: &AssetInstance,510 ) -> Option<TokenId> {511 match collection_locality {512 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),513 CollectionLocality::Foreign(collection_id) => {514 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)515 }516 }517 }518519 /// Creates a foreign item in the the collection.520 fn create_foreign_asset_instance(521 xcm_ext: &dyn XcmExtensions<T>,522 collection_id: CollectionId,523 asset_instance: &AssetInstance,524 to: T::CrossAccountId,525 ) -> DispatchResult {526 let derivative_token_id = xcm_ext.create_item(527 &Self::pallet_account(),528 to,529 CreateItemData::NFT(Default::default()),530 &ZeroBudget,531 )?;532533 <ForeignReserveAssetInstanceToTokenId<T>>::insert(534 collection_id,535 asset_instance,536 derivative_token_id,537 );538539 <TokenIdToForeignReserveAssetInstance<T>>::insert(540 collection_id,541 derivative_token_id,542 asset_instance,543 );544545 Ok(())546 }547548 /// Deposits an asset instance to the `to` account.549 ///550 /// Either transfers an existing item from the pallet's account551 /// or creates a foreign item.552 fn deposit_asset_instance(553 xcm_ext: &dyn XcmExtensions<T>,554 collection_locality: CollectionLocality,555 asset_instance: &AssetInstance,556 to: T::CrossAccountId,557 ) -> XcmResult {558 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);559560 let deposit_result = match (collection_locality, token_id) {561 (_, Some(token_id)) => {562 let depositor = &Self::pallet_account();563 let from = depositor;564 let amount = 1;565566 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)567 }568 (CollectionLocality::Foreign(collection_id), None) => {569 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)570 }571 (CollectionLocality::Local(_), None) => {572 return Err(XcmExecutorError::InstanceConversionFailed.into());573 }574 };575576 deposit_result577 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))578 }579580 /// Withdraws an asset instance from the `from` account.581 ///582 /// Transfers the asset instance to the pallet's account.583 fn withdraw_asset_instance(584 xcm_ext: &dyn XcmExtensions<T>,585 collection_locality: CollectionLocality,586 asset_instance: &AssetInstance,587 from: T::CrossAccountId,588 ) -> XcmResult {589 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)590 .ok_or(XcmExecutorError::InstanceConversionFailed)?;591592 let depositor = &from;593 let to = Self::pallet_account();594 let amount = 1;595 xcm_ext596 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)597 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;598599 Ok(())600 }601}602603// #[derive()]604// pub enum Migration {605606// }607608impl<T: Config> TransactAsset for Pallet<T> {609 fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {610 Err(XcmError::Unimplemented)611 }612613 fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}614615 fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {616 Err(XcmError::Unimplemented)617 }618619 fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}620621 fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {622 let to = T::LocationToAccountId::convert_location(to)623 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;624625 let collection_locality = Self::asset_to_collection(&what.id)?;626 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)627 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;628629 let collection = dispatch.as_dyn();630 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;631632 match what.fun {633 Fungibility::Fungible(amount) => xcm_ext634 .create_item(635 &Self::pallet_account(),636 to,637 CreateItemData::Fungible(CreateFungibleData { value: amount }),638 &ZeroBudget,639 )640 .map(|_| ())641 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),642643 Fungibility::NonFungible(asset_instance) => {644 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)645 }646 }647 }648649 fn withdraw_asset(650 what: &Asset,651 from: &Location,652 _maybe_context: Option<&XcmContext>,653 ) -> Result<AssetsInHolding, XcmError> {654 let from = T::LocationToAccountId::convert_location(from)655 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;656657 let collection_locality = Self::asset_to_collection(&what.id)?;658 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)659 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;660661 let collection = dispatch.as_dyn();662 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;663664 match what.fun {665 Fungibility::Fungible(amount) => xcm_ext666 .burn_item(from, TokenId::default(), amount)667 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,668669 Fungibility::NonFungible(asset_instance) => {670 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;671 }672 }673674 Ok(what.clone().into())675 }676677 fn internal_transfer_asset(678 what: &Asset,679 from: &Location,680 to: &Location,681 _context: &XcmContext,682 ) -> Result<AssetsInHolding, XcmError> {683 let from = T::LocationToAccountId::convert_location(from)684 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;685686 let to = T::LocationToAccountId::convert_location(to)687 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;688689 let collection_locality = Self::asset_to_collection(&what.id)?;690691 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)692 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;693 let collection = dispatch.as_dyn();694 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;695696 let depositor = &from;697698 let token_id;699 let amount;700 let map_error: fn(DispatchError) -> XcmError;701702 match what.fun {703 Fungibility::Fungible(fungible_amount) => {704 token_id = TokenId::default();705 amount = fungible_amount;706 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");707 }708709 Fungibility::NonFungible(asset_instance) => {710 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)711 .ok_or(XcmExecutorError::InstanceConversionFailed)?;712713 amount = 1;714 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")715 }716 }717718 xcm_ext719 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)720 .map_err(map_error)?;721722 Ok(what.clone().into())723 }724}725726#[derive(Clone, Copy)]727pub enum CollectionLocality {728 Local(CollectionId),729 Foreign(CollectionId),730}731732impl Deref for CollectionLocality {733 type Target = CollectionId;734735 fn deref(&self) -> &Self::Target {736 match self {737 Self::Local(id) => id,738 Self::Foreign(id) => id,739 }740 }741}742743pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);744impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>745 for CurrencyIdConvert<T>746{747 fn convert(collection_id: CollectionId) -> Option<Location> {748 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {749 Some(T::SelfLocation::get())750 } else {751 <Pallet<T>>::collection_to_foreign_asset(collection_id)752 .map(|AssetId(location)| location)753 .or_else(|| {754 T::SelfLocation::get()755 .pushed_with_interior(GeneralIndex(collection_id.0.into()))756 .ok()757 })758 }759 }760}761762#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]763pub enum ForeignCollectionMode {764 NFT,765 Fungible(u8),766}767768impl From<ForeignCollectionMode> for CollectionMode {769 fn from(value: ForeignCollectionMode) -> Self {770 match value {771 ForeignCollectionMode::NFT => Self::NFT,772 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),773 }774 }775}776777pub struct FreeForAll;778779impl WeightTrader for FreeForAll {780 fn new() -> Self {781 Self782 }783784 fn buy_weight(785 &mut self,786 weight: Weight,787 payment: AssetsInHolding,788 _xcm: &XcmContext,789 ) -> Result<AssetsInHolding, XcmError> {790 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);791 Ok(payment)792 }793}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::{29 dispatch::DispatchResult, pallet_prelude::*, storage_alias, traits::EnsureOrigin, PalletId,30};31use frame_system::pallet_prelude::*;32use pallet_common::{33 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,34};35use sp_runtime::traits::AccountIdConversion;36use sp_std::{boxed::Box, vec, vec::Vec};37use staging_xcm::{v4::prelude::*, VersionedAssetId};38use staging_xcm_executor::{39 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},40 AssetsInHolding,41};42use up_data_structs::{43 budget::ZeroBudget, CollectionFlags, CollectionId, CollectionMode, CollectionName,44 CollectionTokenPrefix, CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455/// Status of storage migration from an old XCM version to a new one.56#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]57pub enum MigrationStatus {58 V3ToV4(MigrationStatusV3ToV4),59}6061/// Status of storage migration from XCMv3 to XCMv4.62#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]63pub enum MigrationStatusV3ToV4 {64 /// The migration is completed.65 Done,6667 /// An asset is skipped during the migration68 /// due to its inconsistent state.69 SkippedInconsistentAssetData(staging_xcm::v3::AssetId),7071 /// An asset instance is skipped during the migration72 /// due to its inconsistent state.73 SkippedInconsistentAssetInstanceData {74 asset_id: staging_xcm::v3::AssetId,75 asset_instance: staging_xcm::v3::AssetInstance,76 },7778 /// An asset is skipped during the migration79 /// because it couldn't be converted to the new XCM version.80 SkippedNotConvertibleAssetId(staging_xcm::v3::AssetId),8182 /// An asset instance is skipped during the migration83 /// because it couldn't be converted to the new XCM version.84 SkippedNotConvertibleAssetInstance {85 asset_id: staging_xcm::v3::AssetId,86 asset_instance: staging_xcm::v3::AssetInstance,87 },88}8990#[frame_support::pallet]91pub mod module {92 use pallet_common::CollectionIssuer;93 use up_data_structs::CollectionDescription;9495 use super::*;9697 #[pallet::config]98 pub trait Config:99 frame_system::Config100 + pallet_common::Config101 + pallet_fungible::Config102 + pallet_balances::Config103 {104 /// The overarching event type.105 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;106107 /// Origin for force registering of a foreign asset.108 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;109110 /// The ID of the foreign assets pallet.111 type PalletId: Get<PalletId>;112113 /// Self-location of this parachain.114 type SelfLocation: Get<Location>;115116 /// The converter from a Location to a CrossAccountId.117 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;118119 /// Weight information for the extrinsics in this module.120 type WeightInfo: WeightInfo;121 }122123 #[pallet::error]124 pub enum Error<T> {125 /// The foreign asset is already registered.126 ForeignAssetAlreadyRegistered,127128 /// The given asset ID could not be converted into the current XCM version.129 BadForeignAssetId,130 }131132 #[pallet::event]133 #[pallet::generate_deposit(pub(crate) fn deposit_event)]134 pub enum Event<T: Config> {135 /// The foreign asset registered.136 ForeignAssetRegistered {137 collection_id: CollectionId,138 asset_id: Box<VersionedAssetId>,139 },140141 /// The migration status.142 MigrationStatus(MigrationStatus),143 }144145 /// The corresponding collections of foreign assets.146 #[pallet::storage]147 #[pallet::getter(fn foreign_asset_to_collection)]148 pub type ForeignAssetToCollection<T: Config> =149 StorageMap<_, Blake2_128Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>;150151 /// The corresponding foreign assets of collections.152 #[pallet::storage]153 #[pallet::getter(fn collection_to_foreign_asset)]154 pub type CollectionToForeignAsset<T: Config> =155 StorageMap<_, Blake2_128Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>;156157 /// The correponding NFT token id of reserve NFTs158 #[pallet::storage]159 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]160 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<161 Hasher1 = Blake2_128Concat,162 Key1 = CollectionId,163 Hasher2 = Blake2_128Concat,164 Key2 = staging_xcm::v4::AssetInstance,165 Value = TokenId,166 QueryKind = OptionQuery,167 >;168169 /// The correponding reserve NFT of a token ID170 #[pallet::storage]171 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]172 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<173 Hasher1 = Blake2_128Concat,174 Key1 = CollectionId,175 Hasher2 = Blake2_128Concat,176 Key2 = TokenId,177 Value = staging_xcm::v4::AssetInstance,178 QueryKind = OptionQuery,179 >;180181 const STORAGE_VERSION: StorageVersion = StorageVersion::new(staging_xcm::v4::VERSION as u16);182183 #[pallet::pallet]184 #[pallet::storage_version(STORAGE_VERSION)]185 pub struct Pallet<T>(_);186187 #[pallet::call]188 impl<T: Config> Pallet<T> {189 #[pallet::call_index(0)]190 #[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]191 pub fn force_register_foreign_asset(192 origin: OriginFor<T>,193 versioned_asset_id: Box<VersionedAssetId>,194 name: CollectionName,195 token_prefix: CollectionTokenPrefix,196 mode: ForeignCollectionMode,197 ) -> DispatchResult {198 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;199200 let asset_id: AssetId = versioned_asset_id201 .as_ref()202 .clone()203 .try_into()204 .map_err(|()| Error::<T>::BadForeignAssetId)?;205206 ensure!(207 !<ForeignAssetToCollection<T>>::contains_key(&asset_id),208 <Error<T>>::ForeignAssetAlreadyRegistered,209 );210211 let foreign_collection_owner = Self::pallet_account();212213 let description: CollectionDescription = "Foreign Assets Collection"214 .encode_utf16()215 .collect::<Vec<_>>()216 .try_into()217 .expect("description length < max description length; qed");218219 let collection_id = T::CollectionDispatch::create(220 foreign_collection_owner,221 CollectionIssuer::Internals,222 CreateCollectionData {223 name,224 token_prefix,225 description,226 mode: mode.into(),227 flags: CollectionFlags {228 foreign: true,229 ..Default::default()230 },231 ..Default::default()232 },233 )?;234235 <ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);236 <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);237238 Self::deposit_event(Event::<T>::ForeignAssetRegistered {239 collection_id,240 asset_id: versioned_asset_id,241 });242243 Ok(())244 }245 }246247 #[pallet::hooks]248 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {249 fn on_runtime_upgrade() -> Weight {250 if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 {251 let put_version_weight = T::DbWeight::get().writes(1);252 let fix_foreign_flag_weight = Self::fix_foreign_flag();253 let weight_v3_to_v4 = Self::migrate_v3_to_v4();254255 StorageVersion::new(staging_xcm::v4::VERSION as u16).put::<Self>();256257 put_version_weight258 .saturating_add(fix_foreign_flag_weight)259 .saturating_add(weight_v3_to_v4)260 } else {261 Weight::zero()262 }263 }264 }265}266267mod v3_storage {268 use super::*;269270 #[storage_alias]271 pub type ForeignAssetToCollection<T: Config> =272 StorageMap<Pallet<T>, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;273274 #[storage_alias]275 pub type CollectionToForeignAsset<T: Config> =276 StorageMap<Pallet<T>, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;277278 #[storage_alias]279 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<280 Pallet<T>,281 Twox64Concat,282 CollectionId,283 Blake2_128Concat,284 staging_xcm::v3::AssetInstance,285 TokenId,286 OptionQuery,287 >;288289 #[storage_alias]290 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<291 Pallet<T>,292 Twox64Concat,293 CollectionId,294 Blake2_128Concat,295 TokenId,296 staging_xcm::v3::AssetInstance,297 OptionQuery,298 >;299}300301impl<T: Config> Pallet<T> {302 fn fix_foreign_flag() -> Weight {303 let mut weight = Weight::zero();304305 for (_, collection_id) in v3_storage::ForeignAssetToCollection::<T>::iter() {306 pallet_common::CollectionById::<T>::mutate(collection_id, |collection| {307 if let Some(collection) = collection {308 collection.flags.foreign = true;309 }310 });311312 weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1));313 }314315 weight316 }317318 fn migrate_v3_to_v4() -> Weight {319 let event_weight = T::DbWeight::get().writes(1);320 let collection_migration_weight = Self::migrate_collections();321322 Self::deposit_event(Event::<T>::MigrationStatus(MigrationStatus::V3ToV4(323 MigrationStatusV3ToV4::Done,324 )));325326 collection_migration_weight.saturating_add(event_weight)327 }328329 fn migrate_collections() -> Weight {330 use MigrationStatus::*;331 use MigrationStatusV3ToV4::*;332333 let mut weight = Weight::zero();334335 for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::<T>::drain() {336 let bwd_asset_id = v3_storage::CollectionToForeignAsset::<T>::take(collection_id);337 weight = weight.saturating_add(T::DbWeight::get().reads(2));338339 let Some(bwd_asset_id) = bwd_asset_id else {340 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(341 SkippedInconsistentAssetData(fwd_asset_id),342 )));343344 weight = weight.saturating_add(T::DbWeight::get().writes(1));345 continue;346 };347348 if fwd_asset_id != bwd_asset_id {349 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(350 SkippedInconsistentAssetData(fwd_asset_id),351 )));352353 weight = weight.saturating_add(T::DbWeight::get().writes(1));354 continue;355 }356357 let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else {358 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(359 SkippedNotConvertibleAssetId(fwd_asset_id),360 )));361362 weight = weight.saturating_add(T::DbWeight::get().writes(1));363 continue;364 };365366 <ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);367 <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);368 weight = weight.saturating_add(T::DbWeight::get().writes(2));369370 let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id);371 weight = weight.saturating_add(migrate_tokens_weight);372 }373374 weight375 }376377 fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight {378 use MigrationStatus::*;379 use MigrationStatusV3ToV4::*;380381 let mut weight = Weight::zero();382383 for (fwd_asset_instance, token_id) in384 v3_storage::ForeignReserveAssetInstanceToTokenId::<T>::drain_prefix(collection_id)385 {386 let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::<T>::take(387 collection_id,388 token_id,389 );390 weight = weight.saturating_add(T::DbWeight::get().reads(2));391392 let Some(bwd_asset_instance) = bwd_asset_instance else {393 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(394 SkippedInconsistentAssetInstanceData {395 asset_id: *asset_id,396 asset_instance: fwd_asset_instance,397 },398 )));399400 weight = weight.saturating_add(T::DbWeight::get().writes(1));401 continue;402 };403404 if fwd_asset_instance != bwd_asset_instance {405 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(406 SkippedInconsistentAssetInstanceData {407 asset_id: *asset_id,408 asset_instance: fwd_asset_instance,409 },410 )));411412 weight = weight.saturating_add(T::DbWeight::get().writes(1));413 continue;414 }415416 let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance)417 else {418 Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(419 SkippedNotConvertibleAssetInstance {420 asset_id: *asset_id,421 asset_instance: fwd_asset_instance,422 },423 )));424425 weight = weight.saturating_add(T::DbWeight::get().writes(1));426 continue;427 };428429 <ForeignReserveAssetInstanceToTokenId<T>>::insert(430 collection_id,431 &asset_instance,432 token_id,433 );434 <TokenIdToForeignReserveAssetInstance<T>>::insert(435 collection_id,436 token_id,437 asset_instance,438 );439 weight = weight.saturating_add(T::DbWeight::get().writes(2));440 }441442 weight443 }444445 fn pallet_account() -> T::CrossAccountId {446 let owner: T::AccountId = T::PalletId::get().into_account_truncating();447 T::CrossAccountId::from_sub(owner)448 }449450 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.451 ///452 /// The multilocation corresponds to a local collection if:453 /// * It is `Here` location that corresponds to the native token of this parachain.454 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.455 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds456 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,457 /// otherwise `None` is returned.458 /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.459 ///460 /// If the multilocation doesn't match the patterns listed above,461 /// or the `<Collection ID>` points to a foreign collection,462 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.463 fn local_asset_id_to_collection(464 AssetId(asset_location): &AssetId,465 ) -> Option<CollectionLocality> {466 let self_location = T::SelfLocation::get();467468 if *asset_location == Here.into() || *asset_location == self_location {469 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));470 }471472 let prefix = if asset_location.parents == 0 {473 &Here474 } else if asset_location.parents == self_location.parents {475 &self_location.interior476 } else {477 return None;478 };479480 let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {481 return None;482 };483484 let collection_id = CollectionId((*collection_id).try_into().ok()?);485486 Self::collection_to_foreign_asset(collection_id)487 .is_none()488 .then_some(CollectionLocality::Local(collection_id))489 }490491 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).492 ///493 /// The function will check if the asset's reserve location has the corresponding494 /// foreign collection on Unique Network,495 /// and will return the "foreign" locality containing the collection ID if found.496 ///497 /// If no corresponding foreign collection is found, the function will check498 /// if the asset's reserve location corresponds to a local collection.499 /// If the local collection is found, the "local" locality with the collection ID is returned.500 ///501 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.502 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {503 Self::foreign_asset_to_collection(asset_id)504 .map(CollectionLocality::Foreign)505 .or_else(|| Self::local_asset_id_to_collection(asset_id))506 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())507 }508509 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.510 ///511 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:512 /// `AssetInstance::Index(<token ID>)`.513 ///514 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,515 /// `None` will be returned.516 ///517 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.518 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.519 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {520 match asset_instance {521 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),522 _ => None,523 }524 }525526 /// Obtains the token ID of the `asset_instance` in the collection.527 ///528 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.529 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.530 fn asset_instance_to_token_id(531 collection_locality: CollectionLocality,532 asset_instance: &AssetInstance,533 ) -> Option<TokenId> {534 match collection_locality {535 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),536 CollectionLocality::Foreign(collection_id) => {537 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)538 }539 }540 }541542 /// Creates a foreign item in the the collection.543 fn create_foreign_asset_instance(544 xcm_ext: &dyn XcmExtensions<T>,545 collection_id: CollectionId,546 asset_instance: &AssetInstance,547 to: T::CrossAccountId,548 ) -> DispatchResult {549 let derivative_token_id = xcm_ext.create_item(550 &Self::pallet_account(),551 to,552 CreateItemData::NFT(Default::default()),553 &ZeroBudget,554 )?;555556 <ForeignReserveAssetInstanceToTokenId<T>>::insert(557 collection_id,558 asset_instance,559 derivative_token_id,560 );561562 <TokenIdToForeignReserveAssetInstance<T>>::insert(563 collection_id,564 derivative_token_id,565 asset_instance,566 );567568 Ok(())569 }570571 /// Deposits an asset instance to the `to` account.572 ///573 /// Either transfers an existing item from the pallet's account574 /// or creates a foreign item.575 fn deposit_asset_instance(576 xcm_ext: &dyn XcmExtensions<T>,577 collection_locality: CollectionLocality,578 asset_instance: &AssetInstance,579 to: T::CrossAccountId,580 ) -> XcmResult {581 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);582583 let deposit_result = match (collection_locality, token_id) {584 (_, Some(token_id)) => {585 let depositor = &Self::pallet_account();586 let from = depositor;587 let amount = 1;588589 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)590 }591 (CollectionLocality::Foreign(collection_id), None) => {592 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)593 }594 (CollectionLocality::Local(_), None) => {595 return Err(XcmExecutorError::InstanceConversionFailed.into());596 }597 };598599 deposit_result600 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))601 }602603 /// Withdraws an asset instance from the `from` account.604 ///605 /// Transfers the asset instance to the pallet's account.606 fn withdraw_asset_instance(607 xcm_ext: &dyn XcmExtensions<T>,608 collection_locality: CollectionLocality,609 asset_instance: &AssetInstance,610 from: T::CrossAccountId,611 ) -> XcmResult {612 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)613 .ok_or(XcmExecutorError::InstanceConversionFailed)?;614615 let depositor = &from;616 let to = Self::pallet_account();617 let amount = 1;618 xcm_ext619 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)620 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;621622 Ok(())623 }624}625626// #[derive()]627// pub enum Migration {628629// }630631impl<T: Config> TransactAsset for Pallet<T> {632 fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {633 Err(XcmError::Unimplemented)634 }635636 fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}637638 fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {639 Err(XcmError::Unimplemented)640 }641642 fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}643644 fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {645 let to = T::LocationToAccountId::convert_location(to)646 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;647648 let collection_locality = Self::asset_to_collection(&what.id)?;649 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)650 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;651652 let collection = dispatch.as_dyn();653 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;654655 match what.fun {656 Fungibility::Fungible(amount) => xcm_ext657 .create_item(658 &Self::pallet_account(),659 to,660 CreateItemData::Fungible(CreateFungibleData { value: amount }),661 &ZeroBudget,662 )663 .map(|_| ())664 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),665666 Fungibility::NonFungible(asset_instance) => {667 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)668 }669 }670 }671672 fn withdraw_asset(673 what: &Asset,674 from: &Location,675 _maybe_context: Option<&XcmContext>,676 ) -> Result<AssetsInHolding, XcmError> {677 let from = T::LocationToAccountId::convert_location(from)678 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;679680 let collection_locality = Self::asset_to_collection(&what.id)?;681 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)682 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;683684 let collection = dispatch.as_dyn();685 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;686687 match what.fun {688 Fungibility::Fungible(amount) => xcm_ext689 .burn_item(from, TokenId::default(), amount)690 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,691692 Fungibility::NonFungible(asset_instance) => {693 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;694 }695 }696697 Ok(what.clone().into())698 }699700 fn internal_transfer_asset(701 what: &Asset,702 from: &Location,703 to: &Location,704 _context: &XcmContext,705 ) -> Result<AssetsInHolding, XcmError> {706 let from = T::LocationToAccountId::convert_location(from)707 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;708709 let to = T::LocationToAccountId::convert_location(to)710 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;711712 let collection_locality = Self::asset_to_collection(&what.id)?;713714 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)715 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;716 let collection = dispatch.as_dyn();717 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;718719 let depositor = &from;720721 let token_id;722 let amount;723 let map_error: fn(DispatchError) -> XcmError;724725 match what.fun {726 Fungibility::Fungible(fungible_amount) => {727 token_id = TokenId::default();728 amount = fungible_amount;729 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");730 }731732 Fungibility::NonFungible(asset_instance) => {733 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)734 .ok_or(XcmExecutorError::InstanceConversionFailed)?;735736 amount = 1;737 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")738 }739 }740741 xcm_ext742 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)743 .map_err(map_error)?;744745 Ok(what.clone().into())746 }747}748749#[derive(Clone, Copy)]750pub enum CollectionLocality {751 Local(CollectionId),752 Foreign(CollectionId),753}754755impl Deref for CollectionLocality {756 type Target = CollectionId;757758 fn deref(&self) -> &Self::Target {759 match self {760 Self::Local(id) => id,761 Self::Foreign(id) => id,762 }763 }764}765766pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);767impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>768 for CurrencyIdConvert<T>769{770 fn convert(collection_id: CollectionId) -> Option<Location> {771 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {772 Some(T::SelfLocation::get())773 } else {774 <Pallet<T>>::collection_to_foreign_asset(collection_id)775 .map(|AssetId(location)| location)776 .or_else(|| {777 T::SelfLocation::get()778 .pushed_with_interior(GeneralIndex(collection_id.0.into()))779 .ok()780 })781 }782 }783}784785#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]786pub enum ForeignCollectionMode {787 NFT,788 Fungible(u8),789}790791impl From<ForeignCollectionMode> for CollectionMode {792 fn from(value: ForeignCollectionMode) -> Self {793 match value {794 ForeignCollectionMode::NFT => Self::NFT,795 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),796 }797 }798}799800pub struct FreeForAll;801802impl WeightTrader for FreeForAll {803 fn new() -> Self {804 Self805 }806807 fn buy_weight(808 &mut self,809 weight: Weight,810 payment: AssetsInHolding,811 _xcm: &XcmContext,812 ) -> Result<AssetsInHolding, XcmError> {813 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);814 Ok(payment)815 }816}