1234567891011121314151617#![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 51 NotFungibleDataUsedToMintFungibleCollectionToken,52 53 FungibleItemsHaveNoId,54 55 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 215 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 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 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 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}