difftreelog
chore foreign-assets docs and minor improvements
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::{vec, vec::Vec};33use staging_xcm::{34 opaque::latest::{prelude::XcmError, Weight},35 v3::{prelude::*, MultiAsset, XcmContext},36};37use staging_xcm_executor::{38 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},39 Assets,40};41use up_data_structs::{42 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,43 CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,44};4546pub mod weights;4748#[cfg(feature = "runtime-benchmarks")]49mod benchmarking;5051pub use module::*;52pub use weights::WeightInfo;5354#[frame_support::pallet]55pub mod module {56 use up_data_structs::{57 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,58 };5960 use super::*;6162 #[pallet::config]63 pub trait Config:64 frame_system::Config65 + pallet_common::Config66 + pallet_fungible::Config67 + pallet_balances::Config68 {69 /// The overarching event type.70 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7172 /// Origin for force registering of a foreign asset.73 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7475 /// The ID of the foreign assets pallet.76 type PalletId: Get<PalletId>;7778 /// Self-location of this parachain.79 type SelfLocation: Get<MultiLocation>;8081 /// The converter from a MultiLocation to a CrossAccountId.82 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8384 /// Weight information for the extrinsics in this module.85 type WeightInfo: WeightInfo;86 }8788 #[pallet::error]89 pub enum Error<T> {90 /// The foreign asset is already registered91 ForeignAssetAlreadyRegistered,92 }9394 #[pallet::event]95 #[pallet::generate_deposit(fn deposit_event)]96 pub enum Event<T: Config> {97 /// The foreign asset registered.98 ForeignAssetRegistered {99 asset_id: CollectionId,100 reserve_location: MultiLocation,101 },102 }103104 /// The corresponding collections of reserve locations.105 #[pallet::storage]106 #[pallet::getter(fn foreign_reserve_location_to_collection)]107 pub type ForeignReserveLocationToCollection<T: Config> =108 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;109110 /// The corresponding reserve location of collections.111 #[pallet::storage]112 #[pallet::getter(fn collection_to_foreign_reserve_location)]113 pub type CollectionToForeignReserveLocation<T: Config> =114 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;115116 /// The correponding NFT token id of reserve NFTs117 #[pallet::storage]118 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]119 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<120 Hasher1 = Twox64Concat,121 Key1 = CollectionId,122 Hasher2 = Twox64Concat,123 Key2 = staging_xcm::v3::AssetInstance,124 Value = TokenId,125 QueryKind = OptionQuery,126 >;127128 #[pallet::pallet]129 pub struct Pallet<T>(_);130131 #[pallet::call]132 impl<T: Config> Pallet<T> {133 #[pallet::call_index(0)]134 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]135 pub fn force_register_foreign_asset(136 origin: OriginFor<T>,137 reserve_location: MultiLocation,138 name: CollectionName,139 mode: CollectionMode,140 ) -> DispatchResult {141 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;142143 if <ForeignReserveLocationToCollection<T>>::contains_key(reserve_location) {144 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());145 }146147 let foreign_collection_owner = Self::pallet_account();148149 let description: CollectionDescription = "Foreign Assets Collection"150 .encode_utf16()151 .collect::<Vec<_>>()152 .try_into()153 .expect("description length < max description length; qed");154155 let collection_id = T::CollectionDispatch::create_foreign(156 foreign_collection_owner,157 CreateCollectionData {158 name,159 description,160 mode,161162 properties: vec![Property {163 key: Self::reserve_location_property_key(),164 value: reserve_location165 .encode()166 .try_into()167 .expect("multilocation is less than 32k; qed"),168 }]169 .try_into()170 .expect("just one property can always be stored; qed"),171172 token_property_permissions: vec![PropertyKeyPermission {173 key: Self::reserve_asset_instance_property_key(),174 permission: PropertyPermission {175 mutable: false,176 collection_admin: true,177 token_owner: false,178 },179 }]180 .try_into()181 .expect("just one property permission can always be stored; qed"),182 ..Default::default()183 },184 )?;185186 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);187 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);188189 Self::deposit_event(Event::<T>::ForeignAssetRegistered {190 asset_id: collection_id,191 reserve_location,192 });193194 Ok(())195 }196 }197}198199impl<T: Config> Pallet<T> {200 fn pallet_account() -> T::CrossAccountId {201 let owner: T::AccountId = T::PalletId::get().into_account_truncating();202 T::CrossAccountId::from_sub(owner)203 }204205 fn reserve_location_property_key() -> PropertyKey {206 b"reserve-location"207 .to_vec()208 .try_into()209 .expect("key length < max property key length; qed")210 }211212 fn reserve_asset_instance_property_key() -> PropertyKey {213 b"reserve-asset-instance"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 fn native_asset_location_to_collection(220 asset_location: &MultiLocation,221 ) -> Result<Option<CollectionId>, XcmError> {222 let self_location = T::SelfLocation::get();223224 if *asset_location == Here.into() {225 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))226 } else if *asset_location == self_location {227 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))228 } else if asset_location.parents == self_location.parents {229 match asset_location230 .interior231 .match_and_split(&self_location.interior)232 {233 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(234 (*collection_id)235 .try_into()236 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,237 ))),238 _ => Ok(None),239 }240 } else {241 Ok(None)242 }243 }244245 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {246 let AssetId::Concrete(asset_reserve_location) = asset.id else {247 return Err(XcmExecutorError::AssetNotHandled.into());248 };249250 Self::native_asset_location_to_collection(&asset_reserve_location)?251 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))252 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())253 }254255 fn native_asset_instance_to_token_id(256 asset_instance: &AssetInstance,257 ) -> Result<TokenId, XcmError> {258 match asset_instance {259 AssetInstance::Index(token_id) => Ok(TokenId(260 (*token_id)261 .try_into()262 .map_err(|_| XcmError::AssetNotFound)?,263 )),264 _ => Err(XcmError::AssetNotFound),265 }266 }267268 /// Obtains the token id of the `asset_instance` in the collection.269 ///270 /// Returns `Ok(None)` only if the `asset_instance` points to a foreign item271 /// and it haven't been created on this blockchain yet.272 ///273 /// If the `asset_instance` points to a native item, it cannot return `Ok(None)`.274 fn asset_instance_to_token_id(275 xcm_ext: &dyn XcmExtensions<T>,276 collection_id: CollectionId,277 asset_instance: &AssetInstance,278 ) -> Result<Option<TokenId>, XcmError> {279 if xcm_ext.is_foreign() {280 Ok(Self::foreign_reserve_asset_instance_to_token_id(281 collection_id,282 asset_instance,283 ))284 } else {285 Self::native_asset_instance_to_token_id(asset_instance).map(Some)286 }287 }288289 fn create_foreign_asset_instance(290 xcm_ext: &dyn XcmExtensions<T>,291 collection_id: CollectionId,292 asset_instance: &AssetInstance,293 to: T::CrossAccountId,294 ) -> XcmResult {295 let asset_instance_encoded = asset_instance.encode();296297 let derivative_token_id = xcm_ext298 .create_item(299 &Self::pallet_account(),300 to,301 CreateItemData::NFT(CreateNftData {302 properties: vec![Property {303 key: Self::reserve_asset_instance_property_key(),304 value: asset_instance_encoded305 .try_into()306 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),307 }]308 .try_into()309 .expect("just one property can always be stored; qed"),310 }),311 &ZeroBudget,312 )313 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))?;314315 <ForeignReserveAssetInstanceToTokenId<T>>::insert(316 collection_id,317 asset_instance,318 derivative_token_id,319 );320321 Ok(())322 }323324 fn deposit_asset_instance(325 xcm_ext: &dyn XcmExtensions<T>,326 collection_id: CollectionId,327 to: T::CrossAccountId,328 asset_instance: &AssetInstance,329 ) -> XcmResult {330 if let Some(token_id) =331 Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)?332 {333 let depositor = &Self::pallet_account();334 let from = depositor;335 let amount = 1;336337 xcm_ext338 .transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)339 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))340 } else {341 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)342 }343 }344}345346impl<T: Config> TransactAsset for Pallet<T> {347 fn can_check_in(348 _origin: &MultiLocation,349 _what: &MultiAsset,350 _context: &XcmContext,351 ) -> XcmResult {352 Err(XcmError::Unimplemented)353 }354355 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}356357 fn can_check_out(358 _dest: &MultiLocation,359 _what: &MultiAsset,360 _context: &XcmContext,361 ) -> XcmResult {362 Err(XcmError::Unimplemented)363 }364365 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}366367 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {368 let collection_id = Self::multiasset_to_collection(what)?;369 let dispatch =370 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;371372 let collection = dispatch.as_dyn();373 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;374375 let to = T::LocationToAccountId::convert_location(to)376 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;377378 match what.fun {379 Fungibility::Fungible(amount) => xcm_ext380 .create_item(381 &Self::pallet_account(),382 to,383 CreateItemData::Fungible(CreateFungibleData { value: amount }),384 &ZeroBudget,385 )386 .map(|_| ())387 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),388389 Fungibility::NonFungible(asset_instance) => {390 Self::deposit_asset_instance(xcm_ext, collection_id, to, &asset_instance)391 }392 }393 }394395 fn withdraw_asset(396 what: &MultiAsset,397 from: &MultiLocation,398 _maybe_context: Option<&XcmContext>,399 ) -> Result<staging_xcm_executor::Assets, XcmError> {400 let from = T::LocationToAccountId::convert_location(from)401 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;402403 let collection_id = Self::multiasset_to_collection(what)?;404 let dispatch =405 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;406407 let collection = dispatch.as_dyn();408 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;409410 match what.fun {411 Fungibility::Fungible(amount) => xcm_ext412 .burn_item(from, TokenId::default(), amount)413 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,414415 Fungibility::NonFungible(asset_instance) => {416 let token_id =417 Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?418 .ok_or(XcmError::AssetNotFound)?;419420 if xcm_ext.token_has_children(token_id) {421 return Err(XcmError::Unimplemented);422 }423424 let depositor = &from;425 let to = Self::pallet_account();426 let amount = 1;427 xcm_ext428 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)429 .map_err(|_| {430 XcmError::FailedToTransactAsset("nonfungible item withdraw failed")431 })?;432 }433 }434435 Ok(what.clone().into())436 }437438 fn internal_transfer_asset(439 what: &MultiAsset,440 from: &MultiLocation,441 to: &MultiLocation,442 _context: &XcmContext,443 ) -> Result<staging_xcm_executor::Assets, XcmError> {444 let collection_id = Self::multiasset_to_collection(what)?;445446 let dispatch =447 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;448 let collection = dispatch.as_dyn();449 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;450451 let from = T::LocationToAccountId::convert_location(from)452 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;453454 let to = T::LocationToAccountId::convert_location(to)455 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;456457 let depositor = &from;458459 match what.fun {460 Fungibility::Fungible(amount) => xcm_ext461 .transfer_item(462 depositor,463 &from,464 &to,465 TokenId::default(),466 amount,467 &ZeroBudget,468 )469 .map_err(|_| XcmError::FailedToTransactAsset("fungible item transfer failed"))?,470471 Fungibility::NonFungible(asset_instance) => {472 let token_id =473 Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?474 .ok_or(XcmError::AssetNotFound)?;475476 let amount = 1;477478 xcm_ext479 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)480 .map_err(|_| {481 XcmError::FailedToTransactAsset("nonfungible item transfer failed")482 })?;483 }484 }485486 Ok(what.clone().into())487 }488}489490pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);491impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>492 for CurrencyIdConvert<T>493{494 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {495 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {496 Some(Here.into())497 } else {498 let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;499 let collection = dispatch.as_dyn();500 let xcm_ext = collection.xcm_extensions()?;501502 if xcm_ext.is_foreign() {503 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id)504 } else {505 T::SelfLocation::get()506 .pushed_with_interior(GeneralIndex(collection_id.0.into()))507 .ok()508 }509 }510 }511}512513pub use frame_support::{514 traits::{515 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,516 },517 weights::{WeightToFee, WeightToFeePolynomial},518};519520pub struct FreeForAll;521522impl WeightTrader for FreeForAll {523 fn new() -> Self {524 Self525 }526527 fn buy_weight(528 &mut self,529 weight: Weight,530 payment: Assets,531 _xcm: &XcmContext,532 ) -> Result<Assets, XcmError> {533 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);534 Ok(payment)535 }536}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::{vec, vec::Vec};33use staging_xcm::{34 opaque::latest::{prelude::XcmError, Weight},35 v3::{prelude::*, MultiAsset, XcmContext},36};37use staging_xcm_executor::{38 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},39 Assets,40};41use up_data_structs::{42 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,43 CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,44};4546pub mod weights;4748#[cfg(feature = "runtime-benchmarks")]49mod benchmarking;5051pub use module::*;52pub use weights::WeightInfo;5354#[frame_support::pallet]55pub mod module {56 use up_data_structs::{57 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,58 };5960 use super::*;6162 #[pallet::config]63 pub trait Config:64 frame_system::Config65 + pallet_common::Config66 + pallet_fungible::Config67 + pallet_balances::Config68 {69 /// The overarching event type.70 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7172 /// Origin for force registering of a foreign asset.73 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7475 /// The ID of the foreign assets pallet.76 type PalletId: Get<PalletId>;7778 /// Self-location of this parachain.79 type SelfLocation: Get<MultiLocation>;8081 /// The converter from a MultiLocation to a CrossAccountId.82 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8384 /// Weight information for the extrinsics in this module.85 type WeightInfo: WeightInfo;86 }8788 #[pallet::error]89 pub enum Error<T> {90 /// The foreign asset is already registered91 ForeignAssetAlreadyRegistered,92 }9394 #[pallet::event]95 #[pallet::generate_deposit(fn deposit_event)]96 pub enum Event<T: Config> {97 /// The foreign asset registered.98 ForeignAssetRegistered {99 asset_id: CollectionId,100 reserve_location: MultiLocation,101 },102 }103104 /// The corresponding collections of reserve locations.105 #[pallet::storage]106 #[pallet::getter(fn foreign_reserve_location_to_collection)]107 pub type ForeignReserveLocationToCollection<T: Config> =108 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;109110 /// The corresponding reserve location of collections.111 #[pallet::storage]112 #[pallet::getter(fn collection_to_foreign_reserve_location)]113 pub type CollectionToForeignReserveLocation<T: Config> =114 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;115116 /// The correponding NFT token id of reserve NFTs117 #[pallet::storage]118 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]119 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<120 Hasher1 = Twox64Concat,121 Key1 = CollectionId,122 Hasher2 = Twox64Concat,123 Key2 = staging_xcm::v3::AssetInstance,124 Value = TokenId,125 QueryKind = OptionQuery,126 >;127128 #[pallet::pallet]129 pub struct Pallet<T>(_);130131 #[pallet::call]132 impl<T: Config> Pallet<T> {133 #[pallet::call_index(0)]134 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]135 pub fn force_register_foreign_asset(136 origin: OriginFor<T>,137 reserve_location: MultiLocation,138 name: CollectionName,139 mode: CollectionMode,140 ) -> DispatchResult {141 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;142143 if <ForeignReserveLocationToCollection<T>>::contains_key(reserve_location) {144 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());145 }146147 let foreign_collection_owner = Self::pallet_account();148149 let description: CollectionDescription = "Foreign Assets Collection"150 .encode_utf16()151 .collect::<Vec<_>>()152 .try_into()153 .expect("description length < max description length; qed");154155 let collection_id = T::CollectionDispatch::create_foreign(156 foreign_collection_owner,157 CreateCollectionData {158 name,159 description,160 mode,161162 properties: vec![Property {163 key: Self::reserve_location_property_key(),164 value: reserve_location165 .encode()166 .try_into()167 .expect("multilocation is less than 32k; qed"),168 }]169 .try_into()170 .expect("just one property can always be stored; qed"),171172 token_property_permissions: vec![PropertyKeyPermission {173 key: Self::reserve_asset_instance_property_key(),174 permission: PropertyPermission {175 mutable: false,176 collection_admin: true,177 token_owner: false,178 },179 }]180 .try_into()181 .expect("just one property permission can always be stored; qed"),182 ..Default::default()183 },184 )?;185186 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);187 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);188189 Self::deposit_event(Event::<T>::ForeignAssetRegistered {190 asset_id: collection_id,191 reserve_location,192 });193194 Ok(())195 }196 }197}198199impl<T: Config> Pallet<T> {200 fn pallet_account() -> T::CrossAccountId {201 let owner: T::AccountId = T::PalletId::get().into_account_truncating();202 T::CrossAccountId::from_sub(owner)203 }204205 fn reserve_location_property_key() -> PropertyKey {206 b"reserve-location"207 .to_vec()208 .try_into()209 .expect("key length < max property key length; qed")210 }211212 fn reserve_asset_instance_property_key() -> PropertyKey {213 b"reserve-asset-instance"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 /// Converts a multilocation to the Unique Network's local collection220 /// (i.e. the collection originally created on the Unique Network's parachain).221 ///222 /// The multilocation corresponds to a Unique Network's collection if:223 /// * It is `Here` location that corresponds to the native token of this parachain.224 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.225 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds226 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,227 /// otherwise the `AssetIdConversionFailed` error will be returned.228 ///229 /// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,230 /// identifying that the given multilocation doesn't correspond to a local collection.231 fn native_asset_location_to_collection(232 asset_location: &MultiLocation,233 ) -> Result<Option<CollectionId>, XcmError> {234 let self_location = T::SelfLocation::get();235236 if *asset_location == Here.into() {237 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))238 } else if *asset_location == self_location {239 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))240 } else if asset_location.parents == self_location.parents {241 match asset_location242 .interior243 .match_and_split(&self_location.interior)244 {245 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(246 (*collection_id)247 .try_into()248 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,249 ))),250 _ => Ok(None),251 }252 } else {253 Ok(None)254 }255 }256257 /// Converts a multiasset to a Unique Network's collection (either local or the foreign one).258 ///259 /// The function will try to convert the multiasset's reserve location260 /// to the Unique Network's local collection.261 ///262 /// If the multilocation doesn't correspond to a local collection,263 /// the function will check if the reserve location has the corresponding264 /// derivative Unique Network's collection, and will return the said collection ID if found.265 ///266 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.267 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {268 let AssetId::Concrete(asset_reserve_location) = asset.id else {269 return Err(XcmExecutorError::AssetNotHandled.into());270 };271272 Self::native_asset_location_to_collection(&asset_reserve_location)?273 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))274 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())275 }276277 /// Converts an XCM asset instance to the Unique Network's token ID.278 ///279 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:280 /// `AssetInstance::Index(<token ID>)`.281 ///282 /// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,283 /// the `AssetNotFound` error will be returned.284 fn native_asset_instance_to_token_id(285 asset_instance: &AssetInstance,286 ) -> Result<TokenId, XcmError> {287 match asset_instance {288 AssetInstance::Index(token_id) => Ok(TokenId(289 (*token_id)290 .try_into()291 .map_err(|_| XcmError::AssetNotFound)?,292 )),293 _ => Err(XcmError::AssetNotFound),294 }295 }296297 /// Obtains the token ID of the `asset_instance` in the collection.298 ///299 /// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection300 /// and the item hasn't yet been created on this blockchain.301 ///302 /// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.303 ///304 /// If the `asset_instance` is a part of a local collection,305 /// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.306 fn asset_instance_to_token_id(307 xcm_ext: &dyn XcmExtensions<T>,308 collection_id: CollectionId,309 asset_instance: &AssetInstance,310 ) -> Result<Option<TokenId>, XcmError> {311 if xcm_ext.is_foreign() {312 Ok(Self::foreign_reserve_asset_instance_to_token_id(313 collection_id,314 asset_instance,315 ))316 } else {317 Self::native_asset_instance_to_token_id(asset_instance).map(Some)318 }319 }320321 /// Creates a foreign item in the the collection.322 fn create_foreign_asset_instance(323 xcm_ext: &dyn XcmExtensions<T>,324 collection_id: CollectionId,325 asset_instance: &AssetInstance,326 to: T::CrossAccountId,327 ) -> DispatchResult {328 let asset_instance_encoded = asset_instance.encode();329330 let derivative_token_id = xcm_ext.create_item(331 &Self::pallet_account(),332 to,333 CreateItemData::NFT(CreateNftData {334 properties: vec![Property {335 key: Self::reserve_asset_instance_property_key(),336 value: asset_instance_encoded337 .try_into()338 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),339 }]340 .try_into()341 .expect("just one property can always be stored; qed"),342 }),343 &ZeroBudget,344 )?;345346 <ForeignReserveAssetInstanceToTokenId<T>>::insert(347 collection_id,348 asset_instance,349 derivative_token_id,350 );351352 Ok(())353 }354355 /// Deposits an asset instance to the `to` account.356 ///357 /// Either transfers an existing item from the pallet's account358 /// or creates a foreign item.359 fn deposit_asset_instance(360 xcm_ext: &dyn XcmExtensions<T>,361 collection_id: CollectionId,362 asset_instance: &AssetInstance,363 to: T::CrossAccountId,364 ) -> XcmResult {365 let deposit_result = if let Some(token_id) =366 Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)?367 {368 let depositor = &Self::pallet_account();369 let from = depositor;370 let amount = 1;371372 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)373 } else {374 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)375 };376377 deposit_result378 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))379 }380381 /// Withdraws an asset instance from the `from` account.382 ///383 /// Transfers the asset instance to the pallet's account.384 ///385 /// Won't withdraw the instance if it has children.386 fn withdraw_asset_instance(387 xcm_ext: &dyn XcmExtensions<T>,388 collection_id: CollectionId,389 asset_instance: &AssetInstance,390 from: T::CrossAccountId,391 ) -> XcmResult {392 let token_id = Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?393 .ok_or(XcmError::AssetNotFound)?;394395 if xcm_ext.token_has_children(token_id) {396 return Err(XcmError::Unimplemented);397 }398399 let depositor = &from;400 let to = Self::pallet_account();401 let amount = 1;402 xcm_ext403 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)404 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;405406 Ok(())407 }408}409410impl<T: Config> TransactAsset for Pallet<T> {411 fn can_check_in(412 _origin: &MultiLocation,413 _what: &MultiAsset,414 _context: &XcmContext,415 ) -> XcmResult {416 Err(XcmError::Unimplemented)417 }418419 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}420421 fn can_check_out(422 _dest: &MultiLocation,423 _what: &MultiAsset,424 _context: &XcmContext,425 ) -> XcmResult {426 Err(XcmError::Unimplemented)427 }428429 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}430431 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {432 let to = T::LocationToAccountId::convert_location(to)433 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;434435 let collection_id = Self::multiasset_to_collection(what)?;436 let dispatch =437 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;438439 let collection = dispatch.as_dyn();440 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;441442 match what.fun {443 Fungibility::Fungible(amount) => xcm_ext444 .create_item(445 &Self::pallet_account(),446 to,447 CreateItemData::Fungible(CreateFungibleData { value: amount }),448 &ZeroBudget,449 )450 .map(|_| ())451 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),452453 Fungibility::NonFungible(asset_instance) => {454 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)455 }456 }457 }458459 fn withdraw_asset(460 what: &MultiAsset,461 from: &MultiLocation,462 _maybe_context: Option<&XcmContext>,463 ) -> Result<staging_xcm_executor::Assets, XcmError> {464 let from = T::LocationToAccountId::convert_location(from)465 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;466467 let collection_id = Self::multiasset_to_collection(what)?;468 let dispatch =469 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;470471 let collection = dispatch.as_dyn();472 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;473474 match what.fun {475 Fungibility::Fungible(amount) => xcm_ext476 .burn_item(from, TokenId::default(), amount)477 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,478479 Fungibility::NonFungible(asset_instance) => {480 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;481 }482 }483484 Ok(what.clone().into())485 }486487 fn internal_transfer_asset(488 what: &MultiAsset,489 from: &MultiLocation,490 to: &MultiLocation,491 _context: &XcmContext,492 ) -> Result<staging_xcm_executor::Assets, XcmError> {493 let from = T::LocationToAccountId::convert_location(from)494 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;495496 let to = T::LocationToAccountId::convert_location(to)497 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;498499 let collection_id = Self::multiasset_to_collection(what)?;500501 let dispatch =502 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;503 let collection = dispatch.as_dyn();504 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;505506 let depositor = &from;507508 let token_id;509 let amount;510 let map_error: fn(DispatchError) -> XcmError;511512 match what.fun {513 Fungibility::Fungible(fungible_amount) => {514 token_id = TokenId::default();515 amount = fungible_amount;516 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");517 }518519 Fungibility::NonFungible(asset_instance) => {520 token_id =521 Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?522 .ok_or(XcmError::AssetNotFound)?;523524 amount = 1;525 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")526 }527 }528529 xcm_ext530 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)531 .map_err(map_error)?;532533 Ok(what.clone().into())534 }535}536537pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);538impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>539 for CurrencyIdConvert<T>540{541 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {542 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {543 Some(Here.into())544 } else {545 let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;546 let collection = dispatch.as_dyn();547 let xcm_ext = collection.xcm_extensions()?;548549 if xcm_ext.is_foreign() {550 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id)551 } 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};566567pub struct FreeForAll;568569impl WeightTrader for FreeForAll {570 fn new() -> Self {571 Self572 }573574 fn buy_weight(575 &mut self,576 weight: Weight,577 payment: Assets,578 _xcm: &XcmContext,579 ) -> Result<Assets, XcmError> {580 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);581 Ok(payment)582 }583}