difftreelog
fix use versioned asset ID in force_register_foreign_asset
in: master
2 files changed
js-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -108,7 +108,7 @@
await this.helper.executeExtrinsic(
signer,
'api.tx.foreignAssets.forceRegisterForeignAsset',
- [assetId, this.helper.util.str2vec(name), tokenPrefix, mode],
+ [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
true,
);
}
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};39use staging_xcm_executor::{40 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41 Assets,42};43use up_data_structs::{44 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45 CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,46};4748pub mod weights;4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;5253pub use module::*;54pub use weights::WeightInfo;5556#[frame_support::pallet]57pub mod module {58 use pallet_common::CollectionIssuer;59 use up_data_structs::CollectionDescription;6061 use super::*;6263 #[pallet::config]64 pub trait Config:65 frame_system::Config66 + pallet_common::Config67 + pallet_fungible::Config68 + pallet_balances::Config69 {70 /// The overarching event type.71 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7273 /// Origin for force registering of a foreign asset.74 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7576 /// The ID of the foreign assets pallet.77 type PalletId: Get<PalletId>;7879 /// Self-location of this parachain.80 type SelfLocation: Get<MultiLocation>;8182 /// The converter from a MultiLocation to a CrossAccountId.83 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8485 /// Weight information for the extrinsics in this module.86 type WeightInfo: WeightInfo;87 }8889 #[pallet::error]90 pub enum Error<T> {91 /// The foreign asset is already registered.92 ForeignAssetAlreadyRegistered,93 }9495 #[pallet::event]96 #[pallet::generate_deposit(fn deposit_event)]97 pub enum Event<T: Config> {98 /// The foreign asset registered.99 ForeignAssetRegistered {100 collection_id: CollectionId,101 asset_id: Box<AssetId>,102 },103 }104105 /// The corresponding collections of foreign assets.106 #[pallet::storage]107 #[pallet::getter(fn foreign_asset_to_collection)]108 pub type ForeignAssetToCollection<T: Config> =109 StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;110111 /// The corresponding foreign assets of collections.112 #[pallet::storage]113 #[pallet::getter(fn collection_to_foreign_asset)]114 pub type CollectionToForeignAsset<T: Config> =115 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;116117 /// The correponding NFT token id of reserve NFTs118 #[pallet::storage]119 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]120 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<121 Hasher1 = Twox64Concat,122 Key1 = CollectionId,123 Hasher2 = Blake2_128Concat,124 Key2 = staging_xcm::v3::AssetInstance,125 Value = TokenId,126 QueryKind = OptionQuery,127 >;128129 /// The correponding reserve NFT of a token ID130 #[pallet::storage]131 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]132 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<133 Hasher1 = Twox64Concat,134 Key1 = CollectionId,135 Hasher2 = Blake2_128Concat,136 Key2 = TokenId,137 Value = staging_xcm::v3::AssetInstance,138 QueryKind = OptionQuery,139 >;140141 #[pallet::pallet]142 pub struct Pallet<T>(_);143144 #[pallet::call]145 impl<T: Config> Pallet<T> {146 #[pallet::call_index(0)]147 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]148 pub fn force_register_foreign_asset(149 origin: OriginFor<T>,150 asset_id: Box<AssetId>,151 name: CollectionName,152 token_prefix: CollectionTokenPrefix,153 mode: ForeignCollectionMode,154 ) -> DispatchResult {155 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;156157 ensure!(158 !<ForeignAssetToCollection<T>>::contains_key(*asset_id),159 <Error<T>>::ForeignAssetAlreadyRegistered,160 );161162 let foreign_collection_owner = Self::pallet_account();163164 let description: CollectionDescription = "Foreign Assets Collection"165 .encode_utf16()166 .collect::<Vec<_>>()167 .try_into()168 .expect("description length < max description length; qed");169170 let collection_id = T::CollectionDispatch::create(171 foreign_collection_owner,172 CollectionIssuer::Internals,173 CreateCollectionData {174 name,175 token_prefix,176 description,177 mode: mode.into(),178 ..Default::default()179 },180 )?;181182 <ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);183 <CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);184185 Self::deposit_event(Event::<T>::ForeignAssetRegistered {186 collection_id,187 asset_id,188 });189190 Ok(())191 }192 }193}194195impl<T: Config> Pallet<T> {196 fn pallet_account() -> T::CrossAccountId {197 let owner: T::AccountId = T::PalletId::get().into_account_truncating();198 T::CrossAccountId::from_sub(owner)199 }200201 /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.202 ///203 /// The multilocation corresponds to a local collection if:204 /// * It is `Here` location that corresponds to the native token of this parachain.205 /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.206 /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds207 /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,208 /// otherwise `None` is returned.209 /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.210 ///211 /// If the multilocation doesn't match the patterns listed above,212 /// or the `<Collection ID>` points to a foreign collection,213 /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.214 fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {215 let AssetId::Concrete(asset_location) = asset_id else {216 return None;217 };218219 let self_location = T::SelfLocation::get();220221 if *asset_location == Here.into() || *asset_location == self_location {222 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));223 }224225 let prefix = if asset_location.parents == 0 {226 &Here227 } else if asset_location.parents == self_location.parents {228 &self_location.interior229 } else {230 return None;231 };232233 let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {234 return None;235 };236237 let collection_id = CollectionId((*collection_id).try_into().ok()?);238239 Self::collection_to_foreign_asset(collection_id)240 .is_none()241 .then_some(CollectionLocality::Local(collection_id))242 }243244 /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).245 ///246 /// The function will check if the asset's reserve location has the corresponding247 /// foreign collection on Unique Network,248 /// and will return the "foreign" locality containing the collection ID if found.249 ///250 /// If no corresponding foreign collection is found, the function will check251 /// if the asset's reserve location corresponds to a local collection.252 /// If the local collection is found, the "local" locality with the collection ID is returned.253 ///254 /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.255 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {256 Self::foreign_asset_to_collection(asset_id)257 .map(CollectionLocality::Foreign)258 .or_else(|| Self::local_asset_id_to_collection(asset_id))259 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())260 }261262 /// Converts an XCM asset instance of local collection to the Unique Network's token ID.263 ///264 /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:265 /// `AssetInstance::Index(<token ID>)`.266 ///267 /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,268 /// `None` will be returned.269 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {270 match asset_instance {271 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),272 _ => None,273 }274 }275276 /// Obtains the token ID of the `asset_instance` in the collection.277 fn asset_instance_to_token_id(278 collection_locality: CollectionLocality,279 asset_instance: &AssetInstance,280 ) -> Option<TokenId> {281 match collection_locality {282 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),283 CollectionLocality::Foreign(collection_id) => {284 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)285 }286 }287 }288289 /// Creates a foreign item in the the collection.290 fn create_foreign_asset_instance(291 xcm_ext: &dyn XcmExtensions<T>,292 collection_id: CollectionId,293 asset_instance: &AssetInstance,294 to: T::CrossAccountId,295 ) -> DispatchResult {296 let derivative_token_id = xcm_ext.create_item(297 &Self::pallet_account(),298 to,299 CreateItemData::NFT(Default::default()),300 &ZeroBudget,301 )?;302303 <ForeignReserveAssetInstanceToTokenId<T>>::insert(304 collection_id,305 asset_instance,306 derivative_token_id,307 );308309 <TokenIdToForeignReserveAssetInstance<T>>::insert(310 collection_id,311 derivative_token_id,312 asset_instance,313 );314315 Ok(())316 }317318 /// Deposits an asset instance to the `to` account.319 ///320 /// Either transfers an existing item from the pallet's account321 /// or creates a foreign item.322 fn deposit_asset_instance(323 xcm_ext: &dyn XcmExtensions<T>,324 collection_locality: CollectionLocality,325 asset_instance: &AssetInstance,326 to: T::CrossAccountId,327 ) -> XcmResult {328 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);329330 let deposit_result = match (collection_locality, token_id) {331 (_, Some(token_id)) => {332 let depositor = &Self::pallet_account();333 let from = depositor;334 let amount = 1;335336 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)337 }338 (CollectionLocality::Foreign(collection_id), None) => {339 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)340 }341 (CollectionLocality::Local(_), None) => {342 return Err(XcmExecutorError::InstanceConversionFailed.into());343 }344 };345346 deposit_result347 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))348 }349350 /// Withdraws an asset instance from the `from` account.351 ///352 /// Transfers the asset instance to the pallet's account.353 fn withdraw_asset_instance(354 xcm_ext: &dyn XcmExtensions<T>,355 collection_locality: CollectionLocality,356 asset_instance: &AssetInstance,357 from: T::CrossAccountId,358 ) -> XcmResult {359 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)360 .ok_or(XcmExecutorError::InstanceConversionFailed)?;361362 let depositor = &from;363 let to = Self::pallet_account();364 let amount = 1;365 xcm_ext366 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)367 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;368369 Ok(())370 }371}372373impl<T: Config> TransactAsset for Pallet<T> {374 fn can_check_in(375 _origin: &MultiLocation,376 _what: &MultiAsset,377 _context: &XcmContext,378 ) -> XcmResult {379 Err(XcmError::Unimplemented)380 }381382 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}383384 fn can_check_out(385 _dest: &MultiLocation,386 _what: &MultiAsset,387 _context: &XcmContext,388 ) -> XcmResult {389 Err(XcmError::Unimplemented)390 }391392 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}393394 fn deposit_asset(395 what: &MultiAsset,396 to: &MultiLocation,397 _context: Option<&XcmContext>,398 ) -> XcmResult {399 let to = T::LocationToAccountId::convert_location(to)400 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;401402 let collection_locality = Self::asset_to_collection(&what.id)?;403 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)404 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;405406 let collection = dispatch.as_dyn();407 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;408409 match what.fun {410 Fungibility::Fungible(amount) => xcm_ext411 .create_item(412 &Self::pallet_account(),413 to,414 CreateItemData::Fungible(CreateFungibleData { value: amount }),415 &ZeroBudget,416 )417 .map(|_| ())418 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),419420 Fungibility::NonFungible(asset_instance) => {421 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)422 }423 }424 }425426 fn withdraw_asset(427 what: &MultiAsset,428 from: &MultiLocation,429 _maybe_context: Option<&XcmContext>,430 ) -> Result<staging_xcm_executor::Assets, XcmError> {431 let from = T::LocationToAccountId::convert_location(from)432 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;433434 let collection_locality = Self::asset_to_collection(&what.id)?;435 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)436 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;437438 let collection = dispatch.as_dyn();439 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;440441 match what.fun {442 Fungibility::Fungible(amount) => xcm_ext443 .burn_item(from, TokenId::default(), amount)444 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,445446 Fungibility::NonFungible(asset_instance) => {447 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;448 }449 }450451 Ok(what.clone().into())452 }453454 fn internal_transfer_asset(455 what: &MultiAsset,456 from: &MultiLocation,457 to: &MultiLocation,458 _context: &XcmContext,459 ) -> Result<staging_xcm_executor::Assets, XcmError> {460 let from = T::LocationToAccountId::convert_location(from)461 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;462463 let to = T::LocationToAccountId::convert_location(to)464 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;465466 let collection_locality = Self::asset_to_collection(&what.id)?;467468 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)469 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;470 let collection = dispatch.as_dyn();471 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;472473 let depositor = &from;474475 let token_id;476 let amount;477 let map_error: fn(DispatchError) -> XcmError;478479 match what.fun {480 Fungibility::Fungible(fungible_amount) => {481 token_id = TokenId::default();482 amount = fungible_amount;483 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");484 }485486 Fungibility::NonFungible(asset_instance) => {487 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)488 .ok_or(XcmExecutorError::InstanceConversionFailed)?;489490 amount = 1;491 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")492 }493 }494495 xcm_ext496 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)497 .map_err(map_error)?;498499 Ok(what.clone().into())500 }501}502503#[derive(Clone, Copy)]504pub enum CollectionLocality {505 Local(CollectionId),506 Foreign(CollectionId),507}508509impl Deref for CollectionLocality {510 type Target = CollectionId;511512 fn deref(&self) -> &Self::Target {513 match self {514 Self::Local(id) => id,515 Self::Foreign(id) => id,516 }517 }518}519520pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);521impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>522 for CurrencyIdConvert<T>523{524 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {525 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {526 Some(T::SelfLocation::get())527 } else {528 <Pallet<T>>::collection_to_foreign_asset(collection_id)529 .and_then(|asset_id| match asset_id {530 AssetId::Concrete(location) => Some(location),531 _ => None,532 })533 .or_else(|| {534 T::SelfLocation::get()535 .pushed_with_interior(GeneralIndex(collection_id.0.into()))536 .ok()537 })538 }539 }540}541542#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]543pub enum ForeignCollectionMode {544 NFT,545 Fungible(u8),546}547548impl From<ForeignCollectionMode> for CollectionMode {549 fn from(value: ForeignCollectionMode) -> Self {550 match value {551 ForeignCollectionMode::NFT => Self::NFT,552 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),553 }554 }555}556557pub struct FreeForAll;558559impl WeightTrader for FreeForAll {560 fn new() -> Self {561 Self562 }563564 fn buy_weight(565 &mut self,566 weight: Weight,567 payment: Assets,568 _xcm: &XcmContext,569 ) -> Result<Assets, XcmError> {570 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);571 Ok(payment)572 }573}