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 if <ForeignReserveLocationToCollection<T>>::contains_key(reserve_location) {144 return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());145 }146147 let foreign_collection_owner = Self::pallet_account();148149 let description: CollectionDescription = "Foreign Assets Collection"150 .encode_utf16()151 .collect::<Vec<_>>()152 .try_into()153 .expect("description length < max description length; qed");154155 let collection_id = T::CollectionDispatch::create_foreign(156 foreign_collection_owner,157 CreateCollectionData {158 name,159 description,160 mode,161162 properties: vec![Property {163 key: Self::reserve_location_property_key(),164 value: reserve_location165 .encode()166 .try_into()167 .expect("multilocation is less than 32k; qed"),168 }]169 .try_into()170 .expect("just one property can always be stored; qed"),171172 token_property_permissions: vec![PropertyKeyPermission {173 key: Self::reserve_asset_instance_property_key(),174 permission: PropertyPermission {175 mutable: false,176 collection_admin: true,177 token_owner: false,178 },179 }]180 .try_into()181 .expect("just one property permission can always be stored; qed"),182 ..Default::default()183 },184 )?;185186 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);187 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);188189 Self::deposit_event(Event::<T>::ForeignAssetRegistered {190 asset_id: collection_id,191 reserve_location,192 });193194 Ok(())195 }196 }197}198199impl<T: Config> Pallet<T> {200 fn pallet_account() -> T::CrossAccountId {201 let owner: T::AccountId = T::PalletId::get().into_account_truncating();202 T::CrossAccountId::from_sub(owner)203 }204205 fn reserve_location_property_key() -> PropertyKey {206 b"reserve-location"207 .to_vec()208 .try_into()209 .expect("key length < max property key length; qed")210 }211212 fn reserve_asset_instance_property_key() -> PropertyKey {213 b"reserve-asset-instance"214 .to_vec()215 .try_into()216 .expect("key length < max property key length; qed")217 }218219 220 221 222 223 224 225 226 227 228 229 230 231 fn native_asset_location_to_collection(232 asset_location: &MultiLocation,233 ) -> Result<Option<CollectionId>, XcmError> {234 let self_location = T::SelfLocation::get();235236 if *asset_location == Here.into() {237 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))238 } else if *asset_location == self_location {239 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))240 } else if asset_location.parents == self_location.parents {241 match asset_location242 .interior243 .match_and_split(&self_location.interior)244 {245 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(246 (*collection_id)247 .try_into()248 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,249 ))),250 _ => Ok(None),251 }252 } else {253 Ok(None)254 }255 }256257 258 259 260 261 262 263 264 265 266 267 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {268 let AssetId::Concrete(asset_reserve_location) = asset.id else {269 return Err(XcmExecutorError::AssetNotHandled.into());270 };271272 Self::native_asset_location_to_collection(&asset_reserve_location)?273 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))274 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())275 }276277 278 279 280 281 282 283 284 fn native_asset_instance_to_token_id(285 asset_instance: &AssetInstance,286 ) -> Result<TokenId, XcmError> {287 match asset_instance {288 AssetInstance::Index(token_id) => Ok(TokenId(289 (*token_id)290 .try_into()291 .map_err(|_| XcmError::AssetNotFound)?,292 )),293 _ => Err(XcmError::AssetNotFound),294 }295 }296297 298 299 300 301 302 303 304 305 306 fn asset_instance_to_token_id(307 collection_id: CollectionId,308 asset_instance: &AssetInstance,309 ) -> Result<Option<TokenId>, XcmError> {310 if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {311 Ok(Self::foreign_reserve_asset_instance_to_token_id(312 collection_id,313 asset_instance,314 ))315 } else {316 Self::native_asset_instance_to_token_id(asset_instance).map(Some)317 }318 }319320 321 fn create_foreign_asset_instance(322 xcm_ext: &dyn XcmExtensions<T>,323 collection_id: CollectionId,324 asset_instance: &AssetInstance,325 to: T::CrossAccountId,326 ) -> DispatchResult {327 let asset_instance_encoded = asset_instance.encode();328329 let derivative_token_id = xcm_ext.create_item(330 &Self::pallet_account(),331 to,332 CreateItemData::NFT(CreateNftData {333 properties: vec![Property {334 key: Self::reserve_asset_instance_property_key(),335 value: asset_instance_encoded336 .try_into()337 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),338 }]339 .try_into()340 .expect("just one property can always be stored; qed"),341 }),342 &ZeroBudget,343 )?;344345 <ForeignReserveAssetInstanceToTokenId<T>>::insert(346 collection_id,347 asset_instance,348 derivative_token_id,349 );350351 Ok(())352 }353354 355 356 357 358 fn deposit_asset_instance(359 xcm_ext: &dyn XcmExtensions<T>,360 collection_id: CollectionId,361 asset_instance: &AssetInstance,362 to: T::CrossAccountId,363 ) -> XcmResult {364 let deposit_result = if let Some(token_id) =365 Self::asset_instance_to_token_id(collection_id, asset_instance)?366 {367 let depositor = &Self::pallet_account();368 let from = depositor;369 let amount = 1;370371 xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)372 } else {373 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)374 };375376 deposit_result377 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))378 }379380 381 382 383 384 385 fn withdraw_asset_instance(386 xcm_ext: &dyn XcmExtensions<T>,387 collection_id: CollectionId,388 asset_instance: &AssetInstance,389 from: T::CrossAccountId,390 ) -> XcmResult {391 let token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?392 .ok_or(XcmError::AssetNotFound)?;393394 if xcm_ext.token_has_children(token_id) {395 return Err(XcmError::Unimplemented);396 }397398 let depositor = &from;399 let to = Self::pallet_account();400 let amount = 1;401 xcm_ext402 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)403 .map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;404405 Ok(())406 }407}408409impl<T: Config> TransactAsset for Pallet<T> {410 fn can_check_in(411 _origin: &MultiLocation,412 _what: &MultiAsset,413 _context: &XcmContext,414 ) -> XcmResult {415 Err(XcmError::Unimplemented)416 }417418 fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}419420 fn can_check_out(421 _dest: &MultiLocation,422 _what: &MultiAsset,423 _context: &XcmContext,424 ) -> XcmResult {425 Err(XcmError::Unimplemented)426 }427428 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}429430 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {431 let to = T::LocationToAccountId::convert_location(to)432 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;433434 let collection_id = Self::multiasset_to_collection(what)?;435 let dispatch =436 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;437438 let collection = dispatch.as_dyn();439 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;440441 match what.fun {442 Fungibility::Fungible(amount) => xcm_ext443 .create_item(444 &Self::pallet_account(),445 to,446 CreateItemData::Fungible(CreateFungibleData { value: amount }),447 &ZeroBudget,448 )449 .map(|_| ())450 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),451452 Fungibility::NonFungible(asset_instance) => {453 Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)454 }455 }456 }457458 fn withdraw_asset(459 what: &MultiAsset,460 from: &MultiLocation,461 _maybe_context: Option<&XcmContext>,462 ) -> Result<staging_xcm_executor::Assets, XcmError> {463 let from = T::LocationToAccountId::convert_location(from)464 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;465466 let collection_id = Self::multiasset_to_collection(what)?;467 let dispatch =468 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;469470 let collection = dispatch.as_dyn();471 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;472473 match what.fun {474 Fungibility::Fungible(amount) => xcm_ext475 .burn_item(from, TokenId::default(), amount)476 .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,477478 Fungibility::NonFungible(asset_instance) => {479 Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;480 }481 }482483 Ok(what.clone().into())484 }485486 fn internal_transfer_asset(487 what: &MultiAsset,488 from: &MultiLocation,489 to: &MultiLocation,490 _context: &XcmContext,491 ) -> Result<staging_xcm_executor::Assets, XcmError> {492 let from = T::LocationToAccountId::convert_location(from)493 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;494495 let to = T::LocationToAccountId::convert_location(to)496 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;497498 let collection_id = Self::multiasset_to_collection(what)?;499500 let dispatch =501 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;502 let collection = dispatch.as_dyn();503 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;504505 let depositor = &from;506507 let token_id;508 let amount;509 let map_error: fn(DispatchError) -> XcmError;510511 match what.fun {512 Fungibility::Fungible(fungible_amount) => {513 token_id = TokenId::default();514 amount = fungible_amount;515 map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");516 }517518 Fungibility::NonFungible(asset_instance) => {519 token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?520 .ok_or(XcmError::AssetNotFound)?;521522 amount = 1;523 map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")524 }525 }526527 xcm_ext528 .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)529 .map_err(map_error)?;530531 Ok(what.clone().into())532 }533}534535pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);536impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>537 for CurrencyIdConvert<T>538{539 fn convert(collection_id: CollectionId) -> Option<MultiLocation> {540 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {541 Some(Here.into())542 } else {543 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {544 T::SelfLocation::get()545 .pushed_with_interior(GeneralIndex(collection_id.0.into()))546 .ok()547 })548 }549 }550}551552pub use frame_support::{553 traits::{554 fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,555 },556 weights::{WeightToFee, WeightToFeePolynomial},557};558559pub struct FreeForAll;560561impl WeightTrader for FreeForAll {562 fn new() -> Self {563 Self564 }565566 fn buy_weight(567 &mut self,568 weight: Weight,569 payment: Assets,570 _xcm: &XcmContext,571 ) -> Result<Assets, XcmError> {572 log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);573 Ok(payment)574 }575}