git.delta.rocks / unique-network / refs/commits / 2dfd73643010

difftreelog

source

pallets/fungible/src/lib.rs10.4 KiBsourcehistory
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#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::Deref;20use frame_support::{ensure};21use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};22use pallet_common::{23	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,24};25use pallet_evm_coder_substrate::WithRecorder;26use sp_core::H160;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::collections::btree_map::BTreeMap;2930pub use pallet::*;3132use crate::erc::ERC20Events;33#[cfg(feature = "runtime-benchmarks")]34pub mod benchmarking;35pub mod common;36pub mod erc;37pub mod weights;3839pub type CreateItemData<T> = (<T as pallet_common::Config>::CrossAccountId, u128);40pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4142#[frame_support::pallet]43pub mod pallet {44	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};45	use up_data_structs::CollectionId;46	use super::weights::WeightInfo;4748	#[pallet::error]49	pub enum Error<T> {50		/// Not Fungible item data used to mint in Fungible collection.51		NotFungibleDataUsedToMintFungibleCollectionToken,52		/// Not default id passed as TokenId argument53		FungibleItemsHaveNoId,54		/// Tried to set data for fungible item55		FungibleItemsDontHaveData,56	}5758	#[pallet::config]59	pub trait Config: frame_system::Config + pallet_common::Config {60		type WeightInfo: WeightInfo;61	}6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::storage]68	pub type TotalSupply<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub type Balance<T: Config> = StorageNMap<73		Key = (74			Key<Twox64Concat, CollectionId>,75			Key<Blake2_128Concat, T::CrossAccountId>,76		),77		Value = u128,78		QueryKind = ValueQuery,79	>;8081	#[pallet::storage]82	pub type Allowance<T: Config> = StorageNMap<83		Key = (84			Key<Twox64Concat, CollectionId>,85			Key<Blake2_128, T::CrossAccountId>,86			Key<Blake2_128Concat, T::CrossAccountId>,87		),88		Value = u128,89		QueryKind = ValueQuery,90	>;91}9293pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);94impl<T: Config> FungibleHandle<T> {95	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {96		Self(inner)97	}98	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {99		self.0100	}101}102impl<T: Config> WithRecorder<T> for FungibleHandle<T> {103	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {104		self.0.recorder()105	}106	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {107		self.0.into_recorder()108	}109}110impl<T: Config> Deref for FungibleHandle<T> {111	type Target = pallet_common::CollectionHandle<T>;112113	fn deref(&self) -> &Self::Target {114		&self.0115	}116}117118impl<T: Config> Pallet<T> {119	pub fn init_collection(120		owner: T::AccountId,121		data: CreateCollectionData<T::AccountId>,122	) -> Result<CollectionId, DispatchError> {123		<PalletCommon<T>>::init_collection(owner, data)124	}125	pub fn destroy_collection(126		collection: FungibleHandle<T>,127		sender: &T::CrossAccountId,128	) -> DispatchResult {129		let id = collection.id;130131		// =========132133		PalletCommon::destroy_collection(collection.0, sender)?;134135		<TotalSupply<T>>::remove(id);136		<Balance<T>>::remove_prefix((id,), None);137		<Allowance<T>>::remove_prefix((id,), None);138		Ok(())139	}140141	pub fn burn(142		collection: &FungibleHandle<T>,143		owner: &T::CrossAccountId,144		amount: u128,145	) -> DispatchResult {146		let total_supply = <TotalSupply<T>>::get(collection.id)147			.checked_sub(amount)148			.ok_or(<CommonError<T>>::TokenValueTooLow)?;149150		let balance = <Balance<T>>::get((collection.id, owner))151			.checked_sub(amount)152			.ok_or(<CommonError<T>>::TokenValueTooLow)?;153154		if collection.access == AccessMode::AllowList {155			collection.check_allowlist(owner)?;156		}157158		// =========159160		if balance == 0 {161			<Balance<T>>::remove((collection.id, owner));162		} else {163			<Balance<T>>::insert((collection.id, owner), balance);164		}165		<TotalSupply<T>>::insert(collection.id, total_supply);166167		collection.log_mirrored(ERC20Events::Transfer {168			from: *owner.as_eth(),169			to: H160::default(),170			value: amount.into(),171		});172		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(173			collection.id,174			TokenId::default(),175			owner.clone(),176			amount,177		));178		Ok(())179	}180181	pub fn transfer(182		collection: &FungibleHandle<T>,183		from: &T::CrossAccountId,184		to: &T::CrossAccountId,185		amount: u128,186	) -> DispatchResult {187		ensure!(188			collection.limits.transfers_enabled(),189			<CommonError<T>>::TransferNotAllowed,190		);191192		if collection.access == AccessMode::AllowList {193			collection.check_allowlist(from)?;194			collection.check_allowlist(to)?;195		}196		<PalletCommon<T>>::ensure_correct_receiver(to)?;197198		let balance_from = <Balance<T>>::get((collection.id, from))199			.checked_sub(amount)200			.ok_or(<CommonError<T>>::TokenValueTooLow)?;201		let balance_to = if from != to {202			Some(203				<Balance<T>>::get((collection.id, to))204					.checked_add(amount)205					.ok_or(ArithmeticError::Overflow)?,206			)207		} else {208			None209		};210211		// =========212213		if let Some(balance_to) = balance_to {214			// from != to215			if balance_from == 0 {216				<Balance<T>>::remove((collection.id, from));217			} else {218				<Balance<T>>::insert((collection.id, from), balance_from);219			}220			<Balance<T>>::insert((collection.id, to), balance_to);221		}222223		collection.log_mirrored(ERC20Events::Transfer {224			from: *from.as_eth(),225			to: *to.as_eth(),226			value: amount.into(),227		});228		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(229			collection.id,230			TokenId::default(),231			from.clone(),232			to.clone(),233			amount,234		));235		Ok(())236	}237238	pub fn create_multiple_items(239		collection: &FungibleHandle<T>,240		sender: &T::CrossAccountId,241		data: BTreeMap<T::CrossAccountId, u128>,242	) -> DispatchResult {243		if !collection.is_owner_or_admin(sender) {244			ensure!(245				collection.mint_mode,246				<CommonError<T>>::PublicMintingNotAllowed247			);248			collection.check_allowlist(sender)?;249250			for (owner, _) in data.iter() {251				collection.check_allowlist(owner)?;252			}253		}254255		let total_supply = data256			.iter()257			.map(|(_, v)| *v)258			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {259				acc.checked_add(v)260			})261			.ok_or(ArithmeticError::Overflow)?;262263		let mut balances = data;264		for (k, v) in balances.iter_mut() {265			*v = <Balance<T>>::get((collection.id, &k))266				.checked_add(*v)267				.ok_or(ArithmeticError::Overflow)?;268		}269270		// =========271272		<TotalSupply<T>>::insert(collection.id, total_supply);273		for (user, amount) in balances {274			<Balance<T>>::insert((collection.id, &user), amount);275276			collection.log_mirrored(ERC20Events::Transfer {277				from: H160::default(),278				to: *user.as_eth(),279				value: amount.into(),280			});281			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(282				collection.id,283				TokenId::default(),284				user.clone(),285				amount,286			));287		}288289		Ok(())290	}291292	fn set_allowance_unchecked(293		collection: &FungibleHandle<T>,294		owner: &T::CrossAccountId,295		spender: &T::CrossAccountId,296		amount: u128,297	) {298		if amount == 0 {299			<Allowance<T>>::remove((collection.id, owner, spender));300		} else {301			<Allowance<T>>::insert((collection.id, owner, spender), amount);302		}303304		collection.log_mirrored(ERC20Events::Approval {305			owner: *owner.as_eth(),306			spender: *spender.as_eth(),307			value: amount.into(),308		});309		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(310			collection.id,311			TokenId(0),312			owner.clone(),313			spender.clone(),314			amount,315		));316	}317318	pub fn set_allowance(319		collection: &FungibleHandle<T>,320		owner: &T::CrossAccountId,321		spender: &T::CrossAccountId,322		amount: u128,323	) -> DispatchResult {324		if collection.access == AccessMode::AllowList {325			collection.check_allowlist(owner)?;326			collection.check_allowlist(spender)?;327		}328329		if <Balance<T>>::get((collection.id, owner)) < amount {330			ensure!(331				collection.ignores_owned_amount(owner),332				<CommonError<T>>::CantApproveMoreThanOwned333			);334		}335336		// =========337338		Self::set_allowance_unchecked(collection, owner, spender, amount);339		Ok(())340	}341342	pub fn transfer_from(343		collection: &FungibleHandle<T>,344		spender: &T::CrossAccountId,345		from: &T::CrossAccountId,346		to: &T::CrossAccountId,347		amount: u128,348	) -> DispatchResult {349		if spender.conv_eq(from) {350			return Self::transfer(collection, from, to, amount);351		}352		if collection.access == AccessMode::AllowList {353			// `from`, `to` checked in [`transfer`]354			collection.check_allowlist(spender)?;355		}356357		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);358		if allowance.is_none() {359			ensure!(360				collection.ignores_allowance(spender),361				<CommonError<T>>::ApprovedValueTooLow362			);363		}364365		// =========366367		Self::transfer(collection, from, to, amount)?;368		if let Some(allowance) = allowance {369			Self::set_allowance_unchecked(collection, from, spender, allowance);370		}371		Ok(())372	}373374	pub fn burn_from(375		collection: &FungibleHandle<T>,376		spender: &T::CrossAccountId,377		from: &T::CrossAccountId,378		amount: u128,379	) -> DispatchResult {380		if spender.conv_eq(from) {381			return Self::burn(collection, from, amount);382		}383		if collection.access == AccessMode::AllowList {384			// `from` checked in [`burn`]385			collection.check_allowlist(spender)?;386		}387388		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);389		if allowance.is_none() {390			ensure!(391				collection.ignores_allowance(spender),392				<CommonError<T>>::ApprovedValueTooLow393			);394		}395396		// =========397398		Self::burn(collection, from, amount)?;399		if let Some(allowance) = allowance {400			Self::set_allowance_unchecked(collection, from, spender, allowance);401		}402		Ok(())403	}404405	/// Delegated to `create_multiple_items`406	pub fn create_item(407		collection: &FungibleHandle<T>,408		sender: &T::CrossAccountId,409		data: CreateItemData<T>,410	) -> DispatchResult {411		Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())412	}413}