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, vec::Vec};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 FungibleItemsHaveNoId,124 125 FungibleItemsDontHaveData,126 127 FungibleDisallowsNesting,128 129 SettingPropertiesNotAllowed,130 }131132 #[pallet::config]133 pub trait Config:134 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config135 {136 type WeightInfo: WeightInfo;137 }138139 #[pallet::pallet]140 #[pallet::generate_store(pub(super) trait Store)]141 pub struct Pallet<T>(_);142143 144 #[pallet::storage]145 pub type TotalSupply<T: Config> =146 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;147148 149 #[pallet::storage]150 pub type Balance<T: Config> = StorageNMap<151 Key = (152 Key<Twox64Concat, CollectionId>,153 Key<Blake2_128Concat, T::CrossAccountId>,154 ),155 Value = u128,156 QueryKind = ValueQuery,157 >;158159 160 #[pallet::storage]161 pub type Allowance<T: Config> = StorageNMap<162 Key = (163 Key<Twox64Concat, CollectionId>,164 Key<Blake2_128, T::CrossAccountId>,165 Key<Blake2_128Concat, T::CrossAccountId>,166 ),167 Value = u128,168 QueryKind = ValueQuery,169 >;170}171172173174pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);175176177impl<T: Config> FungibleHandle<T> {178 179 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {180 Self(inner)181 }182183 184 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {185 self.0186 }187 188 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {189 &mut self.0190 }191}192impl<T: Config> WithRecorder<T> for FungibleHandle<T> {193 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {194 self.0.recorder()195 }196 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {197 self.0.into_recorder()198 }199}200impl<T: Config> Deref for FungibleHandle<T> {201 type Target = pallet_common::CollectionHandle<T>;202203 fn deref(&self) -> &Self::Target {204 &self.0205 }206}207208209impl<T: Config> Pallet<T> {210 211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, false)216 }217218 219 pub fn destroy_collection(220 collection: FungibleHandle<T>,221 sender: &T::CrossAccountId,222 ) -> DispatchResult {223 let id = collection.id;224225 if Self::collection_has_tokens(id) {226 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());227 }228229 230231 PalletCommon::destroy_collection(collection.0, sender)?;232233 <TotalSupply<T>>::remove(id);234 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);235 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);236 Ok(())237 }238239 240 fn collection_has_tokens(collection_id: CollectionId) -> bool {241 <TotalSupply<T>>::get(collection_id) != 0242 }243244 245 246 247 pub fn burn(248 collection: &FungibleHandle<T>,249 owner: &T::CrossAccountId,250 amount: u128,251 ) -> DispatchResult {252 let total_supply = <TotalSupply<T>>::get(collection.id)253 .checked_sub(amount)254 .ok_or(<CommonError<T>>::TokenValueTooLow)?;255256 let balance = <Balance<T>>::get((collection.id, owner))257 .checked_sub(amount)258 .ok_or(<CommonError<T>>::TokenValueTooLow)?;259260 if collection.permissions.access() == AccessMode::AllowList {261 collection.check_allowlist(owner)?;262 }263264 265266 if balance == 0 {267 <Balance<T>>::remove((collection.id, owner));268 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());269 } else {270 <Balance<T>>::insert((collection.id, owner), balance);271 }272 <TotalSupply<T>>::insert(collection.id, total_supply);273274 <PalletEvm<T>>::deposit_log(275 ERC20Events::Transfer {276 from: *owner.as_eth(),277 to: H160::default(),278 value: amount.into(),279 }280 .to_log(collection_id_to_address(collection.id)),281 );282 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(283 collection.id,284 TokenId::default(),285 owner.clone(),286 amount,287 ));288 Ok(())289 }290291 292 293 294 295 296 297 298 pub fn transfer(299 collection: &FungibleHandle<T>,300 from: &T::CrossAccountId,301 to: &T::CrossAccountId,302 amount: u128,303 nesting_budget: &dyn Budget,304 ) -> DispatchResult {305 ensure!(306 collection.limits.transfers_enabled(),307 <CommonError<T>>::TransferNotAllowed,308 );309310 if collection.permissions.access() == AccessMode::AllowList {311 collection.check_allowlist(from)?;312 collection.check_allowlist(to)?;313 }314 <PalletCommon<T>>::ensure_correct_receiver(to)?;315316 let balance_from = <Balance<T>>::get((collection.id, from))317 .checked_sub(amount)318 .ok_or(<CommonError<T>>::TokenValueTooLow)?;319 let balance_to = if from != to {320 Some(321 <Balance<T>>::get((collection.id, to))322 .checked_add(amount)323 .ok_or(ArithmeticError::Overflow)?,324 )325 } else {326 None327 };328329 330331 <PalletStructure<T>>::nest_if_sent_to_token(332 from.clone(),333 to,334 collection.id,335 TokenId::default(),336 nesting_budget,337 )?;338339 if let Some(balance_to) = balance_to {340 341 if balance_from == 0 {342 <Balance<T>>::remove((collection.id, from));343 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());344 } else {345 <Balance<T>>::insert((collection.id, from), balance_from);346 }347 <Balance<T>>::insert((collection.id, to), balance_to);348 }349350 <PalletEvm<T>>::deposit_log(351 ERC20Events::Transfer {352 from: *from.as_eth(),353 to: *to.as_eth(),354 value: amount.into(),355 }356 .to_log(collection_id_to_address(collection.id)),357 );358 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(359 collection.id,360 TokenId::default(),361 from.clone(),362 to.clone(),363 amount,364 ));365 Ok(())366 }367368 369 370 pub fn create_multiple_items(371 collection: &FungibleHandle<T>,372 sender: &T::CrossAccountId,373 data: BTreeMap<T::CrossAccountId, u128>,374 nesting_budget: &dyn Budget,375 ) -> DispatchResult {376 if !collection.is_owner_or_admin(sender) {377 ensure!(378 collection.permissions.mint_mode(),379 <CommonError<T>>::PublicMintingNotAllowed380 );381 collection.check_allowlist(sender)?;382383 for (owner, _) in data.iter() {384 collection.check_allowlist(owner)?;385 }386 }387388 let total_supply = data389 .iter()390 .map(|(_, v)| *v)391 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {392 acc.checked_add(v)393 })394 .ok_or(ArithmeticError::Overflow)?;395396 for (to, _) in data.iter() {397 <PalletStructure<T>>::check_nesting(398 sender.clone(),399 to,400 collection.id,401 TokenId::default(),402 nesting_budget,403 )?;404 }405406 let updated_balances = data407 .into_iter()408 .map(|(user, amount)| {409 let updated_balance = <Balance<T>>::get((collection.id, &user))410 .checked_add(amount)411 .ok_or(ArithmeticError::Overflow)?;412 Ok((user, amount, updated_balance))413 })414 .collect::<Result<Vec<_>, DispatchError>>()?;415416 417418 <TotalSupply<T>>::insert(collection.id, total_supply);419 for (user, amount, updated_balance) in updated_balances {420 <Balance<T>>::insert((collection.id, &user), updated_balance);421 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(422 &user,423 collection.id,424 TokenId::default(),425 );426 <PalletEvm<T>>::deposit_log(427 ERC20Events::Transfer {428 from: H160::default(),429 to: *user.as_eth(),430 value: amount.into(),431 }432 .to_log(collection_id_to_address(collection.id)),433 );434 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(435 collection.id,436 TokenId::default(),437 user.clone(),438 amount,439 ));440 }441442 Ok(())443 }444445 fn set_allowance_unchecked(446 collection: &FungibleHandle<T>,447 owner: &T::CrossAccountId,448 spender: &T::CrossAccountId,449 amount: u128,450 ) {451 if amount == 0 {452 <Allowance<T>>::remove((collection.id, owner, spender));453 } else {454 <Allowance<T>>::insert((collection.id, owner, spender), amount);455 }456457 <PalletEvm<T>>::deposit_log(458 ERC20Events::Approval {459 owner: *owner.as_eth(),460 spender: *spender.as_eth(),461 value: amount.into(),462 }463 .to_log(collection_id_to_address(collection.id)),464 );465 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(466 collection.id,467 TokenId(0),468 owner.clone(),469 spender.clone(),470 amount,471 ));472 }473474 475 476 477 478 479 480 pub fn set_allowance(481 collection: &FungibleHandle<T>,482 owner: &T::CrossAccountId,483 spender: &T::CrossAccountId,484 amount: u128,485 ) -> DispatchResult {486 if collection.permissions.access() == AccessMode::AllowList {487 collection.check_allowlist(owner)?;488 collection.check_allowlist(spender)?;489 }490491 if <Balance<T>>::get((collection.id, owner)) < amount {492 ensure!(493 collection.ignores_owned_amount(owner),494 <CommonError<T>>::CantApproveMoreThanOwned495 );496 }497498 499500 Self::set_allowance_unchecked(collection, owner, spender, amount);501 Ok(())502 }503504 505 506 507 508 509 510 511 fn check_allowed(512 collection: &FungibleHandle<T>,513 spender: &T::CrossAccountId,514 from: &T::CrossAccountId,515 amount: u128,516 nesting_budget: &dyn Budget,517 ) -> Result<Option<u128>, DispatchError> {518 if spender.conv_eq(from) {519 return Ok(None);520 }521 if collection.permissions.access() == AccessMode::AllowList {522 523 collection.check_allowlist(spender)?;524 }525 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {526 527 ensure!(528 <PalletStructure<T>>::check_indirectly_owned(529 spender.clone(),530 source.0,531 source.1,532 None,533 nesting_budget534 )?,535 <CommonError<T>>::ApprovedValueTooLow,536 );537 return Ok(None);538 }539 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);540 if allowance.is_none() {541 ensure!(542 collection.ignores_allowance(spender),543 <CommonError<T>>::ApprovedValueTooLow544 );545 }546547 Ok(allowance)548 }549550 551 552 553 554555 pub fn transfer_from(556 collection: &FungibleHandle<T>,557 spender: &T::CrossAccountId,558 from: &T::CrossAccountId,559 to: &T::CrossAccountId,560 amount: u128,561 nesting_budget: &dyn Budget,562 ) -> DispatchResult {563 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;564565 566567 Self::transfer(collection, from, to, amount, nesting_budget)?;568 if let Some(allowance) = allowance {569 Self::set_allowance_unchecked(collection, from, spender, allowance);570 }571 Ok(())572 }573574 575 576 577 578 579 pub fn burn_from(580 collection: &FungibleHandle<T>,581 spender: &T::CrossAccountId,582 from: &T::CrossAccountId,583 amount: u128,584 nesting_budget: &dyn Budget,585 ) -> DispatchResult {586 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;587588 589590 Self::burn(collection, from, amount)?;591 if let Some(allowance) = allowance {592 Self::set_allowance_unchecked(collection, from, spender, allowance);593 }594 Ok(())595 }596597 598 599 600 601 602 603 604 pub fn create_item(605 collection: &FungibleHandle<T>,606 sender: &T::CrossAccountId,607 data: CreateItemData<T>,608 nesting_budget: &dyn Budget,609 ) -> DispatchResult {610 Self::create_multiple_items(611 collection,612 sender,613 [(data.0, data.1)].into_iter().collect(),614 nesting_budget,615 )616 }617618 619 620 621 622 623 624 pub fn token_owners(625 collection: CollectionId,626 _token: TokenId,627 ) -> Option<Vec<T::CrossAccountId>> {628 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))629 .map(|(owner, _amount)| owner)630 .take(10)631 .collect();632633 if res.is_empty() {634 None635 } else {636 Some(res)637 }638 }639}