1234567891011121314151617181920212223#![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 72 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7374 75 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7677 78 type PalletId: Get<PalletId>;7980 81 type SelfLocation: Get<MultiLocation>;8283 84 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8586 87 type WeightInfo: WeightInfo;88 }8990 #[pallet::error]91 pub enum Error<T> {92 93 ForeignAssetAlreadyRegistered,9495 96 BadForeignAssetId,97 }9899 #[pallet::event]100 #[pallet::generate_deposit(fn deposit_event)]101 pub enum Event<T: Config> {102 103 ForeignAssetRegistered {104 collection_id: CollectionId,105 asset_id: Box<VersionedAssetId>,106 },107 }108109 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 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 122 #[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 134 #[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::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 212 213 214 215 216 217 218 219 220 221 222 223 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 255 256 257 258 259 260 261 262 263 264 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 273 274 275 276 277 278 279 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {280 match asset_instance {281 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),282 _ => None,283 }284 }285286 287 fn asset_instance_to_token_id(288 collection_locality: CollectionLocality,289 asset_instance: &AssetInstance,290 ) -> Option<TokenId> {291 match collection_locality {292 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),293 CollectionLocality::Foreign(collection_id) => {294 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)295 }296 }297 }298299 300 fn create_foreign_asset_instance(301 xcm_ext: &dyn XcmExtensions<T>,302 collection_id: CollectionId,303 asset_instance: &AssetInstance,304 to: T::CrossAccountId,305 ) -> DispatchResult {306 let derivative_token_id = xcm_ext.create_item(307 &Self::pallet_account(),308 to,309 CreateItemData::NFT(Default::default()),310 &ZeroBudget,311 )?;312313 <ForeignReserveAssetInstanceToTokenId<T>>::insert(314 collection_id,315 asset_instance,316 derivative_token_id,317 );318319 <TokenIdToForeignReserveAssetInstance<T>>::insert(320 collection_id,321 derivative_token_id,322 asset_instance,323 );324325 Ok(())326 }327328 329 330 331 332 fn deposit_asset_instance(333 xcm_ext: &dyn XcmExtensions<T>,334 collection_locality: CollectionLocality,335 asset_instance: &AssetInstance,336 to: T::CrossAccountId,337 ) -> XcmResult {338 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);339340 let deposit_result = match (collection_locality, token_id) {341 (_, Some(token_id)) => {342 let depositor = &Self::pallet_account();343 let from = depositor;344 let amount = 1;345346 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)347 }348 (CollectionLocality::Foreign(collection_id), None) => {349 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)350 }351 (CollectionLocality::Local(_), None) => {352 return Err(XcmExecutorError::InstanceConversionFailed.into());353 }354 };355356 deposit_result357 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))358 }359360 361 362 363 fn withdraw_asset_instance(364 xcm_ext: &dyn XcmExtensions<T>,365 collection_locality: CollectionLocality,366 asset_instance: &AssetInstance,367 from: T::CrossAccountId,368 ) -> XcmResult {369 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)370 .ok_or(XcmExecutorError::InstanceConversionFailed)?;371372 let depositor = &from;373 let to = Self::pallet_account();374 let amount = 1;375 xcm_ext376 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)377 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;378379 Ok(())380 }381}382383impl<T: Config> TransactAsset for Pallet<T> {384 fn can_check_in(385 _origin: &MultiLocation,386 _what: &MultiAsset,387 _context: &XcmContext,388 ) -> XcmResult {389 Err(XcmError::Unimplemented)390 }391392 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}393394 fn can_check_out(395 _dest: &MultiLocation,396 _what: &MultiAsset,397 _context: &XcmContext,398 ) -> XcmResult {399 Err(XcmError::Unimplemented)400 }401402 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}403404 fn deposit_asset(405 what: &MultiAsset,406 to: &MultiLocation,407 _context: Option<&XcmContext>,408 ) -> XcmResult {409 let to = T::LocationToAccountId::convert_location(to)410 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;411412 let collection_locality = Self::asset_to_collection(&what.id)?;413 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)414 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;415416 let collection = dispatch.as_dyn();417 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;418419 match what.fun {420 Fungibility::Fungible(amount) => xcm_ext421 .create_item(422 &Self::pallet_account(),423 to,424 CreateItemData::Fungible(CreateFungibleData { value: amount }),425 &ZeroBudget,426 )427 .map(|_| ())428 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),429430 Fungibility::NonFungible(asset_instance) => {431 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)432 }433 }434 }435436 fn withdraw_asset(437 what: &MultiAsset,438 from: &MultiLocation,439 _maybe_context: Option<&XcmContext>,440 ) -> Result<staging_xcm_executor::Assets, XcmError> {441 let from = T::LocationToAccountId::convert_location(from)442 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;443444 let collection_locality = Self::asset_to_collection(&what.id)?;445 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)446 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;447448 let collection = dispatch.as_dyn();449 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;450451 match what.fun {452 Fungibility::Fungible(amount) => xcm_ext453 .burn_item(from, TokenId::default(), amount)454 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,455456 Fungibility::NonFungible(asset_instance) => {457 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;458 }459 }460461 Ok(what.clone().into())462 }463464 fn internal_transfer_asset(465 what: &MultiAsset,466 from: &MultiLocation,467 to: &MultiLocation,468 _context: &XcmContext,469 ) -> Result<staging_xcm_executor::Assets, XcmError> {470 let from = T::LocationToAccountId::convert_location(from)471 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;472473 let to = T::LocationToAccountId::convert_location(to)474 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;475476 let collection_locality = Self::asset_to_collection(&what.id)?;477478 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)479 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;480 let collection = dispatch.as_dyn();481 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;482483 let depositor = &from;484485 let token_id;486 let amount;487 let map_error: fn(DispatchError) -> XcmError;488489 match what.fun {490 Fungibility::Fungible(fungible_amount) => {491 token_id = TokenId::default();492 amount = fungible_amount;493 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");494 }495496 Fungibility::NonFungible(asset_instance) => {497 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)498 .ok_or(XcmExecutorError::InstanceConversionFailed)?;499500 amount = 1;501 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")502 }503 }504505 xcm_ext506 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)507 .map_err(map_error)?;508509 Ok(what.clone().into())510 }511}512513#[derive(Clone, Copy)]514pub enum CollectionLocality {515 Local(CollectionId),516 Foreign(CollectionId),517}518519impl Deref for CollectionLocality {520 type Target = CollectionId;521522 fn deref(&self) -> &Self::Target {523 match self {524 Self::Local(id) => id,525 Self::Foreign(id) => id,526 }527 }528}529530pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);531impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>532 for CurrencyIdConvert<T>533{534 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {535 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {536 Some(T::SelfLocation::get())537 } else {538 <Pallet<T>>::collection_to_foreign_asset(collection_id)539 .and_then(|asset_id| match asset_id {540 AssetId::Concrete(location) => Some(location),541 _ => None,542 })543 .or_else(|| {544 T::SelfLocation::get()545 .pushed_with_interior(GeneralIndex(collection_id.0.into()))546 .ok()547 })548 }549 }550}551552#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]553pub enum ForeignCollectionMode {554 NFT,555 Fungible(u8),556}557558impl From<ForeignCollectionMode> for CollectionMode {559 fn from(value: ForeignCollectionMode) -> Self {560 match value {561 ForeignCollectionMode::NFT => Self::NFT,562 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),563 }564 }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}