12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use core::ops::Deref;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,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 533 534 #[transactional]535 fn modify_token_properties(536 collection: &RefungibleHandle<T>,537 sender: &T::CrossAccountId,538 token_id: TokenId,539 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,540 is_token_create: bool,541 nesting_budget: &dyn Budget,542 ) -> DispatchResult {543 let is_token_owner = || -> Result<bool, DispatchError> {544 let balance = collection.balance(sender.clone(), token_id);545 let total_pieces: u128 =546 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);547 if balance != total_pieces {548 return Ok(false);549 }550551 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(552 sender.clone(),553 collection.id,554 token_id,555 None,556 nesting_budget,557 )?;558559 Ok(is_bundle_owner)560 };561562 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));563564 <PalletCommon<T>>::modify_token_properties(565 collection,566 sender,567 token_id,568 properties_updates,569 is_token_create,570 stored_properties,571 is_token_owner,572 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),573 erc::ERC721TokenEvent::TokenChanged {574 token_id: token_id.into(),575 }576 .to_log(T::ContractAddress::get()),577 )578 }579580 pub fn set_token_properties(581 collection: &RefungibleHandle<T>,582 sender: &T::CrossAccountId,583 token_id: TokenId,584 properties: impl Iterator<Item = Property>,585 is_token_create: bool,586 nesting_budget: &dyn Budget,587 ) -> DispatchResult {588 Self::modify_token_properties(589 collection,590 sender,591 token_id,592 properties.map(|p| (p.key, Some(p.value))),593 is_token_create,594 nesting_budget,595 )596 }597598 pub fn set_token_property(599 collection: &RefungibleHandle<T>,600 sender: &T::CrossAccountId,601 token_id: TokenId,602 property: Property,603 nesting_budget: &dyn Budget,604 ) -> DispatchResult {605 let is_token_create = false;606607 Self::set_token_properties(608 collection,609 sender,610 token_id,611 [property].into_iter(),612 is_token_create,613 nesting_budget,614 )615 }616617 pub fn delete_token_properties(618 collection: &RefungibleHandle<T>,619 sender: &T::CrossAccountId,620 token_id: TokenId,621 property_keys: impl Iterator<Item = PropertyKey>,622 nesting_budget: &dyn Budget,623 ) -> DispatchResult {624 let is_token_create = false;625626 Self::modify_token_properties(627 collection,628 sender,629 token_id,630 property_keys.into_iter().map(|key| (key, None)),631 is_token_create,632 nesting_budget,633 )634 }635636 pub fn delete_token_property(637 collection: &RefungibleHandle<T>,638 sender: &T::CrossAccountId,639 token_id: TokenId,640 property_key: PropertyKey,641 nesting_budget: &dyn Budget,642 ) -> DispatchResult {643 Self::delete_token_properties(644 collection,645 sender,646 token_id,647 [property_key].into_iter(),648 nesting_budget,649 )650 }651652 653 654 655 656 657 658 659 660 661 pub fn transfer(662 collection: &RefungibleHandle<T>,663 from: &T::CrossAccountId,664 to: &T::CrossAccountId,665 token: TokenId,666 amount: u128,667 nesting_budget: &dyn Budget,668 ) -> DispatchResult {669 ensure!(670 collection.limits.transfers_enabled(),671 <CommonError<T>>::TransferNotAllowed672 );673674 if collection.permissions.access() == AccessMode::AllowList {675 collection.check_allowlist(from)?;676 collection.check_allowlist(to)?;677 }678 <PalletCommon<T>>::ensure_correct_receiver(to)?;679680 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));681682 if initial_balance_from == 0 {683 return Err(<CommonError<T>>::TokenValueTooLow.into());684 }685686 let updated_balance_from = initial_balance_from687 .checked_sub(amount)688 .ok_or(<CommonError<T>>::TokenValueTooLow)?;689 let mut create_target = false;690 let from_to_differ = from != to;691 let updated_balance_to = if from != to && amount != 0 {692 let old_balance = <Balance<T>>::get((collection.id, token, to));693 if old_balance == 0 {694 create_target = true;695 }696 Some(697 old_balance698 .checked_add(amount)699 .ok_or(ArithmeticError::Overflow)?,700 )701 } else {702 None703 };704705 let account_balance_from = if updated_balance_from == 0 {706 Some(707 <AccountBalance<T>>::get((collection.id, from))708 .checked_sub(1)709 710 .ok_or(ArithmeticError::Underflow)?,711 )712 } else {713 None714 };715 716 717 let account_balance_to = if create_target && from_to_differ {718 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))719 .checked_add(1)720 .ok_or(ArithmeticError::Overflow)?;721 ensure!(722 account_balance_to < collection.limits.account_token_ownership_limit(),723 <CommonError<T>>::AccountTokenLimitExceeded,724 );725726 Some(account_balance_to)727 } else {728 None729 };730731 732733 if let Some(updated_balance_to) = updated_balance_to {734 735736 <PalletStructure<T>>::nest_if_sent_to_token(737 from.clone(),738 to,739 collection.id,740 token,741 nesting_budget,742 )?;743744 if updated_balance_from == 0 {745 <Balance<T>>::remove((collection.id, token, from));746 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);747 } else {748 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);749 }750 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);751 if let Some(account_balance_from) = account_balance_from {752 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);753 <Owned<T>>::remove((collection.id, from, token));754 }755 if let Some(account_balance_to) = account_balance_to {756 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);757 <Owned<T>>::insert((collection.id, to, token), true);758 }759 }760761 <PalletEvm<T>>::deposit_log(762 ERC20Events::Transfer {763 from: *from.as_eth(),764 to: *to.as_eth(),765 value: amount.into(),766 }767 .to_log(T::EvmTokenAddressMapping::token_to_address(768 collection.id,769 token,770 )),771 );772773 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774 collection.id,775 token,776 from.clone(),777 to.clone(),778 amount,779 ));780781 let total_supply = <TotalSupply<T>>::get((collection.id, token));782783 if amount == total_supply {784 785 <PalletEvm<T>>::deposit_log(786 ERC721Events::Transfer {787 from: *from.as_eth(),788 to: *to.as_eth(),789 token_id: token.into(),790 }791 .to_log(collection_id_to_address(collection.id)),792 );793 } else if let Some(updated_balance_to) = updated_balance_to {794 795 796 if initial_balance_from == total_supply {797 798 799 <PalletEvm<T>>::deposit_log(800 ERC721Events::Transfer {801 from: *from.as_eth(),802 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,803 token_id: token.into(),804 }805 .to_log(collection_id_to_address(collection.id)),806 );807 } else if updated_balance_to == total_supply {808 809 <PalletEvm<T>>::deposit_log(810 ERC721Events::Transfer {811 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,812 to: *to.as_eth(),813 token_id: token.into(),814 }815 .to_log(collection_id_to_address(collection.id)),816 );817 }818 }819820 Ok(())821 }822823 824 825 826 827 828 pub fn create_multiple_items(829 collection: &RefungibleHandle<T>,830 sender: &T::CrossAccountId,831 data: Vec<CreateItemData<T>>,832 nesting_budget: &dyn Budget,833 ) -> DispatchResult {834 if !collection.is_owner_or_admin(sender) {835 ensure!(836 collection.permissions.mint_mode(),837 <CommonError<T>>::PublicMintingNotAllowed838 );839 collection.check_allowlist(sender)?;840841 for item in data.iter() {842 for user in item.users.keys() {843 collection.check_allowlist(user)?;844 }845 }846 }847848 for item in data.iter() {849 for (owner, _) in item.users.iter() {850 <PalletCommon<T>>::ensure_correct_receiver(owner)?;851 }852 }853854 855 let totals = data856 .iter()857 .map(|data| {858 Ok(data859 .users860 .iter()861 .map(|u| u.1)862 .try_fold(0u128, |acc, v| acc.checked_add(*v))863 .ok_or(ArithmeticError::Overflow)?)864 })865 .collect::<Result<Vec<_>, DispatchError>>()?;866 for total in &totals {867 ensure!(868 *total <= MAX_REFUNGIBLE_PIECES,869 <Error<T>>::WrongRefungiblePieces870 );871 }872873 let first_token_id = <TokensMinted<T>>::get(collection.id);874 let tokens_minted = first_token_id875 .checked_add(data.len() as u32)876 .ok_or(ArithmeticError::Overflow)?;877 ensure!(878 tokens_minted < collection.limits.token_limit(),879 <CommonError<T>>::CollectionTokenLimitExceeded880 );881882 let mut balances = BTreeMap::new();883 for data in &data {884 for owner in data.users.keys() {885 let balance = balances886 .entry(owner)887 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));888 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;889890 ensure!(891 *balance <= collection.limits.account_token_ownership_limit(),892 <CommonError<T>>::AccountTokenLimitExceeded,893 );894 }895 }896897 for (i, token) in data.iter().enumerate() {898 let token_id = TokenId(first_token_id + i as u32 + 1);899 for (to, _) in token.users.iter() {900 <PalletStructure<T>>::check_nesting(901 sender.clone(),902 to,903 collection.id,904 token_id,905 nesting_budget,906 )?;907 }908 }909910 911912 with_transaction(|| {913 for (i, data) in data.iter().enumerate() {914 let token_id = first_token_id + i as u32 + 1;915 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);916917 for (user, amount) in data.users.iter() {918 if *amount == 0 {919 continue;920 }921 <Balance<T>>::insert((collection.id, token_id, &user), amount);922 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);923 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(924 user,925 collection.id,926 TokenId(token_id),927 );928 }929930 if let Err(e) = Self::set_token_properties(931 collection,932 sender,933 TokenId(token_id),934 data.properties.clone().into_iter(),935 true,936 nesting_budget,937 ) {938 return TransactionOutcome::Rollback(Err(e));939 }940 }941 TransactionOutcome::Commit(Ok(()))942 })?;943944 <TokensMinted<T>>::insert(collection.id, tokens_minted);945946 for (account, balance) in balances {947 <AccountBalance<T>>::insert((collection.id, account), balance);948 }949950 for (i, token) in data.into_iter().enumerate() {951 let token_id = first_token_id + i as u32 + 1;952953 let receivers = token954 .users955 .into_iter()956 .filter(|(_, amount)| *amount > 0)957 .collect::<Vec<_>>();958959 if let [(user, _)] = receivers.as_slice() {960 961 <PalletEvm<T>>::deposit_log(962 ERC721Events::Transfer {963 from: H160::default(),964 to: *user.as_eth(),965 token_id: token_id.into(),966 }967 .to_log(collection_id_to_address(collection.id)),968 );969 } else if let [_, ..] = receivers.as_slice() {970 971 <PalletEvm<T>>::deposit_log(972 ERC721Events::Transfer {973 from: H160::default(),974 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,975 token_id: token_id.into(),976 }977 .to_log(collection_id_to_address(collection.id)),978 );979 }980981 for (user, amount) in receivers.into_iter() {982 <PalletEvm<T>>::deposit_log(983 ERC20Events::Transfer {984 from: H160::default(),985 to: *user.as_eth(),986 value: amount.into(),987 }988 .to_log(T::EvmTokenAddressMapping::token_to_address(989 collection.id,990 TokenId(token_id),991 )),992 );993 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(994 collection.id,995 TokenId(token_id),996 user,997 amount,998 ));999 }1000 }1001 Ok(())1002 }10031004 pub fn set_allowance_unchecked(1005 collection: &RefungibleHandle<T>,1006 sender: &T::CrossAccountId,1007 spender: &T::CrossAccountId,1008 token: TokenId,1009 amount: u128,1010 ) {1011 if amount == 0 {1012 <Allowance<T>>::remove((collection.id, token, sender, spender));1013 } else {1014 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1015 }10161017 <PalletEvm<T>>::deposit_log(1018 ERC20Events::Approval {1019 owner: *sender.as_eth(),1020 spender: *spender.as_eth(),1021 value: amount.into(),1022 }1023 .to_log(T::EvmTokenAddressMapping::token_to_address(1024 collection.id,1025 token,1026 )),1027 );1028 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1029 collection.id,1030 token,1031 sender.clone(),1032 spender.clone(),1033 amount,1034 ))1035 }10361037 1038 1039 1040 pub fn set_allowance(1041 collection: &RefungibleHandle<T>,1042 sender: &T::CrossAccountId,1043 spender: &T::CrossAccountId,1044 token: TokenId,1045 amount: u128,1046 ) -> DispatchResult {1047 if collection.permissions.access() == AccessMode::AllowList {1048 collection.check_allowlist(sender)?;1049 collection.check_allowlist(spender)?;1050 }10511052 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10531054 if <Balance<T>>::get((collection.id, token, sender)) < amount {1055 ensure!(1056 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1057 <CommonError<T>>::CantApproveMoreThanOwned1058 );1059 }10601061 10621063 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1064 Ok(())1065 }10661067 1068 1069 1070 1071 1072 pub fn set_allowance_from(1073 collection: &RefungibleHandle<T>,1074 sender: &T::CrossAccountId,1075 from: &T::CrossAccountId,1076 to: &T::CrossAccountId,1077 token_id: TokenId,1078 amount: u128,1079 ) -> DispatchResult {1080 if collection.permissions.access() == AccessMode::AllowList {1081 collection.check_allowlist(sender)?;1082 collection.check_allowlist(from)?;1083 collection.check_allowlist(to)?;1084 }10851086 <PalletCommon<T>>::ensure_correct_receiver(to)?;10871088 ensure!(1089 sender.conv_eq(from),1090 <CommonError<T>>::AddressIsNotEthMirror1091 );10921093 if <Balance<T>>::get((collection.id, token_id, from)) < amount {1094 ensure!(1095 collection.limits.owner_can_transfer()1096 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from))1097 && Self::token_exists(collection, token_id),1098 <CommonError<T>>::CantApproveMoreThanOwned1099 );1100 }11011102 11031104 Self::set_allowance_unchecked(collection, from, to, token_id, amount);1105 Ok(())1106 }11071108 1109 fn check_allowed(1110 collection: &RefungibleHandle<T>,1111 spender: &T::CrossAccountId,1112 from: &T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 nesting_budget: &dyn Budget,1116 ) -> Result<Option<u128>, DispatchError> {1117 if spender.conv_eq(from) {1118 return Ok(None);1119 }1120 if collection.permissions.access() == AccessMode::AllowList {1121 1122 collection.check_allowlist(spender)?;1123 }11241125 if collection.ignores_token_restrictions(spender) {1126 return Ok(Self::compute_allowance_decrease(1127 collection, token, from, &spender, amount,1128 ));1129 }11301131 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1132 1133 ensure!(1134 <PalletStructure<T>>::check_indirectly_owned(1135 spender.clone(),1136 source.0,1137 source.1,1138 None,1139 nesting_budget1140 )?,1141 <CommonError<T>>::ApprovedValueTooLow,1142 );1143 return Ok(None);1144 }11451146 let allowance = Self::compute_allowance_decrease(collection, token, from, &spender, amount);1147 if allowance.is_some() {1148 return Ok(allowance);1149 }11501151 1152 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1153 return Ok(allowance);1154 }11551156 Err(<CommonError<T>>::ApprovedValueTooLow.into())1157 }11581159 1160 1161 fn compute_allowance_decrease(1162 collection: &RefungibleHandle<T>,1163 token: TokenId,1164 from: &T::CrossAccountId,1165 spender: &T::CrossAccountId,1166 amount: u128,1167 ) -> Option<u128> {1168 <Allowance<T>>::get((collection.id, token, from, spender)).checked_sub(amount)1169 }11701171 1172 1173 1174 1175 1176 1177 pub fn transfer_from(1178 collection: &RefungibleHandle<T>,1179 spender: &T::CrossAccountId,1180 from: &T::CrossAccountId,1181 to: &T::CrossAccountId,1182 token: TokenId,1183 amount: u128,1184 nesting_budget: &dyn Budget,1185 ) -> DispatchResult {1186 let allowance =1187 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11881189 11901191 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1192 if let Some(allowance) = allowance {1193 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1194 }1195 Ok(())1196 }11971198 1199 1200 1201 1202 1203 1204 pub fn burn_from(1205 collection: &RefungibleHandle<T>,1206 spender: &T::CrossAccountId,1207 from: &T::CrossAccountId,1208 token: TokenId,1209 amount: u128,1210 nesting_budget: &dyn Budget,1211 ) -> DispatchResult {1212 let allowance =1213 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12141215 12161217 Self::burn(collection, from, token, amount)?;1218 if let Some(allowance) = allowance {1219 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1220 }1221 Ok(())1222 }12231224 1225 1226 1227 1228 1229 1230 1231 pub fn create_item(1232 collection: &RefungibleHandle<T>,1233 sender: &T::CrossAccountId,1234 data: CreateItemData<T>,1235 nesting_budget: &dyn Budget,1236 ) -> DispatchResult {1237 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1238 }12391240 1241 1242 1243 1244 1245 1246 1247 pub fn repartition(1248 collection: &RefungibleHandle<T>,1249 owner: &T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 ) -> DispatchResult {1253 ensure!(1254 amount <= MAX_REFUNGIBLE_PIECES,1255 <Error<T>>::WrongRefungiblePieces1256 );1257 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1258 1259 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1260 let balance = <Balance<T>>::get((collection.id, token, owner));1261 ensure!(1262 total_pieces == balance,1263 <Error<T>>::RepartitionWhileNotOwningAllPieces1264 );12651266 <Balance<T>>::insert((collection.id, token, owner), amount);1267 <TotalSupply<T>>::insert((collection.id, token), amount);12681269 if amount > total_pieces {1270 let mint_amount = amount - total_pieces;1271 <PalletEvm<T>>::deposit_log(1272 ERC20Events::Transfer {1273 from: H160::default(),1274 to: *owner.as_eth(),1275 value: mint_amount.into(),1276 }1277 .to_log(T::EvmTokenAddressMapping::token_to_address(1278 collection.id,1279 token,1280 )),1281 );1282 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1283 collection.id,1284 token,1285 owner.clone(),1286 mint_amount,1287 ));1288 } else if total_pieces > amount {1289 let burn_amount = total_pieces - amount;1290 <PalletEvm<T>>::deposit_log(1291 ERC20Events::Transfer {1292 from: *owner.as_eth(),1293 to: H160::default(),1294 value: burn_amount.into(),1295 }1296 .to_log(T::EvmTokenAddressMapping::token_to_address(1297 collection.id,1298 token,1299 )),1300 );1301 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1302 collection.id,1303 token,1304 owner.clone(),1305 burn_amount,1306 ));1307 }13081309 Ok(())1310 }13111312 fn token_owner(1313 collection_id: CollectionId,1314 token_id: TokenId,1315 ) -> Result<T::CrossAccountId, TokenOwnerError> {1316 let mut owner = None;1317 let mut count = 0;1318 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1319 count += 1;1320 if count > 1 {1321 return Err(TokenOwnerError::MultipleOwners);1322 }1323 owner = Some(key);1324 }1325 owner.ok_or(TokenOwnerError::NotFound)1326 }13271328 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1329 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1330 }13311332 pub fn set_collection_properties(1333 collection: &RefungibleHandle<T>,1334 sender: &T::CrossAccountId,1335 properties: Vec<Property>,1336 ) -> DispatchResult {1337 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())1338 }13391340 pub fn delete_collection_properties(1341 collection: &RefungibleHandle<T>,1342 sender: &T::CrossAccountId,1343 property_keys: Vec<PropertyKey>,1344 ) -> DispatchResult {1345 <PalletCommon<T>>::delete_collection_properties(1346 collection,1347 sender,1348 property_keys.into_iter(),1349 )1350 }13511352 pub fn set_token_property_permissions(1353 collection: &RefungibleHandle<T>,1354 sender: &T::CrossAccountId,1355 property_permissions: Vec<PropertyKeyPermission>,1356 ) -> DispatchResult {1357 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1358 }13591360 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1361 <PalletCommon<T>>::property_permissions(collection_id)1362 }13631364 pub fn set_scoped_token_property_permissions(1365 collection: &RefungibleHandle<T>,1366 sender: &T::CrossAccountId,1367 scope: PropertyScope,1368 property_permissions: Vec<PropertyKeyPermission>,1369 ) -> DispatchResult {1370 <PalletCommon<T>>::set_scoped_token_property_permissions(1371 collection,1372 sender,1373 scope,1374 property_permissions,1375 )1376 }13771378 1379 1380 1381 1382 1383 1384 pub fn token_owners(1385 collection_id: CollectionId,1386 token: TokenId,1387 ) -> Option<Vec<T::CrossAccountId>> {1388 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1389 .map(|(owner, _amount)| owner)1390 .take(10)1391 .collect();13921393 if res.is_empty() {1394 None1395 } else {1396 Some(res)1397 }1398 }13991400 1401 1402 1403 1404 1405 1406 pub fn set_allowance_for_all(1407 collection: &RefungibleHandle<T>,1408 owner: &T::CrossAccountId,1409 spender: &T::CrossAccountId,1410 approve: bool,1411 ) -> DispatchResult {1412 <PalletCommon<T>>::set_allowance_for_all(1413 collection,1414 owner,1415 spender,1416 approve,1417 || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),1418 ERC721Events::ApprovalForAll {1419 owner: *owner.as_eth(),1420 operator: *spender.as_eth(),1421 approved: approve,1422 }1423 .to_log(collection_id_to_address(collection.id)),1424 )1425 }14261427 1428 pub fn allowance_for_all(1429 collection: &RefungibleHandle<T>,1430 owner: &T::CrossAccountId,1431 spender: &T::CrossAccountId,1432 ) -> bool {1433 <CollectionAllowance<T>>::get((collection.id, owner, spender))1434 }14351436 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1437 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1438 properties.recompute_consumed_space();1439 });14401441 Ok(())1442 }1443}