1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::Deref;4use frame_support::{ensure};5use up_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 up_data_structs::CollectionId;30 use super::weights::WeightInfo;3132 #[pallet::error]33 pub enum Error<T> {34 35 NotFungibleDataUsedToMintFungibleCollectionToken,36 37 FungibleItemsHaveNoId,38 39 FungibleItemsDontHaveData,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 type TotalSupply<T: Config> =53 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;5455 #[pallet::storage]56 pub 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 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::AccountId>) -> Result<CollectionId, DispatchError> {104 <PalletCommon<T>>::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_mirrored(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_mirrored(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_mirrored(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 if amount == 0 {284 <Allowance<T>>::remove((collection.id, owner, spender));285 } else {286 <Allowance<T>>::insert((collection.id, owner, spender), amount);287 }288289 collection.log_mirrored(ERC20Events::Approval {290 owner: *owner.as_eth(),291 spender: *spender.as_eth(),292 value: amount.into(),293 });294 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(295 collection.id,296 TokenId(0),297 owner.clone(),298 spender.clone(),299 amount,300 ));301 }302303 pub fn set_allowance(304 collection: &FungibleHandle<T>,305 owner: &T::CrossAccountId,306 spender: &T::CrossAccountId,307 amount: u128,308 ) -> DispatchResult {309 if collection.access == AccessMode::AllowList {310 collection.check_allowlist(owner)?;311 collection.check_allowlist(spender)?;312 }313314 if <Balance<T>>::get((collection.id, owner)) < amount {315 ensure!(316 collection.ignores_owned_amount(owner),317 <CommonError<T>>::CantApproveMoreThanOwned318 );319 }320321 322323 Self::set_allowance_unchecked(collection, owner, spender, amount);324 Ok(())325 }326327 pub fn transfer_from(328 collection: &FungibleHandle<T>,329 spender: &T::CrossAccountId,330 from: &T::CrossAccountId,331 to: &T::CrossAccountId,332 amount: u128,333 ) -> DispatchResult {334 if spender.conv_eq(from) {335 return Self::transfer(collection, from, to, amount);336 }337 if collection.access == AccessMode::AllowList {338 339 collection.check_allowlist(spender)?;340 }341342 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);343 if allowance.is_none() {344 ensure!(345 collection.ignores_allowance(spender),346 <CommonError<T>>::TokenValueNotEnough347 );348 }349350 351352 Self::transfer(collection, from, to, amount)?;353 if let Some(allowance) = allowance {354 Self::set_allowance_unchecked(collection, from, spender, allowance);355 }356 Ok(())357 }358359 pub fn burn_from(360 collection: &FungibleHandle<T>,361 spender: &T::CrossAccountId,362 from: &T::CrossAccountId,363 amount: u128,364 ) -> DispatchResult {365 if spender.conv_eq(from) {366 return Self::burn(collection, from, amount);367 }368 if collection.access == AccessMode::AllowList {369 370 collection.check_allowlist(spender)?;371 }372373 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);374 if allowance.is_none() {375 ensure!(376 collection.ignores_allowance(spender),377 <CommonError<T>>::TokenValueNotEnough378 );379 }380381 382383 Self::burn(collection, from, amount)?;384 if let Some(allowance) = allowance {385 Self::set_allowance_unchecked(collection, from, spender, allowance);386 }387 Ok(())388 }389390 391 pub fn create_item(392 collection: &FungibleHandle<T>,393 sender: &T::CrossAccountId,394 data: CreateItemData<T>,395 ) -> DispatchResult {396 Self::create_multiple_items(collection, sender, vec![data])397 }398}