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 FungibleItemsDontHaveData,128 129 FungibleDisallowsNesting,130 131 SettingPropertiesNotAllowed,132 133 SettingAllowanceForAllNotAllowed,134 135 FungibleTokensAreAlwaysValid,136 }137138 #[pallet::config]139 pub trait Config:140 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config141 {142 type WeightInfo: WeightInfo;143 }144145 #[pallet::pallet]146 pub struct Pallet<T>(_);147148 149 #[pallet::storage]150 pub type TotalSupply<T: Config> =151 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;152153 154 #[pallet::storage]155 pub type Balance<T: Config> = StorageNMap<156 Key = (157 Key<Twox64Concat, CollectionId>,158 Key<Blake2_128Concat, T::CrossAccountId>,159 ),160 Value = u128,161 QueryKind = ValueQuery,162 >;163164 165 #[pallet::storage]166 pub type Allowance<T: Config> = StorageNMap<167 Key = (168 Key<Twox64Concat, CollectionId>,169 Key<Blake2_128, T::CrossAccountId>, 170 Key<Blake2_128Concat, T::CrossAccountId>, 171 ),172 Value = u128,173 QueryKind = ValueQuery,174 >;175}176177178179pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);180181182impl<T: Config> FungibleHandle<T> {183 184 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {185 Self(inner)186 }187188 189 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {190 self.0191 }192 193 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {194 &mut self.0195 }196}197impl<T: Config> WithRecorder<T> for FungibleHandle<T> {198 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {199 self.0.recorder()200 }201 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {202 self.0.into_recorder()203 }204}205impl<T: Config> Deref for FungibleHandle<T> {206 type Target = pallet_common::CollectionHandle<T>;207208 fn deref(&self) -> &Self::Target {209 &self.0210 }211}212213214impl<T: Config> Pallet<T> {215 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 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);232 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);233 Ok(())234 }235236 237 pub fn set_collection_properties(238 collection: &FungibleHandle<T>,239 sender: &T::CrossAccountId,240 properties: Vec<Property>,241 ) -> DispatchResult {242 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())243 }244245 246 pub fn delete_collection_properties(247 collection: &FungibleHandle<T>,248 sender: &T::CrossAccountId,249 property_keys: Vec<PropertyKey>,250 ) -> DispatchResult {251 <PalletCommon<T>>::delete_collection_properties(252 collection,253 sender,254 property_keys.into_iter(),255 )256 }257258 259 fn collection_has_tokens(collection_id: CollectionId) -> bool {260 <TotalSupply<T>>::get(collection_id) != 0261 }262263 264 265 266 pub fn burn(267 collection: &FungibleHandle<T>,268 owner: &T::CrossAccountId,269 amount: u128,270 ) -> DispatchResult {271 let total_supply = <TotalSupply<T>>::get(collection.id)272 .checked_sub(amount)273 .ok_or(<CommonError<T>>::TokenValueTooLow)?;274275 let balance = <Balance<T>>::get((collection.id, owner))276 .checked_sub(amount)277 .ok_or(<CommonError<T>>::TokenValueTooLow)?;278279 280 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);281282 if collection.permissions.access() == AccessMode::AllowList {283 collection.check_allowlist(owner)?;284 }285286 287288 if balance == 0 {289 <Balance<T>>::remove((collection.id, owner));290 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());291 } else {292 <Balance<T>>::insert((collection.id, owner), balance);293 }294 <TotalSupply<T>>::insert(collection.id, total_supply);295296 <PalletEvm<T>>::deposit_log(297 ERC20Events::Transfer {298 from: *owner.as_eth(),299 to: H160::default(),300 value: amount.into(),301 }302 .to_log(collection_id_to_address(collection.id)),303 );304 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(305 collection.id,306 TokenId::default(),307 owner.clone(),308 amount,309 ));310 Ok(())311 }312313 314 pub fn burn_foreign(315 collection: &FungibleHandle<T>,316 owner: &T::CrossAccountId,317 amount: u128,318 ) -> DispatchResult {319 let total_supply = <TotalSupply<T>>::get(collection.id)320 .checked_sub(amount)321 .ok_or(<CommonError<T>>::TokenValueTooLow)?;322323 let balance = <Balance<T>>::get((collection.id, owner))324 .checked_sub(amount)325 .ok_or(<CommonError<T>>::TokenValueTooLow)?;326 327328 if balance == 0 {329 <Balance<T>>::remove((collection.id, owner));330 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());331 } else {332 <Balance<T>>::insert((collection.id, owner), balance);333 }334 <TotalSupply<T>>::insert(collection.id, total_supply);335336 <PalletEvm<T>>::deposit_log(337 ERC20Events::Transfer {338 from: *owner.as_eth(),339 to: H160::default(),340 value: amount.into(),341 }342 .to_log(collection_id_to_address(collection.id)),343 );344 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(345 collection.id,346 TokenId::default(),347 owner.clone(),348 amount,349 ));350 Ok(())351 }352353 354 355 356 357 358 359 360 pub fn transfer(361 collection: &FungibleHandle<T>,362 from: &T::CrossAccountId,363 to: &T::CrossAccountId,364 amount: u128,365 nesting_budget: &dyn Budget,366 ) -> DispatchResultWithPostInfo {367 let depositor = from;368 Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)369 }370371 372 373 374 fn transfer_internal(375 collection: &FungibleHandle<T>,376 depositor: &T::CrossAccountId,377 from: &T::CrossAccountId,378 to: &T::CrossAccountId,379 amount: u128,380 nesting_budget: &dyn Budget,381 ) -> DispatchResultWithPostInfo {382 ensure!(383 collection.limits.transfers_enabled(),384 <CommonError<T>>::TransferNotAllowed,385 );386387 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();388389 if collection.permissions.access() == AccessMode::AllowList {390 collection.check_allowlist(from)?;391 collection.check_allowlist(to)?;392 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;393 }394 <PalletCommon<T>>::ensure_correct_receiver(to)?;395 let balance_from = <Balance<T>>::get((collection.id, from))396 .checked_sub(amount)397 .ok_or(<CommonError<T>>::TokenValueTooLow)?;398 let balance_to = if from != to && amount != 0 {399 Some(400 <Balance<T>>::get((collection.id, to))401 .checked_add(amount)402 .ok_or(ArithmeticError::Overflow)?,403 )404 } else {405 None406 };407408 409410 if let Some(balance_to) = balance_to {411 412413 <PalletStructure<T>>::nest_if_sent_to_token(414 depositor,415 to,416 collection.id,417 TokenId::default(),418 nesting_budget,419 )?;420421 if balance_from == 0 {422 <Balance<T>>::remove((collection.id, from));423 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());424 } else {425 <Balance<T>>::insert((collection.id, from), balance_from);426 }427 <Balance<T>>::insert((collection.id, to), balance_to);428 }429430 <PalletEvm<T>>::deposit_log(431 ERC20Events::Transfer {432 from: *from.as_eth(),433 to: *to.as_eth(),434 value: amount.into(),435 }436 .to_log(collection_id_to_address(collection.id)),437 );438 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(439 collection.id,440 TokenId::default(),441 from.clone(),442 to.clone(),443 amount,444 ));445446 Ok(PostDispatchInfo {447 actual_weight: Some(actual_weight),448 pays_fee: Pays::Yes,449 })450 }451452 453 454 455 pub fn create_multiple_items_common(456 collection: &FungibleHandle<T>,457 sender: &T::CrossAccountId,458 data: BTreeMap<T::CrossAccountId, u128>,459 nesting_budget: &dyn Budget,460 ) -> DispatchResult {461 let total_supply = data462 .values()463 .copied()464 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {465 acc.checked_add(v)466 })467 .ok_or(ArithmeticError::Overflow)?;468469 for (to, _) in data.iter() {470 <PalletStructure<T>>::check_nesting(471 sender,472 to,473 collection.id,474 TokenId::default(),475 nesting_budget,476 )?;477 }478479 let updated_balances = data480 .into_iter()481 .map(|(user, amount)| {482 let updated_balance = <Balance<T>>::get((collection.id, &user))483 .checked_add(amount)484 .ok_or(ArithmeticError::Overflow)?;485 Ok((user, amount, updated_balance))486 })487 .collect::<Result<Vec<_>, DispatchError>>()?;488489 490491 <TotalSupply<T>>::insert(collection.id, total_supply);492 for (user, amount, updated_balance) in updated_balances {493 <Balance<T>>::insert((collection.id, &user), updated_balance);494 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(495 &user,496 collection.id,497 TokenId::default(),498 );499 <PalletEvm<T>>::deposit_log(500 ERC20Events::Transfer {501 from: H160::default(),502 to: *user.as_eth(),503 value: amount.into(),504 }505 .to_log(collection_id_to_address(collection.id)),506 );507 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(508 collection.id,509 TokenId::default(),510 user.clone(),511 amount,512 ));513 }514515 Ok(())516 }517518 519 520 pub fn create_multiple_items(521 collection: &FungibleHandle<T>,522 sender: &T::CrossAccountId,523 data: BTreeMap<T::CrossAccountId, u128>,524 nesting_budget: &dyn Budget,525 ) -> DispatchResult {526 527 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);528529 if !collection.is_owner_or_admin(sender) {530 ensure!(531 collection.permissions.mint_mode(),532 <CommonError<T>>::PublicMintingNotAllowed533 );534 collection.check_allowlist(sender)?;535536 for (owner, _) in data.iter() {537 collection.check_allowlist(owner)?;538 }539 }540541 Self::create_multiple_items_common(collection, sender, data, nesting_budget)542 }543544 545 546 pub fn create_multiple_items_foreign(547 collection: &FungibleHandle<T>,548 sender: &T::CrossAccountId,549 data: BTreeMap<T::CrossAccountId, u128>,550 nesting_budget: &dyn Budget,551 ) -> DispatchResult {552 Self::create_multiple_items_common(collection, sender, data, nesting_budget)553 }554555 fn set_allowance_unchecked(556 collection: &FungibleHandle<T>,557 owner: &T::CrossAccountId,558 spender: &T::CrossAccountId,559 amount: u128,560 ) {561 if amount == 0 {562 <Allowance<T>>::remove((collection.id, owner, spender));563 } else {564 <Allowance<T>>::insert((collection.id, owner, spender), amount);565 }566567 <PalletEvm<T>>::deposit_log(568 ERC20Events::Approval {569 owner: *owner.as_eth(),570 spender: *spender.as_eth(),571 value: amount.into(),572 }573 .to_log(collection_id_to_address(collection.id)),574 );575 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(576 collection.id,577 TokenId(0),578 owner.clone(),579 spender.clone(),580 amount,581 ));582 }583584 585 586 587 588 589 590 pub fn set_allowance(591 collection: &FungibleHandle<T>,592 owner: &T::CrossAccountId,593 spender: &T::CrossAccountId,594 amount: u128,595 ) -> DispatchResult {596 if collection.permissions.access() == AccessMode::AllowList {597 collection.check_allowlist(owner)?;598 collection.check_allowlist(spender)?;599 }600601 if <Balance<T>>::get((collection.id, owner)) < amount {602 ensure!(603 collection.ignores_owned_amount(owner),604 <CommonError<T>>::CantApproveMoreThanOwned605 );606 }607608 609610 Self::set_allowance_unchecked(collection, owner, spender, amount);611 Ok(())612 }613614 615 616 617 618 619 620 621 pub fn set_allowance_from(622 collection: &FungibleHandle<T>,623 sender: &T::CrossAccountId,624 from: &T::CrossAccountId,625 to: &T::CrossAccountId,626 amount: u128,627 ) -> DispatchResult {628 if collection.permissions.access() == AccessMode::AllowList {629 collection.check_allowlist(sender)?;630 collection.check_allowlist(from)?;631 collection.check_allowlist(to)?;632 }633634 ensure!(635 sender.conv_eq(from),636 <CommonError<T>>::AddressIsNotEthMirror637 );638639 if <Balance<T>>::get((collection.id, from)) < amount {640 ensure!(641 collection.limits.owner_can_transfer()642 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),643 <CommonError<T>>::CantApproveMoreThanOwned644 );645 }646647 648649 Self::set_allowance_unchecked(collection, from, to, amount);650 Ok(())651 }652653 654 655 656 657 658 659 660 fn check_allowed(661 collection: &FungibleHandle<T>,662 spender: &T::CrossAccountId,663 from: &T::CrossAccountId,664 amount: u128,665 nesting_budget: &dyn Budget,666 ) -> Result<Option<u128>, DispatchError> {667 if spender.conv_eq(from) {668 return Ok(None);669 }670 if collection.permissions.access() == AccessMode::AllowList {671 672 collection.check_allowlist(spender)?;673 }674675 if collection.ignores_token_restrictions(spender) {676 return Ok(Self::compute_allowance_decrease(677 collection, from, spender, amount,678 ));679 }680681 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {682 ensure!(683 <PalletStructure<T>>::check_indirectly_owned(684 spender.clone(),685 source.0,686 source.1,687 None,688 nesting_budget689 )?,690 <CommonError<T>>::ApprovedValueTooLow,691 );692 return Ok(None);693 }694695 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);696 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);697698 Ok(allowance)699 }700701 702 703 fn compute_allowance_decrease(704 collection: &FungibleHandle<T>,705 from: &T::CrossAccountId,706 spender: &T::CrossAccountId,707 amount: u128,708 ) -> Option<u128> {709 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)710 }711712 713 714 715 716 pub fn transfer_from(717 collection: &FungibleHandle<T>,718 spender: &T::CrossAccountId,719 from: &T::CrossAccountId,720 to: &T::CrossAccountId,721 amount: u128,722 nesting_budget: &dyn Budget,723 ) -> DispatchResultWithPostInfo {724 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;725726 727728 let mut result =729 Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);730 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());731 result?;732733 if let Some(allowance) = allowance {734 Self::set_allowance_unchecked(collection, from, spender, allowance);735 add_weight_to_post_info(736 &mut result,737 <SelfWeightOf<T>>::set_allowance_unchecked_raw(),738 )739 }740 result741 }742743 744 745 746 747 748 pub fn burn_from(749 collection: &FungibleHandle<T>,750 spender: &T::CrossAccountId,751 from: &T::CrossAccountId,752 amount: u128,753 nesting_budget: &dyn Budget,754 ) -> DispatchResult {755 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;756757 758759 Self::burn(collection, from, amount)?;760 if let Some(allowance) = allowance {761 Self::set_allowance_unchecked(collection, from, spender, allowance);762 }763 Ok(())764 }765766 767 768 769 770 771 772 773 pub fn create_item(774 collection: &FungibleHandle<T>,775 sender: &T::CrossAccountId,776 data: CreateItemData<T>,777 nesting_budget: &dyn Budget,778 ) -> DispatchResult {779 Self::create_multiple_items(780 collection,781 sender,782 [(data.0, data.1)].into_iter().collect(),783 nesting_budget,784 )785 }786787 788 789 790 791 pub fn create_item_foreign(792 collection: &FungibleHandle<T>,793 sender: &T::CrossAccountId,794 data: CreateItemData<T>,795 nesting_budget: &dyn Budget,796 ) -> DispatchResult {797 Self::create_multiple_items_foreign(798 collection,799 sender,800 [(data.0, data.1)].into_iter().collect(),801 nesting_budget,802 )803 }804805 806 807 808 809 810 811 pub fn token_owners(812 collection: CollectionId,813 _token: TokenId,814 ) -> Option<Vec<T::CrossAccountId>> {815 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))816 .map(|(owner, _amount)| owner)817 .take(10)818 .collect();819820 if res.is_empty() {821 None822 } else {823 Some(res)824 }825 }826}