difftreelog
feat draft xcm deposit_asset
in: master
8 files changed
pallets/balances-adapter/src/common.rsdiffbeforeafterboth--- 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<T>> {
+ Some(self)
+ }
+
fn set_allowance_for_all(
&self,
_owner: <T>::CrossAccountId,
@@ -356,3 +366,76 @@
fail!(<CommonError<T>>::UnsupportedOperation);
}
}
+
+impl<T: Config> pallet_common::XcmExtensions<T> for NativeFungibleHandle<T> {
+ fn is_foreign(&self) -> bool {
+ false
+ }
+
+ fn create_item_internal(
+ &self,
+ _depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: up_data_structs::CreateItemData,
+ _nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ 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!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken)
+ }
+ }
+ }
+
+ fn transfer_item_internal(
+ &self,
+ _depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ _nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::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(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ T::Mutate::burn_from(
+ from.as_sub(),
+ amount
+ .try_into()
+ .map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+ Precision::Exact,
+ Fortitude::Polite,
+ )?;
+
+ Ok(())
+ }
+}
pallets/common/src/lib.rsdiffbeforeafterboth--- 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<TokenId, DispatchError> {
+ 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<TokenId, DispatchError>;
+ /// 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`].
pallets/foreign-assets/src/lib.rsdiffbeforeafterboth--- 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<PalletId>;
+ /// Self-location of this parachain.
+ type SelfLocation: Get<MultiLocation>;
+
+ /// The converter from a MultiLocation to a CrossAccountId.
+ type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;
+
/// Weight information for the extrinsics in this module.
type WeightInfo: WeightInfo;
}
@@ -113,6 +111,12 @@
pub type ForeignReserveLocationToCollection<T: Config> =
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<T: Config> =
+ 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 @@
)?;
<ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);
+ <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);
Self::deposit_event(Event::<T>::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<Option<CollectionId>, 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<CollectionId, XcmError> {
+ 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<TokenId, XcmError> {
+ 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<T>,
+ collection_id: CollectionId,
+ asset_instance: &AssetInstance,
+ ) -> Result<Option<TokenId>, 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<T>,
+ 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"))?;
+
+ <ForeignReserveAssetInstanceToTokenId<T>>::insert(
+ collection_id,
+ asset_instance,
+ derivative_token_id,
+ );
+
+ Ok(())
+ }
+
+ fn deposit_asset_instance(
+ xcm_ext: &dyn XcmExtensions<T>,
+ 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<T: Config> TransactAsset for Pallet<T> {
@@ -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(&<Pallet<T>>::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() {
+ <Pallet<T>>::collection_to_foreign_reserve_location(collection_id)
+ } else {
+ T::SelfLocation::get()
+ .pushed_with_interior(GeneralIndex(collection_id.0.into()))
+ .ok()
+ }
}
}
}
pallets/fungible/src/common.rsdiffbeforeafterboth--- 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 @@
<Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),
<CommonWeights<T>>::create_item(&data),
),
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
}
}
@@ -133,7 +133,7 @@
.checked_add(data.value)
.ok_or(ArithmeticError::Overflow)?;
}
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
}
}
@@ -152,7 +152,7 @@
let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
let data = match data {
up_data_structs::CreateItemExData::Fungible(f) => f,
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
};
with_weight(
@@ -435,6 +435,10 @@
<TotalSupply<T>>::try_get(self.id).ok()
}
+ fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+ Some(self)
+ }
+
fn set_allowance_for_all(
&self,
_owner: T::CrossAccountId,
@@ -453,3 +457,61 @@
fail!(<Error<T>>::FungibleTokensAreAlwaysValid)
}
}
+
+impl<T: Config> XcmExtensions<T> for FungibleHandle<T> {
+ fn is_foreign(&self) -> bool {
+ self.flags.foreign
+ }
+
+ fn create_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ match &data {
+ up_data_structs::CreateItemData::Fungible(fungible_data) => {
+ <Pallet<T>>::create_multiple_items(
+ self,
+ &depositor,
+ [(to, fungible_data.value)].into_iter().collect(),
+ nesting_budget,
+ )?
+ }
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ }
+
+ Ok(TokenId::default())
+ }
+
+ fn transfer_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::transfer_internal(self, &depositor, &from, &to, amount, 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 {
+ <Self as CommonCollectionOperations<T>>::burn_item(&self, from, token, amount)
+ .map(|_| ())
+ .map_err(|post_info| post_info.error)
+ }
+}
pallets/fungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Fungible Pallet18//!19//! The Fungible pallet provides functionality for dealing with fungible assets.20//!21//! - [`CreateItemData`]22//! - [`Config`]23//! - [`FungibleHandle`]24//! - [`Pallet`]25//! - [`TotalSupply`]26//! - [`Balance`]27//! - [`Allowance`]28//! - [`Error`]29//!30//! ## Fungible tokens31//!32//! Fungible tokens or assets are divisible and non-unique. For instance,33//! fiat currencies like the dollar are fungible: A $1 bill34//! in New York City has the same value as a $1 bill in Miami.35//! A fungible token can also be a cryptocurrency like Bitcoin: 1 BTC is worth 1 BTC,36//! no matter where it is issued. Thus, the fungibility refers to a specific currency’s37//! ability to maintain one standard value. As well, it needs to have uniform acceptance.38//! This means that a currency’s history should not be able to affect its value,39//! and this is due to the fact that each piece that is a part of the currency is equal40//! in value when compared to every other piece of that exact same currency.41//! In the world of cryptocurrencies, this is essentially a coin or a token42//! that can be replaced by another identical coin or token, and they are43//! both mutually interchangeable. A popular implementation of fungible tokens is44//! the ERC-20 token standard.45//!46//! ### ERC-2047//!48//! The [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) (Ethereum Request for Comments 20), proposed by Fabian Vogelsteller in November 2015,49//! is a Token Standard that implements an API for tokens within Smart Contracts.50//!51//! Example functionalities ERC-20 provides:52//!53//! * transfer tokens from one account to another54//! * get the current token balance of an account55//! * get the total supply of the token available on the network56//! * approve whether an amount of token from an account can be spent by a third-party account57//!58//! ## Overview59//!60//! The module provides functionality for asset management of fungible asset, supports ERC-20 standart, includes:61//!62//! * Asset Issuance63//! * Asset Transferal64//! * Asset Destruction65//! * Delegated Asset Transfers66//!67//! **NOTE:** The created fungible asset always has `token_id` = 0.68//! So `tokenA` and `tokenB` will have different `collection_id`.69//!70//! ### Implementations71//!72//! The Fungible pallet provides implementations for the following traits.73//!74//! - [`WithRecorder`](pallet_evm_coder_substrate::WithRecorder): Trait for EVM support75//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing with collections76//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight77//! - [`CommonEvmHandler`](pallet_common::erc::CommonEvmHandler): Function for handling EVM runtime calls7879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;8283use evm_coder::ToLog;84use frame_support::{dispatch::PostDispatchInfo, ensure, pallet_prelude::*};85pub use pallet::*;86use pallet_common::{87 eth::collection_id_to_address, helpers::add_weight_to_post_info,88 weights::WeightInfo as CommonWeightInfo, Error as CommonError, Event as CommonEvent,89 Pallet as PalletCommon, SelfWeightOf as PalletCommonWeightOf,90};91use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};92use pallet_evm_coder_substrate::WithRecorder;93use pallet_structure::Pallet as PalletStructure;94use sp_core::H160;95use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};96use sp_std::{collections::btree_map::BTreeMap, vec::Vec};97use up_data_structs::{98 budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,99 Property, PropertyKey, TokenId,100};101use weights::WeightInfo;102103use crate::erc::ERC20Events;104#[cfg(feature = "runtime-benchmarks")]105pub mod benchmarking;106pub mod common;107pub mod erc;108pub mod weights;109110pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);111pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;112113#[frame_support::pallet]114pub mod pallet {115 use frame_support::{116 pallet_prelude::*, storage::Key, Blake2_128, Blake2_128Concat, Twox64Concat,117 };118 use up_data_structs::CollectionId;119120 use super::weights::WeightInfo;121122 #[pallet::error]123 pub enum Error<T> {124 /// Not Fungible item data used to mint in Fungible collection.125 NotFungibleDataUsedToMintFungibleCollectionToken,126 /// Tried to set data for fungible item.127 FungibleItemsDontHaveData,128 /// Fungible token does not support nesting.129 FungibleDisallowsNesting,130 /// Setting item properties is not allowed.131 SettingPropertiesNotAllowed,132 /// Setting allowance for all is not allowed.133 SettingAllowanceForAllNotAllowed,134 /// Only a fungible collection could be possibly broken; any fungible token is valid.135 FungibleTokensAreAlwaysValid,136 }137138 #[pallet::config]139 pub trait Config:140 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config141 {142 type WeightInfo: WeightInfo;143 }144145 #[pallet::pallet]146 pub struct Pallet<T>(_);147148 /// Total amount of fungible tokens inside a collection.149 #[pallet::storage]150 pub type TotalSupply<T: Config> =151 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;152153 /// Amount of tokens owned by an account inside a collection.154 #[pallet::storage]155 pub type Balance<T: Config> = StorageNMap<156 Key = (157 Key<Twox64Concat, CollectionId>,158 Key<Blake2_128Concat, T::CrossAccountId>,159 ),160 Value = u128,161 QueryKind = ValueQuery,162 >;163164 /// Storage for assets delegated to a limited extent to other users.165 #[pallet::storage]166 pub type Allowance<T: Config> = StorageNMap<167 Key = (168 Key<Twox64Concat, CollectionId>,169 Key<Blake2_128, T::CrossAccountId>, // Owner170 Key<Blake2_128Concat, T::CrossAccountId>, // Spender171 ),172 Value = u128,173 QueryKind = ValueQuery,174 >;175}176177/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.178/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].179pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);180181/// Implementation of methods required for dispatching during runtime.182impl<T: Config> FungibleHandle<T> {183 /// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].184 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {185 Self(inner)186 }187188 /// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].189 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {190 self.0191 }192 /// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].193 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {194 &mut self.0195 }196}197impl<T: Config> WithRecorder<T> for FungibleHandle<T> {198 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {199 self.0.recorder()200 }201 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {202 self.0.into_recorder()203 }204}205impl<T: Config> Deref for FungibleHandle<T> {206 type Target = pallet_common::CollectionHandle<T>;207208 fn deref(&self) -> &Self::Target {209 &self.0210 }211}212213/// Pallet implementation for fungible assets214impl<T: Config> Pallet<T> {215 /// Destroys a collection.216 pub fn destroy_collection(217 collection: FungibleHandle<T>,218 sender: &T::CrossAccountId,219 ) -> DispatchResult {220 let id = collection.id;221222 if Self::collection_has_tokens(id) {223 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());224 }225226 // =========227228 PalletCommon::destroy_collection(collection.0, sender)?;229230 <TotalSupply<T>>::remove(id);231 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);232 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);233 Ok(())234 }235236 /// Add properties to the collection.237 pub fn set_collection_properties(238 collection: &FungibleHandle<T>,239 sender: &T::CrossAccountId,240 properties: Vec<Property>,241 ) -> DispatchResult {242 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())243 }244245 /// Delete properties of the collection, associated with the provided keys.246 pub fn delete_collection_properties(247 collection: &FungibleHandle<T>,248 sender: &T::CrossAccountId,249 property_keys: Vec<PropertyKey>,250 ) -> DispatchResult {251 <PalletCommon<T>>::delete_collection_properties(252 collection,253 sender,254 property_keys.into_iter(),255 )256 }257258 /// Checks if collection has tokens. Return `true` if it has.259 fn collection_has_tokens(collection_id: CollectionId) -> bool {260 <TotalSupply<T>>::get(collection_id) != 0261 }262263 /// Burns the specified amount of the token. If the token balance264 /// or total supply is less than the given value,265 /// it will return [DispatchError].266 pub fn burn(267 collection: &FungibleHandle<T>,268 owner: &T::CrossAccountId,269 amount: u128,270 ) -> DispatchResult {271 let total_supply = <TotalSupply<T>>::get(collection.id)272 .checked_sub(amount)273 .ok_or(<CommonError<T>>::TokenValueTooLow)?;274275 let balance = <Balance<T>>::get((collection.id, owner))276 .checked_sub(amount)277 .ok_or(<CommonError<T>>::TokenValueTooLow)?;278279 // Foreign collection check280 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);281282 if collection.permissions.access() == AccessMode::AllowList {283 collection.check_allowlist(owner)?;284 }285286 // =========287288 if balance == 0 {289 <Balance<T>>::remove((collection.id, owner));290 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());291 } else {292 <Balance<T>>::insert((collection.id, owner), balance);293 }294 <TotalSupply<T>>::insert(collection.id, total_supply);295296 <PalletEvm<T>>::deposit_log(297 ERC20Events::Transfer {298 from: *owner.as_eth(),299 to: H160::default(),300 value: amount.into(),301 }302 .to_log(collection_id_to_address(collection.id)),303 );304 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(305 collection.id,306 TokenId::default(),307 owner.clone(),308 amount,309 ));310 Ok(())311 }312313 /// Burns the specified amount of the token.314 pub fn burn_foreign(315 collection: &FungibleHandle<T>,316 owner: &T::CrossAccountId,317 amount: u128,318 ) -> DispatchResult {319 let total_supply = <TotalSupply<T>>::get(collection.id)320 .checked_sub(amount)321 .ok_or(<CommonError<T>>::TokenValueTooLow)?;322323 let balance = <Balance<T>>::get((collection.id, owner))324 .checked_sub(amount)325 .ok_or(<CommonError<T>>::TokenValueTooLow)?;326 // =========327328 if balance == 0 {329 <Balance<T>>::remove((collection.id, owner));330 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());331 } else {332 <Balance<T>>::insert((collection.id, owner), balance);333 }334 <TotalSupply<T>>::insert(collection.id, total_supply);335336 <PalletEvm<T>>::deposit_log(337 ERC20Events::Transfer {338 from: *owner.as_eth(),339 to: H160::default(),340 value: amount.into(),341 }342 .to_log(collection_id_to_address(collection.id)),343 );344 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(345 collection.id,346 TokenId::default(),347 owner.clone(),348 amount,349 ));350 Ok(())351 }352353 /// Transfers the specified amount of tokens. Will check that354 /// the transfer is allowed for the token.355 ///356 /// - `from`: Owner of tokens to transfer.357 /// - `to`: Recepient of transfered tokens.358 /// - `amount`: Amount of tokens to transfer.359 /// - `collection`: Collection that contains the token360 pub fn transfer(361 collection: &FungibleHandle<T>,362 from: &T::CrossAccountId,363 to: &T::CrossAccountId,364 amount: u128,365 nesting_budget: &dyn Budget,366 ) -> DispatchResultWithPostInfo {367 let depositor = from;368 Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)369 }370371 /// Transfers tokens from the `from` account to the `to` account.372 /// The `depositor` is the account who deposits the tokens.373 /// For instance, the nesting rules will be checked against the `depositor`'s permissions.374 fn transfer_internal(375 collection: &FungibleHandle<T>,376 depositor: &T::CrossAccountId,377 from: &T::CrossAccountId,378 to: &T::CrossAccountId,379 amount: u128,380 nesting_budget: &dyn Budget,381 ) -> DispatchResultWithPostInfo {382 ensure!(383 collection.limits.transfers_enabled(),384 <CommonError<T>>::TransferNotAllowed,385 );386387 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();388389 if collection.permissions.access() == AccessMode::AllowList {390 collection.check_allowlist(from)?;391 collection.check_allowlist(to)?;392 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;393 }394 <PalletCommon<T>>::ensure_correct_receiver(to)?;395 let balance_from = <Balance<T>>::get((collection.id, from))396 .checked_sub(amount)397 .ok_or(<CommonError<T>>::TokenValueTooLow)?;398 let balance_to = if from != to && amount != 0 {399 Some(400 <Balance<T>>::get((collection.id, to))401 .checked_add(amount)402 .ok_or(ArithmeticError::Overflow)?,403 )404 } else {405 None406 };407408 // =========409410 if let Some(balance_to) = balance_to {411 // from != to && amount != 0412413 <PalletStructure<T>>::nest_if_sent_to_token(414 depositor,415 to,416 collection.id,417 TokenId::default(),418 nesting_budget,419 )?;420421 if balance_from == 0 {422 <Balance<T>>::remove((collection.id, from));423 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());424 } else {425 <Balance<T>>::insert((collection.id, from), balance_from);426 }427 <Balance<T>>::insert((collection.id, to), balance_to);428 }429430 <PalletEvm<T>>::deposit_log(431 ERC20Events::Transfer {432 from: *from.as_eth(),433 to: *to.as_eth(),434 value: amount.into(),435 }436 .to_log(collection_id_to_address(collection.id)),437 );438 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(439 collection.id,440 TokenId::default(),441 from.clone(),442 to.clone(),443 amount,444 ));445446 Ok(PostDispatchInfo {447 actual_weight: Some(actual_weight),448 pays_fee: Pays::Yes,449 })450 }451452 /// Minting tokens for multiple IDs.453 /// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]454 /// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]455 pub fn create_multiple_items_common(456 collection: &FungibleHandle<T>,457 sender: &T::CrossAccountId,458 data: BTreeMap<T::CrossAccountId, u128>,459 nesting_budget: &dyn Budget,460 ) -> DispatchResult {461 let total_supply = data462 .values()463 .copied()464 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {465 acc.checked_add(v)466 })467 .ok_or(ArithmeticError::Overflow)?;468469 for (to, _) in data.iter() {470 <PalletStructure<T>>::check_nesting(471 sender,472 to,473 collection.id,474 TokenId::default(),475 nesting_budget,476 )?;477 }478479 let updated_balances = data480 .into_iter()481 .map(|(user, amount)| {482 let updated_balance = <Balance<T>>::get((collection.id, &user))483 .checked_add(amount)484 .ok_or(ArithmeticError::Overflow)?;485 Ok((user, amount, updated_balance))486 })487 .collect::<Result<Vec<_>, DispatchError>>()?;488489 // =========490491 <TotalSupply<T>>::insert(collection.id, total_supply);492 for (user, amount, updated_balance) in updated_balances {493 <Balance<T>>::insert((collection.id, &user), updated_balance);494 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(495 &user,496 collection.id,497 TokenId::default(),498 );499 <PalletEvm<T>>::deposit_log(500 ERC20Events::Transfer {501 from: H160::default(),502 to: *user.as_eth(),503 value: amount.into(),504 }505 .to_log(collection_id_to_address(collection.id)),506 );507 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(508 collection.id,509 TokenId::default(),510 user.clone(),511 amount,512 ));513 }514515 Ok(())516 }517518 /// Minting tokens for multiple IDs.519 /// See [`create_item`][`Pallet::create_item`] for more details.520 pub fn create_multiple_items(521 collection: &FungibleHandle<T>,522 sender: &T::CrossAccountId,523 data: BTreeMap<T::CrossAccountId, u128>,524 nesting_budget: &dyn Budget,525 ) -> DispatchResult {526 // Foreign collection check527 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);528529 if !collection.is_owner_or_admin(sender) {530 ensure!(531 collection.permissions.mint_mode(),532 <CommonError<T>>::PublicMintingNotAllowed533 );534 collection.check_allowlist(sender)?;535536 for (owner, _) in data.iter() {537 collection.check_allowlist(owner)?;538 }539 }540541 Self::create_multiple_items_common(collection, sender, data, nesting_budget)542 }543544 /// Minting tokens for multiple IDs.545 /// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.546 pub fn create_multiple_items_foreign(547 collection: &FungibleHandle<T>,548 sender: &T::CrossAccountId,549 data: BTreeMap<T::CrossAccountId, u128>,550 nesting_budget: &dyn Budget,551 ) -> DispatchResult {552 Self::create_multiple_items_common(collection, sender, data, nesting_budget)553 }554555 fn set_allowance_unchecked(556 collection: &FungibleHandle<T>,557 owner: &T::CrossAccountId,558 spender: &T::CrossAccountId,559 amount: u128,560 ) {561 if amount == 0 {562 <Allowance<T>>::remove((collection.id, owner, spender));563 } else {564 <Allowance<T>>::insert((collection.id, owner, spender), amount);565 }566567 <PalletEvm<T>>::deposit_log(568 ERC20Events::Approval {569 owner: *owner.as_eth(),570 spender: *spender.as_eth(),571 value: amount.into(),572 }573 .to_log(collection_id_to_address(collection.id)),574 );575 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(576 collection.id,577 TokenId(0),578 owner.clone(),579 spender.clone(),580 amount,581 ));582 }583584 /// Set allowance for the spender to `transfer` or `burn` owner's tokens.585 ///586 /// - `collection`: Collection that contains the token587 /// - `owner`: Owner of tokens that sets the allowance.588 /// - `spender`: Recipient of the allowance rights.589 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.590 pub fn set_allowance(591 collection: &FungibleHandle<T>,592 owner: &T::CrossAccountId,593 spender: &T::CrossAccountId,594 amount: u128,595 ) -> DispatchResult {596 if collection.permissions.access() == AccessMode::AllowList {597 collection.check_allowlist(owner)?;598 collection.check_allowlist(spender)?;599 }600601 if <Balance<T>>::get((collection.id, owner)) < amount {602 ensure!(603 collection.ignores_owned_amount(owner),604 <CommonError<T>>::CantApproveMoreThanOwned605 );606 }607608 // =========609610 Self::set_allowance_unchecked(collection, owner, spender, amount);611 Ok(())612 }613614 /// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.615 ///616 /// - `collection`: Collection that contains the token617 /// - `sender`: Owner of tokens that sets the allowance.618 /// - `from`: Owner's eth mirror.619 /// - `to`: Recipient of the allowance rights.620 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.621 pub fn set_allowance_from(622 collection: &FungibleHandle<T>,623 sender: &T::CrossAccountId,624 from: &T::CrossAccountId,625 to: &T::CrossAccountId,626 amount: u128,627 ) -> DispatchResult {628 if collection.permissions.access() == AccessMode::AllowList {629 collection.check_allowlist(sender)?;630 collection.check_allowlist(from)?;631 collection.check_allowlist(to)?;632 }633634 ensure!(635 sender.conv_eq(from),636 <CommonError<T>>::AddressIsNotEthMirror637 );638639 if <Balance<T>>::get((collection.id, from)) < amount {640 ensure!(641 collection.limits.owner_can_transfer()642 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),643 <CommonError<T>>::CantApproveMoreThanOwned644 );645 }646647 // =========648649 Self::set_allowance_unchecked(collection, from, to, amount);650 Ok(())651 }652653 /// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.654 /// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.655 ///656 /// - `collection`: Collection that contains the token.657 /// - `spender`: CrossAccountId who has the allowance rights.658 /// - `from`: The owner of the tokens who sets the allowance.659 /// - `amount`: Amount of tokens by which the allowance sholud be reduced.660 fn check_allowed(661 collection: &FungibleHandle<T>,662 spender: &T::CrossAccountId,663 from: &T::CrossAccountId,664 amount: u128,665 nesting_budget: &dyn Budget,666 ) -> Result<Option<u128>, DispatchError> {667 if spender.conv_eq(from) {668 return Ok(None);669 }670 if collection.permissions.access() == AccessMode::AllowList {671 // `from`, `to` checked in [`transfer`]672 collection.check_allowlist(spender)?;673 }674675 if collection.ignores_token_restrictions(spender) {676 return Ok(Self::compute_allowance_decrease(677 collection, from, spender, amount,678 ));679 }680681 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {682 ensure!(683 <PalletStructure<T>>::check_indirectly_owned(684 spender.clone(),685 source.0,686 source.1,687 None,688 nesting_budget689 )?,690 <CommonError<T>>::ApprovedValueTooLow,691 );692 return Ok(None);693 }694695 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);696 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);697698 Ok(allowance)699 }700701 /// Returns `Some(amount)` if the `spender` have allowance to spend this amount.702 /// Otherwise, it returns `None`.703 fn compute_allowance_decrease(704 collection: &FungibleHandle<T>,705 from: &T::CrossAccountId,706 spender: &T::CrossAccountId,707 amount: u128,708 ) -> Option<u128> {709 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)710 }711712 /// Transfer fungible tokens from one account to another.713 /// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.714 /// The owner should set allowance for the spender to transfer pieces.715 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.716 pub fn transfer_from(717 collection: &FungibleHandle<T>,718 spender: &T::CrossAccountId,719 from: &T::CrossAccountId,720 to: &T::CrossAccountId,721 amount: u128,722 nesting_budget: &dyn Budget,723 ) -> DispatchResultWithPostInfo {724 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;725726 // =========727728 let mut result =729 Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);730 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());731 result?;732733 if let Some(allowance) = allowance {734 Self::set_allowance_unchecked(collection, from, spender, allowance);735 add_weight_to_post_info(736 &mut result,737 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),738 )739 }740 result741 }742743 /// Burn fungible tokens from the account.744 ///745 /// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should746 /// set allowance for the spender to burn tokens.747 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.748 pub fn burn_from(749 collection: &FungibleHandle<T>,750 spender: &T::CrossAccountId,751 from: &T::CrossAccountId,752 amount: u128,753 nesting_budget: &dyn Budget,754 ) -> DispatchResult {755 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;756757 // =========758759 Self::burn(collection, from, amount)?;760 if let Some(allowance) = allowance {761 Self::set_allowance_unchecked(collection, from, spender, allowance);762 }763 Ok(())764 }765766 /// Creates fungible token.767 ///768 /// The sender should be the owner/admin of the collection or collection should be configured769 /// to allow public minting.770 ///771 /// - `data`: Contains user who will become the owners of the tokens and amount772 /// of tokens he will receive.773 pub fn create_item(774 collection: &FungibleHandle<T>,775 sender: &T::CrossAccountId,776 data: CreateItemData<T>,777 nesting_budget: &dyn Budget,778 ) -> DispatchResult {779 Self::create_multiple_items(780 collection,781 sender,782 [(data.0, data.1)].into_iter().collect(),783 nesting_budget,784 )785 }786787 /// Creates fungible token.788 ///789 /// - `data`: Contains user who will become the owners of the tokens and amount790 /// of tokens he will receive.791 pub fn create_item_foreign(792 collection: &FungibleHandle<T>,793 sender: &T::CrossAccountId,794 data: CreateItemData<T>,795 nesting_budget: &dyn Budget,796 ) -> DispatchResult {797 Self::create_multiple_items_foreign(798 collection,799 sender,800 [(data.0, data.1)].into_iter().collect(),801 nesting_budget,802 )803 }804805 /// Returns 10 tokens owners in no particular order806 ///807 /// There is no direct way to get token holders in ascending order,808 /// since `iter_prefix` returns values in no particular order.809 /// Therefore, getting the 10 largest holders with a large value of holders810 /// can lead to impact memory allocation + sorting with `n * log (n)`.811 pub fn token_owners(812 collection: CollectionId,813 _token: TokenId,814 ) -> Option<Vec<T::CrossAccountId>> {815 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))816 .map(|(owner, _amount)| owner)817 .take(10)818 .collect();819820 if res.is_empty() {821 None822 } else {823 Some(res)824 }825 }826}pallets/nonfungible/src/common.rsdiffbeforeafterboth--- 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<T>> {
+ Some(self)
+ }
+
fn set_allowance_for_all(
&self,
owner: T::CrossAccountId,
@@ -566,3 +570,53 @@
)
}
}
+
+impl<T: Config> XcmExtensions<T> for NonfungibleHandle<T> {
+ fn is_foreign(&self) -> bool {
+ self.flags.foreign
+ }
+
+ fn create_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: up_data_structs::CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ <Pallet<T>>::create_multiple_items(
+ self,
+ &depositor,
+ vec![map_create_data::<T>(data, &to)?],
+ nesting_budget,
+ )?;
+
+ Ok(self.last_token_id())
+ }
+
+ fn transfer_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+ <Pallet<T>>::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, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+ <Pallet<T>>::burn(self, &from, token)
+ }
+}
primitives/data-structs/src/budget.rsdiffbeforeafterboth--- 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
+ }
+}
runtime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth--- 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<ConfigCrossAccountId>
+ for LocationToCrossAccountId
+{
+ fn convert_location(location: &MultiLocation) -> Option<ConfigCrossAccountId> {
+ LocationToAccountId::convert_location(location)
+ .map(|sub| ConfigCrossAccountId::from_sub(sub))
+ .or_else(|| {
+ let eth_address =
+ AccountKey20Aliases::<RelayNetwork, H160>::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<Self>;
}