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::{boxed::Box, 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, CollectionTokenPrefix,43 CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,44 TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455#[frame_support::pallet]56pub mod module {57 use up_data_structs::{58 CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,59 };6061 use super::*;6263 #[pallet::config]64 pub trait Config:65 frame_system::Config66 + pallet_common::Config67 + pallet_fungible::Config68 + pallet_balances::Config69 {70 71 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7273 74 type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7576 77 type PalletId: Get<PalletId>;7879 80 type SelfLocation: Get<MultiLocation>;8182 83 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8485 86 type WeightInfo: WeightInfo;87 }8889 #[pallet::error]90 pub enum Error<T> {91 92 ForeignAssetAlreadyRegistered,93 }9495 #[pallet::event]96 #[pallet::generate_deposit(fn deposit_event)]97 pub enum Event<T: Config> {98 99 ForeignAssetRegistered {100 asset_id: CollectionId,101 reserve_location: Box<MultiLocation>,102 },103 }104105 106 #[pallet::storage]107 #[pallet::getter(fn foreign_reserve_location_to_collection)]108 pub type ForeignReserveLocationToCollection<T: Config> =109 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;110111 112 #[pallet::storage]113 #[pallet::getter(fn collection_to_foreign_reserve_location)]114 pub type CollectionToForeignReserveLocation<T: Config> =115 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;116117 118 #[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 = Twox64Concat,124 Key2 = staging_xcm::v3::AssetInstance,125 Value = TokenId,126 QueryKind = OptionQuery,127 >;128129 #[pallet::pallet]130 pub struct Pallet<T>(_);131132 #[pallet::call]133 impl<T: Config> Pallet<T> {134 #[pallet::call_index(0)]135 #[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]136 pub fn force_register_foreign_asset(137 origin: OriginFor<T>,138 reserve_location: Box<MultiLocation>,139 name: CollectionName,140 token_prefix: CollectionTokenPrefix,141 mode: ForeignCollectionMode,142 ) -> DispatchResult {143 T::ForceRegisterOrigin::ensure_origin(origin.clone())?;144145 if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {146 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());147 }148149 let foreign_collection_owner = Self::pallet_account();150151 let description: CollectionDescription = "Foreign Assets Collection"152 .encode_utf16()153 .collect::<Vec<_>>()154 .try_into()155 .expect("description length < max description length; qed");156157 let payer = None;158 let is_special_collection = true;159 let collection_id = T::CollectionDispatch::create_internal(160 foreign_collection_owner,161 payer,162 is_special_collection,163 CreateCollectionData {164 name,165 token_prefix,166 description,167 mode: mode.into(),168169 properties: vec![Property {170 key: Self::reserve_location_property_key(),171 value: reserve_location172 .encode()173 .try_into()174 .expect("multilocation is less than 32k; qed"),175 }]176 .try_into()177 .expect("just one property can always be stored; qed"),178179 token_property_permissions: vec![PropertyKeyPermission {180 key: Self::reserve_asset_instance_property_key(),181 permission: PropertyPermission {182 mutable: false,183 collection_admin: true,184 token_owner: false,185 },186 }]187 .try_into()188 .expect("just one property permission can always be stored; qed"),189 ..Default::default()190 },191 )?;192193 <ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);194 <CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);195196 Self::deposit_event(Event::<T>::ForeignAssetRegistered {197 asset_id: collection_id,198 reserve_location,199 });200201 Ok(())202 }203 }204}205206impl<T: Config> Pallet<T> {207 fn pallet_account() -> T::CrossAccountId {208 let owner: T::AccountId = T::PalletId::get().into_account_truncating();209 T::CrossAccountId::from_sub(owner)210 }211212 fn reserve_location_property_key() -> PropertyKey {213 b"reserve-location"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 fn reserve_asset_instance_property_key() -> PropertyKey {220 b"reserve-asset-instance"221 .to_vec()222 .try_into()223 .expect("key length < max property key length; qed")224 }225226 227 228 229 230 231 232 233 234 235 236 237 238 fn native_asset_location_to_collection(239 asset_location: &MultiLocation,240 ) -> Result<Option<CollectionId>, XcmError> {241 let self_location = T::SelfLocation::get();242243 if *asset_location == Here.into() || *asset_location == self_location {244 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))245 } else if asset_location.parents == self_location.parents {246 match asset_location247 .interior248 .match_and_split(&self_location.interior)249 {250 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(251 (*collection_id)252 .try_into()253 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,254 ))),255 _ => Ok(None),256 }257 } else {258 Ok(None)259 }260 }261262 263 264 265 266 267 268 269 270 271 272 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {273 let AssetId::Concrete(asset_reserve_location) = asset.id else {274 return Err(XcmExecutorError::AssetNotHandled.into());275 };276277 Self::native_asset_location_to_collection(&asset_reserve_location)?278 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))279 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())280 }281282 283 284 285 286 287 288 289 fn native_asset_instance_to_token_id(290 asset_instance: &AssetInstance,291 ) -> Result<TokenId, XcmError> {292 match asset_instance {293 AssetInstance::Index(token_id) => Ok(TokenId(294 (*token_id)295 .try_into()296 .map_err(|_| XcmError::AssetNotFound)?,297 )),298 _ => Err(XcmError::AssetNotFound),299 }300 }301302 303 304 305 306 307 308 309 310 311 fn asset_instance_to_token_id(312 collection_id: CollectionId,313 asset_instance: &AssetInstance,314 ) -> Result<Option<TokenId>, XcmError> {315 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {316 Ok(Self::foreign_reserve_asset_instance_to_token_id(317 collection_id,318 asset_instance,319 ))320 } else {321 Self::native_asset_instance_to_token_id(asset_instance).map(Some)322 }323 }324325 326 fn create_foreign_asset_instance(327 xcm_ext: &dyn XcmExtensions<T>,328 collection_id: CollectionId,329 asset_instance: &AssetInstance,330 to: T::CrossAccountId,331 ) -> DispatchResult {332 let asset_instance_encoded = asset_instance.encode();333334 let derivative_token_id = xcm_ext.create_item(335 &Self::pallet_account(),336 to,337 CreateItemData::NFT(CreateNftData {338 properties: vec![Property {339 key: Self::reserve_asset_instance_property_key(),340 value: asset_instance_encoded341 .try_into()342 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),343 }]344 .try_into()345 .expect("just one property can always be stored; qed"),346 }),347 &ZeroBudget,348 )?;349350 <ForeignReserveAssetInstanceToTokenId<T>>::insert(351 collection_id,352 asset_instance,353 derivative_token_id,354 );355356 Ok(())357 }358359 360 361 362 363 fn deposit_asset_instance(364 xcm_ext: &dyn XcmExtensions<T>,365 collection_id: CollectionId,366 asset_instance: &AssetInstance,367 to: T::CrossAccountId,368 ) -> XcmResult {369 let deposit_result = if let Some(token_id) =370 Self::asset_instance_to_token_id(collection_id, asset_instance)?371 {372 let depositor = &Self::pallet_account();373 let from = depositor;374 let amount = 1;375376 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)377 } else {378 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)379 };380381 deposit_result382 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))383 }384385 386 387 388 389 390 fn withdraw_asset_instance(391 xcm_ext: &dyn XcmExtensions<T>,392 collection_id: CollectionId,393 asset_instance: &AssetInstance,394 from: T::CrossAccountId,395 ) -> XcmResult {396 let token_id = Self::asset_instance_to_token_id(collection_id, asset_instance)?397 .ok_or(XcmError::AssetNotFound)?;398399 if xcm_ext.token_has_children(token_id) {400 return Err(XcmError::Unimplemented);401 }402403 let depositor = &from;404 let to = Self::pallet_account();405 let amount = 1;406 xcm_ext407 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)408 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;409410 Ok(())411 }412}413414impl<T: Config> TransactAsset for Pallet<T> {415 fn can_check_in(416 _origin: &MultiLocation,417 _what: &MultiAsset,418 _context: &XcmContext,419 ) -> XcmResult {420 Err(XcmError::Unimplemented)421 }422423 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}424425 fn can_check_out(426 _dest: &MultiLocation,427 _what: &MultiAsset,428 _context: &XcmContext,429 ) -> XcmResult {430 Err(XcmError::Unimplemented)431 }432433 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}434435 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {436 let to = T::LocationToAccountId::convert_location(to)437 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;438439 let collection_id = Self::multiasset_to_collection(what)?;440 let dispatch =441 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;442443 let collection = dispatch.as_dyn();444 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;445446 match what.fun {447 Fungibility::Fungible(amount) => xcm_ext448 .create_item(449 &Self::pallet_account(),450 to,451 CreateItemData::Fungible(CreateFungibleData { value: amount }),452 &ZeroBudget,453 )454 .map(|_| ())455 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),456457 Fungibility::NonFungible(asset_instance) => {458 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)459 }460 }461 }462463 fn withdraw_asset(464 what: &MultiAsset,465 from: &MultiLocation,466 _maybe_context: Option<&XcmContext>,467 ) -> Result<staging_xcm_executor::Assets, XcmError> {468 let from = T::LocationToAccountId::convert_location(from)469 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;470471 let collection_id = Self::multiasset_to_collection(what)?;472 let dispatch =473 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;474475 let collection = dispatch.as_dyn();476 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;477478 match what.fun {479 Fungibility::Fungible(amount) => xcm_ext480 .burn_item(from, TokenId::default(), amount)481 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,482483 Fungibility::NonFungible(asset_instance) => {484 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;485 }486 }487488 Ok(what.clone().into())489 }490491 fn internal_transfer_asset(492 what: &MultiAsset,493 from: &MultiLocation,494 to: &MultiLocation,495 _context: &XcmContext,496 ) -> Result<staging_xcm_executor::Assets, XcmError> {497 let from = T::LocationToAccountId::convert_location(from)498 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;499500 let to = T::LocationToAccountId::convert_location(to)501 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;502503 let collection_id = Self::multiasset_to_collection(what)?;504505 let dispatch =506 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;507 let collection = dispatch.as_dyn();508 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;509510 let depositor = &from;511512 let token_id;513 let amount;514 let map_error: fn(DispatchError) -> XcmError;515516 match what.fun {517 Fungibility::Fungible(fungible_amount) => {518 token_id = TokenId::default();519 amount = fungible_amount;520 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");521 }522523 Fungibility::NonFungible(asset_instance) => {524 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?525 .ok_or(XcmError::AssetNotFound)?;526527 amount = 1;528 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")529 }530 }531532 xcm_ext533 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)534 .map_err(map_error)?;535536 Ok(what.clone().into())537 }538}539540pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);541impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>542 for CurrencyIdConvert<T>543{544 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {545 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {546 Some(Here.into())547 } else {548 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {549 T::SelfLocation::get()550 .pushed_with_interior(GeneralIndex(collection_id.0.into()))551 .ok()552 })553 }554 }555}556557pub use frame_support::{558 traits::{559 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,560 },561 weights::{WeightToFee, WeightToFeePolynomial},562};563564#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]565pub enum ForeignCollectionMode {566 NFT,567 Fungible(u8),568}569570impl From<ForeignCollectionMode> for CollectionMode {571 fn from(value: ForeignCollectionMode) -> Self {572 match value {573 ForeignCollectionMode::NFT => Self::NFT,574 ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),575 }576 }577}578579pub struct FreeForAll;580581impl WeightTrader for FreeForAll {582 fn new() -> Self {583 Self584 }585586 fn buy_weight(587 &mut self,588 weight: Weight,589 payment: Assets,590 _xcm: &XcmContext,591 ) -> Result<Assets, XcmError> {592 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);593 Ok(payment)594 }595}