1234567891011121314151617181920212223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};27use frame_system::pallet_prelude::*;28use pallet_common::{29 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,30};31use sp_runtime::traits::AccountIdConversion;32use sp_std::{vec, vec::Vec};33use staging_xcm::{34 opaque::latest::{prelude::XcmError, Weight},35 v3::{prelude::*, MultiAsset, XcmContext},36};37use staging_xcm_executor::{38 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},39 Assets,40};41use up_data_structs::{42 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,43 CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,44};4546pub mod weights;4748#[cfg(feature = "runtime-benchmarks")]49mod benchmarking;5051pub use module::*;52pub use weights::WeightInfo;5354#[frame_support::pallet]55pub mod module {56 use up_data_structs::{57 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,58 };5960 use super::*;6162 #[pallet::config]63 pub trait Config:64 frame_system::Config65 + pallet_common::Config66 + pallet_fungible::Config67 + pallet_balances::Config68 {69 70 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7172 73 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7475 76 type PalletId: Get<PalletId>;7778 79 type SelfLocation: Get<MultiLocation>;8081 82 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8384 85 type WeightInfo: WeightInfo;86 }8788 #[pallet::error]89 pub enum Error<T> {90 91 ForeignAssetAlreadyRegistered,92 }9394 #[pallet::event]95 #[pallet::generate_deposit(fn deposit_event)]96 pub enum Event<T: Config> {97 98 ForeignAssetRegistered {99 asset_id: CollectionId,100 reserve_location: MultiLocation,101 },102 }103104 105 #[pallet::storage]106 #[pallet::getter(fn foreign_reserve_location_to_collection)]107 pub type ForeignReserveLocationToCollection<T: Config> =108 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;109110 111 #[pallet::storage]112 #[pallet::getter(fn collection_to_foreign_reserve_location)]113 pub type CollectionToForeignReserveLocation<T: Config> =114 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;115116 117 #[pallet::storage]118 #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]119 pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<120 Hasher1 = Twox64Concat,121 Key1 = CollectionId,122 Hasher2 = Twox64Concat,123 Key2 = staging_xcm::v3::AssetInstance,124 Value = TokenId,125 QueryKind = OptionQuery,126 >;127128 #[pallet::pallet]129 pub struct Pallet<T>(_);130131 #[pallet::call]132 impl<T: Config> Pallet<T> {133 #[pallet::call_index(0)]134 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]135 pub fn force_register_foreign_asset(136 origin: OriginFor<T>,137 reserve_location: MultiLocation,138 name: CollectionName,139 mode: CollectionMode,140 ) -> DispatchResult {141 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;142143 let foreign_collection_owner = Self::pallet_account();144145 let description: CollectionDescription = "Foreign Assets Collection"146 .encode_utf16()147 .collect::<Vec<_>>()148 .try_into()149 .expect("description length < max description length; qed");150151 let collection_id = T::CollectionDispatch::create_foreign(152 foreign_collection_owner,153 CreateCollectionData {154 name,155 description,156 mode,157158 properties: vec![Property {159 key: Self::reserve_location_property_key(),160 value: reserve_location161 .encode()162 .try_into()163 .expect("multilocation is less than 32k; qed"),164 }]165 .try_into()166 .expect("just one property can always be stored; qed"),167168 token_property_permissions: vec![PropertyKeyPermission {169 key: Self::reserve_asset_instance_property_key(),170 permission: PropertyPermission {171 mutable: false,172 collection_admin: true,173 token_owner: false,174 },175 }]176 .try_into()177 .expect("just one property permission can always be stored; qed"),178 ..Default::default()179 },180 )?;181182 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);183 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);184185 Self::deposit_event(Event::<T>::ForeignAssetRegistered {186 asset_id: collection_id,187 reserve_location,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 fn reserve_location_property_key() -> PropertyKey {202 b"reserve-location"203 .to_vec()204 .try_into()205 .expect("key length < max property key length; qed")206 }207208 fn reserve_asset_instance_property_key() -> PropertyKey {209 b"reserve-asset-instance"210 .to_vec()211 .try_into()212 .expect("key length < max property key length; qed")213 }214215 fn native_asset_location_to_collection(216 asset_location: &MultiLocation,217 ) -> Result<Option<CollectionId>, XcmError> {218 let self_location = T::SelfLocation::get();219220 if *asset_location == Here.into() {221 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))222 } else if *asset_location == self_location {223 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))224 } else if asset_location.parents == self_location.parents {225 match asset_location226 .interior227 .match_and_split(&self_location.interior)228 {229 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(230 (*collection_id)231 .try_into()232 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,233 ))),234 _ => Ok(None),235 }236 } else {237 Ok(None)238 }239 }240241 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {242 let AssetId::Concrete(asset_reserve_location) = asset.id else {243 return Err(XcmExecutorError::AssetNotHandled.into());244 };245246 Self::native_asset_location_to_collection(&asset_reserve_location)?247 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))248 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())249 }250251 fn native_asset_instance_to_token_id(252 asset_instance: &AssetInstance,253 ) -> Result<TokenId, XcmError> {254 match asset_instance {255 AssetInstance::Index(token_id) => Ok(TokenId(256 (*token_id)257 .try_into()258 .map_err(|_| XcmError::AssetNotFound)?,259 )),260 _ => Err(XcmError::AssetNotFound),261 }262 }263264 265 266 267 268 269 270 fn asset_instance_to_token_id(271 xcm_ext: &dyn XcmExtensions<T>,272 collection_id: CollectionId,273 asset_instance: &AssetInstance,274 ) -> Result<Option<TokenId>, XcmError> {275 if xcm_ext.is_foreign() {276 Ok(Self::foreign_reserve_asset_instance_to_token_id(277 collection_id,278 asset_instance,279 ))280 } else {281 Self::native_asset_instance_to_token_id(asset_instance).map(Some)282 }283 }284285 fn create_foreign_asset_instance(286 xcm_ext: &dyn XcmExtensions<T>,287 collection_id: CollectionId,288 asset_instance: &AssetInstance,289 to: T::CrossAccountId,290 ) -> XcmResult {291 let asset_instance_encoded = asset_instance.encode();292293 let derivative_token_id = xcm_ext294 .create_item(295 &Self::pallet_account(),296 to,297 CreateItemData::NFT(CreateNftData {298 properties: vec![Property {299 key: Self::reserve_asset_instance_property_key(),300 value: asset_instance_encoded301 .try_into()302 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),303 }]304 .try_into()305 .expect("just one property can always be stored; qed"),306 }),307 &ZeroBudget,308 )309 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))?;310311 <ForeignReserveAssetInstanceToTokenId<T>>::insert(312 collection_id,313 asset_instance,314 derivative_token_id,315 );316317 Ok(())318 }319320 fn deposit_asset_instance(321 xcm_ext: &dyn XcmExtensions<T>,322 collection_id: CollectionId,323 to: T::CrossAccountId,324 asset_instance: &AssetInstance,325 ) -> XcmResult {326 if let Some(token_id) =327 Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)?328 {329 let depositor = &Self::pallet_account();330 let from = depositor;331 let amount = 1;332333 xcm_ext334 .transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)335 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))336 } else {337 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)338 }339 }340}341342impl<T: Config> TransactAsset for Pallet<T> {343 fn can_check_in(344 _origin: &MultiLocation,345 _what: &MultiAsset,346 _context: &XcmContext,347 ) -> XcmResult {348 Err(XcmError::Unimplemented)349 }350351 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}352353 fn can_check_out(354 _dest: &MultiLocation,355 _what: &MultiAsset,356 _context: &XcmContext,357 ) -> XcmResult {358 Err(XcmError::Unimplemented)359 }360361 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}362363 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {364 let collection_id = Self::multiasset_to_collection(what)?;365 let dispatch =366 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;367368 let collection = dispatch.as_dyn();369 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;370371 let to = T::LocationToAccountId::convert_location(to)372 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;373374 match what.fun {375 Fungibility::Fungible(amount) => xcm_ext376 .create_item(377 &Self::pallet_account(),378 to,379 CreateItemData::Fungible(CreateFungibleData { value: amount }),380 &ZeroBudget,381 )382 .map(|_| ())383 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),384385 Fungibility::NonFungible(asset_instance) => {386 Self::deposit_asset_instance(xcm_ext, collection_id, to, &asset_instance)387 }388 }389 }390391 fn withdraw_asset(392 what: &MultiAsset,393 from: &MultiLocation,394 _maybe_context: Option<&XcmContext>,395 ) -> Result<staging_xcm_executor::Assets, XcmError> {396 let from = T::LocationToAccountId::convert_location(from)397 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;398399 let collection_id = Self::multiasset_to_collection(what)?;400 let dispatch =401 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;402403 let collection = dispatch.as_dyn();404 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;405406 match what.fun {407 Fungibility::Fungible(amount) => xcm_ext408 .burn_item(from, TokenId::default(), amount)409 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,410411 Fungibility::NonFungible(asset_instance) => {412 let token_id =413 Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?414 .ok_or(XcmError::AssetNotFound)?;415416 if xcm_ext.token_has_children(token_id) {417 return Err(XcmError::Unimplemented);418 }419420 let depositor = &from;421 let to = Self::pallet_account();422 let amount = 1;423 xcm_ext424 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)425 .map_err(|_| {426 XcmError::FailedToTransactAsset("nonfungible item withdraw failed")427 })?;428 }429 }430431 Ok(what.clone().into())432 }433434 fn internal_transfer_asset(435 what: &MultiAsset,436 from: &MultiLocation,437 to: &MultiLocation,438 _context: &XcmContext,439 ) -> Result<staging_xcm_executor::Assets, XcmError> {440 let collection_id = Self::multiasset_to_collection(what)?;441442 let dispatch =443 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;444 let collection = dispatch.as_dyn();445 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;446447 let from = T::LocationToAccountId::convert_location(from)448 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;449450 let to = T::LocationToAccountId::convert_location(to)451 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;452453 let depositor = &from;454455 match what.fun {456 Fungibility::Fungible(amount) => xcm_ext457 .transfer_item(458 depositor,459 &from,460 &to,461 TokenId::default(),462 amount,463 &ZeroBudget,464 )465 .map_err(|_| XcmError::FailedToTransactAsset("fungible item transfer failed"))?,466467 Fungibility::NonFungible(asset_instance) => {468 let token_id =469 Self::asset_instance_to_token_id(xcm_ext, collection_id, &asset_instance)?470 .ok_or(XcmError::AssetNotFound)?;471472 let amount = 1;473474 xcm_ext475 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)476 .map_err(|_| {477 XcmError::FailedToTransactAsset("nonfungible item transfer failed")478 })?;479 }480 }481482 Ok(what.clone().into())483 }484}485486pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);487impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>488 for CurrencyIdConvert<T>489{490 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {491 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {492 Some(Here.into())493 } else {494 let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;495 let collection = dispatch.as_dyn();496 let xcm_ext = collection.xcm_extensions()?;497498 if xcm_ext.is_foreign() {499 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id)500 } else {501 T::SelfLocation::get()502 .pushed_with_interior(GeneralIndex(collection_id.0.into()))503 .ok()504 }505 }506 }507}508509pub use frame_support::{510 traits::{511 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,512 },513 weights::{WeightToFee, WeightToFeePolynomial},514};515516pub struct FreeForAll;517518impl WeightTrader for FreeForAll {519 fn new() -> Self {520 Self521 }522523 fn buy_weight(524 &mut self,525 weight: Weight,526 payment: Assets,527 _xcm: &XcmContext,528 ) -> Result<Assets, XcmError> {529 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);530 Ok(payment)531 }532}