difftreelog
doc(fungible-pallet): added new details & fix
in: master
1 file changed
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):75//! - [`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;82use evm_coder::ToLog;83use frame_support::{ensure};84use pallet_evm::account::CrossAccountId;85use up_data_structs::{86 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,87 budget::Budget,88};89use pallet_common::{90 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,91 eth::collection_id_to_address,92};93use pallet_evm::Pallet as PalletEvm;94use pallet_structure::Pallet as PalletStructure;95use pallet_evm_coder_substrate::WithRecorder;96use sp_core::H160;97use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};98use sp_std::{collections::btree_map::BTreeMap};99100pub use pallet::*;101102use crate::erc::ERC20Events;103#[cfg(feature = "runtime-benchmarks")]104pub mod benchmarking;105pub mod common;106pub mod erc;107pub mod weights;108109pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);110pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;111112#[frame_support::pallet]113pub mod pallet {114 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};115 use up_data_structs::CollectionId;116 use super::weights::WeightInfo;117118 #[pallet::error]119 pub enum Error<T> {120 /// Not Fungible item data used to mint in Fungible collection.121 NotFungibleDataUsedToMintFungibleCollectionToken,122 /// Not default id passed as TokenId argument.123 /// The default value of TokenId for Fungible collection is 0.124 FungibleItemsHaveNoId,125 /// Tried to set data for fungible item.126 FungibleItemsDontHaveData,127 /// Fungible token does not support nesting.128 FungibleDisallowsNesting,129 /// Setting item properties is not allowed.130 SettingPropertiesNotAllowed,131 }132133 #[pallet::config]134 pub trait Config:135 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config136 {137 type WeightInfo: WeightInfo;138 }139140 #[pallet::pallet]141 #[pallet::generate_store(pub(super) trait Store)]142 pub struct Pallet<T>(_);143144 /// Total amount of fungible tokens inside a collection.145 #[pallet::storage]146 pub type TotalSupply<T: Config> =147 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;148149 /// Amount of tokens owned by an account inside a collection.150 #[pallet::storage]151 pub type Balance<T: Config> = StorageNMap<152 Key = (153 Key<Twox64Concat, CollectionId>,154 Key<Blake2_128Concat, T::CrossAccountId>,155 ),156 Value = u128,157 QueryKind = ValueQuery,158 >;159160 /// Storage for delegated assets.161 #[pallet::storage]162 pub type Allowance<T: Config> = StorageNMap<163 Key = (164 Key<Twox64Concat, CollectionId>,165 Key<Blake2_128, T::CrossAccountId>,166 Key<Blake2_128Concat, T::CrossAccountId>,167 ),168 Value = u128,169 QueryKind = ValueQuery,170 >;171}172/// Handler for fungible assets.173pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);174impl<T: Config> FungibleHandle<T> {175 /// Casts [pallet_common::CollectionHandle] into [FungibleHandle].176 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {177 Self(inner)178 }179180 /// Casts [FungibleHandle] into [pallet_common::CollectionHandle].181 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {182 self.0183 }184 /// Returns a mutable reference to the internal [pallet_common::CollectionHandle].185 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {186 &mut self.0187 }188}189impl<T: Config> WithRecorder<T> for FungibleHandle<T> {190 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {191 self.0.recorder()192 }193 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {194 self.0.into_recorder()195 }196}197impl<T: Config> Deref for FungibleHandle<T> {198 type Target = pallet_common::CollectionHandle<T>;199200 fn deref(&self) -> &Self::Target {201 &self.0202 }203}204205/// Pallet implementation for fungible assets206impl<T: Config> Pallet<T> {207 /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.208 pub fn init_collection(209 owner: T::CrossAccountId,210 data: CreateCollectionData<T::AccountId>,211 ) -> Result<CollectionId, DispatchError> {212 <PalletCommon<T>>::init_collection(owner, data, false)213 }214215 /// 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 <Balance<T>>::remove_prefix((id,), None);232 <Allowance<T>>::remove_prefix((id,), None);233 Ok(())234 }235236 ///Checks if collection has tokens. Return `true` if it has.237 fn collection_has_tokens(collection_id: CollectionId) -> bool {238 <TotalSupply<T>>::get(collection_id) != 0239 }240241 /// Burns the specified amount of the token. If the token balance242 /// or total supply is less than the given value,243 /// it will return [DispatchError].244 pub fn burn(245 collection: &FungibleHandle<T>,246 owner: &T::CrossAccountId,247 amount: u128,248 ) -> DispatchResult {249 let total_supply = <TotalSupply<T>>::get(collection.id)250 .checked_sub(amount)251 .ok_or(<CommonError<T>>::TokenValueTooLow)?;252253 let balance = <Balance<T>>::get((collection.id, owner))254 .checked_sub(amount)255 .ok_or(<CommonError<T>>::TokenValueTooLow)?;256257 if collection.permissions.access() == AccessMode::AllowList {258 collection.check_allowlist(owner)?;259 }260261 // =========262263 if balance == 0 {264 <Balance<T>>::remove((collection.id, owner));265 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());266 } else {267 <Balance<T>>::insert((collection.id, owner), balance);268 }269 <TotalSupply<T>>::insert(collection.id, total_supply);270271 <PalletEvm<T>>::deposit_log(272 ERC20Events::Transfer {273 from: *owner.as_eth(),274 to: H160::default(),275 value: amount.into(),276 }277 .to_log(collection_id_to_address(collection.id)),278 );279 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(280 collection.id,281 TokenId::default(),282 owner.clone(),283 amount,284 ));285 Ok(())286 }287288 /// Transfers the specified amount of tokens. Will check that289 /// the transfer is allowed for the token.290 pub fn transfer(291 collection: &FungibleHandle<T>,292 from: &T::CrossAccountId,293 to: &T::CrossAccountId,294 amount: u128,295 nesting_budget: &dyn Budget,296 ) -> DispatchResult {297 ensure!(298 collection.limits.transfers_enabled(),299 <CommonError<T>>::TransferNotAllowed,300 );301302 if collection.permissions.access() == AccessMode::AllowList {303 collection.check_allowlist(from)?;304 collection.check_allowlist(to)?;305 }306 <PalletCommon<T>>::ensure_correct_receiver(to)?;307308 let balance_from = <Balance<T>>::get((collection.id, from))309 .checked_sub(amount)310 .ok_or(<CommonError<T>>::TokenValueTooLow)?;311 let balance_to = if from != to {312 Some(313 <Balance<T>>::get((collection.id, to))314 .checked_add(amount)315 .ok_or(ArithmeticError::Overflow)?,316 )317 } else {318 None319 };320321 // =========322323 <PalletStructure<T>>::nest_if_sent_to_token(324 from.clone(),325 to,326 collection.id,327 TokenId::default(),328 nesting_budget,329 )?;330331 if let Some(balance_to) = balance_to {332 // from != to333 if balance_from == 0 {334 <Balance<T>>::remove((collection.id, from));335 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());336 } else {337 <Balance<T>>::insert((collection.id, from), balance_from);338 }339 <Balance<T>>::insert((collection.id, to), balance_to);340 }341342 <PalletEvm<T>>::deposit_log(343 ERC20Events::Transfer {344 from: *from.as_eth(),345 to: *to.as_eth(),346 value: amount.into(),347 }348 .to_log(collection_id_to_address(collection.id)),349 );350 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(351 collection.id,352 TokenId::default(),353 from.clone(),354 to.clone(),355 amount,356 ));357 Ok(())358 }359360 /// Minting tokens for multiple IDs.361 pub fn create_multiple_items(362 collection: &FungibleHandle<T>,363 sender: &T::CrossAccountId,364 data: BTreeMap<T::CrossAccountId, u128>,365 nesting_budget: &dyn Budget,366 ) -> DispatchResult {367 if !collection.is_owner_or_admin(sender) {368 ensure!(369 collection.permissions.mint_mode(),370 <CommonError<T>>::PublicMintingNotAllowed371 );372 collection.check_allowlist(sender)?;373374 for (owner, _) in data.iter() {375 collection.check_allowlist(owner)?;376 }377 }378379 let total_supply = data380 .iter()381 .map(|(_, v)| *v)382 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {383 acc.checked_add(v)384 })385 .ok_or(ArithmeticError::Overflow)?;386387 let mut balances = data;388 for (k, v) in balances.iter_mut() {389 *v = <Balance<T>>::get((collection.id, &k))390 .checked_add(*v)391 .ok_or(ArithmeticError::Overflow)?;392 }393394 for (to, _) in balances.iter() {395 <PalletStructure<T>>::check_nesting(396 sender.clone(),397 to,398 collection.id,399 TokenId::default(),400 nesting_budget,401 )?;402 }403404 // =========405406 <TotalSupply<T>>::insert(collection.id, total_supply);407 for (user, amount) in balances {408 <Balance<T>>::insert((collection.id, &user), amount);409 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(410 &user,411 collection.id,412 TokenId::default(),413 );414 <PalletEvm<T>>::deposit_log(415 ERC20Events::Transfer {416 from: H160::default(),417 to: *user.as_eth(),418 value: amount.into(),419 }420 .to_log(collection_id_to_address(collection.id)),421 );422 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(423 collection.id,424 TokenId::default(),425 user.clone(),426 amount,427 ));428 }429430 Ok(())431 }432433 fn set_allowance_unchecked(434 collection: &FungibleHandle<T>,435 owner: &T::CrossAccountId,436 spender: &T::CrossAccountId,437 amount: u128,438 ) {439 if amount == 0 {440 <Allowance<T>>::remove((collection.id, owner, spender));441 } else {442 <Allowance<T>>::insert((collection.id, owner, spender), amount);443 }444445 <PalletEvm<T>>::deposit_log(446 ERC20Events::Approval {447 owner: *owner.as_eth(),448 spender: *spender.as_eth(),449 value: amount.into(),450 }451 .to_log(collection_id_to_address(collection.id)),452 );453 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(454 collection.id,455 TokenId(0),456 owner.clone(),457 spender.clone(),458 amount,459 ));460 }461462 /// Sets the amount of owner tokens that the spender can manage.463 pub fn set_allowance(464 collection: &FungibleHandle<T>,465 owner: &T::CrossAccountId,466 spender: &T::CrossAccountId,467 amount: u128,468 ) -> DispatchResult {469 if collection.permissions.access() == AccessMode::AllowList {470 collection.check_allowlist(owner)?;471 collection.check_allowlist(spender)?;472 }473474 if <Balance<T>>::get((collection.id, owner)) < amount {475 ensure!(476 collection.ignores_owned_amount(owner),477 <CommonError<T>>::CantApproveMoreThanOwned478 );479 }480481 // =========482483 Self::set_allowance_unchecked(collection, owner, spender, amount);484 Ok(())485 }486487 fn check_allowed(488 collection: &FungibleHandle<T>,489 spender: &T::CrossAccountId,490 from: &T::CrossAccountId,491 amount: u128,492 nesting_budget: &dyn Budget,493 ) -> Result<Option<u128>, DispatchError> {494 if spender.conv_eq(from) {495 return Ok(None);496 }497 if collection.permissions.access() == AccessMode::AllowList {498 // `from`, `to` checked in [`transfer`]499 collection.check_allowlist(spender)?;500 }501 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {502 // TODO: should collection owner be allowed to perform this transfer?503 ensure!(504 <PalletStructure<T>>::check_indirectly_owned(505 spender.clone(),506 source.0,507 source.1,508 None,509 nesting_budget510 )?,511 <CommonError<T>>::ApprovedValueTooLow,512 );513 return Ok(None);514 }515 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);516 if allowance.is_none() {517 ensure!(518 collection.ignores_allowance(spender),519 <CommonError<T>>::ApprovedValueTooLow520 );521 }522523 Ok(allowance)524 }525526 /// Transfers of tokens that `from` gave to `spender` to manage, to `to` ID.527 pub fn transfer_from(528 collection: &FungibleHandle<T>,529 spender: &T::CrossAccountId,530 from: &T::CrossAccountId,531 to: &T::CrossAccountId,532 amount: u128,533 nesting_budget: &dyn Budget,534 ) -> DispatchResult {535 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;536537 // =========538539 Self::transfer(collection, from, to, amount, nesting_budget)?;540 if let Some(allowance) = allowance {541 Self::set_allowance_unchecked(collection, from, spender, allowance);542 }543 Ok(())544 }545546 /// Burns managed tokens.547 pub fn burn_from(548 collection: &FungibleHandle<T>,549 spender: &T::CrossAccountId,550 from: &T::CrossAccountId,551 amount: u128,552 nesting_budget: &dyn Budget,553 ) -> DispatchResult {554 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;555556 // =========557558 Self::burn(collection, from, amount)?;559 if let Some(allowance) = allowance {560 Self::set_allowance_unchecked(collection, from, spender, allowance);561 }562 Ok(())563 }564565 /// Delegated to `create_multiple_items`566 pub fn create_item(567 collection: &FungibleHandle<T>,568 sender: &T::CrossAccountId,569 data: CreateItemData<T>,570 nesting_budget: &dyn Budget,571 ) -> DispatchResult {572 Self::create_multiple_items(573 collection,574 sender,575 [(data.0, data.1)].into_iter().collect(),576 nesting_budget,577 )578 }579}1// 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;82use evm_coder::ToLog;83use frame_support::{ensure};84use pallet_evm::account::CrossAccountId;85use up_data_structs::{86 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,87 budget::Budget,88};89use pallet_common::{90 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,91 eth::collection_id_to_address,92};93use pallet_evm::Pallet as PalletEvm;94use pallet_structure::Pallet as PalletStructure;95use pallet_evm_coder_substrate::WithRecorder;96use sp_core::H160;97use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};98use sp_std::{collections::btree_map::BTreeMap};99100pub use pallet::*;101102use crate::erc::ERC20Events;103#[cfg(feature = "runtime-benchmarks")]104pub mod benchmarking;105pub mod common;106pub mod erc;107pub mod weights;108109pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);110pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;111112#[frame_support::pallet]113pub mod pallet {114 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};115 use up_data_structs::CollectionId;116 use super::weights::WeightInfo;117118 #[pallet::error]119 pub enum Error<T> {120 /// Not Fungible item data used to mint in Fungible collection.121 NotFungibleDataUsedToMintFungibleCollectionToken,122 /// Not default id passed as TokenId argument.123 /// The default value of TokenId for Fungible collection is 0.124 FungibleItemsHaveNoId,125 /// Tried to set data for fungible item.126 FungibleItemsDontHaveData,127 /// Fungible token does not support nesting.128 FungibleDisallowsNesting,129 /// Setting item properties is not allowed.130 SettingPropertiesNotAllowed,131 }132133 #[pallet::config]134 pub trait Config:135 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config136 {137 type WeightInfo: WeightInfo;138 }139140 #[pallet::pallet]141 #[pallet::generate_store(pub(super) trait Store)]142 pub struct Pallet<T>(_);143144 /// Total amount of fungible tokens inside a collection.145 #[pallet::storage]146 pub type TotalSupply<T: Config> =147 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;148149 /// Amount of tokens owned by an account inside a collection.150 #[pallet::storage]151 pub type Balance<T: Config> = StorageNMap<152 Key = (153 Key<Twox64Concat, CollectionId>,154 Key<Blake2_128Concat, T::CrossAccountId>,155 ),156 Value = u128,157 QueryKind = ValueQuery,158 >;159160 /// Storage for delegated assets.161 #[pallet::storage]162 pub type Allowance<T: Config> = StorageNMap<163 Key = (164 Key<Twox64Concat, CollectionId>,165 Key<Blake2_128, T::CrossAccountId>,166 Key<Blake2_128Concat, T::CrossAccountId>,167 ),168 Value = u128,169 QueryKind = ValueQuery,170 >;171}172173/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.174/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].175176pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);177178/// Implementation of methods required for dispatching during runtime.179impl<T: Config> FungibleHandle<T> {180 /// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].181 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {182 Self(inner)183 }184185 /// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].186 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {187 self.0188 }189 /// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].190 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {191 &mut self.0192 }193}194impl<T: Config> WithRecorder<T> for FungibleHandle<T> {195 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {196 self.0.recorder()197 }198 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {199 self.0.into_recorder()200 }201}202impl<T: Config> Deref for FungibleHandle<T> {203 type Target = pallet_common::CollectionHandle<T>;204205 fn deref(&self) -> &Self::Target {206 &self.0207 }208}209210/// Pallet implementation for fungible assets211impl<T: Config> Pallet<T> {212 /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.213 pub fn init_collection(214 owner: T::CrossAccountId,215 data: CreateCollectionData<T::AccountId>,216 ) -> Result<CollectionId, DispatchError> {217 <PalletCommon<T>>::init_collection(owner, data, false)218 }219220 /// Destroys a collection.221 pub fn destroy_collection(222 collection: FungibleHandle<T>,223 sender: &T::CrossAccountId,224 ) -> DispatchResult {225 let id = collection.id;226227 if Self::collection_has_tokens(id) {228 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());229 }230231 // =========232233 PalletCommon::destroy_collection(collection.0, sender)?;234235 <TotalSupply<T>>::remove(id);236 <Balance<T>>::remove_prefix((id,), None);237 <Allowance<T>>::remove_prefix((id,), None);238 Ok(())239 }240241 ///Checks if collection has tokens. Return `true` if it has.242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TotalSupply<T>>::get(collection_id) != 0244 }245246 /// Burns the specified amount of the token. If the token balance247 /// or total supply is less than the given value,248 /// it will return [DispatchError].249 pub fn burn(250 collection: &FungibleHandle<T>,251 owner: &T::CrossAccountId,252 amount: u128,253 ) -> DispatchResult {254 let total_supply = <TotalSupply<T>>::get(collection.id)255 .checked_sub(amount)256 .ok_or(<CommonError<T>>::TokenValueTooLow)?;257258 let balance = <Balance<T>>::get((collection.id, owner))259 .checked_sub(amount)260 .ok_or(<CommonError<T>>::TokenValueTooLow)?;261262 if collection.permissions.access() == AccessMode::AllowList {263 collection.check_allowlist(owner)?;264 }265266 // =========267268 if balance == 0 {269 <Balance<T>>::remove((collection.id, owner));270 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());271 } else {272 <Balance<T>>::insert((collection.id, owner), balance);273 }274 <TotalSupply<T>>::insert(collection.id, total_supply);275276 <PalletEvm<T>>::deposit_log(277 ERC20Events::Transfer {278 from: *owner.as_eth(),279 to: H160::default(),280 value: amount.into(),281 }282 .to_log(collection_id_to_address(collection.id)),283 );284 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(285 collection.id,286 TokenId::default(),287 owner.clone(),288 amount,289 ));290 Ok(())291 }292293 /// Transfers the specified amount of tokens. Will check that294 /// the transfer is allowed for the token.295 ///296 /// - `from`: Owner of tokens to transfer.297 /// - `to`: Recepient of transfered tokens.298 /// - `amount`: Amount of tokens to transfer.299 /// - `collection`: Collection that contains the token300 pub fn transfer(301 collection: &FungibleHandle<T>,302 from: &T::CrossAccountId,303 to: &T::CrossAccountId,304 amount: u128,305 nesting_budget: &dyn Budget,306 ) -> DispatchResult {307 ensure!(308 collection.limits.transfers_enabled(),309 <CommonError<T>>::TransferNotAllowed,310 );311312 if collection.permissions.access() == AccessMode::AllowList {313 collection.check_allowlist(from)?;314 collection.check_allowlist(to)?;315 }316 <PalletCommon<T>>::ensure_correct_receiver(to)?;317318 let balance_from = <Balance<T>>::get((collection.id, from))319 .checked_sub(amount)320 .ok_or(<CommonError<T>>::TokenValueTooLow)?;321 let balance_to = if from != to {322 Some(323 <Balance<T>>::get((collection.id, to))324 .checked_add(amount)325 .ok_or(ArithmeticError::Overflow)?,326 )327 } else {328 None329 };330331 // =========332333 <PalletStructure<T>>::nest_if_sent_to_token(334 from.clone(),335 to,336 collection.id,337 TokenId::default(),338 nesting_budget,339 )?;340341 if let Some(balance_to) = balance_to {342 // from != to343 if balance_from == 0 {344 <Balance<T>>::remove((collection.id, from));345 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());346 } else {347 <Balance<T>>::insert((collection.id, from), balance_from);348 }349 <Balance<T>>::insert((collection.id, to), balance_to);350 }351352 <PalletEvm<T>>::deposit_log(353 ERC20Events::Transfer {354 from: *from.as_eth(),355 to: *to.as_eth(),356 value: amount.into(),357 }358 .to_log(collection_id_to_address(collection.id)),359 );360 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(361 collection.id,362 TokenId::default(),363 from.clone(),364 to.clone(),365 amount,366 ));367 Ok(())368 }369370 /// Minting tokens for multiple IDs.371 /// See [`create_item`][`Pallet::create_item`] for more details.372 pub fn create_multiple_items(373 collection: &FungibleHandle<T>,374 sender: &T::CrossAccountId,375 data: BTreeMap<T::CrossAccountId, u128>,376 nesting_budget: &dyn Budget,377 ) -> DispatchResult {378 if !collection.is_owner_or_admin(sender) {379 ensure!(380 collection.permissions.mint_mode(),381 <CommonError<T>>::PublicMintingNotAllowed382 );383 collection.check_allowlist(sender)?;384385 for (owner, _) in data.iter() {386 collection.check_allowlist(owner)?;387 }388 }389390 let total_supply = data391 .iter()392 .map(|(_, v)| *v)393 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {394 acc.checked_add(v)395 })396 .ok_or(ArithmeticError::Overflow)?;397398 let mut balances = data;399 for (k, v) in balances.iter_mut() {400 *v = <Balance<T>>::get((collection.id, &k))401 .checked_add(*v)402 .ok_or(ArithmeticError::Overflow)?;403 }404405 for (to, _) in balances.iter() {406 <PalletStructure<T>>::check_nesting(407 sender.clone(),408 to,409 collection.id,410 TokenId::default(),411 nesting_budget,412 )?;413 }414415 // =========416417 <TotalSupply<T>>::insert(collection.id, total_supply);418 for (user, amount) in balances {419 <Balance<T>>::insert((collection.id, &user), amount);420 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(421 &user,422 collection.id,423 TokenId::default(),424 );425 <PalletEvm<T>>::deposit_log(426 ERC20Events::Transfer {427 from: H160::default(),428 to: *user.as_eth(),429 value: amount.into(),430 }431 .to_log(collection_id_to_address(collection.id)),432 );433 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(434 collection.id,435 TokenId::default(),436 user.clone(),437 amount,438 ));439 }440441 Ok(())442 }443444 fn set_allowance_unchecked(445 collection: &FungibleHandle<T>,446 owner: &T::CrossAccountId,447 spender: &T::CrossAccountId,448 amount: u128,449 ) {450 if amount == 0 {451 <Allowance<T>>::remove((collection.id, owner, spender));452 } else {453 <Allowance<T>>::insert((collection.id, owner, spender), amount);454 }455456 <PalletEvm<T>>::deposit_log(457 ERC20Events::Approval {458 owner: *owner.as_eth(),459 spender: *spender.as_eth(),460 value: amount.into(),461 }462 .to_log(collection_id_to_address(collection.id)),463 );464 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(465 collection.id,466 TokenId(0),467 owner.clone(),468 spender.clone(),469 amount,470 ));471 }472473 /// Set allowance for the spender to `transfer` or `burn` owner's tokens.474 ///475 /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.476 pub fn set_allowance(477 collection: &FungibleHandle<T>,478 owner: &T::CrossAccountId,479 spender: &T::CrossAccountId,480 amount: u128,481 ) -> DispatchResult {482 if collection.permissions.access() == AccessMode::AllowList {483 collection.check_allowlist(owner)?;484 collection.check_allowlist(spender)?;485 }486487 if <Balance<T>>::get((collection.id, owner)) < amount {488 ensure!(489 collection.ignores_owned_amount(owner),490 <CommonError<T>>::CantApproveMoreThanOwned491 );492 }493494 // =========495496 Self::set_allowance_unchecked(collection, owner, spender, amount);497 Ok(())498 }499500 /// Returns allowance, which should be set after transaction501 fn check_allowed(502 collection: &FungibleHandle<T>,503 spender: &T::CrossAccountId,504 from: &T::CrossAccountId,505 amount: u128,506 nesting_budget: &dyn Budget,507 ) -> Result<Option<u128>, DispatchError> {508 if spender.conv_eq(from) {509 return Ok(None);510 }511 if collection.permissions.access() == AccessMode::AllowList {512 // `from`, `to` checked in [`transfer`]513 collection.check_allowlist(spender)?;514 }515 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {516 // TODO: should collection owner be allowed to perform this transfer?517 ensure!(518 <PalletStructure<T>>::check_indirectly_owned(519 spender.clone(),520 source.0,521 source.1,522 None,523 nesting_budget524 )?,525 <CommonError<T>>::ApprovedValueTooLow,526 );527 return Ok(None);528 }529 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);530 if allowance.is_none() {531 ensure!(532 collection.ignores_allowance(spender),533 <CommonError<T>>::ApprovedValueTooLow534 );535 }536537 Ok(allowance)538 }539540 /// Transfer FT tokens from one account to another.541 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.542 /// The owner should set allowance for the spender to transfer pieces.543 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.544 /// 545 /// [`transfer`]: struct.Pallet.html#method.transfer546 pub fn transfer_from(547 collection: &FungibleHandle<T>,548 spender: &T::CrossAccountId,549 from: &T::CrossAccountId,550 to: &T::CrossAccountId,551 amount: u128,552 nesting_budget: &dyn Budget,553 ) -> DispatchResult {554 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;555556 // =========557558 Self::transfer(collection, from, to, amount, nesting_budget)?;559 if let Some(allowance) = allowance {560 Self::set_allowance_unchecked(collection, from, spender, allowance);561 }562 Ok(())563 }564565 /// Burn FT tokens from the account.566 ///567 /// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should568 /// set allowance for the spender to burn tokens.569 /// See [`set_allowance`][`Pallet::set_allowance`] for more details.570 pub fn burn_from(571 collection: &FungibleHandle<T>,572 spender: &T::CrossAccountId,573 from: &T::CrossAccountId,574 amount: u128,575 nesting_budget: &dyn Budget,576 ) -> DispatchResult {577 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;578579 // =========580581 Self::burn(collection, from, amount)?;582 if let Some(allowance) = allowance {583 Self::set_allowance_unchecked(collection, from, spender, allowance);584 }585 Ok(())586 }587588 589 /// Creates FT token.590 ///591 /// The sender should be the owner/admin of the collection or collection should be configured592 /// to allow public minting.593 ///594 /// - `data`: Contains user who will become the owners of the tokens and amount595 /// of tokens he will receive.596 pub fn create_item(597 collection: &FungibleHandle<T>,598 sender: &T::CrossAccountId,599 data: CreateItemData<T>,600 nesting_budget: &dyn Budget,601 ) -> DispatchResult {602 Self::create_multiple_items(603 collection,604 sender,605 [(data.0, data.1)].into_iter().collect(),606 nesting_budget,607 )608 }609}