1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::Deref;20use frame_support::{ensure};21use pallet_evm::account::CrossAccountId;22use up_data_structs::{23 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,24};25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 CollectionHandle, dispatch::CollectionDispatch,28};29use pallet_evm_coder_substrate::WithRecorder;30use sp_core::H160;31use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};32use sp_std::collections::btree_map::BTreeMap;3334pub use pallet::*;3536use crate::erc::ERC20Events;37#[cfg(feature = "runtime-benchmarks")]38pub mod benchmarking;39pub mod common;40pub mod erc;41pub mod weights;4243pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);44pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4546#[frame_support::pallet]47pub mod pallet {48 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};49 use up_data_structs::CollectionId;50 use super::weights::WeightInfo;5152 #[pallet::error]53 pub enum Error<T> {54 55 NotFungibleDataUsedToMintFungibleCollectionToken,56 57 FungibleItemsHaveNoId,58 59 FungibleItemsDontHaveData,60 61 FungibleDisallowsNesting,62 }6364 #[pallet::config]65 pub trait Config: frame_system::Config + pallet_common::Config {66 type WeightInfo: WeightInfo;67 }6869 #[pallet::pallet]70 #[pallet::generate_store(pub(super) trait Store)]71 pub struct Pallet<T>(_);7273 #[pallet::storage]74 pub type TotalSupply<T: Config> =75 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;7677 #[pallet::storage]78 pub type Balance<T: Config> = StorageNMap<79 Key = (80 Key<Twox64Concat, CollectionId>,81 Key<Blake2_128Concat, T::CrossAccountId>,82 ),83 Value = u128,84 QueryKind = ValueQuery,85 >;8687 #[pallet::storage]88 pub type Allowance<T: Config> = StorageNMap<89 Key = (90 Key<Twox64Concat, CollectionId>,91 Key<Blake2_128, T::CrossAccountId>,92 Key<Blake2_128Concat, T::CrossAccountId>,93 ),94 Value = u128,95 QueryKind = ValueQuery,96 >;97}9899pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);100impl<T: Config> FungibleHandle<T> {101 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {102 Self(inner)103 }104 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {105 self.0106 }107}108impl<T: Config> WithRecorder<T> for FungibleHandle<T> {109 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {110 self.0.recorder()111 }112 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {113 self.0.into_recorder()114 }115}116impl<T: Config> Deref for FungibleHandle<T> {117 type Target = pallet_common::CollectionHandle<T>;118119 fn deref(&self) -> &Self::Target {120 &self.0121 }122}123124impl<T: Config> Pallet<T> {125 pub fn init_collection(126 owner: T::AccountId,127 data: CreateCollectionData<T::AccountId>,128 ) -> Result<CollectionId, DispatchError> {129 <PalletCommon<T>>::init_collection(owner, data)130 }131 pub fn destroy_collection(132 collection: FungibleHandle<T>,133 sender: &T::CrossAccountId,134 ) -> DispatchResult {135 let id = collection.id;136137 138139 PalletCommon::destroy_collection(collection.0, sender)?;140141 <TotalSupply<T>>::remove(id);142 <Balance<T>>::remove_prefix((id,), None);143 <Allowance<T>>::remove_prefix((id,), None);144 Ok(())145 }146147 pub fn burn(148 collection: &FungibleHandle<T>,149 owner: &T::CrossAccountId,150 amount: u128,151 ) -> DispatchResult {152 let total_supply = <TotalSupply<T>>::get(collection.id)153 .checked_sub(amount)154 .ok_or(<CommonError<T>>::TokenValueTooLow)?;155156 let balance = <Balance<T>>::get((collection.id, owner))157 .checked_sub(amount)158 .ok_or(<CommonError<T>>::TokenValueTooLow)?;159160 if collection.access == AccessMode::AllowList {161 collection.check_allowlist(owner)?;162 }163164 165166 if balance == 0 {167 <Balance<T>>::remove((collection.id, owner));168 } else {169 <Balance<T>>::insert((collection.id, owner), balance);170 }171 <TotalSupply<T>>::insert(collection.id, total_supply);172173 collection.log_mirrored(ERC20Events::Transfer {174 from: *owner.as_eth(),175 to: H160::default(),176 value: amount.into(),177 });178 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(179 collection.id,180 TokenId::default(),181 owner.clone(),182 amount,183 ));184 Ok(())185 }186187 pub fn transfer(188 collection: &FungibleHandle<T>,189 from: &T::CrossAccountId,190 to: &T::CrossAccountId,191 amount: u128,192 ) -> DispatchResult {193 ensure!(194 collection.limits.transfers_enabled(),195 <CommonError<T>>::TransferNotAllowed,196 );197198 if collection.access == AccessMode::AllowList {199 collection.check_allowlist(from)?;200 collection.check_allowlist(to)?;201 }202 <PalletCommon<T>>::ensure_correct_receiver(to)?;203204 let balance_from = <Balance<T>>::get((collection.id, from))205 .checked_sub(amount)206 .ok_or(<CommonError<T>>::TokenValueTooLow)?;207 let balance_to = if from != to {208 Some(209 <Balance<T>>::get((collection.id, to))210 .checked_add(amount)211 .ok_or(ArithmeticError::Overflow)?,212 )213 } else {214 None215 };216217 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {218 let handle = <CollectionHandle<T>>::try_get(target.0)?;219 let dispatch = T::CollectionDispatch::dispatch(handle);220 let dispatch = dispatch.as_dyn();221222 223224 dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;225 }226227 if let Some(balance_to) = balance_to {228 229 if balance_from == 0 {230 <Balance<T>>::remove((collection.id, from));231 } else {232 <Balance<T>>::insert((collection.id, from), balance_from);233 }234 <Balance<T>>::insert((collection.id, to), balance_to);235 }236237 collection.log_mirrored(ERC20Events::Transfer {238 from: *from.as_eth(),239 to: *to.as_eth(),240 value: amount.into(),241 });242 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(243 collection.id,244 TokenId::default(),245 from.clone(),246 to.clone(),247 amount,248 ));249 Ok(())250 }251252 pub fn create_multiple_items(253 collection: &FungibleHandle<T>,254 sender: &T::CrossAccountId,255 data: BTreeMap<T::CrossAccountId, u128>,256 ) -> DispatchResult {257 if !collection.is_owner_or_admin(sender) {258 ensure!(259 collection.mint_mode,260 <CommonError<T>>::PublicMintingNotAllowed261 );262 collection.check_allowlist(sender)?;263264 for (owner, _) in data.iter() {265 collection.check_allowlist(owner)?;266 }267 }268269 let total_supply = data270 .iter()271 .map(|(_, v)| *v)272 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {273 acc.checked_add(v)274 })275 .ok_or(ArithmeticError::Overflow)?;276277 let mut balances = data;278 for (k, v) in balances.iter_mut() {279 *v = <Balance<T>>::get((collection.id, &k))280 .checked_add(*v)281 .ok_or(ArithmeticError::Overflow)?;282 }283284 285286 <TotalSupply<T>>::insert(collection.id, total_supply);287 for (user, amount) in balances {288 <Balance<T>>::insert((collection.id, &user), amount);289290 collection.log_mirrored(ERC20Events::Transfer {291 from: H160::default(),292 to: *user.as_eth(),293 value: amount.into(),294 });295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(296 collection.id,297 TokenId::default(),298 user.clone(),299 amount,300 ));301 }302303 Ok(())304 }305306 fn set_allowance_unchecked(307 collection: &FungibleHandle<T>,308 owner: &T::CrossAccountId,309 spender: &T::CrossAccountId,310 amount: u128,311 ) {312 if amount == 0 {313 <Allowance<T>>::remove((collection.id, owner, spender));314 } else {315 <Allowance<T>>::insert((collection.id, owner, spender), amount);316 }317318 collection.log_mirrored(ERC20Events::Approval {319 owner: *owner.as_eth(),320 spender: *spender.as_eth(),321 value: amount.into(),322 });323 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(324 collection.id,325 TokenId(0),326 owner.clone(),327 spender.clone(),328 amount,329 ));330 }331332 pub fn set_allowance(333 collection: &FungibleHandle<T>,334 owner: &T::CrossAccountId,335 spender: &T::CrossAccountId,336 amount: u128,337 ) -> DispatchResult {338 if collection.access == AccessMode::AllowList {339 collection.check_allowlist(owner)?;340 collection.check_allowlist(spender)?;341 }342343 if <Balance<T>>::get((collection.id, owner)) < amount {344 ensure!(345 collection.ignores_owned_amount(owner),346 <CommonError<T>>::CantApproveMoreThanOwned347 );348 }349350 351352 Self::set_allowance_unchecked(collection, owner, spender, amount);353 Ok(())354 }355356 pub fn transfer_from(357 collection: &FungibleHandle<T>,358 spender: &T::CrossAccountId,359 from: &T::CrossAccountId,360 to: &T::CrossAccountId,361 amount: u128,362 ) -> DispatchResult {363 if spender.conv_eq(from) {364 return Self::transfer(collection, from, to, amount);365 }366 if collection.access == AccessMode::AllowList {367 368 collection.check_allowlist(spender)?;369 }370371 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);372 if allowance.is_none() {373 ensure!(374 collection.ignores_allowance(spender),375 <CommonError<T>>::ApprovedValueTooLow376 );377 }378379 380381 Self::transfer(collection, from, to, amount)?;382 if let Some(allowance) = allowance {383 Self::set_allowance_unchecked(collection, from, spender, allowance);384 }385 Ok(())386 }387388 pub fn burn_from(389 collection: &FungibleHandle<T>,390 spender: &T::CrossAccountId,391 from: &T::CrossAccountId,392 amount: u128,393 ) -> DispatchResult {394 if spender.conv_eq(from) {395 return Self::burn(collection, from, amount);396 }397 if collection.access == AccessMode::AllowList {398 399 collection.check_allowlist(spender)?;400 }401402 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);403 if allowance.is_none() {404 ensure!(405 collection.ignores_allowance(spender),406 <CommonError<T>>::ApprovedValueTooLow407 );408 }409410 411412 Self::burn(collection, from, amount)?;413 if let Some(allowance) = allowance {414 Self::set_allowance_unchecked(collection, from, spender, allowance);415 }416 Ok(())417 }418419 420 pub fn create_item(421 collection: &FungibleHandle<T>,422 sender: &T::CrossAccountId,423 data: CreateItemData<T>,424 ) -> DispatchResult {425 Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())426 }427}