From 5bde44b9e4f50048c9beb3073ffb7e1b7b5a94b5 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Tue, 17 Oct 2023 19:30:03 +0000 Subject: [PATCH] feat: draft xcm deposit_asset --- --- a/pallets/balances-adapter/src/common.rs +++ b/pallets/balances-adapter/src/common.rs @@ -1,10 +1,16 @@ use alloc::{vec, vec::Vec}; use core::marker::PhantomData; -use frame_support::{ensure, fail, weights::Weight}; +use frame_support::{ + ensure, fail, + traits::tokens::{fungible::Mutate, Fortitude, Precision}, + weights::Weight, +}; use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo}; -use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError}; -use up_data_structs::TokenId; +use pallet_common::{ + erc::CrossAccountId, CommonCollectionOperations, CommonWeightInfo, Error as CommonError, +}; +use up_data_structs::{budget::Budget, TokenId}; use crate::{Config, NativeFungibleHandle, Pallet}; @@ -332,6 +338,10 @@ 0 } + fn xcm_extensions(&self) -> Option<&dyn pallet_common::XcmExtensions> { + Some(self) + } + fn set_allowance_for_all( &self, _owner: ::CrossAccountId, @@ -356,3 +366,76 @@ fail!(>::UnsupportedOperation); } } + +impl pallet_common::XcmExtensions for NativeFungibleHandle { + fn is_foreign(&self) -> bool { + false + } + + fn create_item_internal( + &self, + _depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: up_data_structs::CreateItemData, + _nesting_budget: &dyn Budget, + ) -> Result { + match &data { + up_data_structs::CreateItemData::Fungible(fungible_data) => { + T::Mutate::mint_into( + to.as_sub(), + fungible_data + .value + .try_into() + .map_err(|_| sp_runtime::ArithmeticError::Overflow)?, + )?; + + Ok(TokenId::default()) + } + _ => { + fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken) + } + } + } + + fn transfer_item_internal( + &self, + _depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + _nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!( + token == TokenId::default(), + >::FungibleItemsHaveNoId + ); + + >::transfer(from, to, amount) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } + + fn burn_item_internal( + &self, + from: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + ensure!( + token == TokenId::default(), + >::FungibleItemsHaveNoId + ); + + T::Mutate::burn_from( + from.as_sub(), + amount + .try_into() + .map_err(|_| sp_runtime::ArithmeticError::Overflow)?, + Precision::Exact, + Fortitude::Polite, + )?; + + Ok(()) + } +} --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -80,15 +80,16 @@ use sp_std::vec::Vec; use sp_weights::Weight; use up_data_structs::{ - budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode, - CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats, - CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError, - PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission, - PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit, - SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties, - TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, - FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, - MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + budget::Budget, mapping::TokenAddressMapping, AccessMode, Collection, CollectionId, + CollectionLimits, CollectionMode, CollectionPermissions, + CollectionProperties as CollectionPropertiesT, CollectionStats, CreateCollectionData, + CreateItemData, CreateItemExData, PhantomType, PropertiesError, PropertiesPermissionMap, + Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, + RpcCollection, RpcCollectionFlags, SponsoringRateLimit, SponsorshipState, TokenChild, + TokenData, TokenId, TokenOwnerError, TokenProperties, TrySetProperty, COLLECTION_ADMINS_LIMIT, + COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_TOKEN_PREFIX_LENGTH, + NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, }; use up_pov_estimate_rpc::PovInfo; @@ -786,6 +787,9 @@ /// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0. FungibleItemsHaveNoId, + + /// Not Fungible item data used to mint in Fungible collection. + NotFungibleDataUsedToMintFungibleCollectionToken, } /// Storage of the count of created collections. Essentially contains the last collection ID. @@ -2347,24 +2351,77 @@ /// Is the collection a foreign one? fn is_foreign(&self) -> bool; - /// Create a collection's item. + /// Create a collection's item using a transaction. + /// + /// This function performs additional XCM-related checks before the actual creation. + #[transactional] fn create_item( &self, + depositor: &T::CrossAccountId, + to: T::CrossAccountId, + data: CreateItemData, + nesting_budget: &dyn Budget, + ) -> Result { + if T::CrossTokenAddressMapping::is_token_address(&to) { + return unsupported!(T); + } + + self.create_item_internal(depositor, to, data, nesting_budget) + } + + /// Create a collection's item. + fn create_item_internal( + &self, + depositor: &T::CrossAccountId, to: T::CrossAccountId, data: CreateItemData, + nesting_budget: &dyn Budget, ) -> Result; + /// Transfer an item from the `from` account to the `to` account using a transaction. + /// + /// This function performs additional XCM-related checks before the actual transfer. + #[transactional] + fn transfer_item( + &self, + depositor: &T::CrossAccountId, + from: &T::CrossAccountId, + to: &T::CrossAccountId, + token: TokenId, + amount: u128, + nesting_budget: &dyn Budget, + ) -> DispatchResult { + if T::CrossTokenAddressMapping::is_token_address(&to) { + return unsupported!(T); + } + + self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget) + } + /// Transfer an item from the `from` account to the `to` account. - fn transfer( + fn transfer_item_internal( &self, - from: T::CrossAccountId, - to: T::CrossAccountId, + depositor: &T::CrossAccountId, + from: &T::CrossAccountId, + to: &T::CrossAccountId, token: TokenId, amount: u128, + nesting_budget: &dyn Budget, ) -> DispatchResult; + /// Burn a collection's item using a transaction. + #[transactional] + fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult { + self.burn_item_internal(from, token, amount) + } + /// Burn a collection's item. - fn burn(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult; + fn burn_item_internal( + &self, + from: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResult; } /// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`]. --- a/pallets/foreign-assets/src/lib.rs +++ b/pallets/foreign-assets/src/lib.rs @@ -22,13 +22,6 @@ //! //! ## Overview //! -//! The foreign assests pallet provides functions for: -//! -//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade. -//! - Bounds between asset and target collection for cross chain transfer and inner transfers. -//! -//! ## Overview -//! //! Under construction #![cfg_attr(not(feature = "std"), no_std)] @@ -37,22 +30,21 @@ use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId}; use frame_system::pallet_prelude::*; use pallet_common::{ - dispatch::CollectionDispatch, erc::CrossAccountId, NATIVE_FUNGIBLE_COLLECTION_ID, + dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID, }; use sp_runtime::traits::AccountIdConversion; use sp_std::{vec, vec::Vec}; -// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the -// MultiLocation to the XCM v3. use staging_xcm::{ opaque::latest::{prelude::XcmError, Weight}, v3::{prelude::*, MultiAsset, XcmContext}, }; use staging_xcm_executor::{ - traits::{TransactAsset, WeightTrader}, + traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader}, Assets, }; use up_data_structs::{ - CollectionId, CollectionMode, CollectionName, CreateCollectionData, PropertyKey, TokenId, + budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData, + CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId, }; pub mod weights; @@ -87,6 +79,12 @@ /// The ID of the foreign assets pallet. type PalletId: Get; + /// Self-location of this parachain. + type SelfLocation: Get; + + /// The converter from a MultiLocation to a CrossAccountId. + type LocationToAccountId: ConvertLocation; + /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -113,6 +111,12 @@ pub type ForeignReserveLocationToCollection = StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>; + /// The corresponding reserve location of collections. + #[pallet::storage] + #[pallet::getter(fn collection_to_foreign_reserve_location)] + pub type CollectionToForeignReserveLocation = + StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>; + /// The correponding NFT token id of reserve NFTs #[pallet::storage] #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)] @@ -180,6 +184,7 @@ )?; >::insert(reserve_location, collection_id); + >::insert(collection_id, reserve_location); Self::deposit_event(Event::::ForeignAssetRegistered { asset_id: collection_id, @@ -210,6 +215,132 @@ .try_into() .expect("key length < max property key length; qed") } + + fn native_asset_location_to_collection( + asset_location: &MultiLocation, + ) -> Result, XcmError> { + let self_location = T::SelfLocation::get(); + + if *asset_location == Here.into() { + Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID)) + } else if *asset_location == self_location { + Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID)) + } else if asset_location.parents == self_location.parents { + match asset_location + .interior + .match_and_split(&self_location.interior) + { + Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId( + (*collection_id) + .try_into() + .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?, + ))), + _ => Ok(None), + } + } else { + Ok(None) + } + } + + fn multiasset_to_collection(asset: &MultiAsset) -> Result { + let AssetId::Concrete(asset_reserve_location) = asset.id else { + return Err(XcmExecutorError::AssetNotHandled.into()); + }; + + Self::native_asset_location_to_collection(&asset_reserve_location)? + .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location)) + .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into()) + } + + fn native_asset_instance_to_token_id( + asset_instance: &AssetInstance, + ) -> Result { + match asset_instance { + AssetInstance::Index(token_id) => Ok(TokenId( + (*token_id) + .try_into() + .map_err(|_| XcmError::AssetNotFound)?, + )), + _ => Err(XcmError::AssetNotFound), + } + } + + /// Obtains the token id of the `asset_instance` in the collection. + /// + /// Returns `Ok(None)` only if the `asset_instance` points to a foreign item + /// and it haven't been created on this blockchain yet. + /// + /// If the `asset_instance` points to a native item, it cannot return `Ok(None)`. + fn asset_instance_to_token_id( + xcm_ext: &dyn XcmExtensions, + collection_id: CollectionId, + asset_instance: &AssetInstance, + ) -> Result, XcmError> { + if xcm_ext.is_foreign() { + Ok(Self::foreign_reserve_asset_instance_to_token_id( + collection_id, + asset_instance, + )) + } else { + Self::native_asset_instance_to_token_id(asset_instance).map(Some) + } + } + + fn create_foreign_asset_instance( + xcm_ext: &dyn XcmExtensions, + collection_id: CollectionId, + asset_instance: &AssetInstance, + to: T::CrossAccountId, + ) -> XcmResult { + let asset_instance_encoded = asset_instance.encode(); + + let derivative_token_id = xcm_ext + .create_item( + &Self::pallet_account(), + to, + CreateItemData::NFT(CreateNftData { + properties: vec![Property { + key: Self::reserve_asset_instance_property_key(), + value: asset_instance_encoded + .try_into() + .expect("asset instance length <= 32 bytes which is less than value length limit; qed"), + }] + .try_into() + .expect("just one property can always be stored; qed"), + }), + &ZeroBudget, + ) + .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))?; + + >::insert( + collection_id, + asset_instance, + derivative_token_id, + ); + + Ok(()) + } + + fn deposit_asset_instance( + xcm_ext: &dyn XcmExtensions, + collection_id: CollectionId, + to: T::CrossAccountId, + asset_instance: &AssetInstance, + ) -> XcmResult { + if let Some(token_id) = + Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)? + { + let depositor = &Self::pallet_account(); + let from = depositor; + let amount = 1; + + xcm_ext + .transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget) + .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed")) + } else { + Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to) + } + } } impl TransactAsset for Pallet { @@ -234,7 +365,31 @@ fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {} fn deposit_asset(what: &MultiAsset, to: &MultiLocation, context: &XcmContext) -> XcmResult { - Err(XcmError::Unimplemented) + let collection_id = Self::multiasset_to_collection(what)?; + let dispatch = + T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?; + + let collection = dispatch.as_dyn(); + let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?; + + let to = T::LocationToAccountId::convert_location(to) + .ok_or(XcmExecutorError::AccountIdConversionFailed)?; + + match what.fun { + Fungibility::Fungible(amount) => xcm_ext + .create_item( + &Self::pallet_account(), + to, + CreateItemData::Fungible(CreateFungibleData { value: amount }), + &ZeroBudget, + ) + .map(|_| ()) + .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")), + + Fungibility::NonFungible(asset_instance) => { + Self::deposit_asset_instance(xcm_ext, collection_id, to, &asset_instance) + } + } } fn withdraw_asset( @@ -263,20 +418,17 @@ if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID { Some(Here.into()) } else { - // let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?; - // let collection = dispatch.as_dyn(); - // let xcm_ext = collection.xcm_extensions()?; + let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?; + let collection = dispatch.as_dyn(); + let xcm_ext = collection.xcm_extensions()?; - // if xcm_ext.is_foreign() { - // let encoded_location = - // collection.property(&>::reserve_location_property_key())?; - // MultiLocation::decode(&mut &encoded_location[..]).ok() - // } else { - // T::SelfLocation::get() - // .pushed_with_interior(GeneralIndex(collection_id.0.into())) - // .ok() - // } - todo!() + if xcm_ext.is_foreign() { + >::collection_to_foreign_reserve_location(collection_id) + } else { + T::SelfLocation::get() + .pushed_with_interior(GeneralIndex(collection_id.0.into())) + .ok() + } } } } --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -19,7 +19,7 @@ use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight}; use pallet_common::{ weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo, - Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf, + Error as CommonError, SelfWeightOf as PalletCommonWeightOf, XcmExtensions, }; use sp_runtime::{ArithmeticError, DispatchError}; use sp_std::{vec, vec::Vec}; @@ -114,7 +114,7 @@ >::create_item(self, &sender, (to, fungible_data.value), nesting_budget), >::create_item(&data), ), - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), } } @@ -133,7 +133,7 @@ .checked_add(data.value) .ok_or(ArithmeticError::Overflow)?; } - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), } } @@ -152,7 +152,7 @@ let weight = >::create_multiple_items_ex(&data); let data = match data { up_data_structs::CreateItemExData::Fungible(f) => f, - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), }; with_weight( @@ -435,6 +435,10 @@ >::try_get(self.id).ok() } + fn xcm_extensions(&self) -> Option<&dyn XcmExtensions> { + Some(self) + } + fn set_allowance_for_all( &self, _owner: T::CrossAccountId, @@ -453,3 +457,61 @@ fail!(>::FungibleTokensAreAlwaysValid) } } + +impl XcmExtensions for FungibleHandle { + fn is_foreign(&self) -> bool { + self.flags.foreign + } + + fn create_item_internal( + &self, + depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: CreateItemData, + nesting_budget: &dyn Budget, + ) -> Result { + match &data { + up_data_structs::CreateItemData::Fungible(fungible_data) => { + >::create_multiple_items( + self, + &depositor, + [(to, fungible_data.value)].into_iter().collect(), + nesting_budget, + )? + } + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + } + + Ok(TokenId::default()) + } + + fn transfer_item_internal( + &self, + depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!( + token == TokenId::default(), + >::FungibleItemsHaveNoId + ); + + >::transfer_internal(self, &depositor, &from, &to, amount, nesting_budget) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } + + fn burn_item_internal( + &self, + from: ::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + >::burn_item(&self, from, token, amount) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } +} --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -95,8 +95,9 @@ use sp_runtime::{ArithmeticError, DispatchError, DispatchResult}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; use up_data_structs::{ - budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData, - Property, PropertyKey, TokenId, + budget::{Budget, ZeroBudget}, + mapping::TokenAddressMapping, + AccessMode, CollectionId, CreateCollectionData, Property, PropertyKey, TokenId, }; use weights::WeightInfo; @@ -121,8 +122,6 @@ #[pallet::error] pub enum Error { - /// Not Fungible item data used to mint in Fungible collection. - NotFungibleDataUsedToMintFungibleCollectionToken, /// Tried to set data for fungible item. FungibleItemsDontHaveData, /// Fungible token does not support nesting. @@ -275,9 +274,6 @@ let balance = >::get((collection.id, owner)) .checked_sub(amount) .ok_or(>::TokenValueTooLow)?; - - // Foreign collection check - ensure!(!collection.flags.foreign, >::NoPermission); if collection.permissions.access() == AccessMode::AllowList { collection.check_allowlist(owner)?; @@ -309,47 +305,7 @@ )); Ok(()) } - - /// Burns the specified amount of the token. - pub fn burn_foreign( - collection: &FungibleHandle, - owner: &T::CrossAccountId, - amount: u128, - ) -> DispatchResult { - let total_supply = >::get(collection.id) - .checked_sub(amount) - .ok_or(>::TokenValueTooLow)?; - - let balance = >::get((collection.id, owner)) - .checked_sub(amount) - .ok_or(>::TokenValueTooLow)?; - // ========= - if balance == 0 { - >::remove((collection.id, owner)); - >::unnest_if_nested(owner, collection.id, TokenId::default()); - } else { - >::insert((collection.id, owner), balance); - } - >::insert(collection.id, total_supply); - - >::deposit_log( - ERC20Events::Transfer { - from: *owner.as_eth(), - to: H160::default(), - value: amount.into(), - } - .to_log(collection_id_to_address(collection.id)), - ); - >::deposit_event(CommonEvent::ItemDestroyed( - collection.id, - TokenId::default(), - owner.clone(), - amount, - )); - Ok(()) - } - /// Transfers the specified amount of tokens. Will check that /// the transfer is allowed for the token. /// @@ -450,14 +406,25 @@ } /// Minting tokens for multiple IDs. - /// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`] - /// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`] - pub fn create_multiple_items_common( + /// See [`create_item`][`Pallet::create_item`] for more details. + pub fn create_multiple_items( collection: &FungibleHandle, - sender: &T::CrossAccountId, + depositor: &T::CrossAccountId, data: BTreeMap, nesting_budget: &dyn Budget, ) -> DispatchResult { + if !collection.is_owner_or_admin(depositor) { + ensure!( + collection.permissions.mint_mode(), + >::PublicMintingNotAllowed + ); + collection.check_allowlist(depositor)?; + + for (owner, _) in data.iter() { + collection.check_allowlist(owner)?; + } + } + let total_supply = data .values() .copied() @@ -468,7 +435,7 @@ for (to, _) in data.iter() { >::check_nesting( - sender, + depositor, to, collection.id, TokenId::default(), @@ -514,44 +481,7 @@ Ok(()) } - - /// Minting tokens for multiple IDs. - /// See [`create_item`][`Pallet::create_item`] for more details. - pub fn create_multiple_items( - collection: &FungibleHandle, - sender: &T::CrossAccountId, - data: BTreeMap, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - // Foreign collection check - ensure!(!collection.flags.foreign, >::NoPermission); - - if !collection.is_owner_or_admin(sender) { - ensure!( - collection.permissions.mint_mode(), - >::PublicMintingNotAllowed - ); - collection.check_allowlist(sender)?; - - for (owner, _) in data.iter() { - collection.check_allowlist(owner)?; - } - } - Self::create_multiple_items_common(collection, sender, data, nesting_budget) - } - - /// Minting tokens for multiple IDs. - /// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details. - pub fn create_multiple_items_foreign( - collection: &FungibleHandle, - sender: &T::CrossAccountId, - data: BTreeMap, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - Self::create_multiple_items_common(collection, sender, data, nesting_budget) - } - fn set_allowance_unchecked( collection: &FungibleHandle, owner: &T::CrossAccountId, @@ -777,24 +707,6 @@ nesting_budget: &dyn Budget, ) -> DispatchResult { Self::create_multiple_items( - collection, - sender, - [(data.0, data.1)].into_iter().collect(), - nesting_budget, - ) - } - - /// Creates fungible token. - /// - /// - `data`: Contains user who will become the owners of the tokens and amount - /// of tokens he will receive. - pub fn create_item_foreign( - collection: &FungibleHandle, - sender: &T::CrossAccountId, - data: CreateItemData, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - Self::create_multiple_items_foreign( collection, sender, [(data.0, data.1)].into_iter().collect(), --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -19,8 +19,8 @@ use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight}; use pallet_common::{ weights::WeightInfo as _, with_weight, write_token_properties_total_weight, - CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, - SelfWeightOf as PalletCommonWeightOf, + CommonCollectionOperations, CommonWeightInfo, SelfWeightOf as PalletCommonWeightOf, + XcmExtensions, }; use pallet_structure::Pallet as PalletStructure; use sp_runtime::DispatchError; @@ -543,6 +543,10 @@ } } + fn xcm_extensions(&self) -> Option<&dyn XcmExtensions> { + Some(self) + } + fn set_allowance_for_all( &self, owner: T::CrossAccountId, @@ -566,3 +570,53 @@ ) } } + +impl XcmExtensions for NonfungibleHandle { + fn is_foreign(&self) -> bool { + self.flags.foreign + } + + fn create_item_internal( + &self, + depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: up_data_structs::CreateItemData, + nesting_budget: &dyn Budget, + ) -> Result { + >::create_multiple_items( + self, + &depositor, + vec![map_create_data::(data, &to)?], + nesting_budget, + )?; + + Ok(self.last_token_id()) + } + + fn transfer_item_internal( + &self, + depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!(amount == 1, >::NonfungibleItemsHaveNoAmount); + + >::transfer_internal(self, &depositor, &from, &to, token, nesting_budget) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } + + fn burn_item_internal( + &self, + from: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + ensure!(amount == 1, >::NonfungibleItemsHaveNoAmount); + + >::burn(self, &from, token) + } +} --- a/primitives/data-structs/src/budget.rs +++ b/primitives/data-structs/src/budget.rs @@ -36,3 +36,10 @@ true } } + +pub struct ZeroBudget; +impl Budget for ZeroBudget { + fn consume_custom(&self, _calls: u32) -> bool { + false + } +} --- a/runtime/common/config/pallets/foreign_asset.rs +++ b/runtime/common/config/pallets/foreign_asset.rs @@ -1,14 +1,43 @@ use frame_support::{parameter_types, PalletId}; +use pallet_evm::account::CrossAccountId; +use sp_core::H160; +use staging_xcm::prelude::*; +use staging_xcm_builder::AccountKey20Aliases; -use crate::{runtime_common::config::governance, Runtime, RuntimeEvent}; +use crate::{ + runtime_common::config::{ + ethereum::CrossAccountId as ConfigCrossAccountId, + governance, + xcm::{LocationToAccountId, SelfLocation}, + }, + RelayNetwork, Runtime, RuntimeEvent, +}; parameter_types! { pub ForeignAssetPalletId: PalletId = PalletId(*b"frgnasts"); } +pub struct LocationToCrossAccountId; +impl staging_xcm_executor::traits::ConvertLocation + for LocationToCrossAccountId +{ + fn convert_location(location: &MultiLocation) -> Option { + LocationToAccountId::convert_location(location) + .map(|sub| ConfigCrossAccountId::from_sub(sub)) + .or_else(|| { + let eth_address = + AccountKey20Aliases::::convert_location(location)?; + + Some(ConfigCrossAccountId::from_eth(eth_address)) + }) + } +} + impl pallet_foreign_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember; type PalletId = ForeignAssetPalletId; + type SelfLocation = SelfLocation; + type LocationToAccountId = LocationToCrossAccountId; type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight; } -- gitstuff