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 asset_id: CollectionId,103 reserve_location: Box<MultiLocation>,104 },105 }106107 108 #[pallet::storage]109 #[pallet::getter(fn foreign_reserve_location_to_collection)]110 pub type ForeignReserveLocationToCollection<T: Config> =111 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;112113 114 #[pallet::storage]115 #[pallet::getter(fn collection_to_foreign_reserve_location)]116 pub type CollectionToForeignReserveLocation<T: Config> =117 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, 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 reserve_location: Box<MultiLocation>,153 name: CollectionName,154 token_prefix: CollectionTokenPrefix,155 mode: ForeignCollectionMode,156 ) -> DispatchResult {157 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {160 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());161 }162163 let foreign_collection_owner = Self::pallet_account();164165 let description: CollectionDescription = "Foreign Assets Collection"166 .encode_utf16()167 .collect::<Vec<_>>()168 .try_into()169 .expect("description length < max description length; qed");170171 let payer = None;172 let is_special_collection = true;173 let collection_id = T::CollectionDispatch::create_raw(174 foreign_collection_owner,175 payer,176 is_special_collection,177 CreateCollectionData {178 name,179 token_prefix,180 description,181 mode: mode.into(),182183 properties: vec![Property {184 key: Self::reserve_location_property_key(),185 value: reserve_location186 .encode()187 .try_into()188 .expect("multilocation is less than 32k; qed"),189 }]190 .try_into()191 .expect("just one property can always be stored; qed"),192193 token_property_permissions: vec![PropertyKeyPermission {194 key: Self::reserve_asset_instance_property_key(),195 permission: PropertyPermission {196 mutable: false,197 collection_admin: true,198 token_owner: false,199 },200 }]201 .try_into()202 .expect("just one property permission can always be stored; qed"),203 ..Default::default()204 },205 )?;206207 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);208 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);209210 Self::deposit_event(Event::<T>::ForeignAssetRegistered {211 asset_id: collection_id,212 reserve_location,213 });214215 Ok(())216 }217 }218}219220impl<T: Config> Pallet<T> {221 fn pallet_account() -> T::CrossAccountId {222 let owner: T::AccountId = T::PalletId::get().into_account_truncating();223 T::CrossAccountId::from_sub(owner)224 }225226 fn reserve_location_property_key() -> PropertyKey {227 b"reserve-location"228 .to_vec()229 .try_into()230 .expect("key length < max property key length; qed")231 }232233 fn reserve_asset_instance_property_key() -> PropertyKey {234 b"reserve-asset-instance"235 .to_vec()236 .try_into()237 .expect("key length < max property key length; qed")238 }239240 241 242 243 244 245 246 247 248 249 250 251 252 fn local_asset_location_to_collection(253 asset_location: &MultiLocation,254 ) -> Option<CollectionLocality> {255 let self_location = T::SelfLocation::get();256257 if *asset_location == Here.into() || *asset_location == self_location {258 Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID))259 } else if asset_location.parents == self_location.parents {260 match asset_location261 .interior262 .match_and_split(&self_location.interior)263 {264 Some(GeneralIndex(collection_id)) => {265 let collection_id = CollectionId((*collection_id).try_into().ok()?);266267 Self::collection_to_foreign_reserve_location(collection_id)268 .is_none()269 .then_some(CollectionLocality::Local(collection_id))270 }271 _ => None,272 }273 } else {274 None275 }276 }277278 279 280 281 282 283 284 285 286 287 288 289 fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {290 let AssetId::Concrete(asset_reserve_location) = asset_id else {291 return Err(XcmExecutorError::AssetNotHandled.into());292 };293294 Self::foreign_reserve_location_to_collection(asset_reserve_location)295 .map(CollectionLocality::Foreign)296 .or_else(|| Self::local_asset_location_to_collection(asset_reserve_location))297 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())298 }299300 301 302 303 304 305 306 307 fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {308 match asset_instance {309 AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),310 _ => None,311 }312 }313314 315 fn asset_instance_to_token_id(316 collection_locality: CollectionLocality,317 asset_instance: &AssetInstance,318 ) -> Option<TokenId> {319 match collection_locality {320 CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),321 CollectionLocality::Foreign(collection_id) => {322 Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)323 }324 }325 }326327 328 fn create_foreign_asset_instance(329 xcm_ext: &dyn XcmExtensions<T>,330 collection_id: CollectionId,331 asset_instance: &AssetInstance,332 to: T::CrossAccountId,333 ) -> DispatchResult {334 let asset_instance_encoded = asset_instance.encode();335336 let derivative_token_id = xcm_ext.create_item(337 &Self::pallet_account(),338 to,339 CreateItemData::NFT(CreateNftData {340 properties: vec![Property {341 key: Self::reserve_asset_instance_property_key(),342 value: asset_instance_encoded343 .try_into()344 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),345 }]346 .try_into()347 .expect("just one property can always be stored; qed"),348 }),349 &ZeroBudget,350 )?;351352 <ForeignReserveAssetInstanceToTokenId<T>>::insert(353 collection_id,354 asset_instance,355 derivative_token_id,356 );357358 <TokenIdToForeignReserveAssetInstance<T>>::insert(359 collection_id,360 derivative_token_id,361 asset_instance,362 );363364 Ok(())365 }366367 368 369 370 371 fn deposit_asset_instance(372 xcm_ext: &dyn XcmExtensions<T>,373 collection_locality: CollectionLocality,374 asset_instance: &AssetInstance,375 to: T::CrossAccountId,376 ) -> XcmResult {377 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);378379 let deposit_result = match (collection_locality, token_id) {380 (_, Some(token_id)) => {381 let depositor = &Self::pallet_account();382 let from = depositor;383 let amount = 1;384385 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)386 }387 (CollectionLocality::Foreign(collection_id), None) => {388 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)389 }390 (CollectionLocality::Local(_), None) => {391 return Err(XcmError::AssetNotFound);392 }393 };394395 deposit_result396 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))397 }398399 400 401 402 fn withdraw_asset_instance(403 xcm_ext: &dyn XcmExtensions<T>,404 collection_locality: CollectionLocality,405 asset_instance: &AssetInstance,406 from: T::CrossAccountId,407 ) -> XcmResult {408 let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)409 .ok_or(XcmError::AssetNotFound)?;410411 let depositor = &from;412 let to = Self::pallet_account();413 let amount = 1;414 xcm_ext415 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)416 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;417418 Ok(())419 }420}421422impl<T: Config> TransactAsset for Pallet<T> {423 fn can_check_in(424 _origin: &MultiLocation,425 _what: &MultiAsset,426 _context: &XcmContext,427 ) -> XcmResult {428 Err(XcmError::Unimplemented)429 }430431 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}432433 fn can_check_out(434 _dest: &MultiLocation,435 _what: &MultiAsset,436 _context: &XcmContext,437 ) -> XcmResult {438 Err(XcmError::Unimplemented)439 }440441 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}442443 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {444 let to = T::LocationToAccountId::convert_location(to)445 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;446447 let collection_locality = Self::asset_to_collection(&what.id)?;448 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)449 .map_err(|_| XcmError::AssetNotFound)?;450451 let collection = dispatch.as_dyn();452 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;453454 match what.fun {455 Fungibility::Fungible(amount) => xcm_ext456 .create_item(457 &Self::pallet_account(),458 to,459 CreateItemData::Fungible(CreateFungibleData { value: amount }),460 &ZeroBudget,461 )462 .map(|_| ())463 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),464465 Fungibility::NonFungible(asset_instance) => {466 Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)467 }468 }469 }470471 fn withdraw_asset(472 what: &MultiAsset,473 from: &MultiLocation,474 _maybe_context: Option<&XcmContext>,475 ) -> Result<staging_xcm_executor::Assets, XcmError> {476 let from = T::LocationToAccountId::convert_location(from)477 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;478479 let collection_locality = Self::asset_to_collection(&what.id)?;480 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)481 .map_err(|_| XcmError::AssetNotFound)?;482483 let collection = dispatch.as_dyn();484 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;485486 match what.fun {487 Fungibility::Fungible(amount) => xcm_ext488 .burn_item(from, TokenId::default(), amount)489 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,490491 Fungibility::NonFungible(asset_instance) => {492 Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;493 }494 }495496 Ok(what.clone().into())497 }498499 fn internal_transfer_asset(500 what: &MultiAsset,501 from: &MultiLocation,502 to: &MultiLocation,503 _context: &XcmContext,504 ) -> Result<staging_xcm_executor::Assets, XcmError> {505 let from = T::LocationToAccountId::convert_location(from)506 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;507508 let to = T::LocationToAccountId::convert_location(to)509 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;510511 let collection_locality = Self::asset_to_collection(&what.id)?;512513 let dispatch = T::CollectionDispatch::dispatch(*collection_locality)514 .map_err(|_| XcmError::AssetNotFound)?;515 let collection = dispatch.as_dyn();516 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;517518 let depositor = &from;519520 let token_id;521 let amount;522 let map_error: fn(DispatchError) -> XcmError;523524 match what.fun {525 Fungibility::Fungible(fungible_amount) => {526 token_id = TokenId::default();527 amount = fungible_amount;528 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");529 }530531 Fungibility::NonFungible(asset_instance) => {532 token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)533 .ok_or(XcmError::AssetNotFound)?;534535 amount = 1;536 map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")537 }538 }539540 xcm_ext541 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)542 .map_err(map_error)?;543544 Ok(what.clone().into())545 }546}547548#[derive(Clone, Copy)]549pub enum CollectionLocality {550 Local(CollectionId),551 Foreign(CollectionId),552}553554impl Deref for CollectionLocality {555 type Target = CollectionId;556557 fn deref(&self) -> &Self::Target {558 match self {559 Self::Local(id) => id,560 Self::Foreign(id) => id,561 }562 }563}564565pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);566impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>567 for CurrencyIdConvert<T>568{569 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {570 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {571 Some(T::SelfLocation::get())572 } else {573 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {574 T::SelfLocation::get()575 .pushed_with_interior(GeneralIndex(collection_id.0.into()))576 .ok()577 })578 }579 }580}581582#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]583pub enum ForeignCollectionMode {584 NFT,585 Fungible(u8),586}587588impl From<ForeignCollectionMode> for CollectionMode {589 fn from(value: ForeignCollectionMode) -> Self {590 match value {591 ForeignCollectionMode::NFT => Self::NFT,592 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),593 }594 }595}596597pub struct FreeForAll;598599impl WeightTrader for FreeForAll {600 fn new() -> Self {601 Self602 }603604 fn buy_weight(605 &mut self,606 weight: Weight,607 payment: Assets,608 _xcm: &XcmContext,609 ) -> Result<Assets, XcmError> {610 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);611 Ok(payment)612 }613}