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 /// 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 /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.216 pub fn init_collection(217 owner: T::CrossAccountId,218 payer: T::CrossAccountId,219 data: CreateCollectionData<T::CrossAccountId>,220 ) -> Result<CollectionId, DispatchError> {221 <PalletCommon<T>>::init_collection(owner, payer, data)222 }223224 /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.225 pub fn init_foreign_collection(226 owner: T::CrossAccountId,227 payer: T::CrossAccountId,228 data: CreateCollectionData<T::CrossAccountId>,229 ) -> Result<CollectionId, DispatchError> {230 <PalletCommon<T>>::init_foreign_collection(owner, payer, data)231 }232233 /// Destroys a collection.234 pub fn destroy_collection(235 collection: FungibleHandle<T>,236 sender: &T::CrossAccountId,237 ) -> DispatchResult {238 let id = collection.id;239240 if Self::collection_has_tokens(id) {241 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());242 }243244 // =========245246 PalletCommon::destroy_collection(collection.0, sender)?;247248 <TotalSupply<T>>::remove(id);249 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);250 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);251 Ok(())252 }253254 /// Add properties to the collection.255 pub fn set_collection_properties(256 collection: &FungibleHandle<T>,257 sender: &T::CrossAccountId,258 properties: Vec<Property>,259 ) -> DispatchResult {260 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())261 }262263 /// Delete properties of the collection, associated with the provided keys.264 pub fn delete_collection_properties(265 collection: &FungibleHandle<T>,266 sender: &T::CrossAccountId,267 property_keys: Vec<PropertyKey>,268 ) -> DispatchResult {269 <PalletCommon<T>>::delete_collection_properties(270 collection,271 sender,272 property_keys.into_iter(),273 )274 }275276 /// Checks if collection has tokens. Return `true` if it has.277 fn collection_has_tokens(collection_id: CollectionId) -> bool {278 <TotalSupply<T>>::get(collection_id) != 0279 }280281 /// Burns the specified amount of the token. If the token balance282 /// or total supply is less than the given value,283 /// it will return [DispatchError].284 pub fn burn(285 collection: &FungibleHandle<T>,286 owner: &T::CrossAccountId,287 amount: u128,288 ) -> DispatchResult {289 let total_supply = <TotalSupply<T>>::get(collection.id)290 .checked_sub(amount)291 .ok_or(<CommonError<T>>::TokenValueTooLow)?;292293 let balance = <Balance<T>>::get((collection.id, owner))294 .checked_sub(amount)295 .ok_or(<CommonError<T>>::TokenValueTooLow)?;296297 // Foreign collection check298 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);299300 if collection.permissions.access() == AccessMode::AllowList {301 collection.check_allowlist(owner)?;302 }303304 // =========305306 if balance == 0 {307 <Balance<T>>::remove((collection.id, owner));308 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());309 } else {310 <Balance<T>>::insert((collection.id, owner), balance);311 }312 <TotalSupply<T>>::insert(collection.id, total_supply);313314 <PalletEvm<T>>::deposit_log(315 ERC20Events::Transfer {316 from: *owner.as_eth(),317 to: H160::default(),318 value: amount.into(),319 }320 .to_log(collection_id_to_address(collection.id)),321 );322 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(323 collection.id,324 TokenId::default(),325 owner.clone(),326 amount,327 ));328 Ok(())329 }330331 /// Burns the specified amount of the token.332 pub fn burn_foreign(333 collection: &FungibleHandle<T>,334 owner: &T::CrossAccountId,335 amount: u128,336 ) -> DispatchResult {337 let total_supply = <TotalSupply<T>>::get(collection.id)338 .checked_sub(amount)339 .ok_or(<CommonError<T>>::TokenValueTooLow)?;340341 let balance = <Balance<T>>::get((collection.id, owner))342 .checked_sub(amount)343 .ok_or(<CommonError<T>>::TokenValueTooLow)?;344 // =========345346 if balance == 0 {347 <Balance<T>>::remove((collection.id, owner));348 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());349 } else {350 <Balance<T>>::insert((collection.id, owner), balance);351 }352 <TotalSupply<T>>::insert(collection.id, total_supply);353354 <PalletEvm<T>>::deposit_log(355 ERC20Events::Transfer {356 from: *owner.as_eth(),357 to: H160::default(),358 value: amount.into(),359 }360 .to_log(collection_id_to_address(collection.id)),361 );362 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(363 collection.id,364 TokenId::default(),365 owner.clone(),366 amount,367 ));368 Ok(())369 }370371 /// Transfers the specified amount of tokens. Will check that372 /// the transfer is allowed for the token.373 ///374 /// - `from`: Owner of tokens to transfer.375 /// - `to`: Recepient of transfered tokens.376 /// - `amount`: Amount of tokens to transfer.377 /// - `collection`: Collection that contains the token378 pub fn transfer(379 collection: &FungibleHandle<T>,380 from: &T::CrossAccountId,381 to: &T::CrossAccountId,382 amount: u128,383 nesting_budget: &dyn Budget,384 ) -> DispatchResultWithPostInfo {385 let depositor = from;386 Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)387 }388389 /// Transfers tokens from the `from` account to the `to` account.390 /// The `depositor` is the account who deposits the tokens.391 /// For instance, the nesting rules will be checked against the `depositor`'s permissions.392 fn transfer_internal(393 collection: &FungibleHandle<T>,394 depositor: &T::CrossAccountId,395 from: &T::CrossAccountId,396 to: &T::CrossAccountId,397 amount: u128,398 nesting_budget: &dyn Budget,399 ) -> DispatchResultWithPostInfo {400 ensure!(401 collection.limits.transfers_enabled(),402 <CommonError<T>>::TransferNotAllowed,403 );404405 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();406407 if collection.permissions.access() == AccessMode::AllowList {408 collection.check_allowlist(from)?;409 collection.check_allowlist(to)?;410 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;411 }412 <PalletCommon<T>>::ensure_correct_receiver(to)?;413 let balance_from = <Balance<T>>::get((collection.id, from))414 .checked_sub(amount)415 .ok_or(<CommonError<T>>::TokenValueTooLow)?;416 let balance_to = if from != to && amount != 0 {417 Some(418 <Balance<T>>::get((collection.id, to))419 .checked_add(amount)420 .ok_or(ArithmeticError::Overflow)?,421 )422 } else {423 None424 };425426 // =========427428 if let Some(balance_to) = balance_to {429 // from != to && amount != 0430431 <PalletStructure<T>>::nest_if_sent_to_token(432 depositor,433 to,434 collection.id,435 TokenId::default(),436 nesting_budget,437 )?;438439 if balance_from == 0 {440 <Balance<T>>::remove((collection.id, from));441 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());442 } else {443 <Balance<T>>::insert((collection.id, from), balance_from);444 }445 <Balance<T>>::insert((collection.id, to), balance_to);446 }447448 <PalletEvm<T>>::deposit_log(449 ERC20Events::Transfer {450 from: *from.as_eth(),451 to: *to.as_eth(),452 value: amount.into(),453 }454 .to_log(collection_id_to_address(collection.id)),455 );456 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(457 collection.id,458 TokenId::default(),459 from.clone(),460 to.clone(),461 amount,462 ));463464 Ok(PostDispatchInfo {465 actual_weight: Some(actual_weight),466 pays_fee: Pays::Yes,467 })468 }469470 /// Minting tokens for multiple IDs.471 /// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]472 /// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]473 pub fn create_multiple_items_common(474 collection: &FungibleHandle<T>,475 sender: &T::CrossAccountId,476 data: BTreeMap<T::CrossAccountId, u128>,477 nesting_budget: &dyn Budget,478 ) -> DispatchResult {479 let total_supply = data480 .values()481 .copied()482 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {483 acc.checked_add(v)484 })485 .ok_or(ArithmeticError::Overflow)?;486487 for (to, _) in data.iter() {488 <PalletStructure<T>>::check_nesting(489 sender,490 to,491 collection.id,492 TokenId::default(),493 nesting_budget,494 )?;495 }496497 let updated_balances = data498 .into_iter()499 .map(|(user, amount)| {500 let updated_balance = <Balance<T>>::get((collection.id, &user))501 .checked_add(amount)502 .ok_or(ArithmeticError::Overflow)?;503 Ok((user, amount, updated_balance))504 })505 .collect::<Result<Vec<_>, DispatchError>>()?;506507 // =========508509 <TotalSupply<T>>::insert(collection.id, total_supply);510 for (user, amount, updated_balance) in updated_balances {511 <Balance<T>>::insert((collection.id, &user), updated_balance);512 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(513 &user,514 collection.id,515 TokenId::default(),516 );517 <PalletEvm<T>>::deposit_log(518 ERC20Events::Transfer {519 from: H160::default(),520 to: *user.as_eth(),521 value: amount.into(),522 }523 .to_log(collection_id_to_address(collection.id)),524 );525 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(526 collection.id,527 TokenId::default(),528 user.clone(),529 amount,530 ));531 }532533 Ok(())534 }535536 /// Minting tokens for multiple IDs.537 /// See [`create_item`][`Pallet::create_item`] for more details.538 pub fn create_multiple_items(539 collection: &FungibleHandle<T>,540 sender: &T::CrossAccountId,541 data: BTreeMap<T::CrossAccountId, u128>,542 nesting_budget: &dyn Budget,543 ) -> DispatchResult {544 // Foreign collection check545 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);546547 if !collection.is_owner_or_admin(sender) {548 ensure!(549 collection.permissions.mint_mode(),550 <CommonError<T>>::PublicMintingNotAllowed551 );552 collection.check_allowlist(sender)?;553554 for (owner, _) in data.iter() {555 collection.check_allowlist(owner)?;556 }557 }558559 Self::create_multiple_items_common(collection, sender, data, nesting_budget)560 }561562 /// Minting tokens for multiple IDs.563 /// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.564 pub fn create_multiple_items_foreign(565 collection: &FungibleHandle<T>,566 sender: &T::CrossAccountId,567 data: BTreeMap<T::CrossAccountId, u128>,568 nesting_budget: &dyn Budget,569 ) -> DispatchResult {570 Self::create_multiple_items_common(collection, sender, data, nesting_budget)571 }572573 fn set_allowance_unchecked(574 collection: &FungibleHandle<T>,575 owner: &T::CrossAccountId,576 spender: &T::CrossAccountId,577 amount: u128,578 ) {579 if amount == 0 {580 <Allowance<T>>::remove((collection.id, owner, spender));581 } else {582 <Allowance<T>>::insert((collection.id, owner, spender), amount);583 }584585 <PalletEvm<T>>::deposit_log(586 ERC20Events::Approval {587 owner: *owner.as_eth(),588 spender: *spender.as_eth(),589 value: amount.into(),590 }591 .to_log(collection_id_to_address(collection.id)),592 );593 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(594 collection.id,595 TokenId(0),596 owner.clone(),597 spender.clone(),598 amount,599 ));600 }601602 /// Set allowance for the spender to `transfer` or `burn` owner's tokens.603 ///604 /// - `collection`: Collection that contains the token605 /// - `owner`: Owner of tokens that sets the allowance.606 /// - `spender`: Recipient of the allowance rights.607 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.608 pub fn set_allowance(609 collection: &FungibleHandle<T>,610 owner: &T::CrossAccountId,611 spender: &T::CrossAccountId,612 amount: u128,613 ) -> DispatchResult {614 if collection.permissions.access() == AccessMode::AllowList {615 collection.check_allowlist(owner)?;616 collection.check_allowlist(spender)?;617 }618619 if <Balance<T>>::get((collection.id, owner)) < amount {620 ensure!(621 collection.ignores_owned_amount(owner),622 <CommonError<T>>::CantApproveMoreThanOwned623 );624 }625626 // =========627628 Self::set_allowance_unchecked(collection, owner, spender, amount);629 Ok(())630 }631632 /// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.633 ///634 /// - `collection`: Collection that contains the token635 /// - `sender`: Owner of tokens that sets the allowance.636 /// - `from`: Owner's eth mirror.637 /// - `to`: Recipient of the allowance rights.638 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.639 pub fn set_allowance_from(640 collection: &FungibleHandle<T>,641 sender: &T::CrossAccountId,642 from: &T::CrossAccountId,643 to: &T::CrossAccountId,644 amount: u128,645 ) -> DispatchResult {646 if collection.permissions.access() == AccessMode::AllowList {647 collection.check_allowlist(sender)?;648 collection.check_allowlist(from)?;649 collection.check_allowlist(to)?;650 }651652 ensure!(653 sender.conv_eq(from),654 <CommonError<T>>::AddressIsNotEthMirror655 );656657 if <Balance<T>>::get((collection.id, from)) < amount {658 ensure!(659 collection.limits.owner_can_transfer()660 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),661 <CommonError<T>>::CantApproveMoreThanOwned662 );663 }664665 // =========666667 Self::set_allowance_unchecked(collection, from, to, amount);668 Ok(())669 }670671 /// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.672 /// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.673 ///674 /// - `collection`: Collection that contains the token.675 /// - `spender`: CrossAccountId who has the allowance rights.676 /// - `from`: The owner of the tokens who sets the allowance.677 /// - `amount`: Amount of tokens by which the allowance sholud be reduced.678 fn check_allowed(679 collection: &FungibleHandle<T>,680 spender: &T::CrossAccountId,681 from: &T::CrossAccountId,682 amount: u128,683 nesting_budget: &dyn Budget,684 ) -> Result<Option<u128>, DispatchError> {685 if spender.conv_eq(from) {686 return Ok(None);687 }688 if collection.permissions.access() == AccessMode::AllowList {689 // `from`, `to` checked in [`transfer`]690 collection.check_allowlist(spender)?;691 }692693 if collection.ignores_token_restrictions(spender) {694 return Ok(Self::compute_allowance_decrease(695 collection, from, spender, amount,696 ));697 }698699 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {700 ensure!(701 <PalletStructure<T>>::check_indirectly_owned(702 spender.clone(),703 source.0,704 source.1,705 None,706 nesting_budget707 )?,708 <CommonError<T>>::ApprovedValueTooLow,709 );710 return Ok(None);711 }712713 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);714 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);715716 Ok(allowance)717 }718719 /// Returns `Some(amount)` if the `spender` have allowance to spend this amount.720 /// Otherwise, it returns `None`.721 fn compute_allowance_decrease(722 collection: &FungibleHandle<T>,723 from: &T::CrossAccountId,724 spender: &T::CrossAccountId,725 amount: u128,726 ) -> Option<u128> {727 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)728 }729730 /// Transfer fungible tokens from one account to another.731 /// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.732 /// The owner should set allowance for the spender to transfer pieces.733 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.734 pub fn transfer_from(735 collection: &FungibleHandle<T>,736 spender: &T::CrossAccountId,737 from: &T::CrossAccountId,738 to: &T::CrossAccountId,739 amount: u128,740 nesting_budget: &dyn Budget,741 ) -> DispatchResultWithPostInfo {742 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;743744 // =========745746 let mut result =747 Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);748 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());749 result?;750751 if let Some(allowance) = allowance {752 Self::set_allowance_unchecked(collection, from, spender, allowance);753 add_weight_to_post_info(754 &mut result,755 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),756 )757 }758 result759 }760761 /// Burn fungible tokens from the account.762 ///763 /// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should764 /// set allowance for the spender to burn tokens.765 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.766 pub fn burn_from(767 collection: &FungibleHandle<T>,768 spender: &T::CrossAccountId,769 from: &T::CrossAccountId,770 amount: u128,771 nesting_budget: &dyn Budget,772 ) -> DispatchResult {773 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;774775 // =========776777 Self::burn(collection, from, amount)?;778 if let Some(allowance) = allowance {779 Self::set_allowance_unchecked(collection, from, spender, allowance);780 }781 Ok(())782 }783784 /// Creates fungible token.785 ///786 /// The sender should be the owner/admin of the collection or collection should be configured787 /// to allow public minting.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(792 collection: &FungibleHandle<T>,793 sender: &T::CrossAccountId,794 data: CreateItemData<T>,795 nesting_budget: &dyn Budget,796 ) -> DispatchResult {797 Self::create_multiple_items(798 collection,799 sender,800 [(data.0, data.1)].into_iter().collect(),801 nesting_budget,802 )803 }804805 /// Creates fungible token.806 ///807 /// - `data`: Contains user who will become the owners of the tokens and amount808 /// of tokens he will receive.809 pub fn create_item_foreign(810 collection: &FungibleHandle<T>,811 sender: &T::CrossAccountId,812 data: CreateItemData<T>,813 nesting_budget: &dyn Budget,814 ) -> DispatchResult {815 Self::create_multiple_items_foreign(816 collection,817 sender,818 [(data.0, data.1)].into_iter().collect(),819 nesting_budget,820 )821 }822823 /// Returns 10 tokens owners in no particular order824 ///825 /// There is no direct way to get token holders in ascending order,826 /// since `iter_prefix` returns values in no particular order.827 /// Therefore, getting the 10 largest holders with a large value of holders828 /// can lead to impact memory allocation + sorting with `n * log (n)`.829 pub fn token_owners(830 collection: CollectionId,831 _token: TokenId,832 ) -> Option<Vec<T::CrossAccountId>> {833 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))834 .map(|(owner, _amount)| owner)835 .take(10)836 .collect();837838 if res.is_empty() {839 None840 } else {841 Some(res)842 }843 }844}