difftreelog
fix force_register_foreign_asset weight
in: master
1 file changed
pallets/foreign-assets/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Foreign Assets18//!19//! ## Overview20//!21//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.2223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use core::ops::Deref;2728use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36 opaque::latest::{prelude::XcmError, Weight},37 v3::{prelude::*, MultiAsset, XcmContext},38 VersionedAssetId,39};40use staging_xcm_executor::{41 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},42 Assets,43};44use up_data_structs::{45 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,46 CreateCollectionData, CreateFungibleData, CreateItemData, 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 pallet_common::CollectionIssuer;60 use up_data_structs::CollectionDescription;6162 use super::*;6364 #[pallet::config]65 pub trait Config:66 frame_system::Config67 + pallet_common::Config68 + pallet_fungible::Config69 + pallet_balances::Config70 {71 /// The overarching event type.72 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7374 /// Origin for force registering of a foreign asset.75 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7677 /// The ID of the foreign assets pallet.78 type PalletId: Get<PalletId>;7980 /// Self-location of this parachain.81 type SelfLocation: Get<MultiLocation>;8283 /// The converter from a MultiLocation to a CrossAccountId.84 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8586 /// Weight information for the extrinsics in this module.87 type WeightInfo: WeightInfo;88 }8990 #[pallet::error]91 pub enum Error<T> {92 /// The foreign asset is already registered.93 ForeignAssetAlreadyRegistered,9495 /// The given asset ID could not be converted into the current XCM version.96 BadForeignAssetId,97 }9899 #[pallet::event]100 #[pallet::generate_deposit(fn deposit_event)]101 pub enum Event<T: Config> {102 /// The foreign asset registered.103 ForeignAssetRegistered {104 collection_id: CollectionId,105 asset_id: Box<VersionedAssetId>,106 },107 }108109 /// The corresponding collections of foreign assets.110 #[pallet::storage]111 #[pallet::getter(fn foreign_asset_to_collection)]112 pub type ForeignAssetToCollection<T: Config> =113 StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;114115 /// The corresponding foreign assets of collections.116 #[pallet::storage]117 #[pallet::getter(fn collection_to_foreign_asset)]118 pub type CollectionToForeignAsset<T: Config> =119 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;120121 /// The correponding NFT token id of reserve NFTs122 #[pallet::storage]123 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]124 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<125 Hasher1 = Twox64Concat,126 Key1 = CollectionId,127 Hasher2 = Blake2_128Concat,128 Key2 = staging_xcm::v3::AssetInstance,129 Value = TokenId,130 QueryKind = OptionQuery,131 >;132133 /// The correponding reserve NFT of a token ID134 #[pallet::storage]135 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]136 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<137 Hasher1 = Twox64Concat,138 Key1 = CollectionId,139 Hasher2 = Blake2_128Concat,140 Key2 = TokenId,141 Value = staging_xcm::v3::AssetInstance,142 QueryKind = OptionQuery,143 >;144145 #[pallet::pallet]146 pub struct Pallet<T>(_);147148 #[pallet::call]149 impl<T: Config> Pallet<T> {150 #[pallet::call_index(0)]151 #[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]152 pub fn force_register_foreign_asset(153 origin: OriginFor<T>,154 versioned_asset_id: Box<VersionedAssetId>,155 name: CollectionName,156 token_prefix: CollectionTokenPrefix,157 mode: ForeignCollectionMode,158 ) -> DispatchResult {159 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;160161 let asset_id: AssetId = versioned_asset_id162 .as_ref()163 .clone()164 .try_into()165 .map_err(|()| Error::<T>::BadForeignAssetId)?;166167 ensure!(168 !<ForeignAssetToCollection<T>>::contains_key(asset_id),169 <Error<T>>::ForeignAssetAlreadyRegistered,170 );171172 let foreign_collection_owner = Self::pallet_account();173174 let description: CollectionDescription = "Foreign Assets Collection"175 .encode_utf16()176 .collect::<Vec<_>>()177 .try_into()178 .expect("description length < max description length; qed");179180 let collection_id = T::CollectionDispatch::create(181 foreign_collection_owner,182 CollectionIssuer::Internals,183 CreateCollectionData {184 name,185 token_prefix,186 description,187 mode: mode.into(),188 ..Default::default()189 },190 )?;191192 <ForeignAssetToCollection<T>>::insert(asset_id, collection_id);193 <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);194195 Self::deposit_event(Event::<T>::ForeignAssetRegistered {196 collection_id,197 asset_id: versioned_asset_id,198 });199200 Ok(())201 }202 }203}204205impl<T: Config> Pallet<T> {206 fn pallet_account() -> T::CrossAccountId {207 let owner: T::AccountId = T::PalletId::get().into_account_truncating();208 T::CrossAccountId::from_sub(owner)209 }210211 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.212 ///213 /// The multilocation corresponds to a local collection if:214 /// * It is `Here` location that corresponds to the native token of this parachain.215 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.216 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds217 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,218 /// otherwise `None` is returned.219 /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.220 ///221 /// If the multilocation doesn't match the patterns listed above,222 /// or the `<Collection ID>` points to a foreign collection,223 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.224 fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {225 let AssetId::Concrete(asset_location) = asset_id else {226 return None;227 };228229 let self_location = T::SelfLocation::get();230231 if *asset_location == Here.into() || *asset_location == self_location {232 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));233 }234235 let prefix = if asset_location.parents == 0 {236 &Here237 } else if asset_location.parents == self_location.parents {238 &self_location.interior239 } else {240 return None;241 };242243 let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {244 return None;245 };246247 let collection_id = CollectionId((*collection_id).try_into().ok()?);248249 Self::collection_to_foreign_asset(collection_id)250 .is_none()251 .then_some(CollectionLocality::Local(collection_id))252 }253254 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).255 ///256 /// The function will check if the asset's reserve location has the corresponding257 /// foreign collection on Unique Network,258 /// and will return the "foreign" locality containing the collection ID if found.259 ///260 /// If no corresponding foreign collection is found, the function will check261 /// if the asset's reserve location corresponds to a local collection.262 /// If the local collection is found, the "local" locality with the collection ID is returned.263 ///264 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.265 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {266 Self::foreign_asset_to_collection(asset_id)267 .map(CollectionLocality::Foreign)268 .or_else(|| Self::local_asset_id_to_collection(asset_id))269 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())270 }271272 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.273 ///274 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:275 /// `AssetInstance::Index(<token ID>)`.276 ///277 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,278 /// `None` will be returned.279 ///280 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.281 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.282 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {283 match asset_instance {284 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),285 _ => None,286 }287 }288289 /// Obtains the token ID of the `asset_instance` in the collection.290 ///291 /// Note: this function can return `Some` containing the token ID of a non-existing NFT.292 /// It returns `None` when it failed to convert the `asset_instance` to a local ID.293 fn asset_instance_to_token_id(294 collection_locality: CollectionLocality,295 asset_instance: &AssetInstance,296 ) -> Option<TokenId> {297 match collection_locality {298 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),299 CollectionLocality::Foreign(collection_id) => {300 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)301 }302 }303 }304305 /// Creates a foreign item in the the collection.306 fn create_foreign_asset_instance(307 xcm_ext: &dyn XcmExtensions<T>,308 collection_id: CollectionId,309 asset_instance: &AssetInstance,310 to: T::CrossAccountId,311 ) -> DispatchResult {312 let derivative_token_id = xcm_ext.create_item(313 &Self::pallet_account(),314 to,315 CreateItemData::NFT(Default::default()),316 &ZeroBudget,317 )?;318319 <ForeignReserveAssetInstanceToTokenId<T>>::insert(320 collection_id,321 asset_instance,322 derivative_token_id,323 );324325 <TokenIdToForeignReserveAssetInstance<T>>::insert(326 collection_id,327 derivative_token_id,328 asset_instance,329 );330331 Ok(())332 }333334 /// Deposits an asset instance to the `to` account.335 ///336 /// Either transfers an existing item from the pallet's account337 /// or creates a foreign item.338 fn deposit_asset_instance(339 xcm_ext: &dyn XcmExtensions<T>,340 collection_locality: CollectionLocality,341 asset_instance: &AssetInstance,342 to: T::CrossAccountId,343 ) -> XcmResult {344 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);345346 let deposit_result = match (collection_locality, token_id) {347 (_, Some(token_id)) => {348 let depositor = &Self::pallet_account();349 let from = depositor;350 let amount = 1;351352 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)353 }354 (CollectionLocality::Foreign(collection_id), None) => {355 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)356 }357 (CollectionLocality::Local(_), None) => {358 return Err(XcmExecutorError::InstanceConversionFailed.into());359 }360 };361362 deposit_result363 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))364 }365366 /// Withdraws an asset instance from the `from` account.367 ///368 /// Transfers the asset instance to the pallet's account.369 fn withdraw_asset_instance(370 xcm_ext: &dyn XcmExtensions<T>,371 collection_locality: CollectionLocality,372 asset_instance: &AssetInstance,373 from: T::CrossAccountId,374 ) -> XcmResult {375 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)376 .ok_or(XcmExecutorError::InstanceConversionFailed)?;377378 let depositor = &from;379 let to = Self::pallet_account();380 let amount = 1;381 xcm_ext382 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)383 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;384385 Ok(())386 }387}388389impl<T: Config> TransactAsset for Pallet<T> {390 fn can_check_in(391 _origin: &MultiLocation,392 _what: &MultiAsset,393 _context: &XcmContext,394 ) -> XcmResult {395 Err(XcmError::Unimplemented)396 }397398 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}399400 fn can_check_out(401 _dest: &MultiLocation,402 _what: &MultiAsset,403 _context: &XcmContext,404 ) -> XcmResult {405 Err(XcmError::Unimplemented)406 }407408 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}409410 fn deposit_asset(411 what: &MultiAsset,412 to: &MultiLocation,413 _context: Option<&XcmContext>,414 ) -> XcmResult {415 let to = T::LocationToAccountId::convert_location(to)416 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;417418 let collection_locality = Self::asset_to_collection(&what.id)?;419 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)420 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;421422 let collection = dispatch.as_dyn();423 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;424425 match what.fun {426 Fungibility::Fungible(amount) => xcm_ext427 .create_item(428 &Self::pallet_account(),429 to,430 CreateItemData::Fungible(CreateFungibleData { value: amount }),431 &ZeroBudget,432 )433 .map(|_| ())434 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),435436 Fungibility::NonFungible(asset_instance) => {437 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)438 }439 }440 }441442 fn withdraw_asset(443 what: &MultiAsset,444 from: &MultiLocation,445 _maybe_context: Option<&XcmContext>,446 ) -> Result<staging_xcm_executor::Assets, XcmError> {447 let from = T::LocationToAccountId::convert_location(from)448 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;449450 let collection_locality = Self::asset_to_collection(&what.id)?;451 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)452 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;453454 let collection = dispatch.as_dyn();455 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;456457 match what.fun {458 Fungibility::Fungible(amount) => xcm_ext459 .burn_item(from, TokenId::default(), amount)460 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,461462 Fungibility::NonFungible(asset_instance) => {463 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;464 }465 }466467 Ok(what.clone().into())468 }469470 fn internal_transfer_asset(471 what: &MultiAsset,472 from: &MultiLocation,473 to: &MultiLocation,474 _context: &XcmContext,475 ) -> Result<staging_xcm_executor::Assets, XcmError> {476 let from = T::LocationToAccountId::convert_location(from)477 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;478479 let to = T::LocationToAccountId::convert_location(to)480 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;481482 let collection_locality = Self::asset_to_collection(&what.id)?;483484 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)485 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;486 let collection = dispatch.as_dyn();487 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;488489 let depositor = &from;490491 let token_id;492 let amount;493 let map_error: fn(DispatchError) -> XcmError;494495 match what.fun {496 Fungibility::Fungible(fungible_amount) => {497 token_id = TokenId::default();498 amount = fungible_amount;499 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");500 }501502 Fungibility::NonFungible(asset_instance) => {503 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)504 .ok_or(XcmExecutorError::InstanceConversionFailed)?;505506 amount = 1;507 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")508 }509 }510511 xcm_ext512 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)513 .map_err(map_error)?;514515 Ok(what.clone().into())516 }517}518519#[derive(Clone, Copy)]520pub enum CollectionLocality {521 Local(CollectionId),522 Foreign(CollectionId),523}524525impl Deref for CollectionLocality {526 type Target = CollectionId;527528 fn deref(&self) -> &Self::Target {529 match self {530 Self::Local(id) => id,531 Self::Foreign(id) => id,532 }533 }534}535536pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);537impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>538 for CurrencyIdConvert<T>539{540 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {541 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {542 Some(T::SelfLocation::get())543 } else {544 <Pallet<T>>::collection_to_foreign_asset(collection_id)545 .and_then(|asset_id| match asset_id {546 AssetId::Concrete(location) => Some(location),547 _ => None,548 })549 .or_else(|| {550 T::SelfLocation::get()551 .pushed_with_interior(GeneralIndex(collection_id.0.into()))552 .ok()553 })554 }555 }556}557558#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]559pub enum ForeignCollectionMode {560 NFT,561 Fungible(u8),562}563564impl From<ForeignCollectionMode> for CollectionMode {565 fn from(value: ForeignCollectionMode) -> Self {566 match value {567 ForeignCollectionMode::NFT => Self::NFT,568 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),569 }570 }571}572573pub struct FreeForAll;574575impl WeightTrader for FreeForAll {576 fn new() -> Self {577 Self578 }579580 fn buy_weight(581 &mut self,582 weight: Weight,583 payment: Assets,584 _xcm: &XcmContext,585 ) -> Result<Assets, XcmError> {586 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);587 Ok(payment)588 }589}