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, CollectionFlags, TokenId, CreateCollectionData,87 mapping::TokenAddressMapping, 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::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 131 SettingAllowanceForAllNotAllowed,132 133 FungibleTokensAreAlwaysValid,134 }135136 #[pallet::config]137 pub trait Config:138 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config139 {140 type WeightInfo: WeightInfo;141 }142143 #[pallet::pallet]144 #[pallet::generate_store(pub(super) trait Store)]145 pub struct Pallet<T>(_);146147 148 #[pallet::storage]149 pub type TotalSupply<T: Config> =150 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;151152 153 #[pallet::storage]154 pub type Balance<T: Config> = StorageNMap<155 Key = (156 Key<Twox64Concat, CollectionId>,157 Key<Blake2_128Concat, T::CrossAccountId>,158 ),159 Value = u128,160 QueryKind = ValueQuery,161 >;162163 164 #[pallet::storage]165 pub type Allowance<T: Config> = StorageNMap<166 Key = (167 Key<Twox64Concat, CollectionId>,168 Key<Blake2_128, T::CrossAccountId>,169 Key<Blake2_128Concat, T::CrossAccountId>,170 ),171 Value = u128,172 QueryKind = ValueQuery,173 >;174}175176177178pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);179180181impl<T: Config> FungibleHandle<T> {182 183 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {184 Self(inner)185 }186187 188 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189 self.0190 }191 192 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {193 &mut self.0194 }195}196impl<T: Config> WithRecorder<T> for FungibleHandle<T> {197 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {198 self.0.recorder()199 }200 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {201 self.0.into_recorder()202 }203}204impl<T: Config> Deref for FungibleHandle<T> {205 type Target = pallet_common::CollectionHandle<T>;206207 fn deref(&self) -> &Self::Target {208 &self.0209 }210}211212213impl<T: Config> Pallet<T> {214 215 pub fn init_collection(216 owner: T::CrossAccountId,217 payer: T::CrossAccountId,218 data: CreateCollectionData<T::AccountId>,219 flags: CollectionFlags,220 ) -> Result<CollectionId, DispatchError> {221 <PalletCommon<T>>::init_collection(owner, payer, data, flags)222 }223224 225 pub fn init_foreign_collection(226 owner: T::CrossAccountId,227 payer: T::CrossAccountId,228 data: CreateCollectionData<T::AccountId>,229 ) -> Result<CollectionId, DispatchError> {230 let id = <PalletCommon<T>>::init_collection(231 owner,232 payer,233 data,234 CollectionFlags {235 foreign: true,236 ..Default::default()237 },238 )?;239 Ok(id)240 }241242 243 pub fn destroy_collection(244 collection: FungibleHandle<T>,245 sender: &T::CrossAccountId,246 ) -> DispatchResult {247 let id = collection.id;248249 if Self::collection_has_tokens(id) {250 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());251 }252253 254255 PalletCommon::destroy_collection(collection.0, sender)?;256257 <TotalSupply<T>>::remove(id);258 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);259 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);260 Ok(())261 }262263 264 fn collection_has_tokens(collection_id: CollectionId) -> bool {265 <TotalSupply<T>>::get(collection_id) != 0266 }267268 269 270 271 pub fn burn(272 collection: &FungibleHandle<T>,273 owner: &T::CrossAccountId,274 amount: u128,275 ) -> DispatchResult {276 let total_supply = <TotalSupply<T>>::get(collection.id)277 .checked_sub(amount)278 .ok_or(<CommonError<T>>::TokenValueTooLow)?;279280 let balance = <Balance<T>>::get((collection.id, owner))281 .checked_sub(amount)282 .ok_or(<CommonError<T>>::TokenValueTooLow)?;283284 285 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);286287 if collection.permissions.access() == AccessMode::AllowList {288 collection.check_allowlist(owner)?;289 }290291 292293 if balance == 0 {294 <Balance<T>>::remove((collection.id, owner));295 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());296 } else {297 <Balance<T>>::insert((collection.id, owner), balance);298 }299 <TotalSupply<T>>::insert(collection.id, total_supply);300301 <PalletEvm<T>>::deposit_log(302 ERC20Events::Transfer {303 from: *owner.as_eth(),304 to: H160::default(),305 value: amount.into(),306 }307 .to_log(collection_id_to_address(collection.id)),308 );309 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(310 collection.id,311 TokenId::default(),312 owner.clone(),313 amount,314 ));315 Ok(())316 }317318 319 pub fn burn_foreign(320 collection: &FungibleHandle<T>,321 owner: &T::CrossAccountId,322 amount: u128,323 ) -> DispatchResult {324 let total_supply = <TotalSupply<T>>::get(collection.id)325 .checked_sub(amount)326 .ok_or(<CommonError<T>>::TokenValueTooLow)?;327328 let balance = <Balance<T>>::get((collection.id, owner))329 .checked_sub(amount)330 .ok_or(<CommonError<T>>::TokenValueTooLow)?;331 332333 if balance == 0 {334 <Balance<T>>::remove((collection.id, owner));335 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());336 } else {337 <Balance<T>>::insert((collection.id, owner), balance);338 }339 <TotalSupply<T>>::insert(collection.id, total_supply);340341 <PalletEvm<T>>::deposit_log(342 ERC20Events::Transfer {343 from: *owner.as_eth(),344 to: H160::default(),345 value: amount.into(),346 }347 .to_log(collection_id_to_address(collection.id)),348 );349 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(350 collection.id,351 TokenId::default(),352 owner.clone(),353 amount,354 ));355 Ok(())356 }357358 359 360 361 362 363 364 365 pub fn transfer(366 collection: &FungibleHandle<T>,367 from: &T::CrossAccountId,368 to: &T::CrossAccountId,369 amount: u128,370 nesting_budget: &dyn Budget,371 ) -> DispatchResult {372 ensure!(373 collection.limits.transfers_enabled(),374 <CommonError<T>>::TransferNotAllowed,375 );376377 if collection.permissions.access() == AccessMode::AllowList {378 collection.check_allowlist(from)?;379 collection.check_allowlist(to)?;380 }381 <PalletCommon<T>>::ensure_correct_receiver(to)?;382383 let balance_from = <Balance<T>>::get((collection.id, from))384 .checked_sub(amount)385 .ok_or(<CommonError<T>>::TokenValueTooLow)?;386 let balance_to = if from != to && amount != 0 {387 Some(388 <Balance<T>>::get((collection.id, to))389 .checked_add(amount)390 .ok_or(ArithmeticError::Overflow)?,391 )392 } else {393 None394 };395396 397398 if let Some(balance_to) = balance_to {399 400401 <PalletStructure<T>>::nest_if_sent_to_token(402 from.clone(),403 to,404 collection.id,405 TokenId::default(),406 nesting_budget,407 )?;408409 if balance_from == 0 {410 <Balance<T>>::remove((collection.id, from));411 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());412 } else {413 <Balance<T>>::insert((collection.id, from), balance_from);414 }415 <Balance<T>>::insert((collection.id, to), balance_to);416 }417418 <PalletEvm<T>>::deposit_log(419 ERC20Events::Transfer {420 from: *from.as_eth(),421 to: *to.as_eth(),422 value: amount.into(),423 }424 .to_log(collection_id_to_address(collection.id)),425 );426 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(427 collection.id,428 TokenId::default(),429 from.clone(),430 to.clone(),431 amount,432 ));433 Ok(())434 }435436 437 438 439 pub fn create_multiple_items_common(440 collection: &FungibleHandle<T>,441 sender: &T::CrossAccountId,442 data: BTreeMap<T::CrossAccountId, u128>,443 nesting_budget: &dyn Budget,444 ) -> DispatchResult {445 let total_supply = data446 .iter()447 .map(|(_, v)| *v)448 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {449 acc.checked_add(v)450 })451 .ok_or(ArithmeticError::Overflow)?;452453 for (to, _) in data.iter() {454 <PalletStructure<T>>::check_nesting(455 sender.clone(),456 to,457 collection.id,458 TokenId::default(),459 nesting_budget,460 )?;461 }462463 let updated_balances = data464 .into_iter()465 .map(|(user, amount)| {466 let updated_balance = <Balance<T>>::get((collection.id, &user))467 .checked_add(amount)468 .ok_or(ArithmeticError::Overflow)?;469 Ok((user, amount, updated_balance))470 })471 .collect::<Result<Vec<_>, DispatchError>>()?;472473 474475 <TotalSupply<T>>::insert(collection.id, total_supply);476 for (user, amount, updated_balance) in updated_balances {477 <Balance<T>>::insert((collection.id, &user), updated_balance);478 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(479 &user,480 collection.id,481 TokenId::default(),482 );483 <PalletEvm<T>>::deposit_log(484 ERC20Events::Transfer {485 from: H160::default(),486 to: *user.as_eth(),487 value: amount.into(),488 }489 .to_log(collection_id_to_address(collection.id)),490 );491 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(492 collection.id,493 TokenId::default(),494 user.clone(),495 amount,496 ));497 }498499 Ok(())500 }501502 503 504 pub fn create_multiple_items(505 collection: &FungibleHandle<T>,506 sender: &T::CrossAccountId,507 data: BTreeMap<T::CrossAccountId, u128>,508 nesting_budget: &dyn Budget,509 ) -> DispatchResult {510 511 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);512513 if !collection.is_owner_or_admin(sender) {514 ensure!(515 collection.permissions.mint_mode(),516 <CommonError<T>>::PublicMintingNotAllowed517 );518 collection.check_allowlist(sender)?;519520 for (owner, _) in data.iter() {521 collection.check_allowlist(owner)?;522 }523 }524525 Self::create_multiple_items_common(collection, sender, data, nesting_budget)526 }527528 529 530 pub fn create_multiple_items_foreign(531 collection: &FungibleHandle<T>,532 sender: &T::CrossAccountId,533 data: BTreeMap<T::CrossAccountId, u128>,534 nesting_budget: &dyn Budget,535 ) -> DispatchResult {536 Self::create_multiple_items_common(collection, sender, data, nesting_budget)537 }538539 fn set_allowance_unchecked(540 collection: &FungibleHandle<T>,541 owner: &T::CrossAccountId,542 spender: &T::CrossAccountId,543 amount: u128,544 ) {545 if amount == 0 {546 <Allowance<T>>::remove((collection.id, owner, spender));547 } else {548 <Allowance<T>>::insert((collection.id, owner, spender), amount);549 }550551 <PalletEvm<T>>::deposit_log(552 ERC20Events::Approval {553 owner: *owner.as_eth(),554 spender: *spender.as_eth(),555 value: amount.into(),556 }557 .to_log(collection_id_to_address(collection.id)),558 );559 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(560 collection.id,561 TokenId(0),562 owner.clone(),563 spender.clone(),564 amount,565 ));566 }567568 569 570 571 572 573 574 pub fn set_allowance(575 collection: &FungibleHandle<T>,576 owner: &T::CrossAccountId,577 spender: &T::CrossAccountId,578 amount: u128,579 ) -> DispatchResult {580 if collection.permissions.access() == AccessMode::AllowList {581 collection.check_allowlist(owner)?;582 collection.check_allowlist(spender)?;583 }584585 if <Balance<T>>::get((collection.id, owner)) < amount {586 ensure!(587 collection.ignores_owned_amount(owner),588 <CommonError<T>>::CantApproveMoreThanOwned589 );590 }591592 593594 Self::set_allowance_unchecked(collection, owner, spender, amount);595 Ok(())596 }597598 599 600 601 602 603 604 605 fn check_allowed(606 collection: &FungibleHandle<T>,607 spender: &T::CrossAccountId,608 from: &T::CrossAccountId,609 amount: u128,610 nesting_budget: &dyn Budget,611 ) -> Result<Option<u128>, DispatchError> {612 if spender.conv_eq(from) {613 return Ok(None);614 }615 if collection.permissions.access() == AccessMode::AllowList {616 617 collection.check_allowlist(spender)?;618 }619 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {620 621 ensure!(622 <PalletStructure<T>>::check_indirectly_owned(623 spender.clone(),624 source.0,625 source.1,626 None,627 nesting_budget628 )?,629 <CommonError<T>>::ApprovedValueTooLow,630 );631 return Ok(None);632 }633 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);634 if allowance.is_none() {635 ensure!(636 collection.ignores_allowance(spender),637 <CommonError<T>>::ApprovedValueTooLow638 );639 }640641 Ok(allowance)642 }643644 645 646 647 648649 pub fn transfer_from(650 collection: &FungibleHandle<T>,651 spender: &T::CrossAccountId,652 from: &T::CrossAccountId,653 to: &T::CrossAccountId,654 amount: u128,655 nesting_budget: &dyn Budget,656 ) -> DispatchResult {657 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;658659 660661 Self::transfer(collection, from, to, amount, nesting_budget)?;662 if let Some(allowance) = allowance {663 Self::set_allowance_unchecked(collection, from, spender, allowance);664 }665 Ok(())666 }667668 669 670 671 672 673 pub fn burn_from(674 collection: &FungibleHandle<T>,675 spender: &T::CrossAccountId,676 from: &T::CrossAccountId,677 amount: u128,678 nesting_budget: &dyn Budget,679 ) -> DispatchResult {680 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;681682 683684 Self::burn(collection, from, amount)?;685 if let Some(allowance) = allowance {686 Self::set_allowance_unchecked(collection, from, spender, allowance);687 }688 Ok(())689 }690691 692 693 694 695 696 697 698 pub fn create_item(699 collection: &FungibleHandle<T>,700 sender: &T::CrossAccountId,701 data: CreateItemData<T>,702 nesting_budget: &dyn Budget,703 ) -> DispatchResult {704 Self::create_multiple_items(705 collection,706 sender,707 [(data.0, data.1)].into_iter().collect(),708 nesting_budget,709 )710 }711712 713 714 715 716 pub fn create_item_foreign(717 collection: &FungibleHandle<T>,718 sender: &T::CrossAccountId,719 data: CreateItemData<T>,720 nesting_budget: &dyn Budget,721 ) -> DispatchResult {722 Self::create_multiple_items_foreign(723 collection,724 sender,725 [(data.0, data.1)].into_iter().collect(),726 nesting_budget,727 )728 }729730 731 732 733 734 735 736 pub fn token_owners(737 collection: CollectionId,738 _token: TokenId,739 ) -> Option<Vec<T::CrossAccountId>> {740 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))741 .map(|(owner, _amount)| owner)742 .take(10)743 .collect();744745 if res.is_empty() {746 None747 } else {748 Some(res)749 }750 }751}