12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;8283use evm_coder::ToLog;84use frame_support::{dispatch::PostDispatchInfo, ensure, pallet_prelude::*};85pub use pallet::*;86use pallet_common::{87 eth::collection_id_to_address, helpers::add_weight_to_post_info,88 weights::WeightInfo as CommonWeightInfo, Error as CommonError, Event as CommonEvent,89 Pallet as PalletCommon, SelfWeightOf as PalletCommonWeightOf,90};91use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};92use pallet_evm_coder_substrate::WithRecorder;93use pallet_structure::Pallet as PalletStructure;94use sp_core::H160;95use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};96use sp_std::{collections::btree_map::BTreeMap, vec::Vec};97use up_data_structs::{98 budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,99 Property, PropertyKey, TokenId,100};101use weights::WeightInfo;102103use crate::erc::ERC20Events;104#[cfg(feature = "runtime-benchmarks")]105pub mod benchmarking;106pub mod common;107pub mod erc;108pub mod weights;109110pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);111pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;112113#[frame_support::pallet]114pub mod pallet {115 use frame_support::{116 pallet_prelude::*, storage::Key, Blake2_128, Blake2_128Concat, Twox64Concat,117 };118 use up_data_structs::CollectionId;119120 use super::weights::WeightInfo;121122 #[pallet::error]123 pub enum Error<T> {124 125 NotFungibleDataUsedToMintFungibleCollectionToken,126 127 FungibleItemsHaveNoId,128 129 FungibleItemsDontHaveData,130 131 FungibleDisallowsNesting,132 133 SettingPropertiesNotAllowed,134 135 SettingAllowanceForAllNotAllowed,136 137 FungibleTokensAreAlwaysValid,138 }139140 #[pallet::config]141 pub trait Config:142 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config143 {144 type WeightInfo: WeightInfo;145 }146147 #[pallet::pallet]148 pub struct Pallet<T>(_);149150 151 #[pallet::storage]152 pub type TotalSupply<T: Config> =153 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;154155 156 #[pallet::storage]157 pub type Balance<T: Config> = StorageNMap<158 Key = (159 Key<Twox64Concat, CollectionId>,160 Key<Blake2_128Concat, T::CrossAccountId>,161 ),162 Value = u128,163 QueryKind = ValueQuery,164 >;165166 167 #[pallet::storage]168 pub type Allowance<T: Config> = StorageNMap<169 Key = (170 Key<Twox64Concat, CollectionId>,171 Key<Blake2_128, T::CrossAccountId>, 172 Key<Blake2_128Concat, T::CrossAccountId>, 173 ),174 Value = u128,175 QueryKind = ValueQuery,176 >;177}178179180181pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);182183184impl<T: Config> FungibleHandle<T> {185 186 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {187 Self(inner)188 }189190 191 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {192 self.0193 }194 195 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {196 &mut self.0197 }198}199impl<T: Config> WithRecorder<T> for FungibleHandle<T> {200 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {201 self.0.recorder()202 }203 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {204 self.0.into_recorder()205 }206}207impl<T: Config> Deref for FungibleHandle<T> {208 type Target = pallet_common::CollectionHandle<T>;209210 fn deref(&self) -> &Self::Target {211 &self.0212 }213}214215216impl<T: Config> Pallet<T> {217 218 pub fn init_collection(219 owner: T::CrossAccountId,220 payer: T::CrossAccountId,221 data: CreateCollectionData<T::CrossAccountId>,222 ) -> Result<CollectionId, DispatchError> {223 <PalletCommon<T>>::init_collection(owner, payer, data)224 }225226 227 pub fn init_foreign_collection(228 owner: T::CrossAccountId,229 payer: T::CrossAccountId,230 data: CreateCollectionData<T::CrossAccountId>,231 ) -> Result<CollectionId, DispatchError> {232 <PalletCommon<T>>::init_foreign_collection(owner, payer, data)233 }234235 236 pub fn destroy_collection(237 collection: FungibleHandle<T>,238 sender: &T::CrossAccountId,239 ) -> DispatchResult {240 let id = collection.id;241242 if Self::collection_has_tokens(id) {243 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());244 }245246 247248 PalletCommon::destroy_collection(collection.0, sender)?;249250 <TotalSupply<T>>::remove(id);251 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);252 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);253 Ok(())254 }255256 257 pub fn set_collection_properties(258 collection: &FungibleHandle<T>,259 sender: &T::CrossAccountId,260 properties: Vec<Property>,261 ) -> DispatchResult {262 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())263 }264265 266 pub fn delete_collection_properties(267 collection: &FungibleHandle<T>,268 sender: &T::CrossAccountId,269 property_keys: Vec<PropertyKey>,270 ) -> DispatchResult {271 <PalletCommon<T>>::delete_collection_properties(272 collection,273 sender,274 property_keys.into_iter(),275 )276 }277278 279 fn collection_has_tokens(collection_id: CollectionId) -> bool {280 <TotalSupply<T>>::get(collection_id) != 0281 }282283 284 285 286 pub fn burn(287 collection: &FungibleHandle<T>,288 owner: &T::CrossAccountId,289 amount: u128,290 ) -> DispatchResult {291 let total_supply = <TotalSupply<T>>::get(collection.id)292 .checked_sub(amount)293 .ok_or(<CommonError<T>>::TokenValueTooLow)?;294295 let balance = <Balance<T>>::get((collection.id, owner))296 .checked_sub(amount)297 .ok_or(<CommonError<T>>::TokenValueTooLow)?;298299 300 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);301302 if collection.permissions.access() == AccessMode::AllowList {303 collection.check_allowlist(owner)?;304 }305306 307308 if balance == 0 {309 <Balance<T>>::remove((collection.id, owner));310 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());311 } else {312 <Balance<T>>::insert((collection.id, owner), balance);313 }314 <TotalSupply<T>>::insert(collection.id, total_supply);315316 <PalletEvm<T>>::deposit_log(317 ERC20Events::Transfer {318 from: *owner.as_eth(),319 to: H160::default(),320 value: amount.into(),321 }322 .to_log(collection_id_to_address(collection.id)),323 );324 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(325 collection.id,326 TokenId::default(),327 owner.clone(),328 amount,329 ));330 Ok(())331 }332333 334 pub fn burn_foreign(335 collection: &FungibleHandle<T>,336 owner: &T::CrossAccountId,337 amount: u128,338 ) -> DispatchResult {339 let total_supply = <TotalSupply<T>>::get(collection.id)340 .checked_sub(amount)341 .ok_or(<CommonError<T>>::TokenValueTooLow)?;342343 let balance = <Balance<T>>::get((collection.id, owner))344 .checked_sub(amount)345 .ok_or(<CommonError<T>>::TokenValueTooLow)?;346 347348 if balance == 0 {349 <Balance<T>>::remove((collection.id, owner));350 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());351 } else {352 <Balance<T>>::insert((collection.id, owner), balance);353 }354 <TotalSupply<T>>::insert(collection.id, total_supply);355356 <PalletEvm<T>>::deposit_log(357 ERC20Events::Transfer {358 from: *owner.as_eth(),359 to: H160::default(),360 value: amount.into(),361 }362 .to_log(collection_id_to_address(collection.id)),363 );364 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(365 collection.id,366 TokenId::default(),367 owner.clone(),368 amount,369 ));370 Ok(())371 }372373 374 375 376 377 378 379 380 pub fn transfer(381 collection: &FungibleHandle<T>,382 from: &T::CrossAccountId,383 to: &T::CrossAccountId,384 amount: u128,385 nesting_budget: &dyn Budget,386 ) -> DispatchResultWithPostInfo {387 ensure!(388 collection.limits.transfers_enabled(),389 <CommonError<T>>::TransferNotAllowed,390 );391392 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();393394 if collection.permissions.access() == AccessMode::AllowList {395 collection.check_allowlist(from)?;396 collection.check_allowlist(to)?;397 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;398 }399 <PalletCommon<T>>::ensure_correct_receiver(to)?;400 let balance_from = <Balance<T>>::get((collection.id, from))401 .checked_sub(amount)402 .ok_or(<CommonError<T>>::TokenValueTooLow)?;403 let balance_to = if from != to && amount != 0 {404 Some(405 <Balance<T>>::get((collection.id, to))406 .checked_add(amount)407 .ok_or(ArithmeticError::Overflow)?,408 )409 } else {410 None411 };412413 414415 if let Some(balance_to) = balance_to {416 417418 <PalletStructure<T>>::nest_if_sent_to_token(419 from.clone(),420 to,421 collection.id,422 TokenId::default(),423 nesting_budget,424 )?;425426 if balance_from == 0 {427 <Balance<T>>::remove((collection.id, from));428 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());429 } else {430 <Balance<T>>::insert((collection.id, from), balance_from);431 }432 <Balance<T>>::insert((collection.id, to), balance_to);433 }434435 <PalletEvm<T>>::deposit_log(436 ERC20Events::Transfer {437 from: *from.as_eth(),438 to: *to.as_eth(),439 value: amount.into(),440 }441 .to_log(collection_id_to_address(collection.id)),442 );443 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(444 collection.id,445 TokenId::default(),446 from.clone(),447 to.clone(),448 amount,449 ));450451 Ok(PostDispatchInfo {452 actual_weight: Some(actual_weight),453 pays_fee: Pays::Yes,454 })455 }456457 458 459 460 pub fn create_multiple_items_common(461 collection: &FungibleHandle<T>,462 sender: &T::CrossAccountId,463 data: BTreeMap<T::CrossAccountId, u128>,464 nesting_budget: &dyn Budget,465 ) -> DispatchResult {466 let total_supply = data467 .values()468 .copied()469 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {470 acc.checked_add(v)471 })472 .ok_or(ArithmeticError::Overflow)?;473474 for (to, _) in data.iter() {475 <PalletStructure<T>>::check_nesting(476 sender.clone(),477 to,478 collection.id,479 TokenId::default(),480 nesting_budget,481 )?;482 }483484 let updated_balances = data485 .into_iter()486 .map(|(user, amount)| {487 let updated_balance = <Balance<T>>::get((collection.id, &user))488 .checked_add(amount)489 .ok_or(ArithmeticError::Overflow)?;490 Ok((user, amount, updated_balance))491 })492 .collect::<Result<Vec<_>, DispatchError>>()?;493494 495496 <TotalSupply<T>>::insert(collection.id, total_supply);497 for (user, amount, updated_balance) in updated_balances {498 <Balance<T>>::insert((collection.id, &user), updated_balance);499 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(500 &user,501 collection.id,502 TokenId::default(),503 );504 <PalletEvm<T>>::deposit_log(505 ERC20Events::Transfer {506 from: H160::default(),507 to: *user.as_eth(),508 value: amount.into(),509 }510 .to_log(collection_id_to_address(collection.id)),511 );512 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(513 collection.id,514 TokenId::default(),515 user.clone(),516 amount,517 ));518 }519520 Ok(())521 }522523 524 525 pub fn create_multiple_items(526 collection: &FungibleHandle<T>,527 sender: &T::CrossAccountId,528 data: BTreeMap<T::CrossAccountId, u128>,529 nesting_budget: &dyn Budget,530 ) -> DispatchResult {531 532 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);533534 if !collection.is_owner_or_admin(sender) {535 ensure!(536 collection.permissions.mint_mode(),537 <CommonError<T>>::PublicMintingNotAllowed538 );539 collection.check_allowlist(sender)?;540541 for (owner, _) in data.iter() {542 collection.check_allowlist(owner)?;543 }544 }545546 Self::create_multiple_items_common(collection, sender, data, nesting_budget)547 }548549 550 551 pub fn create_multiple_items_foreign(552 collection: &FungibleHandle<T>,553 sender: &T::CrossAccountId,554 data: BTreeMap<T::CrossAccountId, u128>,555 nesting_budget: &dyn Budget,556 ) -> DispatchResult {557 Self::create_multiple_items_common(collection, sender, data, nesting_budget)558 }559560 fn set_allowance_unchecked(561 collection: &FungibleHandle<T>,562 owner: &T::CrossAccountId,563 spender: &T::CrossAccountId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, owner, spender));568 } else {569 <Allowance<T>>::insert((collection.id, owner, spender), amount);570 }571572 <PalletEvm<T>>::deposit_log(573 ERC20Events::Approval {574 owner: *owner.as_eth(),575 spender: *spender.as_eth(),576 value: amount.into(),577 }578 .to_log(collection_id_to_address(collection.id)),579 );580 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(581 collection.id,582 TokenId(0),583 owner.clone(),584 spender.clone(),585 amount,586 ));587 }588589 590 591 592 593 594 595 pub fn set_allowance(596 collection: &FungibleHandle<T>,597 owner: &T::CrossAccountId,598 spender: &T::CrossAccountId,599 amount: u128,600 ) -> DispatchResult {601 if collection.permissions.access() == AccessMode::AllowList {602 collection.check_allowlist(owner)?;603 collection.check_allowlist(spender)?;604 }605606 if <Balance<T>>::get((collection.id, owner)) < amount {607 ensure!(608 collection.ignores_owned_amount(owner),609 <CommonError<T>>::CantApproveMoreThanOwned610 );611 }612613 614615 Self::set_allowance_unchecked(collection, owner, spender, amount);616 Ok(())617 }618619 620 621 622 623 624 625 626 pub fn set_allowance_from(627 collection: &FungibleHandle<T>,628 sender: &T::CrossAccountId,629 from: &T::CrossAccountId,630 to: &T::CrossAccountId,631 amount: u128,632 ) -> DispatchResult {633 if collection.permissions.access() == AccessMode::AllowList {634 collection.check_allowlist(sender)?;635 collection.check_allowlist(from)?;636 collection.check_allowlist(to)?;637 }638639 ensure!(640 sender.conv_eq(from),641 <CommonError<T>>::AddressIsNotEthMirror642 );643644 if <Balance<T>>::get((collection.id, from)) < amount {645 ensure!(646 collection.limits.owner_can_transfer()647 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),648 <CommonError<T>>::CantApproveMoreThanOwned649 );650 }651652 653654 Self::set_allowance_unchecked(collection, from, to, amount);655 Ok(())656 }657658 659 660 661 662 663 664 665 fn check_allowed(666 collection: &FungibleHandle<T>,667 spender: &T::CrossAccountId,668 from: &T::CrossAccountId,669 amount: u128,670 nesting_budget: &dyn Budget,671 ) -> Result<Option<u128>, DispatchError> {672 if spender.conv_eq(from) {673 return Ok(None);674 }675 if collection.permissions.access() == AccessMode::AllowList {676 677 collection.check_allowlist(spender)?;678 }679680 if collection.ignores_token_restrictions(spender) {681 return Ok(Self::compute_allowance_decrease(682 collection, from, spender, amount,683 ));684 }685686 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {687 ensure!(688 <PalletStructure<T>>::check_indirectly_owned(689 spender.clone(),690 source.0,691 source.1,692 None,693 nesting_budget694 )?,695 <CommonError<T>>::ApprovedValueTooLow,696 );697 return Ok(None);698 }699700 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);701 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);702703 Ok(allowance)704 }705706 707 708 fn compute_allowance_decrease(709 collection: &FungibleHandle<T>,710 from: &T::CrossAccountId,711 spender: &T::CrossAccountId,712 amount: u128,713 ) -> Option<u128> {714 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)715 }716717 718 719 720 721 pub fn transfer_from(722 collection: &FungibleHandle<T>,723 spender: &T::CrossAccountId,724 from: &T::CrossAccountId,725 to: &T::CrossAccountId,726 amount: u128,727 nesting_budget: &dyn Budget,728 ) -> DispatchResultWithPostInfo {729 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;730731 732733 let mut result = Self::transfer(collection, from, to, amount, nesting_budget);734 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());735 result?;736737 if let Some(allowance) = allowance {738 Self::set_allowance_unchecked(collection, from, spender, allowance);739 add_weight_to_post_info(740 &mut result,741 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),742 )743 }744 result745 }746747 748 749 750 751 752 pub fn burn_from(753 collection: &FungibleHandle<T>,754 spender: &T::CrossAccountId,755 from: &T::CrossAccountId,756 amount: u128,757 nesting_budget: &dyn Budget,758 ) -> DispatchResult {759 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;760761 762763 Self::burn(collection, from, amount)?;764 if let Some(allowance) = allowance {765 Self::set_allowance_unchecked(collection, from, spender, allowance);766 }767 Ok(())768 }769770 771 772 773 774 775 776 777 pub fn create_item(778 collection: &FungibleHandle<T>,779 sender: &T::CrossAccountId,780 data: CreateItemData<T>,781 nesting_budget: &dyn Budget,782 ) -> DispatchResult {783 Self::create_multiple_items(784 collection,785 sender,786 [(data.0, data.1)].into_iter().collect(),787 nesting_budget,788 )789 }790791 792 793 794 795 pub fn create_item_foreign(796 collection: &FungibleHandle<T>,797 sender: &T::CrossAccountId,798 data: CreateItemData<T>,799 nesting_budget: &dyn Budget,800 ) -> DispatchResult {801 Self::create_multiple_items_foreign(802 collection,803 sender,804 [(data.0, data.1)].into_iter().collect(),805 nesting_budget,806 )807 }808809 810 811 812 813 814 815 pub fn token_owners(816 collection: CollectionId,817 _token: TokenId,818 ) -> Option<Vec<T::CrossAccountId>> {819 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))820 .map(|(owner, _amount)| owner)821 .take(10)822 .collect();823824 if res.is_empty() {825 None826 } else {827 Some(res)828 }829 }830}