12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use core::{ops::Deref, cmp::Ordering};94use evm_coder::ToLog;95use frame_support::{ensure, storage::with_transaction, transactional};96use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};97use pallet_evm_coder_substrate::WithRecorder;98use pallet_common::{99 CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,100 Event as CommonEvent, Pallet as PalletCommon, SetPropertyMode,101};102use pallet_structure::Pallet as PalletStructure;103use sp_core::{Get, H160};104use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};105use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};106use up_data_structs::{107 AccessMode, budget::Budget, CollectionId, CreateCollectionData, mapping::TokenAddressMapping,108 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyScope,109 PropertyValue, TokenId, TrySetProperty, PropertiesPermissionMap,110 CreateRefungibleExMultipleOwners, TokenOwnerError, TokenProperties as TokenPropertiesT,111};112113pub use pallet::*;114#[cfg(feature = "runtime-benchmarks")]115pub mod benchmarking;116pub mod common;117pub mod erc;118pub mod erc_token;119pub mod weights;120121pub type CreateItemData<T> =122 CreateRefungibleExMultipleOwners<<T as pallet_evm::Config>::CrossAccountId>;123pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;124125#[frame_support::pallet]126pub mod pallet {127 use super::*;128 use frame_support::{129 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,130 traits::StorageVersion,131 };132 use up_data_structs::{CollectionId, TokenId};133 use super::weights::WeightInfo;134135 #[pallet::error]136 pub enum Error<T> {137 138 NotRefungibleDataUsedToMintFungibleCollectionToken,139 140 WrongRefungiblePieces,141 142 RepartitionWhileNotOwningAllPieces,143 144 RefungibleDisallowsNesting,145 146 SettingPropertiesNotAllowed,147 }148149 #[pallet::config]150 pub trait Config:151 frame_system::Config + pallet_common::Config + pallet_structure::Config152 {153 type WeightInfo: WeightInfo;154 }155156 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);157158 #[pallet::pallet]159 #[pallet::storage_version(STORAGE_VERSION)]160 pub struct Pallet<T>(_);161162 163 #[pallet::storage]164 pub type TokensMinted<T: Config> =165 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;166167 168 #[pallet::storage]169 pub type TokensBurnt<T: Config> =170 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;171172 173 #[pallet::storage]174 #[pallet::getter(fn token_properties)]175 pub type TokenProperties<T: Config> = StorageNMap<176 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),177 Value = TokenPropertiesT,178 QueryKind = ValueQuery,179 >;180181 182 #[pallet::storage]183 pub type TotalSupply<T: Config> = StorageNMap<184 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),185 Value = u128,186 QueryKind = ValueQuery,187 >;188189 190 #[pallet::storage]191 pub type Owned<T: Config> = StorageNMap<192 Key = (193 Key<Twox64Concat, CollectionId>,194 Key<Blake2_128Concat, T::CrossAccountId>,195 Key<Twox64Concat, TokenId>,196 ),197 Value = bool,198 QueryKind = ValueQuery,199 >;200201 202 #[pallet::storage]203 pub type AccountBalance<T: Config> = StorageNMap<204 Key = (205 Key<Twox64Concat, CollectionId>,206 207 Key<Blake2_128Concat, T::CrossAccountId>,208 ),209 Value = u32,210 QueryKind = ValueQuery,211 >;212213 214 #[pallet::storage]215 pub type Balance<T: Config> = StorageNMap<216 Key = (217 Key<Twox64Concat, CollectionId>,218 Key<Twox64Concat, TokenId>,219 220 Key<Blake2_128Concat, T::CrossAccountId>,221 ),222 Value = u128,223 QueryKind = ValueQuery,224 >;225226 227 #[pallet::storage]228 pub type Allowance<T: Config> = StorageNMap<229 Key = (230 Key<Twox64Concat, CollectionId>,231 Key<Twox64Concat, TokenId>,232 233 Key<Blake2_128, T::CrossAccountId>,234 235 Key<Blake2_128Concat, T::CrossAccountId>,236 ),237 Value = u128,238 QueryKind = ValueQuery,239 >;240241 242 #[pallet::storage]243 pub type CollectionAllowance<T: Config> = StorageNMap<244 Key = (245 Key<Twox64Concat, CollectionId>,246 Key<Blake2_128Concat, T::CrossAccountId>, 247 Key<Blake2_128Concat, T::CrossAccountId>, 248 ),249 Value = bool,250 QueryKind = ValueQuery,251 >;252}253254pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);255impl<T: Config> RefungibleHandle<T> {256 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {257 Self(inner)258 }259 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {260 self.0261 }262 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {263 &mut self.0264 }265}266267impl<T: Config> Deref for RefungibleHandle<T> {268 type Target = pallet_common::CollectionHandle<T>;269270 fn deref(&self) -> &Self::Target {271 &self.0272 }273}274275impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {276 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {277 self.0.recorder()278 }279 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {280 self.0.into_recorder()281 }282}283284impl<T: Config> Pallet<T> {285 286 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {287 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)288 }289290 291 292 293 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {294 <TotalSupply<T>>::contains_key((collection.id, token))295 }296297 pub fn set_scoped_token_property(298 collection_id: CollectionId,299 token_id: TokenId,300 scope: PropertyScope,301 property: Property,302 ) -> DispatchResult {303 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {304 properties.try_scoped_set(scope, property.key, property.value)305 })306 .map_err(<CommonError<T>>::from)?;307308 Ok(())309 }310311 pub fn set_scoped_token_properties(312 collection_id: CollectionId,313 token_id: TokenId,314 scope: PropertyScope,315 properties: impl Iterator<Item = Property>,316 ) -> DispatchResult {317 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {318 stored_properties.try_scoped_set_from_iter(scope, properties)319 })320 .map_err(<CommonError<T>>::from)?;321322 Ok(())323 }324}325326327impl<T: Config> Pallet<T> {328 329 330 331 332 333 pub fn init_collection(334 owner: T::CrossAccountId,335 payer: T::CrossAccountId,336 data: CreateCollectionData<T::CrossAccountId>,337 ) -> Result<CollectionId, DispatchError> {338 <PalletCommon<T>>::init_collection(owner, payer, data)339 }340341 342 343 344 345 pub fn destroy_collection(346 collection: RefungibleHandle<T>,347 sender: &T::CrossAccountId,348 ) -> DispatchResult {349 let id = collection.id;350351 if Self::collection_has_tokens(id) {352 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());353 }354355 356357 PalletCommon::destroy_collection(collection.0, sender)?;358359 <TokensMinted<T>>::remove(id);360 <TokensBurnt<T>>::remove(id);361 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);362 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);363 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);364 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);365 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);366 Ok(())367 }368369 fn collection_has_tokens(collection_id: CollectionId) -> bool {370 <TotalSupply<T>>::iter_prefix((collection_id,))371 .next()372 .is_some()373 }374375 pub fn burn_token_unchecked(376 collection: &RefungibleHandle<T>,377 owner: &T::CrossAccountId,378 token_id: TokenId,379 ) -> DispatchResult {380 let burnt = <TokensBurnt<T>>::get(collection.id)381 .checked_add(1)382 .ok_or(ArithmeticError::Overflow)?;383384 <TokensBurnt<T>>::insert(collection.id, burnt);385 <TokenProperties<T>>::remove((collection.id, token_id));386 <TotalSupply<T>>::remove((collection.id, token_id));387 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);388 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);389 <PalletEvm<T>>::deposit_log(390 ERC721Events::Transfer {391 from: *owner.as_eth(),392 to: H160::default(),393 token_id: token_id.into(),394 }395 .to_log(collection_id_to_address(collection.id)),396 );397 Ok(())398 }399400 401 402 403 404 405 406 407 408 409 410 411 pub fn burn(412 collection: &RefungibleHandle<T>,413 owner: &T::CrossAccountId,414 token: TokenId,415 amount: u128,416 ) -> DispatchResult {417 if <Balance<T>>::get((collection.id, token, owner)) == 0 {418 return Err(<CommonError<T>>::TokenValueTooLow.into());419 }420421 let total_supply = <TotalSupply<T>>::get((collection.id, token))422 .checked_sub(amount)423 .ok_or(<CommonError<T>>::TokenValueTooLow)?;424425 426 if total_supply == 0 {427 428 ensure!(429 <Balance<T>>::get((collection.id, token, owner)) == amount,430 <CommonError<T>>::TokenValueTooLow431 );432 let account_balance = <AccountBalance<T>>::get((collection.id, owner))433 .checked_sub(1)434 435 .ok_or(ArithmeticError::Underflow)?;436437 438439 <Owned<T>>::remove((collection.id, owner, token));440 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);441 <AccountBalance<T>>::insert((collection.id, owner), account_balance);442 Self::burn_token_unchecked(collection, owner, token)?;443 <PalletEvm<T>>::deposit_log(444 ERC20Events::Transfer {445 from: *owner.as_eth(),446 to: H160::default(),447 value: amount.into(),448 }449 .to_log(collection_id_to_address(collection.id)),450 );451 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(452 collection.id,453 token,454 owner.clone(),455 amount,456 ));457 return Ok(());458 }459460 let balance = <Balance<T>>::get((collection.id, token, owner))461 .checked_sub(amount)462 .ok_or(<CommonError<T>>::TokenValueTooLow)?;463 let account_balance = if balance == 0 {464 <AccountBalance<T>>::get((collection.id, owner))465 .checked_sub(1)466 467 .ok_or(ArithmeticError::Underflow)?468 } else {469 0470 };471472 473474 if balance == 0 {475 <Owned<T>>::remove((collection.id, owner, token));476 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);477 <Balance<T>>::remove((collection.id, token, owner));478 <AccountBalance<T>>::insert((collection.id, owner), account_balance);479480 if let Ok(user) = Self::token_owner(collection.id, token) {481 <PalletEvm<T>>::deposit_log(482 ERC721Events::Transfer {483 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,484 to: *user.as_eth(),485 token_id: token.into(),486 }487 .to_log(collection_id_to_address(collection.id)),488 );489 }490 } else {491 <Balance<T>>::insert((collection.id, token, owner), balance);492 }493 <TotalSupply<T>>::insert((collection.id, token), total_supply);494495 <PalletEvm<T>>::deposit_log(496 ERC20Events::Transfer {497 from: *owner.as_eth(),498 to: H160::default(),499 value: amount.into(),500 }501 .to_log(T::EvmTokenAddressMapping::token_to_address(502 collection.id,503 token,504 )),505 );506 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(507 collection.id,508 token,509 owner.clone(),510 amount,511 ));512 Ok(())513 }514515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 #[transactional]531 fn modify_token_properties(532 collection: &RefungibleHandle<T>,533 sender: &T::CrossAccountId,534 token_id: TokenId,535 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,536 mode: SetPropertyMode,537 nesting_budget: &dyn Budget,538 ) -> DispatchResult {539 let mut is_token_owner =540 pallet_common::LazyValue::new(|| -> Result<bool, DispatchError> {541 if let SetPropertyMode::NewToken {542 mint_target_is_sender,543 } = mode544 {545 return Ok(mint_target_is_sender);546 }547548 let balance = collection.balance(sender.clone(), token_id);549 let total_pieces: u128 =550 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);551 if balance != total_pieces {552 return Ok(false);553 }554555 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(556 sender.clone(),557 collection.id,558 token_id,559 None,560 nesting_budget,561 )?;562563 Ok(is_bundle_owner)564 });565566 let mut is_token_exist =567 pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));568569 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));570571 <PalletCommon<T>>::modify_token_properties(572 collection,573 sender,574 token_id,575 &mut is_token_exist,576 properties_updates,577 stored_properties,578 &mut is_token_owner,579 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),580 erc::ERC721TokenEvent::TokenChanged {581 token_id: token_id.into(),582 }583 .to_log(T::ContractAddress::get()),584 )585 }586587 pub fn next_token_id(collection: &RefungibleHandle<T>) -> Result<TokenId, DispatchError> {588 let next_token_id = <TokensMinted<T>>::get(collection.id)589 .checked_add(1)590 .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;591592 ensure!(593 collection.limits.token_limit() >= next_token_id,594 <CommonError<T>>::CollectionTokenLimitExceeded595 );596597 Ok(TokenId(next_token_id))598 }599600 pub fn set_token_properties(601 collection: &RefungibleHandle<T>,602 sender: &T::CrossAccountId,603 token_id: TokenId,604 properties: impl Iterator<Item = Property>,605 mode: SetPropertyMode,606 nesting_budget: &dyn Budget,607 ) -> DispatchResult {608 Self::modify_token_properties(609 collection,610 sender,611 token_id,612 properties.map(|p| (p.key, Some(p.value))),613 mode,614 nesting_budget,615 )616 }617618 pub fn set_token_property(619 collection: &RefungibleHandle<T>,620 sender: &T::CrossAccountId,621 token_id: TokenId,622 property: Property,623 nesting_budget: &dyn Budget,624 ) -> DispatchResult {625 Self::set_token_properties(626 collection,627 sender,628 token_id,629 [property].into_iter(),630 SetPropertyMode::ExistingToken,631 nesting_budget,632 )633 }634635 pub fn delete_token_properties(636 collection: &RefungibleHandle<T>,637 sender: &T::CrossAccountId,638 token_id: TokenId,639 property_keys: impl Iterator<Item = PropertyKey>,640 nesting_budget: &dyn Budget,641 ) -> DispatchResult {642 Self::modify_token_properties(643 collection,644 sender,645 token_id,646 property_keys.into_iter().map(|key| (key, None)),647 SetPropertyMode::ExistingToken,648 nesting_budget,649 )650 }651652 pub fn delete_token_property(653 collection: &RefungibleHandle<T>,654 sender: &T::CrossAccountId,655 token_id: TokenId,656 property_key: PropertyKey,657 nesting_budget: &dyn Budget,658 ) -> DispatchResult {659 Self::delete_token_properties(660 collection,661 sender,662 token_id,663 [property_key].into_iter(),664 nesting_budget,665 )666 }667668 669 670 671 672 673 674 675 676 677 pub fn transfer(678 collection: &RefungibleHandle<T>,679 from: &T::CrossAccountId,680 to: &T::CrossAccountId,681 token: TokenId,682 amount: u128,683 nesting_budget: &dyn Budget,684 ) -> DispatchResult {685 ensure!(686 collection.limits.transfers_enabled(),687 <CommonError<T>>::TransferNotAllowed688 );689690 if collection.permissions.access() == AccessMode::AllowList {691 collection.check_allowlist(from)?;692 collection.check_allowlist(to)?;693 }694 <PalletCommon<T>>::ensure_correct_receiver(to)?;695696 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));697698 if initial_balance_from == 0 {699 return Err(<CommonError<T>>::TokenValueTooLow.into());700 }701702 let updated_balance_from = initial_balance_from703 .checked_sub(amount)704 .ok_or(<CommonError<T>>::TokenValueTooLow)?;705 let mut create_target = false;706 let from_to_differ = from != to;707 let updated_balance_to = if from != to && amount != 0 {708 let old_balance = <Balance<T>>::get((collection.id, token, to));709 if old_balance == 0 {710 create_target = true;711 }712 Some(713 old_balance714 .checked_add(amount)715 .ok_or(ArithmeticError::Overflow)?,716 )717 } else {718 None719 };720721 let account_balance_from = if updated_balance_from == 0 {722 Some(723 <AccountBalance<T>>::get((collection.id, from))724 .checked_sub(1)725 726 .ok_or(ArithmeticError::Underflow)?,727 )728 } else {729 None730 };731 732 733 let account_balance_to = if create_target && from_to_differ {734 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))735 .checked_add(1)736 .ok_or(ArithmeticError::Overflow)?;737 ensure!(738 account_balance_to < collection.limits.account_token_ownership_limit(),739 <CommonError<T>>::AccountTokenLimitExceeded,740 );741742 Some(account_balance_to)743 } else {744 None745 };746747 748749 if let Some(updated_balance_to) = updated_balance_to {750 751752 <PalletStructure<T>>::nest_if_sent_to_token(753 from.clone(),754 to,755 collection.id,756 token,757 nesting_budget,758 )?;759760 if updated_balance_from == 0 {761 <Balance<T>>::remove((collection.id, token, from));762 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);763 } else {764 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);765 }766 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);767 if let Some(account_balance_from) = account_balance_from {768 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);769 <Owned<T>>::remove((collection.id, from, token));770 }771 if let Some(account_balance_to) = account_balance_to {772 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);773 <Owned<T>>::insert((collection.id, to, token), true);774 }775 }776777 <PalletEvm<T>>::deposit_log(778 ERC20Events::Transfer {779 from: *from.as_eth(),780 to: *to.as_eth(),781 value: amount.into(),782 }783 .to_log(T::EvmTokenAddressMapping::token_to_address(784 collection.id,785 token,786 )),787 );788789 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(790 collection.id,791 token,792 from.clone(),793 to.clone(),794 amount,795 ));796797 let total_supply = <TotalSupply<T>>::get((collection.id, token));798799 if amount == total_supply {800 801 <PalletEvm<T>>::deposit_log(802 ERC721Events::Transfer {803 from: *from.as_eth(),804 to: *to.as_eth(),805 token_id: token.into(),806 }807 .to_log(collection_id_to_address(collection.id)),808 );809 } else if let Some(updated_balance_to) = updated_balance_to {810 811 812 if initial_balance_from == total_supply {813 814 815 <PalletEvm<T>>::deposit_log(816 ERC721Events::Transfer {817 from: *from.as_eth(),818 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,819 token_id: token.into(),820 }821 .to_log(collection_id_to_address(collection.id)),822 );823 } else if updated_balance_to == total_supply {824 825 <PalletEvm<T>>::deposit_log(826 ERC721Events::Transfer {827 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,828 to: *to.as_eth(),829 token_id: token.into(),830 }831 .to_log(collection_id_to_address(collection.id)),832 );833 }834 }835836 Ok(())837 }838839 840 841 842 843 844 pub fn create_multiple_items(845 collection: &RefungibleHandle<T>,846 sender: &T::CrossAccountId,847 data: Vec<CreateItemData<T>>,848 nesting_budget: &dyn Budget,849 ) -> DispatchResult {850 if !collection.is_owner_or_admin(sender) {851 ensure!(852 collection.permissions.mint_mode(),853 <CommonError<T>>::PublicMintingNotAllowed854 );855 collection.check_allowlist(sender)?;856857 for item in data.iter() {858 for user in item.users.keys() {859 collection.check_allowlist(user)?;860 }861 }862 }863864 for item in data.iter() {865 for (owner, _) in item.users.iter() {866 <PalletCommon<T>>::ensure_correct_receiver(owner)?;867 }868 }869870 871 let totals = data872 .iter()873 .map(|data| {874 Ok(data875 .users876 .iter()877 .map(|u| u.1)878 .try_fold(0u128, |acc, v| acc.checked_add(*v))879 .ok_or(ArithmeticError::Overflow)?)880 })881 .collect::<Result<Vec<_>, DispatchError>>()?;882 for total in &totals {883 ensure!(884 *total <= MAX_REFUNGIBLE_PIECES,885 <Error<T>>::WrongRefungiblePieces886 );887 }888889 let first_token_id = <TokensMinted<T>>::get(collection.id);890 let tokens_minted = first_token_id891 .checked_add(data.len() as u32)892 .ok_or(ArithmeticError::Overflow)?;893 ensure!(894 tokens_minted < collection.limits.token_limit(),895 <CommonError<T>>::CollectionTokenLimitExceeded896 );897898 let mut balances = BTreeMap::new();899 for data in &data {900 for owner in data.users.keys() {901 let balance = balances902 .entry(owner)903 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));904 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;905906 ensure!(907 *balance <= collection.limits.account_token_ownership_limit(),908 <CommonError<T>>::AccountTokenLimitExceeded,909 );910 }911 }912913 for (i, token) in data.iter().enumerate() {914 let token_id = TokenId(first_token_id + i as u32 + 1);915 for (to, _) in token.users.iter() {916 <PalletStructure<T>>::check_nesting(917 sender.clone(),918 to,919 collection.id,920 token_id,921 nesting_budget,922 )?;923 }924 }925926 927928 with_transaction(|| {929 for (i, data) in data.iter().enumerate() {930 let token_id = first_token_id + i as u32 + 1;931 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);932933 let mut mint_target_is_sender = true;934 for (user, amount) in data.users.iter() {935 if *amount == 0 {936 continue;937 }938939 mint_target_is_sender = mint_target_is_sender && sender.conv_eq(user);940941 <Balance<T>>::insert((collection.id, token_id, &user), amount);942 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);943 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(944 user,945 collection.id,946 TokenId(token_id),947 );948 }949950 if let Err(e) = Self::set_token_properties(951 collection,952 sender,953 TokenId(token_id),954 data.properties.clone().into_iter(),955 SetPropertyMode::NewToken {956 mint_target_is_sender,957 },958 nesting_budget,959 ) {960 return TransactionOutcome::Rollback(Err(e));961 }962 }963 TransactionOutcome::Commit(Ok(()))964 })?;965966 <TokensMinted<T>>::insert(collection.id, tokens_minted);967968 for (account, balance) in balances {969 <AccountBalance<T>>::insert((collection.id, account), balance);970 }971972 for (i, token) in data.into_iter().enumerate() {973 let token_id = first_token_id + i as u32 + 1;974975 let receivers = token976 .users977 .into_iter()978 .filter(|(_, amount)| *amount > 0)979 .collect::<Vec<_>>();980981 if let [(user, _)] = receivers.as_slice() {982 983 <PalletEvm<T>>::deposit_log(984 ERC721Events::Transfer {985 from: H160::default(),986 to: *user.as_eth(),987 token_id: token_id.into(),988 }989 .to_log(collection_id_to_address(collection.id)),990 );991 } else if let [_, ..] = receivers.as_slice() {992 993 <PalletEvm<T>>::deposit_log(994 ERC721Events::Transfer {995 from: H160::default(),996 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,997 token_id: token_id.into(),998 }999 .to_log(collection_id_to_address(collection.id)),1000 );1001 }10021003 for (user, amount) in receivers.into_iter() {1004 <PalletEvm<T>>::deposit_log(1005 ERC20Events::Transfer {1006 from: H160::default(),1007 to: *user.as_eth(),1008 value: amount.into(),1009 }1010 .to_log(T::EvmTokenAddressMapping::token_to_address(1011 collection.id,1012 TokenId(token_id),1013 )),1014 );1015 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1016 collection.id,1017 TokenId(token_id),1018 user,1019 amount,1020 ));1021 }1022 }1023 Ok(())1024 }10251026 pub fn set_allowance_unchecked(1027 collection: &RefungibleHandle<T>,1028 sender: &T::CrossAccountId,1029 spender: &T::CrossAccountId,1030 token: TokenId,1031 amount: u128,1032 ) {1033 if amount == 0 {1034 <Allowance<T>>::remove((collection.id, token, sender, spender));1035 } else {1036 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1037 }10381039 <PalletEvm<T>>::deposit_log(1040 ERC20Events::Approval {1041 owner: *sender.as_eth(),1042 spender: *spender.as_eth(),1043 value: amount.into(),1044 }1045 .to_log(T::EvmTokenAddressMapping::token_to_address(1046 collection.id,1047 token,1048 )),1049 );1050 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1051 collection.id,1052 token,1053 sender.clone(),1054 spender.clone(),1055 amount,1056 ))1057 }10581059 1060 1061 1062 pub fn set_allowance(1063 collection: &RefungibleHandle<T>,1064 sender: &T::CrossAccountId,1065 spender: &T::CrossAccountId,1066 token: TokenId,1067 amount: u128,1068 ) -> DispatchResult {1069 if collection.permissions.access() == AccessMode::AllowList {1070 collection.check_allowlist(sender)?;1071 collection.check_allowlist(spender)?;1072 }10731074 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10751076 if <Balance<T>>::get((collection.id, token, sender)) < amount {1077 ensure!(1078 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1079 <CommonError<T>>::CantApproveMoreThanOwned1080 );1081 }10821083 10841085 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1086 Ok(())1087 }10881089 1090 1091 1092 1093 1094 pub fn set_allowance_from(1095 collection: &RefungibleHandle<T>,1096 sender: &T::CrossAccountId,1097 from: &T::CrossAccountId,1098 to: &T::CrossAccountId,1099 token_id: TokenId,1100 amount: u128,1101 ) -> DispatchResult {1102 if collection.permissions.access() == AccessMode::AllowList {1103 collection.check_allowlist(sender)?;1104 collection.check_allowlist(from)?;1105 collection.check_allowlist(to)?;1106 }11071108 <PalletCommon<T>>::ensure_correct_receiver(to)?;11091110 ensure!(1111 sender.conv_eq(from),1112 <CommonError<T>>::AddressIsNotEthMirror1113 );11141115 if <Balance<T>>::get((collection.id, token_id, from)) < amount {1116 ensure!(1117 collection.limits.owner_can_transfer()1118 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from))1119 && Self::token_exists(collection, token_id),1120 <CommonError<T>>::CantApproveMoreThanOwned1121 );1122 }11231124 11251126 Self::set_allowance_unchecked(collection, from, to, token_id, amount);1127 Ok(())1128 }11291130 1131 fn check_allowed(1132 collection: &RefungibleHandle<T>,1133 spender: &T::CrossAccountId,1134 from: &T::CrossAccountId,1135 token: TokenId,1136 amount: u128,1137 nesting_budget: &dyn Budget,1138 ) -> Result<Option<u128>, DispatchError> {1139 if spender.conv_eq(from) {1140 return Ok(None);1141 }1142 if collection.permissions.access() == AccessMode::AllowList {1143 1144 collection.check_allowlist(spender)?;1145 }11461147 if collection.ignores_token_restrictions(spender) {1148 return Ok(Self::compute_allowance_decrease(1149 collection, token, from, spender, amount,1150 ));1151 }11521153 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1154 1155 ensure!(1156 <PalletStructure<T>>::check_indirectly_owned(1157 spender.clone(),1158 source.0,1159 source.1,1160 None,1161 nesting_budget1162 )?,1163 <CommonError<T>>::ApprovedValueTooLow,1164 );1165 return Ok(None);1166 }11671168 let allowance = Self::compute_allowance_decrease(collection, token, from, spender, amount);1169 if allowance.is_some() {1170 return Ok(allowance);1171 }11721173 1174 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1175 return Ok(allowance);1176 }11771178 Err(<CommonError<T>>::ApprovedValueTooLow.into())1179 }11801181 1182 1183 fn compute_allowance_decrease(1184 collection: &RefungibleHandle<T>,1185 token: TokenId,1186 from: &T::CrossAccountId,1187 spender: &T::CrossAccountId,1188 amount: u128,1189 ) -> Option<u128> {1190 <Allowance<T>>::get((collection.id, token, from, spender)).checked_sub(amount)1191 }11921193 1194 1195 1196 1197 1198 1199 pub fn transfer_from(1200 collection: &RefungibleHandle<T>,1201 spender: &T::CrossAccountId,1202 from: &T::CrossAccountId,1203 to: &T::CrossAccountId,1204 token: TokenId,1205 amount: u128,1206 nesting_budget: &dyn Budget,1207 ) -> DispatchResult {1208 let allowance =1209 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12101211 12121213 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1214 if let Some(allowance) = allowance {1215 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1216 }1217 Ok(())1218 }12191220 1221 1222 1223 1224 1225 1226 pub fn burn_from(1227 collection: &RefungibleHandle<T>,1228 spender: &T::CrossAccountId,1229 from: &T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResult {1234 let allowance =1235 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12361237 12381239 Self::burn(collection, from, token, amount)?;1240 if let Some(allowance) = allowance {1241 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1242 }1243 Ok(())1244 }12451246 1247 1248 1249 1250 1251 1252 1253 pub fn create_item(1254 collection: &RefungibleHandle<T>,1255 sender: &T::CrossAccountId,1256 data: CreateItemData<T>,1257 nesting_budget: &dyn Budget,1258 ) -> DispatchResult {1259 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1260 }12611262 1263 1264 1265 1266 1267 1268 1269 pub fn repartition(1270 collection: &RefungibleHandle<T>,1271 owner: &T::CrossAccountId,1272 token: TokenId,1273 amount: u128,1274 ) -> DispatchResult {1275 ensure!(1276 amount <= MAX_REFUNGIBLE_PIECES,1277 <Error<T>>::WrongRefungiblePieces1278 );1279 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1280 1281 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1282 let balance = <Balance<T>>::get((collection.id, token, owner));1283 ensure!(1284 total_pieces == balance,1285 <Error<T>>::RepartitionWhileNotOwningAllPieces1286 );12871288 <Balance<T>>::insert((collection.id, token, owner), amount);1289 <TotalSupply<T>>::insert((collection.id, token), amount);12901291 match total_pieces.cmp(&amount) {1292 Ordering::Less => {1293 let mint_amount = amount - total_pieces;1294 <PalletEvm<T>>::deposit_log(1295 ERC20Events::Transfer {1296 from: H160::default(),1297 to: *owner.as_eth(),1298 value: mint_amount.into(),1299 }1300 .to_log(T::EvmTokenAddressMapping::token_to_address(1301 collection.id,1302 token,1303 )),1304 );1305 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1306 collection.id,1307 token,1308 owner.clone(),1309 mint_amount,1310 ));1311 }1312 Ordering::Greater => {1313 let burn_amount = total_pieces - amount;1314 <PalletEvm<T>>::deposit_log(1315 ERC20Events::Transfer {1316 from: *owner.as_eth(),1317 to: H160::default(),1318 value: burn_amount.into(),1319 }1320 .to_log(T::EvmTokenAddressMapping::token_to_address(1321 collection.id,1322 token,1323 )),1324 );1325 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1326 collection.id,1327 token,1328 owner.clone(),1329 burn_amount,1330 ));1331 }1332 Ordering::Equal => {}1333 }13341335 Ok(())1336 }13371338 fn token_owner(1339 collection_id: CollectionId,1340 token_id: TokenId,1341 ) -> Result<T::CrossAccountId, TokenOwnerError> {1342 let mut owner = None;1343 let mut count = 0;1344 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1345 count += 1;1346 if count > 1 {1347 return Err(TokenOwnerError::MultipleOwners);1348 }1349 owner = Some(key);1350 }1351 owner.ok_or(TokenOwnerError::NotFound)1352 }13531354 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1355 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1356 }13571358 pub fn set_collection_properties(1359 collection: &RefungibleHandle<T>,1360 sender: &T::CrossAccountId,1361 properties: Vec<Property>,1362 ) -> DispatchResult {1363 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())1364 }13651366 pub fn delete_collection_properties(1367 collection: &RefungibleHandle<T>,1368 sender: &T::CrossAccountId,1369 property_keys: Vec<PropertyKey>,1370 ) -> DispatchResult {1371 <PalletCommon<T>>::delete_collection_properties(1372 collection,1373 sender,1374 property_keys.into_iter(),1375 )1376 }13771378 pub fn set_token_property_permissions(1379 collection: &RefungibleHandle<T>,1380 sender: &T::CrossAccountId,1381 property_permissions: Vec<PropertyKeyPermission>,1382 ) -> DispatchResult {1383 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1384 }13851386 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1387 <PalletCommon<T>>::property_permissions(collection_id)1388 }13891390 pub fn set_scoped_token_property_permissions(1391 collection: &RefungibleHandle<T>,1392 sender: &T::CrossAccountId,1393 scope: PropertyScope,1394 property_permissions: Vec<PropertyKeyPermission>,1395 ) -> DispatchResult {1396 <PalletCommon<T>>::set_scoped_token_property_permissions(1397 collection,1398 sender,1399 scope,1400 property_permissions,1401 )1402 }14031404 1405 1406 1407 1408 1409 1410 pub fn token_owners(1411 collection_id: CollectionId,1412 token: TokenId,1413 ) -> Option<Vec<T::CrossAccountId>> {1414 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1415 .map(|(owner, _amount)| owner)1416 .take(10)1417 .collect();14181419 if res.is_empty() {1420 None1421 } else {1422 Some(res)1423 }1424 }14251426 1427 1428 1429 1430 1431 1432 pub fn set_allowance_for_all(1433 collection: &RefungibleHandle<T>,1434 owner: &T::CrossAccountId,1435 spender: &T::CrossAccountId,1436 approve: bool,1437 ) -> DispatchResult {1438 <PalletCommon<T>>::set_allowance_for_all(1439 collection,1440 owner,1441 spender,1442 approve,1443 || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),1444 ERC721Events::ApprovalForAll {1445 owner: *owner.as_eth(),1446 operator: *spender.as_eth(),1447 approved: approve,1448 }1449 .to_log(collection_id_to_address(collection.id)),1450 )1451 }14521453 1454 pub fn allowance_for_all(1455 collection: &RefungibleHandle<T>,1456 owner: &T::CrossAccountId,1457 spender: &T::CrossAccountId,1458 ) -> bool {1459 <CollectionAllowance<T>>::get((collection.id, owner, spender))1460 }14611462 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1463 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1464 properties.recompute_consumed_space();1465 });14661467 Ok(())1468 }1469}