12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;82use evm_coder::ToLog;83use frame_support::{84 ensure,85 pallet_prelude::{DispatchResultWithPostInfo, Pays},86 dispatch::PostDispatchInfo,87};88use pallet_evm::account::CrossAccountId;89use up_data_structs::{90 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,91 budget::Budget, PropertyKey, Property,92};93use pallet_common::{94 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,95 eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,96 weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,97};98use pallet_evm::Pallet as PalletEvm;99use pallet_structure::Pallet as PalletStructure;100use pallet_evm_coder_substrate::WithRecorder;101use sp_core::H160;102use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};103use sp_std::{collections::btree_map::BTreeMap, vec::Vec};104use weights::WeightInfo;105pub use pallet::*;106107use crate::erc::ERC20Events;108#[cfg(feature = "runtime-benchmarks")]109pub mod benchmarking;110pub mod common;111pub mod erc;112pub mod weights;113114pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);115pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;116117#[frame_support::pallet]118pub mod pallet {119 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};120 use up_data_structs::CollectionId;121 use super::weights::WeightInfo;122123 #[pallet::error]124 pub enum Error<T> {125 126 NotFungibleDataUsedToMintFungibleCollectionToken,127 128 FungibleItemsHaveNoId,129 130 FungibleItemsDontHaveData,131 132 FungibleDisallowsNesting,133 134 SettingPropertiesNotAllowed,135 136 SettingAllowanceForAllNotAllowed,137 138 FungibleTokensAreAlwaysValid,139 }140141 #[pallet::config]142 pub trait Config:143 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config144 {145 type WeightInfo: WeightInfo;146 }147148 #[pallet::pallet]149 pub struct Pallet<T>(_);150151 152 #[pallet::storage]153 pub type TotalSupply<T: Config> =154 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;155156 157 #[pallet::storage]158 pub type Balance<T: Config> = StorageNMap<159 Key = (160 Key<Twox64Concat, CollectionId>,161 Key<Blake2_128Concat, T::CrossAccountId>,162 ),163 Value = u128,164 QueryKind = ValueQuery,165 >;166167 168 #[pallet::storage]169 pub type Allowance<T: Config> = StorageNMap<170 Key = (171 Key<Twox64Concat, CollectionId>,172 Key<Blake2_128, T::CrossAccountId>, 173 Key<Blake2_128Concat, T::CrossAccountId>, 174 ),175 Value = u128,176 QueryKind = ValueQuery,177 >;178}179180181182pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);183184185impl<T: Config> FungibleHandle<T> {186 187 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {188 Self(inner)189 }190191 192 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {193 self.0194 }195 196 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {197 &mut self.0198 }199}200impl<T: Config> WithRecorder<T> for FungibleHandle<T> {201 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {202 self.0.recorder()203 }204 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {205 self.0.into_recorder()206 }207}208impl<T: Config> Deref for FungibleHandle<T> {209 type Target = pallet_common::CollectionHandle<T>;210211 fn deref(&self) -> &Self::Target {212 &self.0213 }214}215216217impl<T: Config> Pallet<T> {218 219 pub fn init_collection(220 owner: T::CrossAccountId,221 payer: T::CrossAccountId,222 data: CreateCollectionData<T::CrossAccountId>,223 ) -> Result<CollectionId, DispatchError> {224 <PalletCommon<T>>::init_collection(owner, payer, data)225 }226227 228 pub fn init_foreign_collection(229 owner: T::CrossAccountId,230 payer: T::CrossAccountId,231 data: CreateCollectionData<T::CrossAccountId>,232 ) -> Result<CollectionId, DispatchError> {233 <PalletCommon<T>>::init_foreign_collection(owner, payer, data)234 }235236 237 pub fn destroy_collection(238 collection: FungibleHandle<T>,239 sender: &T::CrossAccountId,240 ) -> DispatchResult {241 let id = collection.id;242243 if Self::collection_has_tokens(id) {244 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());245 }246247 248249 PalletCommon::destroy_collection(collection.0, sender)?;250251 <TotalSupply<T>>::remove(id);252 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);253 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);254 Ok(())255 }256257 258 pub fn set_collection_properties(259 collection: &FungibleHandle<T>,260 sender: &T::CrossAccountId,261 properties: Vec<Property>,262 ) -> DispatchResult {263 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())264 }265266 267 pub fn delete_collection_properties(268 collection: &FungibleHandle<T>,269 sender: &T::CrossAccountId,270 property_keys: Vec<PropertyKey>,271 ) -> DispatchResult {272 <PalletCommon<T>>::delete_collection_properties(273 collection,274 sender,275 property_keys.into_iter(),276 )277 }278279 280 fn collection_has_tokens(collection_id: CollectionId) -> bool {281 <TotalSupply<T>>::get(collection_id) != 0282 }283284 285 286 287 pub fn burn(288 collection: &FungibleHandle<T>,289 owner: &T::CrossAccountId,290 amount: u128,291 ) -> DispatchResult {292 let total_supply = <TotalSupply<T>>::get(collection.id)293 .checked_sub(amount)294 .ok_or(<CommonError<T>>::TokenValueTooLow)?;295296 let balance = <Balance<T>>::get((collection.id, owner))297 .checked_sub(amount)298 .ok_or(<CommonError<T>>::TokenValueTooLow)?;299300 301 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);302303 if collection.permissions.access() == AccessMode::AllowList {304 collection.check_allowlist(owner)?;305 }306307 308309 if balance == 0 {310 <Balance<T>>::remove((collection.id, owner));311 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());312 } else {313 <Balance<T>>::insert((collection.id, owner), balance);314 }315 <TotalSupply<T>>::insert(collection.id, total_supply);316317 <PalletEvm<T>>::deposit_log(318 ERC20Events::Transfer {319 from: *owner.as_eth(),320 to: H160::default(),321 value: amount.into(),322 }323 .to_log(collection_id_to_address(collection.id)),324 );325 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(326 collection.id,327 TokenId::default(),328 owner.clone(),329 amount,330 ));331 Ok(())332 }333334 335 pub fn burn_foreign(336 collection: &FungibleHandle<T>,337 owner: &T::CrossAccountId,338 amount: u128,339 ) -> DispatchResult {340 let total_supply = <TotalSupply<T>>::get(collection.id)341 .checked_sub(amount)342 .ok_or(<CommonError<T>>::TokenValueTooLow)?;343344 let balance = <Balance<T>>::get((collection.id, owner))345 .checked_sub(amount)346 .ok_or(<CommonError<T>>::TokenValueTooLow)?;347 348349 if balance == 0 {350 <Balance<T>>::remove((collection.id, owner));351 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());352 } else {353 <Balance<T>>::insert((collection.id, owner), balance);354 }355 <TotalSupply<T>>::insert(collection.id, total_supply);356357 <PalletEvm<T>>::deposit_log(358 ERC20Events::Transfer {359 from: *owner.as_eth(),360 to: H160::default(),361 value: amount.into(),362 }363 .to_log(collection_id_to_address(collection.id)),364 );365 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(366 collection.id,367 TokenId::default(),368 owner.clone(),369 amount,370 ));371 Ok(())372 }373374 375 376 377 378 379 380 381 pub fn transfer(382 collection: &FungibleHandle<T>,383 from: &T::CrossAccountId,384 to: &T::CrossAccountId,385 amount: u128,386 nesting_budget: &dyn Budget,387 ) -> DispatchResultWithPostInfo {388 ensure!(389 collection.limits.transfers_enabled(),390 <CommonError<T>>::TransferNotAllowed,391 );392393 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();394395 if collection.permissions.access() == AccessMode::AllowList {396 collection.check_allowlist(from)?;397 collection.check_allowlist(to)?;398 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;399 }400 <PalletCommon<T>>::ensure_correct_receiver(to)?;401 let balance_from = <Balance<T>>::get((collection.id, from))402 .checked_sub(amount)403 .ok_or(<CommonError<T>>::TokenValueTooLow)?;404 let balance_to = if from != to && amount != 0 {405 Some(406 <Balance<T>>::get((collection.id, to))407 .checked_add(amount)408 .ok_or(ArithmeticError::Overflow)?,409 )410 } else {411 None412 };413414 415416 if let Some(balance_to) = balance_to {417 418419 <PalletStructure<T>>::nest_if_sent_to_token(420 from.clone(),421 to,422 collection.id,423 TokenId::default(),424 nesting_budget,425 )?;426427 if balance_from == 0 {428 <Balance<T>>::remove((collection.id, from));429 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());430 } else {431 <Balance<T>>::insert((collection.id, from), balance_from);432 }433 <Balance<T>>::insert((collection.id, to), balance_to);434 }435436 <PalletEvm<T>>::deposit_log(437 ERC20Events::Transfer {438 from: *from.as_eth(),439 to: *to.as_eth(),440 value: amount.into(),441 }442 .to_log(collection_id_to_address(collection.id)),443 );444 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(445 collection.id,446 TokenId::default(),447 from.clone(),448 to.clone(),449 amount,450 ));451452 Ok(PostDispatchInfo {453 actual_weight: Some(actual_weight),454 pays_fee: Pays::Yes,455 })456 }457458 459 460 461 pub fn create_multiple_items_common(462 collection: &FungibleHandle<T>,463 sender: &T::CrossAccountId,464 data: BTreeMap<T::CrossAccountId, u128>,465 nesting_budget: &dyn Budget,466 ) -> DispatchResult {467 let total_supply = data468 .values()469 .copied()470 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {471 acc.checked_add(v)472 })473 .ok_or(ArithmeticError::Overflow)?;474475 for (to, _) in data.iter() {476 <PalletStructure<T>>::check_nesting(477 sender.clone(),478 to,479 collection.id,480 TokenId::default(),481 nesting_budget,482 )?;483 }484485 let updated_balances = data486 .into_iter()487 .map(|(user, amount)| {488 let updated_balance = <Balance<T>>::get((collection.id, &user))489 .checked_add(amount)490 .ok_or(ArithmeticError::Overflow)?;491 Ok((user, amount, updated_balance))492 })493 .collect::<Result<Vec<_>, DispatchError>>()?;494495 496497 <TotalSupply<T>>::insert(collection.id, total_supply);498 for (user, amount, updated_balance) in updated_balances {499 <Balance<T>>::insert((collection.id, &user), updated_balance);500 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(501 &user,502 collection.id,503 TokenId::default(),504 );505 <PalletEvm<T>>::deposit_log(506 ERC20Events::Transfer {507 from: H160::default(),508 to: *user.as_eth(),509 value: amount.into(),510 }511 .to_log(collection_id_to_address(collection.id)),512 );513 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(514 collection.id,515 TokenId::default(),516 user.clone(),517 amount,518 ));519 }520521 Ok(())522 }523524 525 526 pub fn create_multiple_items(527 collection: &FungibleHandle<T>,528 sender: &T::CrossAccountId,529 data: BTreeMap<T::CrossAccountId, u128>,530 nesting_budget: &dyn Budget,531 ) -> DispatchResult {532 533 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);534535 if !collection.is_owner_or_admin(sender) {536 ensure!(537 collection.permissions.mint_mode(),538 <CommonError<T>>::PublicMintingNotAllowed539 );540 collection.check_allowlist(sender)?;541542 for (owner, _) in data.iter() {543 collection.check_allowlist(owner)?;544 }545 }546547 Self::create_multiple_items_common(collection, sender, data, nesting_budget)548 }549550 551 552 pub fn create_multiple_items_foreign(553 collection: &FungibleHandle<T>,554 sender: &T::CrossAccountId,555 data: BTreeMap<T::CrossAccountId, u128>,556 nesting_budget: &dyn Budget,557 ) -> DispatchResult {558 Self::create_multiple_items_common(collection, sender, data, nesting_budget)559 }560561 fn set_allowance_unchecked(562 collection: &FungibleHandle<T>,563 owner: &T::CrossAccountId,564 spender: &T::CrossAccountId,565 amount: u128,566 ) {567 if amount == 0 {568 <Allowance<T>>::remove((collection.id, owner, spender));569 } else {570 <Allowance<T>>::insert((collection.id, owner, spender), amount);571 }572573 <PalletEvm<T>>::deposit_log(574 ERC20Events::Approval {575 owner: *owner.as_eth(),576 spender: *spender.as_eth(),577 value: amount.into(),578 }579 .to_log(collection_id_to_address(collection.id)),580 );581 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(582 collection.id,583 TokenId(0),584 owner.clone(),585 spender.clone(),586 amount,587 ));588 }589590 591 592 593 594 595 596 pub fn set_allowance(597 collection: &FungibleHandle<T>,598 owner: &T::CrossAccountId,599 spender: &T::CrossAccountId,600 amount: u128,601 ) -> DispatchResult {602 if collection.permissions.access() == AccessMode::AllowList {603 collection.check_allowlist(owner)?;604 collection.check_allowlist(spender)?;605 }606607 if <Balance<T>>::get((collection.id, owner)) < amount {608 ensure!(609 collection.ignores_owned_amount(owner),610 <CommonError<T>>::CantApproveMoreThanOwned611 );612 }613614 615616 Self::set_allowance_unchecked(collection, owner, spender, amount);617 Ok(())618 }619620 621 622 623 624 625 626 627 pub fn set_allowance_from(628 collection: &FungibleHandle<T>,629 sender: &T::CrossAccountId,630 from: &T::CrossAccountId,631 to: &T::CrossAccountId,632 amount: u128,633 ) -> DispatchResult {634 if collection.permissions.access() == AccessMode::AllowList {635 collection.check_allowlist(sender)?;636 collection.check_allowlist(from)?;637 collection.check_allowlist(to)?;638 }639640 ensure!(641 sender.conv_eq(from),642 <CommonError<T>>::AddressIsNotEthMirror643 );644645 if <Balance<T>>::get((collection.id, from)) < amount {646 ensure!(647 collection.limits.owner_can_transfer()648 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),649 <CommonError<T>>::CantApproveMoreThanOwned650 );651 }652653 654655 Self::set_allowance_unchecked(collection, from, to, amount);656 Ok(())657 }658659 660 661 662 663 664 665 666 fn check_allowed(667 collection: &FungibleHandle<T>,668 spender: &T::CrossAccountId,669 from: &T::CrossAccountId,670 amount: u128,671 nesting_budget: &dyn Budget,672 ) -> Result<Option<u128>, DispatchError> {673 if spender.conv_eq(from) {674 return Ok(None);675 }676 if collection.permissions.access() == AccessMode::AllowList {677 678 collection.check_allowlist(spender)?;679 }680681 if collection.ignores_token_restrictions(spender) {682 return Ok(Self::compute_allowance_decrease(683 collection, from, spender, amount,684 ));685 }686687 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {688 ensure!(689 <PalletStructure<T>>::check_indirectly_owned(690 spender.clone(),691 source.0,692 source.1,693 None,694 nesting_budget695 )?,696 <CommonError<T>>::ApprovedValueTooLow,697 );698 return Ok(None);699 }700701 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);702 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);703704 Ok(allowance)705 }706707 708 709 fn compute_allowance_decrease(710 collection: &FungibleHandle<T>,711 from: &T::CrossAccountId,712 spender: &T::CrossAccountId,713 amount: u128,714 ) -> Option<u128> {715 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)716 }717718 719 720 721 722 pub fn transfer_from(723 collection: &FungibleHandle<T>,724 spender: &T::CrossAccountId,725 from: &T::CrossAccountId,726 to: &T::CrossAccountId,727 amount: u128,728 nesting_budget: &dyn Budget,729 ) -> DispatchResultWithPostInfo {730 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;731732 733734 let mut result = Self::transfer(collection, from, to, amount, nesting_budget);735 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());736 result?;737738 if let Some(allowance) = allowance {739 Self::set_allowance_unchecked(collection, from, spender, allowance);740 add_weight_to_post_info(741 &mut result,742 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),743 )744 }745 result746 }747748 749 750 751 752 753 pub fn burn_from(754 collection: &FungibleHandle<T>,755 spender: &T::CrossAccountId,756 from: &T::CrossAccountId,757 amount: u128,758 nesting_budget: &dyn Budget,759 ) -> DispatchResult {760 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;761762 763764 Self::burn(collection, from, amount)?;765 if let Some(allowance) = allowance {766 Self::set_allowance_unchecked(collection, from, spender, allowance);767 }768 Ok(())769 }770771 772 773 774 775 776 777 778 pub fn create_item(779 collection: &FungibleHandle<T>,780 sender: &T::CrossAccountId,781 data: CreateItemData<T>,782 nesting_budget: &dyn Budget,783 ) -> DispatchResult {784 Self::create_multiple_items(785 collection,786 sender,787 [(data.0, data.1)].into_iter().collect(),788 nesting_budget,789 )790 }791792 793 794 795 796 pub fn create_item_foreign(797 collection: &FungibleHandle<T>,798 sender: &T::CrossAccountId,799 data: CreateItemData<T>,800 nesting_budget: &dyn Budget,801 ) -> DispatchResult {802 Self::create_multiple_items_foreign(803 collection,804 sender,805 [(data.0, data.1)].into_iter().collect(),806 nesting_budget,807 )808 }809810 811 812 813 814 815 816 pub fn token_owners(817 collection: CollectionId,818 _token: TokenId,819 ) -> Option<Vec<T::CrossAccountId>> {820 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))821 .map(|(owner, _amount)| owner)822 .take(10)823 .collect();824825 if res.is_empty() {826 None827 } else {828 Some(res)829 }830 }831}