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};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, CreateNftData, Property, PropertyKey,46 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 up_data_structs::{60 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,61 };6263 use super::*;6465 #[pallet::config]66 pub trait Config:67 frame_system::Config68 + pallet_common::Config69 + pallet_fungible::Config70 + pallet_balances::Config71 {72 73 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7475 76 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7778 79 type PalletId: Get<PalletId>;8081 82 type SelfLocation: Get<MultiLocation>;8384 85 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8687 88 type WeightInfo: WeightInfo;89 }9091 #[pallet::error]92 pub enum Error<T> {93 94 ForeignAssetAlreadyRegistered,95 }9697 #[pallet::event]98 #[pallet::generate_deposit(fn deposit_event)]99 pub enum Event<T: Config> {100 101 ForeignAssetRegistered {102 collection_id: CollectionId,103 asset_id: Box<AssetId>,104 },105 }106107 108 #[pallet::storage]109 #[pallet::getter(fn foreign_asset_to_collection)]110 pub type ForeignAssetToCollection<T: Config> =111 StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;112113 114 #[pallet::storage]115 #[pallet::getter(fn collection_to_foreign_asset)]116 pub type CollectionToForeignAsset<T: Config> =117 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;118119 120 #[pallet::storage]121 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]122 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<123 Hasher1 = Twox64Concat,124 Key1 = CollectionId,125 Hasher2 = Twox64Concat,126 Key2 = staging_xcm::v3::AssetInstance,127 Value = TokenId,128 QueryKind = OptionQuery,129 >;130131 132 #[pallet::storage]133 #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]134 pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<135 Hasher1 = Twox64Concat,136 Key1 = CollectionId,137 Hasher2 = Twox64Concat,138 Key2 = TokenId,139 Value = staging_xcm::v3::AssetInstance,140 QueryKind = OptionQuery,141 >;142143 #[pallet::pallet]144 pub struct Pallet<T>(_);145146 #[pallet::call]147 impl<T: Config> Pallet<T> {148 #[pallet::call_index(0)]149 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]150 pub fn force_register_foreign_asset(151 origin: OriginFor<T>,152 asset_id: Box<AssetId>,153 name: CollectionName,154 token_prefix: CollectionTokenPrefix,155 mode: ForeignCollectionMode,156 ) -> DispatchResult {157 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159 ensure!(160 !<ForeignAssetToCollection<T>>::contains_key(*asset_id),161 <Error<T>>::ForeignAssetAlreadyRegistered,162 );163164 let foreign_collection_owner = Self::pallet_account();165166 let description: CollectionDescription = "Foreign Assets Collection"167 .encode_utf16()168 .collect::<Vec<_>>()169 .try_into()170 .expect("description length < max description length; qed");171172 let payer = None;173 let is_special_collection = true;174 let collection_id = T::CollectionDispatch::create_raw(175 foreign_collection_owner,176 payer,177 is_special_collection,178 CreateCollectionData {179 name,180 token_prefix,181 description,182 mode: mode.into(),183184 properties: vec![Property {185 key: Self::reserve_location_property_key(),186 value: asset_id187 .encode()188 .try_into()189 .expect("multilocation is less than 32k; qed"),190 }]191 .try_into()192 .expect("just one property can always be stored; qed"),193194 token_property_permissions: vec![PropertyKeyPermission {195 key: Self::reserve_asset_instance_property_key(),196 permission: PropertyPermission {197 mutable: false,198 collection_admin: true,199 token_owner: false,200 },201 }]202 .try_into()203 .expect("just one property permission can always be stored; qed"),204 ..Default::default()205 },206 )?;207208 <ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);209 <CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);210211 Self::deposit_event(Event::<T>::ForeignAssetRegistered {212 collection_id,213 asset_id,214 });215216 Ok(())217 }218 }219}220221impl<T: Config> Pallet<T> {222 fn pallet_account() -> T::CrossAccountId {223 let owner: T::AccountId = T::PalletId::get().into_account_truncating();224 T::CrossAccountId::from_sub(owner)225 }226227 fn reserve_location_property_key() -> PropertyKey {228 b"reserve-location"229 .to_vec()230 .try_into()231 .expect("key length < max property key length; qed")232 }233234 fn reserve_asset_instance_property_key() -> PropertyKey {235 b"reserve-asset-instance"236 .to_vec()237 .try_into()238 .expect("key length < max property key length; qed")239 }240241 242 243 244 245 246 247 248 249 250 251 252 253 254 fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {255 let AssetId::Concrete(asset_location) = asset_id else {256 return None;257 };258259 let self_location = T::SelfLocation::get();260261 if *asset_location == Here.into() || *asset_location == self_location {262 return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));263 }264265 let prefix = if asset_location.parents == 0 {266 &Here267 } else if asset_location.parents == self_location.parents {268 &self_location.interior269 } else {270 return None;271 };272273 let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {274 return None;275 };276277 let collection_id = CollectionId((*collection_id).try_into().ok()?);278279 Self::collection_to_foreign_asset(collection_id)280 .is_none()281 .then_some(CollectionLocality::Local(collection_id))282 }283284 285 286 287 288 289 290 291 292 293 294 295 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {296 Self::foreign_asset_to_collection(asset_id)297 .map(CollectionLocality::Foreign)298 .or_else(|| Self::local_asset_id_to_collection(asset_id))299 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())300 }301302 303 304 305 306 307 308 309 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {310 match asset_instance {311 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),312 _ => None,313 }314 }315316 317 fn asset_instance_to_token_id(318 collection_locality: CollectionLocality,319 asset_instance: &AssetInstance,320 ) -> Option<TokenId> {321 match collection_locality {322 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),323 CollectionLocality::Foreign(collection_id) => {324 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)325 }326 }327 }328329 330 fn create_foreign_asset_instance(331 xcm_ext: &dyn XcmExtensions<T>,332 collection_id: CollectionId,333 asset_instance: &AssetInstance,334 to: T::CrossAccountId,335 ) -> DispatchResult {336 let asset_instance_encoded = asset_instance.encode();337338 let derivative_token_id = xcm_ext.create_item(339 &Self::pallet_account(),340 to,341 CreateItemData::NFT(CreateNftData {342 properties: vec![Property {343 key: Self::reserve_asset_instance_property_key(),344 value: asset_instance_encoded345 .try_into()346 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),347 }]348 .try_into()349 .expect("just one property can always be stored; qed"),350 }),351 &ZeroBudget,352 )?;353354 <ForeignReserveAssetInstanceToTokenId<T>>::insert(355 collection_id,356 asset_instance,357 derivative_token_id,358 );359360 <TokenIdToForeignReserveAssetInstance<T>>::insert(361 collection_id,362 derivative_token_id,363 asset_instance,364 );365366 Ok(())367 }368369 370 371 372 373 fn deposit_asset_instance(374 xcm_ext: &dyn XcmExtensions<T>,375 collection_locality: CollectionLocality,376 asset_instance: &AssetInstance,377 to: T::CrossAccountId,378 ) -> XcmResult {379 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);380381 let deposit_result = match (collection_locality, token_id) {382 (_, Some(token_id)) => {383 let depositor = &Self::pallet_account();384 let from = depositor;385 let amount = 1;386387 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)388 }389 (CollectionLocality::Foreign(collection_id), None) => {390 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)391 }392 (CollectionLocality::Local(_), None) => {393 return Err(XcmError::AssetNotFound);394 }395 };396397 deposit_result398 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))399 }400401 402 403 404 fn withdraw_asset_instance(405 xcm_ext: &dyn XcmExtensions<T>,406 collection_locality: CollectionLocality,407 asset_instance: &AssetInstance,408 from: T::CrossAccountId,409 ) -> XcmResult {410 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)411 .ok_or(XcmError::AssetNotFound)?;412413 let depositor = &from;414 let to = Self::pallet_account();415 let amount = 1;416 xcm_ext417 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)418 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;419420 Ok(())421 }422}423424impl<T: Config> TransactAsset for Pallet<T> {425 fn can_check_in(426 _origin: &MultiLocation,427 _what: &MultiAsset,428 _context: &XcmContext,429 ) -> XcmResult {430 Err(XcmError::Unimplemented)431 }432433 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}434435 fn can_check_out(436 _dest: &MultiLocation,437 _what: &MultiAsset,438 _context: &XcmContext,439 ) -> XcmResult {440 Err(XcmError::Unimplemented)441 }442443 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}444445 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {446 let to = T::LocationToAccountId::convert_location(to)447 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;448449 let collection_locality = Self::asset_to_collection(&what.id)?;450 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)451 .map_err(|_| XcmError::AssetNotFound)?;452453 let collection = dispatch.as_dyn();454 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;455456 match what.fun {457 Fungibility::Fungible(amount) => xcm_ext458 .create_item(459 &Self::pallet_account(),460 to,461 CreateItemData::Fungible(CreateFungibleData { value: amount }),462 &ZeroBudget,463 )464 .map(|_| ())465 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),466467 Fungibility::NonFungible(asset_instance) => {468 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)469 }470 }471 }472473 fn withdraw_asset(474 what: &MultiAsset,475 from: &MultiLocation,476 _maybe_context: Option<&XcmContext>,477 ) -> Result<staging_xcm_executor::Assets, XcmError> {478 let from = T::LocationToAccountId::convert_location(from)479 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;480481 let collection_locality = Self::asset_to_collection(&what.id)?;482 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)483 .map_err(|_| XcmError::AssetNotFound)?;484485 let collection = dispatch.as_dyn();486 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;487488 match what.fun {489 Fungibility::Fungible(amount) => xcm_ext490 .burn_item(from, TokenId::default(), amount)491 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,492493 Fungibility::NonFungible(asset_instance) => {494 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;495 }496 }497498 Ok(what.clone().into())499 }500501 fn internal_transfer_asset(502 what: &MultiAsset,503 from: &MultiLocation,504 to: &MultiLocation,505 _context: &XcmContext,506 ) -> Result<staging_xcm_executor::Assets, XcmError> {507 let from = T::LocationToAccountId::convert_location(from)508 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;509510 let to = T::LocationToAccountId::convert_location(to)511 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;512513 let collection_locality = Self::asset_to_collection(&what.id)?;514515 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)516 .map_err(|_| XcmError::AssetNotFound)?;517 let collection = dispatch.as_dyn();518 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;519520 let depositor = &from;521522 let token_id;523 let amount;524 let map_error: fn(DispatchError) -> XcmError;525526 match what.fun {527 Fungibility::Fungible(fungible_amount) => {528 token_id = TokenId::default();529 amount = fungible_amount;530 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");531 }532533 Fungibility::NonFungible(asset_instance) => {534 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)535 .ok_or(XcmError::AssetNotFound)?;536537 amount = 1;538 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")539 }540 }541542 xcm_ext543 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)544 .map_err(map_error)?;545546 Ok(what.clone().into())547 }548}549550#[derive(Clone, Copy)]551pub enum CollectionLocality {552 Local(CollectionId),553 Foreign(CollectionId),554}555556impl Deref for CollectionLocality {557 type Target = CollectionId;558559 fn deref(&self) -> &Self::Target {560 match self {561 Self::Local(id) => id,562 Self::Foreign(id) => id,563 }564 }565}566567pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);568impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>569 for CurrencyIdConvert<T>570{571 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {572 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {573 Some(T::SelfLocation::get())574 } else {575 <Pallet<T>>::collection_to_foreign_asset(collection_id)576 .and_then(|asset_id| match asset_id {577 AssetId::Concrete(location) => Some(location),578 _ => None,579 })580 .or_else(|| {581 T::SelfLocation::get()582 .pushed_with_interior(GeneralIndex(collection_id.0.into()))583 .ok()584 })585 }586 }587}588589#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]590pub enum ForeignCollectionMode {591 NFT,592 Fungible(u8),593}594595impl From<ForeignCollectionMode> for CollectionMode {596 fn from(value: ForeignCollectionMode) -> Self {597 match value {598 ForeignCollectionMode::NFT => Self::NFT,599 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),600 }601 }602}603604pub struct FreeForAll;605606impl WeightTrader for FreeForAll {607 fn new() -> Self {608 Self609 }610611 fn buy_weight(612 &mut self,613 weight: Weight,614 payment: Assets,615 _xcm: &XcmContext,616 ) -> Result<Assets, XcmError> {617 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);618 Ok(payment)619 }620}