1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::Deref;4use frame_support::{ensure};5use nft_data_structs::{AccessMode, Collection, CollectionId, TokenId};6use pallet_common::{7 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,8};9use pallet_evm_coder_substrate::WithRecorder;10use sp_core::H160;11use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};12use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};1314pub use pallet::*;1516use crate::erc::ERC20Events;17#[cfg(feature = "runtime-benchmarks")]18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;2223pub type CreateItemData<T> = (<T as pallet_common::Config>::CrossAccountId, u128);24pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2526#[frame_support::pallet]27pub mod pallet {28 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};29 use nft_data_structs::CollectionId;30 use super::weights::WeightInfo;3132 #[pallet::error]33 pub enum Error<T> {34 35 NotFungibleDataUsedToMintFungibleCollectionToken,36 37 FungibleItemsHaveNoId,38 39 FungibleItemsHaveData,40 }4142 #[pallet::config]43 pub trait Config: frame_system::Config + pallet_common::Config {44 type WeightInfo: WeightInfo;45 }4647 #[pallet::pallet]48 #[pallet::generate_store(pub(super) trait Store)]49 pub struct Pallet<T>(_);5051 #[pallet::storage]52 pub(super) type TotalSupply<T: Config> =53 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;5455 #[pallet::storage]56 pub(super) type Balance<T: Config> = StorageNMap<57 Key = (58 Key<Twox64Concat, CollectionId>,59 Key<Blake2_128Concat, T::CrossAccountId>,60 ),61 Value = u128,62 QueryKind = ValueQuery,63 >;6465 #[pallet::storage]66 pub(super) type Allowance<T: Config> = StorageNMap<67 Key = (68 Key<Twox64Concat, CollectionId>,69 Key<Blake2_128, T::CrossAccountId>,70 Key<Blake2_128Concat, T::CrossAccountId>,71 ),72 Value = u128,73 QueryKind = ValueQuery,74 >;75}7677pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);78impl<T: Config> FungibleHandle<T> {79 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {80 Self(inner)81 }82 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {83 self.084 }85}86impl<T: Config> WithRecorder<T> for FungibleHandle<T> {87 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {88 self.0.recorder()89 }90 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {91 self.0.into_recorder()92 }93}94impl<T: Config> Deref for FungibleHandle<T> {95 type Target = pallet_common::CollectionHandle<T>;9697 fn deref(&self) -> &Self::Target {98 &self.099 }100}101102impl<T: Config> Pallet<T> {103 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {104 PalletCommon::init_collection(data)105 }106 pub fn destroy_collection(107 collection: FungibleHandle<T>,108 sender: &T::CrossAccountId,109 ) -> DispatchResult {110 let id = collection.id;111112 113114 PalletCommon::destroy_collection(collection.0, sender)?;115116 <TotalSupply<T>>::remove(id);117 <Balance<T>>::remove_prefix((id,), None);118 <Allowance<T>>::remove_prefix((id,), None);119 Ok(())120 }121122 pub fn burn(123 collection: &FungibleHandle<T>,124 owner: &T::CrossAccountId,125 amount: u128,126 ) -> DispatchResult {127 let total_supply = <TotalSupply<T>>::get(collection.id)128 .checked_sub(amount)129 .ok_or(<CommonError<T>>::TokenValueTooLow)?;130131 let balance = <Balance<T>>::get((collection.id, owner))132 .checked_sub(amount)133 .ok_or(<CommonError<T>>::TokenValueTooLow)?;134135 if collection.access == AccessMode::AllowList {136 collection.check_allowlist(owner)?;137 }138139 140141 if balance == 0 {142 <Balance<T>>::remove((collection.id, owner));143 } else {144 <Balance<T>>::insert((collection.id, owner), balance);145 }146 <TotalSupply<T>>::insert(collection.id, total_supply);147148 collection.log(ERC20Events::Transfer {149 from: *owner.as_eth(),150 to: H160::default(),151 value: amount.into(),152 });153 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(154 collection.id,155 TokenId::default(),156 owner.clone(),157 amount,158 ));159 Ok(())160 }161162 pub fn transfer(163 collection: &FungibleHandle<T>,164 from: &T::CrossAccountId,165 to: &T::CrossAccountId,166 amount: u128,167 ) -> DispatchResult {168 ensure!(169 collection.limits.transfers_enabled(),170 <CommonError<T>>::TransferNotAllowed,171 );172173 if collection.access == AccessMode::AllowList {174 collection.check_allowlist(from)?;175 collection.check_allowlist(to)?;176 }177 <PalletCommon<T>>::ensure_correct_receiver(to)?;178179 let balance_from = <Balance<T>>::get((collection.id, from))180 .checked_sub(amount)181 .ok_or(<CommonError<T>>::TokenValueTooLow)?;182 let balance_to = if from != to {183 Some(184 <Balance<T>>::get((collection.id, to))185 .checked_add(amount)186 .ok_or(ArithmeticError::Overflow)?,187 )188 } else {189 None190 };191192 193194 if let Some(balance_to) = balance_to {195 196 if balance_from == 0 {197 <Balance<T>>::remove((collection.id, from));198 } else {199 <Balance<T>>::insert((collection.id, from), balance_from);200 }201 <Balance<T>>::insert((collection.id, to), balance_to);202 }203204 collection.log(ERC20Events::Transfer {205 from: *from.as_eth(),206 to: *to.as_eth(),207 value: amount.into(),208 });209 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(210 collection.id,211 TokenId::default(),212 from.clone(),213 to.clone(),214 amount,215 ));216 Ok(())217 }218219 pub fn create_multiple_items(220 collection: &FungibleHandle<T>,221 sender: &T::CrossAccountId,222 data: Vec<CreateItemData<T>>,223 ) -> DispatchResult {224 if !collection.is_owner_or_admin(sender) {225 ensure!(226 collection.mint_mode,227 <CommonError<T>>::PublicMintingNotAllowed228 );229 collection.check_allowlist(sender)?;230231 for (owner, _) in data.iter() {232 collection.check_allowlist(owner)?;233 }234 }235236 let mut balances = BTreeMap::new();237238 let total_supply = data239 .iter()240 .map(|u| u.1)241 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {242 acc.checked_add(v)243 })244 .ok_or(ArithmeticError::Overflow)?;245246 for (user, amount) in data.into_iter() {247 let balance = balances248 .entry(user.clone())249 .or_insert_with(|| <Balance<T>>::get((collection.id, user)));250 *balance = (*balance)251 .checked_add(amount)252 .ok_or(ArithmeticError::Overflow)?;253 }254255 256257 <TotalSupply<T>>::insert(collection.id, total_supply);258 for (user, amount) in balances {259 <Balance<T>>::insert((collection.id, &user), amount);260261 collection.log(ERC20Events::Transfer {262 from: H160::default(),263 to: *user.as_eth(),264 value: amount.into(),265 });266 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(267 collection.id,268 TokenId::default(),269 user.clone(),270 amount,271 ));272 }273274 Ok(())275 }276277 fn set_allowance_unchecked(278 collection: &FungibleHandle<T>,279 owner: &T::CrossAccountId,280 spender: &T::CrossAccountId,281 amount: u128,282 ) {283 <Allowance<T>>::insert((collection.id, owner, spender), amount);284285 collection.log(ERC20Events::Approval {286 owner: *owner.as_eth(),287 spender: *spender.as_eth(),288 value: amount.into(),289 });290 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(291 collection.id,292 TokenId(0),293 owner.clone(),294 spender.clone(),295 amount,296 ));297 }298299 pub fn set_allowance(300 collection: &FungibleHandle<T>,301 owner: &T::CrossAccountId,302 spender: &T::CrossAccountId,303 amount: u128,304 ) -> DispatchResult {305 if collection.access == AccessMode::AllowList {306 collection.check_allowlist(&owner)?;307 collection.check_allowlist(&spender)?;308 }309310 if <Balance<T>>::get((collection.id, owner)) < amount {311 ensure!(312 collection.ignores_owned_amount(owner),313 <CommonError<T>>::CantApproveMoreThanOwned314 );315 }316317 318319 Self::set_allowance_unchecked(collection, owner, spender, amount);320 Ok(())321 }322323 pub fn transfer_from(324 collection: &FungibleHandle<T>,325 spender: &T::CrossAccountId,326 from: &T::CrossAccountId,327 to: &T::CrossAccountId,328 amount: u128,329 ) -> DispatchResult {330 if spender.conv_eq(from) {331 return Self::transfer(collection, from, to, amount);332 }333 if collection.access == AccessMode::AllowList {334 335 collection.check_allowlist(spender)?;336 }337338 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);339 if allowance.is_none() {340 ensure!(341 collection.ignores_allowance(spender),342 <CommonError<T>>::TokenValueNotEnough343 );344 }345346 347348 Self::transfer(collection, from, to, amount)?;349 if let Some(allowance) = allowance {350 Self::set_allowance_unchecked(collection, from, spender, allowance);351 }352 Ok(())353 }354355 pub fn burn_from(356 collection: &FungibleHandle<T>,357 spender: &T::CrossAccountId,358 from: &T::CrossAccountId,359 amount: u128,360 ) -> DispatchResult {361 if spender.conv_eq(from) {362 return Self::burn(collection, from, amount);363 }364 if collection.access == AccessMode::AllowList {365 366 collection.check_allowlist(spender)?;367 }368369 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);370 if allowance.is_none() {371 ensure!(372 collection.ignores_allowance(spender),373 <CommonError<T>>::TokenValueNotEnough374 );375 }376377 378379 Self::burn(collection, from, amount)?;380 if let Some(allowance) = allowance {381 Self::set_allowance_unchecked(collection, from, spender, allowance);382 }383 Ok(())384 }385386 387 pub fn create_item(388 collection: &FungibleHandle<T>,389 sender: &T::CrossAccountId,390 data: CreateItemData<T>,391 ) -> DispatchResult {392 Self::create_multiple_items(collection, sender, vec![data])393 }394}