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, CollectionFlags, CreateCollectionData,108 mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,109 PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TrySetProperty,110 PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,111 TokenProperties as TokenPropertiesT,112};113114pub use pallet::*;115#[cfg(feature = "runtime-benchmarks")]116pub mod benchmarking;117pub mod common;118pub mod erc;119pub mod erc_token;120pub mod weights;121122pub type CreateItemData<T> =123 CreateRefungibleExMultipleOwners<<T as pallet_evm::Config>::CrossAccountId>;124pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;125126#[frame_support::pallet]127pub mod pallet {128 use super::*;129 use frame_support::{130 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,131 traits::StorageVersion,132 };133 use up_data_structs::{CollectionId, TokenId};134 use super::weights::WeightInfo;135136 #[pallet::error]137 pub enum Error<T> {138 139 NotRefungibleDataUsedToMintFungibleCollectionToken,140 141 WrongRefungiblePieces,142 143 RepartitionWhileNotOwningAllPieces,144 145 RefungibleDisallowsNesting,146 147 SettingPropertiesNotAllowed,148 }149150 #[pallet::config]151 pub trait Config:152 frame_system::Config + pallet_common::Config + pallet_structure::Config153 {154 type WeightInfo: WeightInfo;155 }156157 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);158159 #[pallet::pallet]160 #[pallet::storage_version(STORAGE_VERSION)]161 pub struct Pallet<T>(_);162163 164 #[pallet::storage]165 pub type TokensMinted<T: Config> =166 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;167168 169 #[pallet::storage]170 pub type TokensBurnt<T: Config> =171 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;172173 174 #[pallet::storage]175 #[pallet::getter(fn token_properties)]176 pub type TokenProperties<T: Config> = StorageNMap<177 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),178 Value = TokenPropertiesT,179 QueryKind = ValueQuery,180 >;181182 183 #[pallet::storage]184 pub type TotalSupply<T: Config> = StorageNMap<185 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),186 Value = u128,187 QueryKind = ValueQuery,188 >;189190 191 #[pallet::storage]192 pub type Owned<T: Config> = StorageNMap<193 Key = (194 Key<Twox64Concat, CollectionId>,195 Key<Blake2_128Concat, T::CrossAccountId>,196 Key<Twox64Concat, TokenId>,197 ),198 Value = bool,199 QueryKind = ValueQuery,200 >;201202 203 #[pallet::storage]204 pub type AccountBalance<T: Config> = StorageNMap<205 Key = (206 Key<Twox64Concat, CollectionId>,207 208 Key<Blake2_128Concat, T::CrossAccountId>,209 ),210 Value = u32,211 QueryKind = ValueQuery,212 >;213214 215 #[pallet::storage]216 pub type Balance<T: Config> = StorageNMap<217 Key = (218 Key<Twox64Concat, CollectionId>,219 Key<Twox64Concat, TokenId>,220 221 Key<Blake2_128Concat, T::CrossAccountId>,222 ),223 Value = u128,224 QueryKind = ValueQuery,225 >;226227 228 #[pallet::storage]229 pub type Allowance<T: Config> = StorageNMap<230 Key = (231 Key<Twox64Concat, CollectionId>,232 Key<Twox64Concat, TokenId>,233 234 Key<Blake2_128, T::CrossAccountId>,235 236 Key<Blake2_128Concat, T::CrossAccountId>,237 ),238 Value = u128,239 QueryKind = ValueQuery,240 >;241242 243 #[pallet::storage]244 pub type CollectionAllowance<T: Config> = StorageNMap<245 Key = (246 Key<Twox64Concat, CollectionId>,247 Key<Blake2_128Concat, T::CrossAccountId>, 248 Key<Blake2_128Concat, T::CrossAccountId>, 249 ),250 Value = bool,251 QueryKind = ValueQuery,252 >;253}254255pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);256impl<T: Config> RefungibleHandle<T> {257 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {258 Self(inner)259 }260 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {261 self.0262 }263 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {264 &mut self.0265 }266}267268impl<T: Config> Deref for RefungibleHandle<T> {269 type Target = pallet_common::CollectionHandle<T>;270271 fn deref(&self) -> &Self::Target {272 &self.0273 }274}275276impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {277 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {278 self.0.recorder()279 }280 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {281 self.0.into_recorder()282 }283}284285impl<T: Config> Pallet<T> {286 287 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {288 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289 }290291 292 293 294 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {295 <TotalSupply<T>>::contains_key((collection.id, token))296 }297298 pub fn set_scoped_token_property(299 collection_id: CollectionId,300 token_id: TokenId,301 scope: PropertyScope,302 property: Property,303 ) -> DispatchResult {304 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {305 properties.try_scoped_set(scope, property.key, property.value)306 })307 .map_err(<CommonError<T>>::from)?;308309 Ok(())310 }311312 pub fn set_scoped_token_properties(313 collection_id: CollectionId,314 token_id: TokenId,315 scope: PropertyScope,316 properties: impl Iterator<Item = Property>,317 ) -> DispatchResult {318 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {319 stored_properties.try_scoped_set_from_iter(scope, properties)320 })321 .map_err(<CommonError<T>>::from)?;322323 Ok(())324 }325}326327328impl<T: Config> Pallet<T> {329 330 331 332 333 334 pub fn init_collection(335 owner: T::CrossAccountId,336 payer: T::CrossAccountId,337 data: CreateCollectionData<T::AccountId>,338 flags: CollectionFlags,339 ) -> Result<CollectionId, DispatchError> {340 <PalletCommon<T>>::init_collection(owner, payer, data, flags)341 }342343 344 345 346 347 pub fn destroy_collection(348 collection: RefungibleHandle<T>,349 sender: &T::CrossAccountId,350 ) -> DispatchResult {351 let id = collection.id;352353 if Self::collection_has_tokens(id) {354 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());355 }356357 358359 PalletCommon::destroy_collection(collection.0, sender)?;360361 <TokensMinted<T>>::remove(id);362 <TokensBurnt<T>>::remove(id);363 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);364 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);365 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);366 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);367 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);368 Ok(())369 }370371 fn collection_has_tokens(collection_id: CollectionId) -> bool {372 <TotalSupply<T>>::iter_prefix((collection_id,))373 .next()374 .is_some()375 }376377 pub fn burn_token_unchecked(378 collection: &RefungibleHandle<T>,379 owner: &T::CrossAccountId,380 token_id: TokenId,381 ) -> DispatchResult {382 let burnt = <TokensBurnt<T>>::get(collection.id)383 .checked_add(1)384 .ok_or(ArithmeticError::Overflow)?;385386 <TokensBurnt<T>>::insert(collection.id, burnt);387 <TokenProperties<T>>::remove((collection.id, token_id));388 <TotalSupply<T>>::remove((collection.id, token_id));389 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);390 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);391 <PalletEvm<T>>::deposit_log(392 ERC721Events::Transfer {393 from: *owner.as_eth(),394 to: H160::default(),395 token_id: token_id.into(),396 }397 .to_log(collection_id_to_address(collection.id)),398 );399 Ok(())400 }401402 403 404 405 406 407 408 409 410 411 412 413 pub fn burn(414 collection: &RefungibleHandle<T>,415 owner: &T::CrossAccountId,416 token: TokenId,417 amount: u128,418 ) -> DispatchResult {419 if <Balance<T>>::get((collection.id, token, owner)) == 0 {420 return Err(<CommonError<T>>::TokenValueTooLow.into());421 }422423 let total_supply = <TotalSupply<T>>::get((collection.id, token))424 .checked_sub(amount)425 .ok_or(<CommonError<T>>::TokenValueTooLow)?;426427 428 if total_supply == 0 {429 430 ensure!(431 <Balance<T>>::get((collection.id, token, owner)) == amount,432 <CommonError<T>>::TokenValueTooLow433 );434 let account_balance = <AccountBalance<T>>::get((collection.id, owner))435 .checked_sub(1)436 437 .ok_or(ArithmeticError::Underflow)?;438439 440441 <Owned<T>>::remove((collection.id, owner, token));442 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);443 <AccountBalance<T>>::insert((collection.id, owner), account_balance);444 Self::burn_token_unchecked(collection, owner, token)?;445 <PalletEvm<T>>::deposit_log(446 ERC20Events::Transfer {447 from: *owner.as_eth(),448 to: H160::default(),449 value: amount.into(),450 }451 .to_log(collection_id_to_address(collection.id)),452 );453 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(454 collection.id,455 token,456 owner.clone(),457 amount,458 ));459 return Ok(());460 }461462 let balance = <Balance<T>>::get((collection.id, token, owner))463 .checked_sub(amount)464 .ok_or(<CommonError<T>>::TokenValueTooLow)?;465 let account_balance = if balance == 0 {466 <AccountBalance<T>>::get((collection.id, owner))467 .checked_sub(1)468 469 .ok_or(ArithmeticError::Underflow)?470 } else {471 0472 };473474 475476 if balance == 0 {477 <Owned<T>>::remove((collection.id, owner, token));478 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);479 <Balance<T>>::remove((collection.id, token, owner));480 <AccountBalance<T>>::insert((collection.id, owner), account_balance);481482 if let Ok(user) = Self::token_owner(collection.id, token) {483 <PalletEvm<T>>::deposit_log(484 ERC721Events::Transfer {485 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,486 to: *user.as_eth(),487 token_id: token.into(),488 }489 .to_log(collection_id_to_address(collection.id)),490 );491 }492 } else {493 <Balance<T>>::insert((collection.id, token, owner), balance);494 }495 <TotalSupply<T>>::insert((collection.id, token), total_supply);496497 <PalletEvm<T>>::deposit_log(498 ERC20Events::Transfer {499 from: *owner.as_eth(),500 to: H160::default(),501 value: amount.into(),502 }503 .to_log(T::EvmTokenAddressMapping::token_to_address(504 collection.id,505 token,506 )),507 );508 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(509 collection.id,510 token,511 owner.clone(),512 amount,513 ));514 Ok(())515 }516517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 #[transactional]533 fn modify_token_properties(534 collection: &RefungibleHandle<T>,535 sender: &T::CrossAccountId,536 token_id: TokenId,537 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,538 mode: SetPropertyMode,539 nesting_budget: &dyn Budget,540 ) -> DispatchResult {541 let mut is_token_owner =542 pallet_common::LazyValue::new(|| -> Result<bool, DispatchError> {543 if let SetPropertyMode::NewToken {544 mint_target_is_sender,545 } = mode546 {547 return Ok(mint_target_is_sender);548 }549550 let balance = collection.balance(sender.clone(), token_id);551 let total_pieces: u128 =552 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);553 if balance != total_pieces {554 return Ok(false);555 }556557 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(558 sender.clone(),559 collection.id,560 token_id,561 None,562 nesting_budget,563 )?;564565 Ok(is_bundle_owner)566 });567568 let mut is_token_exist =569 pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));570571 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));572573 <PalletCommon<T>>::modify_token_properties(574 collection,575 sender,576 token_id,577 &mut is_token_exist,578 properties_updates,579 stored_properties,580 &mut is_token_owner,581 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),582 erc::ERC721TokenEvent::TokenChanged {583 token_id: token_id.into(),584 }585 .to_log(T::ContractAddress::get()),586 )587 }588589 pub fn next_token_id(collection: &RefungibleHandle<T>) -> Result<TokenId, DispatchError> {590 let next_token_id = <TokensMinted<T>>::get(collection.id)591 .checked_add(1)592 .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;593594 ensure!(595 collection.limits.token_limit() >= next_token_id,596 <CommonError<T>>::CollectionTokenLimitExceeded597 );598599 Ok(TokenId(next_token_id))600 }601602 pub fn set_token_properties(603 collection: &RefungibleHandle<T>,604 sender: &T::CrossAccountId,605 token_id: TokenId,606 properties: impl Iterator<Item = Property>,607 mode: SetPropertyMode,608 nesting_budget: &dyn Budget,609 ) -> DispatchResult {610 Self::modify_token_properties(611 collection,612 sender,613 token_id,614 properties.map(|p| (p.key, Some(p.value))),615 mode,616 nesting_budget,617 )618 }619620 pub fn set_token_property(621 collection: &RefungibleHandle<T>,622 sender: &T::CrossAccountId,623 token_id: TokenId,624 property: Property,625 nesting_budget: &dyn Budget,626 ) -> DispatchResult {627 Self::set_token_properties(628 collection,629 sender,630 token_id,631 [property].into_iter(),632 SetPropertyMode::ExistingToken,633 nesting_budget,634 )635 }636637 pub fn delete_token_properties(638 collection: &RefungibleHandle<T>,639 sender: &T::CrossAccountId,640 token_id: TokenId,641 property_keys: impl Iterator<Item = PropertyKey>,642 nesting_budget: &dyn Budget,643 ) -> DispatchResult {644 Self::modify_token_properties(645 collection,646 sender,647 token_id,648 property_keys.into_iter().map(|key| (key, None)),649 SetPropertyMode::ExistingToken,650 nesting_budget,651 )652 }653654 pub fn delete_token_property(655 collection: &RefungibleHandle<T>,656 sender: &T::CrossAccountId,657 token_id: TokenId,658 property_key: PropertyKey,659 nesting_budget: &dyn Budget,660 ) -> DispatchResult {661 Self::delete_token_properties(662 collection,663 sender,664 token_id,665 [property_key].into_iter(),666 nesting_budget,667 )668 }669670 671 672 673 674 675 676 677 678 679 pub fn transfer(680 collection: &RefungibleHandle<T>,681 from: &T::CrossAccountId,682 to: &T::CrossAccountId,683 token: TokenId,684 amount: u128,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 ensure!(688 collection.limits.transfers_enabled(),689 <CommonError<T>>::TransferNotAllowed690 );691692 if collection.permissions.access() == AccessMode::AllowList {693 collection.check_allowlist(from)?;694 collection.check_allowlist(to)?;695 }696 <PalletCommon<T>>::ensure_correct_receiver(to)?;697698 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));699700 if initial_balance_from == 0 {701 return Err(<CommonError<T>>::TokenValueTooLow.into());702 }703704 let updated_balance_from = initial_balance_from705 .checked_sub(amount)706 .ok_or(<CommonError<T>>::TokenValueTooLow)?;707 let mut create_target = false;708 let from_to_differ = from != to;709 let updated_balance_to = if from != to && amount != 0 {710 let old_balance = <Balance<T>>::get((collection.id, token, to));711 if old_balance == 0 {712 create_target = true;713 }714 Some(715 old_balance716 .checked_add(amount)717 .ok_or(ArithmeticError::Overflow)?,718 )719 } else {720 None721 };722723 let account_balance_from = if updated_balance_from == 0 {724 Some(725 <AccountBalance<T>>::get((collection.id, from))726 .checked_sub(1)727 728 .ok_or(ArithmeticError::Underflow)?,729 )730 } else {731 None732 };733 734 735 let account_balance_to = if create_target && from_to_differ {736 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))737 .checked_add(1)738 .ok_or(ArithmeticError::Overflow)?;739 ensure!(740 account_balance_to < collection.limits.account_token_ownership_limit(),741 <CommonError<T>>::AccountTokenLimitExceeded,742 );743744 Some(account_balance_to)745 } else {746 None747 };748749 750751 if let Some(updated_balance_to) = updated_balance_to {752 753754 <PalletStructure<T>>::nest_if_sent_to_token(755 from.clone(),756 to,757 collection.id,758 token,759 nesting_budget,760 )?;761762 if updated_balance_from == 0 {763 <Balance<T>>::remove((collection.id, token, from));764 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);765 } else {766 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);767 }768 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);769 if let Some(account_balance_from) = account_balance_from {770 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);771 <Owned<T>>::remove((collection.id, from, token));772 }773 if let Some(account_balance_to) = account_balance_to {774 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);775 <Owned<T>>::insert((collection.id, to, token), true);776 }777 }778779 <PalletEvm<T>>::deposit_log(780 ERC20Events::Transfer {781 from: *from.as_eth(),782 to: *to.as_eth(),783 value: amount.into(),784 }785 .to_log(T::EvmTokenAddressMapping::token_to_address(786 collection.id,787 token,788 )),789 );790791 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(792 collection.id,793 token,794 from.clone(),795 to.clone(),796 amount,797 ));798799 let total_supply = <TotalSupply<T>>::get((collection.id, token));800801 if amount == total_supply {802 803 <PalletEvm<T>>::deposit_log(804 ERC721Events::Transfer {805 from: *from.as_eth(),806 to: *to.as_eth(),807 token_id: token.into(),808 }809 .to_log(collection_id_to_address(collection.id)),810 );811 } else if let Some(updated_balance_to) = updated_balance_to {812 813 814 if initial_balance_from == total_supply {815 816 817 <PalletEvm<T>>::deposit_log(818 ERC721Events::Transfer {819 from: *from.as_eth(),820 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,821 token_id: token.into(),822 }823 .to_log(collection_id_to_address(collection.id)),824 );825 } else if updated_balance_to == total_supply {826 827 <PalletEvm<T>>::deposit_log(828 ERC721Events::Transfer {829 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,830 to: *to.as_eth(),831 token_id: token.into(),832 }833 .to_log(collection_id_to_address(collection.id)),834 );835 }836 }837838 Ok(())839 }840841 842 843 844 845 846 pub fn create_multiple_items(847 collection: &RefungibleHandle<T>,848 sender: &T::CrossAccountId,849 data: Vec<CreateItemData<T>>,850 nesting_budget: &dyn Budget,851 ) -> DispatchResult {852 if !collection.is_owner_or_admin(sender) {853 ensure!(854 collection.permissions.mint_mode(),855 <CommonError<T>>::PublicMintingNotAllowed856 );857 collection.check_allowlist(sender)?;858859 for item in data.iter() {860 for user in item.users.keys() {861 collection.check_allowlist(user)?;862 }863 }864 }865866 for item in data.iter() {867 for (owner, _) in item.users.iter() {868 <PalletCommon<T>>::ensure_correct_receiver(owner)?;869 }870 }871872 873 let totals = data874 .iter()875 .map(|data| {876 Ok(data877 .users878 .iter()879 .map(|u| u.1)880 .try_fold(0u128, |acc, v| acc.checked_add(*v))881 .ok_or(ArithmeticError::Overflow)?)882 })883 .collect::<Result<Vec<_>, DispatchError>>()?;884 for total in &totals {885 ensure!(886 *total <= MAX_REFUNGIBLE_PIECES,887 <Error<T>>::WrongRefungiblePieces888 );889 }890891 let first_token_id = <TokensMinted<T>>::get(collection.id);892 let tokens_minted = first_token_id893 .checked_add(data.len() as u32)894 .ok_or(ArithmeticError::Overflow)?;895 ensure!(896 tokens_minted < collection.limits.token_limit(),897 <CommonError<T>>::CollectionTokenLimitExceeded898 );899900 let mut balances = BTreeMap::new();901 for data in &data {902 for owner in data.users.keys() {903 let balance = balances904 .entry(owner)905 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));906 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;907908 ensure!(909 *balance <= collection.limits.account_token_ownership_limit(),910 <CommonError<T>>::AccountTokenLimitExceeded,911 );912 }913 }914915 for (i, token) in data.iter().enumerate() {916 let token_id = TokenId(first_token_id + i as u32 + 1);917 for (to, _) in token.users.iter() {918 <PalletStructure<T>>::check_nesting(919 sender.clone(),920 to,921 collection.id,922 token_id,923 nesting_budget,924 )?;925 }926 }927928 929930 with_transaction(|| {931 for (i, data) in data.iter().enumerate() {932 let token_id = first_token_id + i as u32 + 1;933 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);934935 let mut mint_target_is_sender = true;936 for (user, amount) in data.users.iter() {937 if *amount == 0 {938 continue;939 }940941 mint_target_is_sender = mint_target_is_sender && sender.conv_eq(user);942943 <Balance<T>>::insert((collection.id, token_id, &user), amount);944 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);945 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(946 user,947 collection.id,948 TokenId(token_id),949 );950 }951952 if let Err(e) = Self::set_token_properties(953 collection,954 sender,955 TokenId(token_id),956 data.properties.clone().into_iter(),957 SetPropertyMode::NewToken {958 mint_target_is_sender,959 },960 nesting_budget,961 ) {962 return TransactionOutcome::Rollback(Err(e));963 }964 }965 TransactionOutcome::Commit(Ok(()))966 })?;967968 <TokensMinted<T>>::insert(collection.id, tokens_minted);969970 for (account, balance) in balances {971 <AccountBalance<T>>::insert((collection.id, account), balance);972 }973974 for (i, token) in data.into_iter().enumerate() {975 let token_id = first_token_id + i as u32 + 1;976977 let receivers = token978 .users979 .into_iter()980 .filter(|(_, amount)| *amount > 0)981 .collect::<Vec<_>>();982983 if let [(user, _)] = receivers.as_slice() {984 985 <PalletEvm<T>>::deposit_log(986 ERC721Events::Transfer {987 from: H160::default(),988 to: *user.as_eth(),989 token_id: token_id.into(),990 }991 .to_log(collection_id_to_address(collection.id)),992 );993 } else if let [_, ..] = receivers.as_slice() {994 995 <PalletEvm<T>>::deposit_log(996 ERC721Events::Transfer {997 from: H160::default(),998 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,999 token_id: token_id.into(),1000 }1001 .to_log(collection_id_to_address(collection.id)),1002 );1003 }10041005 for (user, amount) in receivers.into_iter() {1006 <PalletEvm<T>>::deposit_log(1007 ERC20Events::Transfer {1008 from: H160::default(),1009 to: *user.as_eth(),1010 value: amount.into(),1011 }1012 .to_log(T::EvmTokenAddressMapping::token_to_address(1013 collection.id,1014 TokenId(token_id),1015 )),1016 );1017 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1018 collection.id,1019 TokenId(token_id),1020 user,1021 amount,1022 ));1023 }1024 }1025 Ok(())1026 }10271028 pub fn set_allowance_unchecked(1029 collection: &RefungibleHandle<T>,1030 sender: &T::CrossAccountId,1031 spender: &T::CrossAccountId,1032 token: TokenId,1033 amount: u128,1034 ) {1035 if amount == 0 {1036 <Allowance<T>>::remove((collection.id, token, sender, spender));1037 } else {1038 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1039 }10401041 <PalletEvm<T>>::deposit_log(1042 ERC20Events::Approval {1043 owner: *sender.as_eth(),1044 spender: *spender.as_eth(),1045 value: amount.into(),1046 }1047 .to_log(T::EvmTokenAddressMapping::token_to_address(1048 collection.id,1049 token,1050 )),1051 );1052 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1053 collection.id,1054 token,1055 sender.clone(),1056 spender.clone(),1057 amount,1058 ))1059 }10601061 1062 1063 1064 pub fn set_allowance(1065 collection: &RefungibleHandle<T>,1066 sender: &T::CrossAccountId,1067 spender: &T::CrossAccountId,1068 token: TokenId,1069 amount: u128,1070 ) -> DispatchResult {1071 if collection.permissions.access() == AccessMode::AllowList {1072 collection.check_allowlist(sender)?;1073 collection.check_allowlist(spender)?;1074 }10751076 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10771078 if <Balance<T>>::get((collection.id, token, sender)) < amount {1079 ensure!(1080 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1081 <CommonError<T>>::CantApproveMoreThanOwned1082 );1083 }10841085 10861087 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1088 Ok(())1089 }10901091 1092 1093 1094 1095 1096 pub fn set_allowance_from(1097 collection: &RefungibleHandle<T>,1098 sender: &T::CrossAccountId,1099 from: &T::CrossAccountId,1100 to: &T::CrossAccountId,1101 token_id: TokenId,1102 amount: u128,1103 ) -> DispatchResult {1104 if collection.permissions.access() == AccessMode::AllowList {1105 collection.check_allowlist(sender)?;1106 collection.check_allowlist(from)?;1107 collection.check_allowlist(to)?;1108 }11091110 <PalletCommon<T>>::ensure_correct_receiver(to)?;11111112 ensure!(1113 sender.conv_eq(from),1114 <CommonError<T>>::AddressIsNotEthMirror1115 );11161117 if <Balance<T>>::get((collection.id, token_id, from)) < amount {1118 ensure!(1119 collection.limits.owner_can_transfer()1120 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from))1121 && Self::token_exists(collection, token_id),1122 <CommonError<T>>::CantApproveMoreThanOwned1123 );1124 }11251126 11271128 Self::set_allowance_unchecked(collection, from, to, token_id, amount);1129 Ok(())1130 }11311132 1133 fn check_allowed(1134 collection: &RefungibleHandle<T>,1135 spender: &T::CrossAccountId,1136 from: &T::CrossAccountId,1137 token: TokenId,1138 amount: u128,1139 nesting_budget: &dyn Budget,1140 ) -> Result<Option<u128>, DispatchError> {1141 if spender.conv_eq(from) {1142 return Ok(None);1143 }1144 if collection.permissions.access() == AccessMode::AllowList {1145 1146 collection.check_allowlist(spender)?;1147 }11481149 if collection.ignores_token_restrictions(spender) {1150 return Ok(Self::compute_allowance_decrease(1151 collection, token, from, spender, amount,1152 ));1153 }11541155 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1156 1157 ensure!(1158 <PalletStructure<T>>::check_indirectly_owned(1159 spender.clone(),1160 source.0,1161 source.1,1162 None,1163 nesting_budget1164 )?,1165 <CommonError<T>>::ApprovedValueTooLow,1166 );1167 return Ok(None);1168 }11691170 let allowance = Self::compute_allowance_decrease(collection, token, from, spender, amount);1171 if allowance.is_some() {1172 return Ok(allowance);1173 }11741175 1176 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1177 return Ok(allowance);1178 }11791180 Err(<CommonError<T>>::ApprovedValueTooLow.into())1181 }11821183 1184 1185 fn compute_allowance_decrease(1186 collection: &RefungibleHandle<T>,1187 token: TokenId,1188 from: &T::CrossAccountId,1189 spender: &T::CrossAccountId,1190 amount: u128,1191 ) -> Option<u128> {1192 <Allowance<T>>::get((collection.id, token, from, spender)).checked_sub(amount)1193 }11941195 1196 1197 1198 1199 1200 1201 pub fn transfer_from(1202 collection: &RefungibleHandle<T>,1203 spender: &T::CrossAccountId,1204 from: &T::CrossAccountId,1205 to: &T::CrossAccountId,1206 token: TokenId,1207 amount: u128,1208 nesting_budget: &dyn Budget,1209 ) -> DispatchResult {1210 let allowance =1211 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12121213 12141215 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1216 if let Some(allowance) = allowance {1217 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1218 }1219 Ok(())1220 }12211222 1223 1224 1225 1226 1227 1228 pub fn burn_from(1229 collection: &RefungibleHandle<T>,1230 spender: &T::CrossAccountId,1231 from: &T::CrossAccountId,1232 token: TokenId,1233 amount: u128,1234 nesting_budget: &dyn Budget,1235 ) -> DispatchResult {1236 let allowance =1237 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12381239 12401241 Self::burn(collection, from, token, amount)?;1242 if let Some(allowance) = allowance {1243 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1244 }1245 Ok(())1246 }12471248 1249 1250 1251 1252 1253 1254 1255 pub fn create_item(1256 collection: &RefungibleHandle<T>,1257 sender: &T::CrossAccountId,1258 data: CreateItemData<T>,1259 nesting_budget: &dyn Budget,1260 ) -> DispatchResult {1261 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1262 }12631264 1265 1266 1267 1268 1269 1270 1271 pub fn repartition(1272 collection: &RefungibleHandle<T>,1273 owner: &T::CrossAccountId,1274 token: TokenId,1275 amount: u128,1276 ) -> DispatchResult {1277 ensure!(1278 amount <= MAX_REFUNGIBLE_PIECES,1279 <Error<T>>::WrongRefungiblePieces1280 );1281 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1282 1283 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1284 let balance = <Balance<T>>::get((collection.id, token, owner));1285 ensure!(1286 total_pieces == balance,1287 <Error<T>>::RepartitionWhileNotOwningAllPieces1288 );12891290 <Balance<T>>::insert((collection.id, token, owner), amount);1291 <TotalSupply<T>>::insert((collection.id, token), amount);12921293 match total_pieces.cmp(&amount) {1294 Ordering::Less => {1295 let mint_amount = amount - total_pieces;1296 <PalletEvm<T>>::deposit_log(1297 ERC20Events::Transfer {1298 from: H160::default(),1299 to: *owner.as_eth(),1300 value: mint_amount.into(),1301 }1302 .to_log(T::EvmTokenAddressMapping::token_to_address(1303 collection.id,1304 token,1305 )),1306 );1307 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1308 collection.id,1309 token,1310 owner.clone(),1311 mint_amount,1312 ));1313 }1314 Ordering::Greater => {1315 let burn_amount = total_pieces - amount;1316 <PalletEvm<T>>::deposit_log(1317 ERC20Events::Transfer {1318 from: *owner.as_eth(),1319 to: H160::default(),1320 value: burn_amount.into(),1321 }1322 .to_log(T::EvmTokenAddressMapping::token_to_address(1323 collection.id,1324 token,1325 )),1326 );1327 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1328 collection.id,1329 token,1330 owner.clone(),1331 burn_amount,1332 ));1333 }1334 Ordering::Equal => {}1335 }13361337 Ok(())1338 }13391340 fn token_owner(1341 collection_id: CollectionId,1342 token_id: TokenId,1343 ) -> Result<T::CrossAccountId, TokenOwnerError> {1344 let mut owner = None;1345 let mut count = 0;1346 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1347 count += 1;1348 if count > 1 {1349 return Err(TokenOwnerError::MultipleOwners);1350 }1351 owner = Some(key);1352 }1353 owner.ok_or(TokenOwnerError::NotFound)1354 }13551356 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1357 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1358 }13591360 pub fn set_collection_properties(1361 collection: &RefungibleHandle<T>,1362 sender: &T::CrossAccountId,1363 properties: Vec<Property>,1364 ) -> DispatchResult {1365 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())1366 }13671368 pub fn delete_collection_properties(1369 collection: &RefungibleHandle<T>,1370 sender: &T::CrossAccountId,1371 property_keys: Vec<PropertyKey>,1372 ) -> DispatchResult {1373 <PalletCommon<T>>::delete_collection_properties(1374 collection,1375 sender,1376 property_keys.into_iter(),1377 )1378 }13791380 pub fn set_token_property_permissions(1381 collection: &RefungibleHandle<T>,1382 sender: &T::CrossAccountId,1383 property_permissions: Vec<PropertyKeyPermission>,1384 ) -> DispatchResult {1385 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1386 }13871388 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1389 <PalletCommon<T>>::property_permissions(collection_id)1390 }13911392 pub fn set_scoped_token_property_permissions(1393 collection: &RefungibleHandle<T>,1394 sender: &T::CrossAccountId,1395 scope: PropertyScope,1396 property_permissions: Vec<PropertyKeyPermission>,1397 ) -> DispatchResult {1398 <PalletCommon<T>>::set_scoped_token_property_permissions(1399 collection,1400 sender,1401 scope,1402 property_permissions,1403 )1404 }14051406 1407 1408 1409 1410 1411 1412 pub fn token_owners(1413 collection_id: CollectionId,1414 token: TokenId,1415 ) -> Option<Vec<T::CrossAccountId>> {1416 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1417 .map(|(owner, _amount)| owner)1418 .take(10)1419 .collect();14201421 if res.is_empty() {1422 None1423 } else {1424 Some(res)1425 }1426 }14271428 1429 1430 1431 1432 1433 1434 pub fn set_allowance_for_all(1435 collection: &RefungibleHandle<T>,1436 owner: &T::CrossAccountId,1437 spender: &T::CrossAccountId,1438 approve: bool,1439 ) -> DispatchResult {1440 <PalletCommon<T>>::set_allowance_for_all(1441 collection,1442 owner,1443 spender,1444 approve,1445 || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),1446 ERC721Events::ApprovalForAll {1447 owner: *owner.as_eth(),1448 operator: *spender.as_eth(),1449 approved: approve,1450 }1451 .to_log(collection_id_to_address(collection.id)),1452 )1453 }14541455 1456 pub fn allowance_for_all(1457 collection: &RefungibleHandle<T>,1458 owner: &T::CrossAccountId,1459 spender: &T::CrossAccountId,1460 ) -> bool {1461 <CollectionAllowance<T>>::get((collection.id, owner, spender))1462 }14631464 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1465 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1466 properties.recompute_consumed_space();1467 });14681469 Ok(())1470 }1471}