difftreelog
fix forbid non-1 tokenid in native fungibles (#1015)
in: master
* fix: native fungibles collection * refactor: move FungibleItemsHaveNoId to pallet-common
6 files changed
pallets/balances-adapter/src/common.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -1,9 +1,9 @@
use alloc::{vec, vec::Vec};
use core::marker::PhantomData;
-use frame_support::{fail, weights::Weight};
+use frame_support::{ensure, fail, weights::Weight};
use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo};
+use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError};
use up_data_structs::TokenId;
use crate::{Config, NativeFungibleHandle, Pallet};
@@ -77,7 +77,7 @@
_data: up_data_structs::CreateItemData,
_nesting_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn create_multiple_items(
@@ -87,7 +87,7 @@
_data: Vec<up_data_structs::CreateItemData>,
_nesting_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn create_multiple_items_ex(
@@ -96,7 +96,7 @@
_data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
_nesting_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn burn_item(
@@ -105,7 +105,7 @@
_token: TokenId,
_amount: u128,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn set_collection_properties(
@@ -113,7 +113,7 @@
_sender: <T>::CrossAccountId,
_properties: Vec<up_data_structs::Property>,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn delete_collection_properties(
@@ -121,7 +121,7 @@
_sender: &<T>::CrossAccountId,
_property_keys: Vec<up_data_structs::PropertyKey>,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn set_token_properties(
@@ -131,7 +131,7 @@
_properties: Vec<up_data_structs::Property>,
_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn delete_token_properties(
@@ -141,7 +141,7 @@
_property_keys: Vec<up_data_structs::PropertyKey>,
_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn get_token_properties_raw(
@@ -161,18 +161,23 @@
_sender: &<T>::CrossAccountId,
_property_permissions: Vec<up_data_structs::PropertyKeyPermission>,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn transfer(
&self,
sender: <T>::CrossAccountId,
to: <T>::CrossAccountId,
- _token: TokenId,
+ token: TokenId,
amount: u128,
- budget: &dyn up_data_structs::budget::Budget,
+ _budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- <Pallet<T>>::transfer(self, &sender, &to, amount, budget)
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::transfer(&sender, &to, amount)
}
fn approve(
@@ -182,7 +187,7 @@
_token: TokenId,
_amount: u128,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn approve_from(
@@ -193,7 +198,7 @@
_token: TokenId,
_amount: u128,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn transfer_from(
@@ -201,11 +206,16 @@
sender: <T>::CrossAccountId,
from: <T>::CrossAccountId,
to: <T>::CrossAccountId,
- _token: TokenId,
+ token: TokenId,
amount: u128,
budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- <Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, budget)
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::transfer_from(&sender, &from, &to, amount, budget)
}
fn burn_from(
@@ -216,7 +226,7 @@
_amount: u128,
_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn check_nesting(
@@ -226,7 +236,7 @@
_under: TokenId,
_budget: &dyn up_data_structs::budget::Budget,
) -> frame_support::sp_runtime::DispatchResult {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn nest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
@@ -332,7 +342,7 @@
_operator: <T>::CrossAccountId,
_approve: bool,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
fn allowance_for_all(
@@ -347,6 +357,6 @@
&self,
_token: TokenId,
) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
- fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ fail!(<CommonError<T>>::UnsupportedOperation);
}
}
pallets/balances-adapter/src/erc.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/erc.rs
+++ b/pallets/balances-adapter/src/erc.rs
@@ -58,12 +58,8 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let amount = amount.try_into().map_err(|_| "amount overflow")?;
- let budget = self
- .recorder()
- .weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
- .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+ <Pallet<T>>::transfer(&caller, &to, amount).map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
}
@@ -83,7 +79,7 @@
.recorder()
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+ <Pallet<T>>::transfer_from(&caller, &from, &to, amount, &budget)
.map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
}
@@ -106,12 +102,8 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = to.into_sub_cross_account::<T>()?;
let amount = amount.try_into().map_err(|_| "amount overflow")?;
- let budget = self
- .recorder()
- .weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
- .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+ <Pallet<T>>::transfer(&caller, &to, amount).map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
}
@@ -137,7 +129,7 @@
.recorder()
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+ <Pallet<T>>::transfer_from(&caller, &from, &to, amount, &budget)
.map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
pallets/balances-adapter/src/lib.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -155,20 +155,15 @@
Ok(Self::balance_of(from))
}
- /// Transfers the specified amount of tokens. Will check that
- /// the transfer is allowed for the token.
+ /// Transfers the specified amount of tokens.
///
- /// - `collection`: Collection that contains the token.
/// - `from`: Owner of tokens to transfer.
/// - `to`: Recepient of transfered tokens.
/// - `amount`: Amount of tokens to transfer.
- /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
pub fn transfer(
- _collection: &NativeFungibleHandle<T>,
from: &T::CrossAccountId,
to: &T::CrossAccountId,
amount: u128,
- _nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
<PalletCommon<T>>::ensure_correct_receiver(to)?;
@@ -185,19 +180,18 @@
})
}
- /// Transfer NFT token from one account to another.
+ /// Transfer tokens from one account to another.
///
- /// Same as the [`Self::transfer`] but spender doesn't needs to be the owner of the token.
- /// The owner should set allowance for the spender to transfer token.
+ /// Same as the [`Self::transfer`] but the spender doesn't needs to be the direct owner of the token.
+ /// The spender must be allowed to transfer token.
+ /// If the tokens are nested in an NFT and the spender owns the NFT, the allowance is considered to be set.
///
- /// - `collection`: Collection that contains the token.
/// - `spender`: Account that spend the money.
/// - `from`: Owner of tokens to transfer.
/// - `to`: Recepient of transfered tokens.
/// - `amount`: Amount of tokens to transfer.
/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
pub fn transfer_from(
- collection: &NativeFungibleHandle<T>,
spender: &T::CrossAccountId,
from: &T::CrossAccountId,
to: &T::CrossAccountId,
@@ -208,7 +202,7 @@
if allowance < amount {
return Err(<CommonError<T>>::ApprovedValueTooLow.into());
}
- Self::transfer(collection, from, to, amount, nesting_budget)
+ Self::transfer(from, to, amount)
}
}
}
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -783,6 +783,9 @@
/// The user is not an administrator.
UserIsNotCollectionAdmin,
+
+ /// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0.
+ FungibleItemsHaveNoId,
}
/// Storage of the count of created collections. Essentially contains the last collection ID.
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,
- RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+ Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
};
use sp_runtime::{ArithmeticError, DispatchError};
use sp_std::{vec, vec::Vec};
@@ -169,7 +169,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
with_weight(
@@ -188,7 +188,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
<Pallet<T>>::transfer(self, &from, &to, amount, nesting_budget)
@@ -203,7 +203,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
with_weight(
@@ -222,7 +222,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
with_weight(
@@ -242,7 +242,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
<Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, nesting_budget)
@@ -258,7 +258,7 @@
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
- <Error<T>>::FungibleItemsHaveNoId
+ <CommonError<T>>::FungibleItemsHaveNoId
);
with_weight(
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 /// Fungible tokens hold no ID, and the default value of TokenId for Fungible collection is 0.127 FungibleItemsHaveNoId,128 /// Tried to set data for fungible item.129 FungibleItemsDontHaveData,130 /// Fungible token does not support nesting.131 FungibleDisallowsNesting,132 /// Setting item properties is not allowed.133 SettingPropertiesNotAllowed,134 /// Setting allowance for all is not allowed.135 SettingAllowanceForAllNotAllowed,136 /// Only a fungible collection could be possibly broken; any fungible token is valid.137 FungibleTokensAreAlwaysValid,138 }139140 #[pallet::config]141 pub trait Config:142 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config143 {144 type WeightInfo: WeightInfo;145 }146147 #[pallet::pallet]148 pub struct Pallet<T>(_);149150 /// Total amount of fungible tokens inside a collection.151 #[pallet::storage]152 pub type TotalSupply<T: Config> =153 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;154155 /// Amount of tokens owned by an account inside a collection.156 #[pallet::storage]157 pub type Balance<T: Config> = StorageNMap<158 Key = (159 Key<Twox64Concat, CollectionId>,160 Key<Blake2_128Concat, T::CrossAccountId>,161 ),162 Value = u128,163 QueryKind = ValueQuery,164 >;165166 /// Storage for assets delegated to a limited extent to other users.167 #[pallet::storage]168 pub type Allowance<T: Config> = StorageNMap<169 Key = (170 Key<Twox64Concat, CollectionId>,171 Key<Blake2_128, T::CrossAccountId>, // Owner172 Key<Blake2_128Concat, T::CrossAccountId>, // Spender173 ),174 Value = u128,175 QueryKind = ValueQuery,176 >;177}178179/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.180/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].181pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);182183/// Implementation of methods required for dispatching during runtime.184impl<T: Config> FungibleHandle<T> {185 /// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].186 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {187 Self(inner)188 }189190 /// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].191 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {192 self.0193 }194 /// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].195 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {196 &mut self.0197 }198}199impl<T: Config> WithRecorder<T> for FungibleHandle<T> {200 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {201 self.0.recorder()202 }203 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {204 self.0.into_recorder()205 }206}207impl<T: Config> Deref for FungibleHandle<T> {208 type Target = pallet_common::CollectionHandle<T>;209210 fn deref(&self) -> &Self::Target {211 &self.0212 }213}214215/// Pallet implementation for fungible assets216impl<T: Config> Pallet<T> {217 /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.218 pub fn init_collection(219 owner: T::CrossAccountId,220 payer: T::CrossAccountId,221 data: CreateCollectionData<T::CrossAccountId>,222 ) -> Result<CollectionId, DispatchError> {223 <PalletCommon<T>>::init_collection(owner, payer, data)224 }225226 /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.227 pub fn init_foreign_collection(228 owner: T::CrossAccountId,229 payer: T::CrossAccountId,230 data: CreateCollectionData<T::CrossAccountId>,231 ) -> Result<CollectionId, DispatchError> {232 <PalletCommon<T>>::init_foreign_collection(owner, payer, data)233 }234235 /// Destroys a collection.236 pub fn destroy_collection(237 collection: FungibleHandle<T>,238 sender: &T::CrossAccountId,239 ) -> DispatchResult {240 let id = collection.id;241242 if Self::collection_has_tokens(id) {243 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());244 }245246 // =========247248 PalletCommon::destroy_collection(collection.0, sender)?;249250 <TotalSupply<T>>::remove(id);251 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);252 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);253 Ok(())254 }255256 /// Add properties to the collection.257 pub fn set_collection_properties(258 collection: &FungibleHandle<T>,259 sender: &T::CrossAccountId,260 properties: Vec<Property>,261 ) -> DispatchResult {262 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())263 }264265 /// Delete properties of the collection, associated with the provided keys.266 pub fn delete_collection_properties(267 collection: &FungibleHandle<T>,268 sender: &T::CrossAccountId,269 property_keys: Vec<PropertyKey>,270 ) -> DispatchResult {271 <PalletCommon<T>>::delete_collection_properties(272 collection,273 sender,274 property_keys.into_iter(),275 )276 }277278 /// Checks if collection has tokens. Return `true` if it has.279 fn collection_has_tokens(collection_id: CollectionId) -> bool {280 <TotalSupply<T>>::get(collection_id) != 0281 }282283 /// Burns the specified amount of the token. If the token balance284 /// or total supply is less than the given value,285 /// it will return [DispatchError].286 pub fn burn(287 collection: &FungibleHandle<T>,288 owner: &T::CrossAccountId,289 amount: u128,290 ) -> DispatchResult {291 let total_supply = <TotalSupply<T>>::get(collection.id)292 .checked_sub(amount)293 .ok_or(<CommonError<T>>::TokenValueTooLow)?;294295 let balance = <Balance<T>>::get((collection.id, owner))296 .checked_sub(amount)297 .ok_or(<CommonError<T>>::TokenValueTooLow)?;298299 // Foreign collection check300 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);301302 if collection.permissions.access() == AccessMode::AllowList {303 collection.check_allowlist(owner)?;304 }305306 // =========307308 if balance == 0 {309 <Balance<T>>::remove((collection.id, owner));310 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());311 } else {312 <Balance<T>>::insert((collection.id, owner), balance);313 }314 <TotalSupply<T>>::insert(collection.id, total_supply);315316 <PalletEvm<T>>::deposit_log(317 ERC20Events::Transfer {318 from: *owner.as_eth(),319 to: H160::default(),320 value: amount.into(),321 }322 .to_log(collection_id_to_address(collection.id)),323 );324 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(325 collection.id,326 TokenId::default(),327 owner.clone(),328 amount,329 ));330 Ok(())331 }332333 /// Burns the specified amount of the token.334 pub fn burn_foreign(335 collection: &FungibleHandle<T>,336 owner: &T::CrossAccountId,337 amount: u128,338 ) -> DispatchResult {339 let total_supply = <TotalSupply<T>>::get(collection.id)340 .checked_sub(amount)341 .ok_or(<CommonError<T>>::TokenValueTooLow)?;342343 let balance = <Balance<T>>::get((collection.id, owner))344 .checked_sub(amount)345 .ok_or(<CommonError<T>>::TokenValueTooLow)?;346 // =========347348 if balance == 0 {349 <Balance<T>>::remove((collection.id, owner));350 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());351 } else {352 <Balance<T>>::insert((collection.id, owner), balance);353 }354 <TotalSupply<T>>::insert(collection.id, total_supply);355356 <PalletEvm<T>>::deposit_log(357 ERC20Events::Transfer {358 from: *owner.as_eth(),359 to: H160::default(),360 value: amount.into(),361 }362 .to_log(collection_id_to_address(collection.id)),363 );364 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(365 collection.id,366 TokenId::default(),367 owner.clone(),368 amount,369 ));370 Ok(())371 }372373 /// Transfers the specified amount of tokens. Will check that374 /// the transfer is allowed for the token.375 ///376 /// - `from`: Owner of tokens to transfer.377 /// - `to`: Recepient of transfered tokens.378 /// - `amount`: Amount of tokens to transfer.379 /// - `collection`: Collection that contains the token380 pub fn transfer(381 collection: &FungibleHandle<T>,382 from: &T::CrossAccountId,383 to: &T::CrossAccountId,384 amount: u128,385 nesting_budget: &dyn Budget,386 ) -> DispatchResultWithPostInfo {387 let depositor = from;388 Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)389 }390391 /// Transfers tokens from the `from` account to the `to` account.392 /// The `depositor` is the account who deposits the tokens.393 /// For instance, the nesting rules will be checked against the `depositor`'s permissions.394 fn transfer_internal(395 collection: &FungibleHandle<T>,396 depositor: &T::CrossAccountId,397 from: &T::CrossAccountId,398 to: &T::CrossAccountId,399 amount: u128,400 nesting_budget: &dyn Budget,401 ) -> DispatchResultWithPostInfo {402 ensure!(403 collection.limits.transfers_enabled(),404 <CommonError<T>>::TransferNotAllowed,405 );406407 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();408409 if collection.permissions.access() == AccessMode::AllowList {410 collection.check_allowlist(from)?;411 collection.check_allowlist(to)?;412 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;413 }414 <PalletCommon<T>>::ensure_correct_receiver(to)?;415 let balance_from = <Balance<T>>::get((collection.id, from))416 .checked_sub(amount)417 .ok_or(<CommonError<T>>::TokenValueTooLow)?;418 let balance_to = if from != to && amount != 0 {419 Some(420 <Balance<T>>::get((collection.id, to))421 .checked_add(amount)422 .ok_or(ArithmeticError::Overflow)?,423 )424 } else {425 None426 };427428 // =========429430 if let Some(balance_to) = balance_to {431 // from != to && amount != 0432433 <PalletStructure<T>>::nest_if_sent_to_token(434 depositor,435 to,436 collection.id,437 TokenId::default(),438 nesting_budget,439 )?;440441 if balance_from == 0 {442 <Balance<T>>::remove((collection.id, from));443 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());444 } else {445 <Balance<T>>::insert((collection.id, from), balance_from);446 }447 <Balance<T>>::insert((collection.id, to), balance_to);448 }449450 <PalletEvm<T>>::deposit_log(451 ERC20Events::Transfer {452 from: *from.as_eth(),453 to: *to.as_eth(),454 value: amount.into(),455 }456 .to_log(collection_id_to_address(collection.id)),457 );458 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(459 collection.id,460 TokenId::default(),461 from.clone(),462 to.clone(),463 amount,464 ));465466 Ok(PostDispatchInfo {467 actual_weight: Some(actual_weight),468 pays_fee: Pays::Yes,469 })470 }471472 /// Minting tokens for multiple IDs.473 /// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]474 /// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]475 pub fn create_multiple_items_common(476 collection: &FungibleHandle<T>,477 sender: &T::CrossAccountId,478 data: BTreeMap<T::CrossAccountId, u128>,479 nesting_budget: &dyn Budget,480 ) -> DispatchResult {481 let total_supply = data482 .values()483 .copied()484 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {485 acc.checked_add(v)486 })487 .ok_or(ArithmeticError::Overflow)?;488489 for (to, _) in data.iter() {490 <PalletStructure<T>>::check_nesting(491 sender,492 to,493 collection.id,494 TokenId::default(),495 nesting_budget,496 )?;497 }498499 let updated_balances = data500 .into_iter()501 .map(|(user, amount)| {502 let updated_balance = <Balance<T>>::get((collection.id, &user))503 .checked_add(amount)504 .ok_or(ArithmeticError::Overflow)?;505 Ok((user, amount, updated_balance))506 })507 .collect::<Result<Vec<_>, DispatchError>>()?;508509 // =========510511 <TotalSupply<T>>::insert(collection.id, total_supply);512 for (user, amount, updated_balance) in updated_balances {513 <Balance<T>>::insert((collection.id, &user), updated_balance);514 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(515 &user,516 collection.id,517 TokenId::default(),518 );519 <PalletEvm<T>>::deposit_log(520 ERC20Events::Transfer {521 from: H160::default(),522 to: *user.as_eth(),523 value: amount.into(),524 }525 .to_log(collection_id_to_address(collection.id)),526 );527 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(528 collection.id,529 TokenId::default(),530 user.clone(),531 amount,532 ));533 }534535 Ok(())536 }537538 /// Minting tokens for multiple IDs.539 /// See [`create_item`][`Pallet::create_item`] for more details.540 pub fn create_multiple_items(541 collection: &FungibleHandle<T>,542 sender: &T::CrossAccountId,543 data: BTreeMap<T::CrossAccountId, u128>,544 nesting_budget: &dyn Budget,545 ) -> DispatchResult {546 // Foreign collection check547 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);548549 if !collection.is_owner_or_admin(sender) {550 ensure!(551 collection.permissions.mint_mode(),552 <CommonError<T>>::PublicMintingNotAllowed553 );554 collection.check_allowlist(sender)?;555556 for (owner, _) in data.iter() {557 collection.check_allowlist(owner)?;558 }559 }560561 Self::create_multiple_items_common(collection, sender, data, nesting_budget)562 }563564 /// Minting tokens for multiple IDs.565 /// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.566 pub fn create_multiple_items_foreign(567 collection: &FungibleHandle<T>,568 sender: &T::CrossAccountId,569 data: BTreeMap<T::CrossAccountId, u128>,570 nesting_budget: &dyn Budget,571 ) -> DispatchResult {572 Self::create_multiple_items_common(collection, sender, data, nesting_budget)573 }574575 fn set_allowance_unchecked(576 collection: &FungibleHandle<T>,577 owner: &T::CrossAccountId,578 spender: &T::CrossAccountId,579 amount: u128,580 ) {581 if amount == 0 {582 <Allowance<T>>::remove((collection.id, owner, spender));583 } else {584 <Allowance<T>>::insert((collection.id, owner, spender), amount);585 }586587 <PalletEvm<T>>::deposit_log(588 ERC20Events::Approval {589 owner: *owner.as_eth(),590 spender: *spender.as_eth(),591 value: amount.into(),592 }593 .to_log(collection_id_to_address(collection.id)),594 );595 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(596 collection.id,597 TokenId(0),598 owner.clone(),599 spender.clone(),600 amount,601 ));602 }603604 /// Set allowance for the spender to `transfer` or `burn` owner's tokens.605 ///606 /// - `collection`: Collection that contains the token607 /// - `owner`: Owner of tokens that sets the allowance.608 /// - `spender`: Recipient of the allowance rights.609 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.610 pub fn set_allowance(611 collection: &FungibleHandle<T>,612 owner: &T::CrossAccountId,613 spender: &T::CrossAccountId,614 amount: u128,615 ) -> DispatchResult {616 if collection.permissions.access() == AccessMode::AllowList {617 collection.check_allowlist(owner)?;618 collection.check_allowlist(spender)?;619 }620621 if <Balance<T>>::get((collection.id, owner)) < amount {622 ensure!(623 collection.ignores_owned_amount(owner),624 <CommonError<T>>::CantApproveMoreThanOwned625 );626 }627628 // =========629630 Self::set_allowance_unchecked(collection, owner, spender, amount);631 Ok(())632 }633634 /// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.635 ///636 /// - `collection`: Collection that contains the token637 /// - `sender`: Owner of tokens that sets the allowance.638 /// - `from`: Owner's eth mirror.639 /// - `to`: Recipient of the allowance rights.640 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.641 pub fn set_allowance_from(642 collection: &FungibleHandle<T>,643 sender: &T::CrossAccountId,644 from: &T::CrossAccountId,645 to: &T::CrossAccountId,646 amount: u128,647 ) -> DispatchResult {648 if collection.permissions.access() == AccessMode::AllowList {649 collection.check_allowlist(sender)?;650 collection.check_allowlist(from)?;651 collection.check_allowlist(to)?;652 }653654 ensure!(655 sender.conv_eq(from),656 <CommonError<T>>::AddressIsNotEthMirror657 );658659 if <Balance<T>>::get((collection.id, from)) < amount {660 ensure!(661 collection.limits.owner_can_transfer()662 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),663 <CommonError<T>>::CantApproveMoreThanOwned664 );665 }666667 // =========668669 Self::set_allowance_unchecked(collection, from, to, amount);670 Ok(())671 }672673 /// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.674 /// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.675 ///676 /// - `collection`: Collection that contains the token.677 /// - `spender`: CrossAccountId who has the allowance rights.678 /// - `from`: The owner of the tokens who sets the allowance.679 /// - `amount`: Amount of tokens by which the allowance sholud be reduced.680 fn check_allowed(681 collection: &FungibleHandle<T>,682 spender: &T::CrossAccountId,683 from: &T::CrossAccountId,684 amount: u128,685 nesting_budget: &dyn Budget,686 ) -> Result<Option<u128>, DispatchError> {687 if spender.conv_eq(from) {688 return Ok(None);689 }690 if collection.permissions.access() == AccessMode::AllowList {691 // `from`, `to` checked in [`transfer`]692 collection.check_allowlist(spender)?;693 }694695 if collection.ignores_token_restrictions(spender) {696 return Ok(Self::compute_allowance_decrease(697 collection, from, spender, amount,698 ));699 }700701 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {702 ensure!(703 <PalletStructure<T>>::check_indirectly_owned(704 spender.clone(),705 source.0,706 source.1,707 None,708 nesting_budget709 )?,710 <CommonError<T>>::ApprovedValueTooLow,711 );712 return Ok(None);713 }714715 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);716 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);717718 Ok(allowance)719 }720721 /// Returns `Some(amount)` if the `spender` have allowance to spend this amount.722 /// Otherwise, it returns `None`.723 fn compute_allowance_decrease(724 collection: &FungibleHandle<T>,725 from: &T::CrossAccountId,726 spender: &T::CrossAccountId,727 amount: u128,728 ) -> Option<u128> {729 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)730 }731732 /// Transfer fungible tokens from one account to another.733 /// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.734 /// The owner should set allowance for the spender to transfer pieces.735 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.736 pub fn transfer_from(737 collection: &FungibleHandle<T>,738 spender: &T::CrossAccountId,739 from: &T::CrossAccountId,740 to: &T::CrossAccountId,741 amount: u128,742 nesting_budget: &dyn Budget,743 ) -> DispatchResultWithPostInfo {744 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;745746 // =========747748 let mut result =749 Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);750 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());751 result?;752753 if let Some(allowance) = allowance {754 Self::set_allowance_unchecked(collection, from, spender, allowance);755 add_weight_to_post_info(756 &mut result,757 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),758 )759 }760 result761 }762763 /// Burn fungible tokens from the account.764 ///765 /// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should766 /// set allowance for the spender to burn tokens.767 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.768 pub fn burn_from(769 collection: &FungibleHandle<T>,770 spender: &T::CrossAccountId,771 from: &T::CrossAccountId,772 amount: u128,773 nesting_budget: &dyn Budget,774 ) -> DispatchResult {775 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;776777 // =========778779 Self::burn(collection, from, amount)?;780 if let Some(allowance) = allowance {781 Self::set_allowance_unchecked(collection, from, spender, allowance);782 }783 Ok(())784 }785786 /// Creates fungible token.787 ///788 /// The sender should be the owner/admin of the collection or collection should be configured789 /// to allow public minting.790 ///791 /// - `data`: Contains user who will become the owners of the tokens and amount792 /// of tokens he will receive.793 pub fn create_item(794 collection: &FungibleHandle<T>,795 sender: &T::CrossAccountId,796 data: CreateItemData<T>,797 nesting_budget: &dyn Budget,798 ) -> DispatchResult {799 Self::create_multiple_items(800 collection,801 sender,802 [(data.0, data.1)].into_iter().collect(),803 nesting_budget,804 )805 }806807 /// Creates fungible token.808 ///809 /// - `data`: Contains user who will become the owners of the tokens and amount810 /// of tokens he will receive.811 pub fn create_item_foreign(812 collection: &FungibleHandle<T>,813 sender: &T::CrossAccountId,814 data: CreateItemData<T>,815 nesting_budget: &dyn Budget,816 ) -> DispatchResult {817 Self::create_multiple_items_foreign(818 collection,819 sender,820 [(data.0, data.1)].into_iter().collect(),821 nesting_budget,822 )823 }824825 /// Returns 10 tokens owners in no particular order826 ///827 /// There is no direct way to get token holders in ascending order,828 /// since `iter_prefix` returns values in no particular order.829 /// Therefore, getting the 10 largest holders with a large value of holders830 /// can lead to impact memory allocation + sorting with `n * log (n)`.831 pub fn token_owners(832 collection: CollectionId,833 _token: TokenId,834 ) -> Option<Vec<T::CrossAccountId>> {835 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))836 .map(|(owner, _amount)| owner)837 .take(10)838 .collect();839840 if res.is_empty() {841 None842 } else {843 Some(res)844 }845 }846}