12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;82use evm_coder::ToLog;83use frame_support::{ensure};84use pallet_evm::account::CrossAccountId;85use up_data_structs::{86 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,87 budget::Budget,88};89use pallet_common::{90 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,91 eth::collection_id_to_address,92};93use pallet_evm::Pallet as PalletEvm;94use pallet_structure::Pallet as PalletStructure;95use pallet_evm_coder_substrate::WithRecorder;96use sp_core::H160;97use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};98use sp_std::{collections::btree_map::BTreeMap};99100pub use pallet::*;101102use crate::erc::ERC20Events;103#[cfg(feature = "runtime-benchmarks")]104pub mod benchmarking;105pub mod common;106pub mod erc;107pub mod weights;108109pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);110pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;111112#[frame_support::pallet]113pub mod pallet {114 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};115 use up_data_structs::CollectionId;116 use super::weights::WeightInfo;117118 #[pallet::error]119 pub enum Error<T> {120 121 NotFungibleDataUsedToMintFungibleCollectionToken,122 123 124 FungibleItemsHaveNoId,125 126 FungibleItemsDontHaveData,127 128 FungibleDisallowsNesting,129 130 SettingPropertiesNotAllowed,131 }132133 #[pallet::config]134 pub trait Config:135 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config136 {137 type WeightInfo: WeightInfo;138 }139140 #[pallet::pallet]141 #[pallet::generate_store(pub(super) trait Store)]142 pub struct Pallet<T>(_);143144 145 #[pallet::storage]146 pub type TotalSupply<T: Config> =147 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;148149 150 #[pallet::storage]151 pub type Balance<T: Config> = StorageNMap<152 Key = (153 Key<Twox64Concat, CollectionId>,154 Key<Blake2_128Concat, T::CrossAccountId>,155 ),156 Value = u128,157 QueryKind = ValueQuery,158 >;159160 161 #[pallet::storage]162 pub type Allowance<T: Config> = StorageNMap<163 Key = (164 Key<Twox64Concat, CollectionId>,165 Key<Blake2_128, T::CrossAccountId>,166 Key<Blake2_128Concat, T::CrossAccountId>,167 ),168 Value = u128,169 QueryKind = ValueQuery,170 >;171}172173pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);174impl<T: Config> FungibleHandle<T> {175 176 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {177 Self(inner)178 }179180 181 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {182 self.0183 }184 185 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {186 &mut self.0187 }188}189impl<T: Config> WithRecorder<T> for FungibleHandle<T> {190 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {191 self.0.recorder()192 }193 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {194 self.0.into_recorder()195 }196}197impl<T: Config> Deref for FungibleHandle<T> {198 type Target = pallet_common::CollectionHandle<T>;199200 fn deref(&self) -> &Self::Target {201 &self.0202 }203}204205206impl<T: Config> Pallet<T> {207 208 pub fn init_collection(209 owner: T::CrossAccountId,210 data: CreateCollectionData<T::AccountId>,211 ) -> Result<CollectionId, DispatchError> {212 <PalletCommon<T>>::init_collection(owner, data, false)213 }214215 216 pub fn destroy_collection(217 collection: FungibleHandle<T>,218 sender: &T::CrossAccountId,219 ) -> DispatchResult {220 let id = collection.id;221222 if Self::collection_has_tokens(id) {223 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());224 }225226 227228 PalletCommon::destroy_collection(collection.0, sender)?;229230 <TotalSupply<T>>::remove(id);231 <Balance<T>>::remove_prefix((id,), None);232 <Allowance<T>>::remove_prefix((id,), None);233 Ok(())234 }235236 237 fn collection_has_tokens(collection_id: CollectionId) -> bool {238 <TotalSupply<T>>::get(collection_id) != 0239 }240241 242 243 244 pub fn burn(245 collection: &FungibleHandle<T>,246 owner: &T::CrossAccountId,247 amount: u128,248 ) -> DispatchResult {249 let total_supply = <TotalSupply<T>>::get(collection.id)250 .checked_sub(amount)251 .ok_or(<CommonError<T>>::TokenValueTooLow)?;252253 let balance = <Balance<T>>::get((collection.id, owner))254 .checked_sub(amount)255 .ok_or(<CommonError<T>>::TokenValueTooLow)?;256257 if collection.permissions.access() == AccessMode::AllowList {258 collection.check_allowlist(owner)?;259 }260261 262263 if balance == 0 {264 <Balance<T>>::remove((collection.id, owner));265 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());266 } else {267 <Balance<T>>::insert((collection.id, owner), balance);268 }269 <TotalSupply<T>>::insert(collection.id, total_supply);270271 <PalletEvm<T>>::deposit_log(272 ERC20Events::Transfer {273 from: *owner.as_eth(),274 to: H160::default(),275 value: amount.into(),276 }277 .to_log(collection_id_to_address(collection.id)),278 );279 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(280 collection.id,281 TokenId::default(),282 owner.clone(),283 amount,284 ));285 Ok(())286 }287288 289 290 pub fn transfer(291 collection: &FungibleHandle<T>,292 from: &T::CrossAccountId,293 to: &T::CrossAccountId,294 amount: u128,295 nesting_budget: &dyn Budget,296 ) -> DispatchResult {297 ensure!(298 collection.limits.transfers_enabled(),299 <CommonError<T>>::TransferNotAllowed,300 );301302 if collection.permissions.access() == AccessMode::AllowList {303 collection.check_allowlist(from)?;304 collection.check_allowlist(to)?;305 }306 <PalletCommon<T>>::ensure_correct_receiver(to)?;307308 let balance_from = <Balance<T>>::get((collection.id, from))309 .checked_sub(amount)310 .ok_or(<CommonError<T>>::TokenValueTooLow)?;311 let balance_to = if from != to {312 Some(313 <Balance<T>>::get((collection.id, to))314 .checked_add(amount)315 .ok_or(ArithmeticError::Overflow)?,316 )317 } else {318 None319 };320321 322323 <PalletStructure<T>>::nest_if_sent_to_token(324 from.clone(),325 to,326 collection.id,327 TokenId::default(),328 nesting_budget,329 )?;330331 if let Some(balance_to) = balance_to {332 333 if balance_from == 0 {334 <Balance<T>>::remove((collection.id, from));335 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());336 } else {337 <Balance<T>>::insert((collection.id, from), balance_from);338 }339 <Balance<T>>::insert((collection.id, to), balance_to);340 }341342 <PalletEvm<T>>::deposit_log(343 ERC20Events::Transfer {344 from: *from.as_eth(),345 to: *to.as_eth(),346 value: amount.into(),347 }348 .to_log(collection_id_to_address(collection.id)),349 );350 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(351 collection.id,352 TokenId::default(),353 from.clone(),354 to.clone(),355 amount,356 ));357 Ok(())358 }359360 361 pub fn create_multiple_items(362 collection: &FungibleHandle<T>,363 sender: &T::CrossAccountId,364 data: BTreeMap<T::CrossAccountId, u128>,365 nesting_budget: &dyn Budget,366 ) -> DispatchResult {367 if !collection.is_owner_or_admin(sender) {368 ensure!(369 collection.permissions.mint_mode(),370 <CommonError<T>>::PublicMintingNotAllowed371 );372 collection.check_allowlist(sender)?;373374 for (owner, _) in data.iter() {375 collection.check_allowlist(owner)?;376 }377 }378379 let total_supply = data380 .iter()381 .map(|(_, v)| *v)382 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {383 acc.checked_add(v)384 })385 .ok_or(ArithmeticError::Overflow)?;386387 let mut balances = data;388 for (k, v) in balances.iter_mut() {389 *v = <Balance<T>>::get((collection.id, &k))390 .checked_add(*v)391 .ok_or(ArithmeticError::Overflow)?;392 }393394 for (to, _) in balances.iter() {395 <PalletStructure<T>>::check_nesting(396 sender.clone(),397 to,398 collection.id,399 TokenId::default(),400 nesting_budget,401 )?;402 }403404 405406 <TotalSupply<T>>::insert(collection.id, total_supply);407 for (user, amount) in balances {408 <Balance<T>>::insert((collection.id, &user), amount);409 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(410 &user,411 collection.id,412 TokenId::default(),413 );414 <PalletEvm<T>>::deposit_log(415 ERC20Events::Transfer {416 from: H160::default(),417 to: *user.as_eth(),418 value: amount.into(),419 }420 .to_log(collection_id_to_address(collection.id)),421 );422 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(423 collection.id,424 TokenId::default(),425 user.clone(),426 amount,427 ));428 }429430 Ok(())431 }432433 fn set_allowance_unchecked(434 collection: &FungibleHandle<T>,435 owner: &T::CrossAccountId,436 spender: &T::CrossAccountId,437 amount: u128,438 ) {439 if amount == 0 {440 <Allowance<T>>::remove((collection.id, owner, spender));441 } else {442 <Allowance<T>>::insert((collection.id, owner, spender), amount);443 }444445 <PalletEvm<T>>::deposit_log(446 ERC20Events::Approval {447 owner: *owner.as_eth(),448 spender: *spender.as_eth(),449 value: amount.into(),450 }451 .to_log(collection_id_to_address(collection.id)),452 );453 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(454 collection.id,455 TokenId(0),456 owner.clone(),457 spender.clone(),458 amount,459 ));460 }461462 463 pub fn set_allowance(464 collection: &FungibleHandle<T>,465 owner: &T::CrossAccountId,466 spender: &T::CrossAccountId,467 amount: u128,468 ) -> DispatchResult {469 if collection.permissions.access() == AccessMode::AllowList {470 collection.check_allowlist(owner)?;471 collection.check_allowlist(spender)?;472 }473474 if <Balance<T>>::get((collection.id, owner)) < amount {475 ensure!(476 collection.ignores_owned_amount(owner),477 <CommonError<T>>::CantApproveMoreThanOwned478 );479 }480481 482483 Self::set_allowance_unchecked(collection, owner, spender, amount);484 Ok(())485 }486487 fn check_allowed(488 collection: &FungibleHandle<T>,489 spender: &T::CrossAccountId,490 from: &T::CrossAccountId,491 amount: u128,492 nesting_budget: &dyn Budget,493 ) -> Result<Option<u128>, DispatchError> {494 if spender.conv_eq(from) {495 return Ok(None);496 }497 if collection.permissions.access() == AccessMode::AllowList {498 499 collection.check_allowlist(spender)?;500 }501 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {502 503 ensure!(504 <PalletStructure<T>>::check_indirectly_owned(505 spender.clone(),506 source.0,507 source.1,508 None,509 nesting_budget510 )?,511 <CommonError<T>>::ApprovedValueTooLow,512 );513 return Ok(None);514 }515 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);516 if allowance.is_none() {517 ensure!(518 collection.ignores_allowance(spender),519 <CommonError<T>>::ApprovedValueTooLow520 );521 }522523 Ok(allowance)524 }525526 527 pub fn transfer_from(528 collection: &FungibleHandle<T>,529 spender: &T::CrossAccountId,530 from: &T::CrossAccountId,531 to: &T::CrossAccountId,532 amount: u128,533 nesting_budget: &dyn Budget,534 ) -> DispatchResult {535 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;536537 538539 Self::transfer(collection, from, to, amount, nesting_budget)?;540 if let Some(allowance) = allowance {541 Self::set_allowance_unchecked(collection, from, spender, allowance);542 }543 Ok(())544 }545546 547 pub fn burn_from(548 collection: &FungibleHandle<T>,549 spender: &T::CrossAccountId,550 from: &T::CrossAccountId,551 amount: u128,552 nesting_budget: &dyn Budget,553 ) -> DispatchResult {554 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;555556 557558 Self::burn(collection, from, amount)?;559 if let Some(allowance) = allowance {560 Self::set_allowance_unchecked(collection, from, spender, allowance);561 }562 Ok(())563 }564565 566 pub fn create_item(567 collection: &FungibleHandle<T>,568 sender: &T::CrossAccountId,569 data: CreateItemData<T>,570 nesting_budget: &dyn Budget,571 ) -> DispatchResult {572 Self::create_multiple_items(573 collection,574 sender,575 [(data.0, data.1)].into_iter().collect(),576 nesting_budget,577 )578 }579}