12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use evm_coder::ToLog;96use frame_support::{97 BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,98};99use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};100use pallet_evm_coder_substrate::WithRecorder;101use pallet_common::{102 CollectionHandle, CommonCollectionOperations,103 dispatch::CollectionDispatch,104 erc::static_property::{key, property_value_from_bytes},105 Error as CommonError,106 eth::collection_id_to_address,107 Event as CommonEvent, Pallet as PalletCommon,108};109use pallet_structure::Pallet as PalletStructure;110use scale_info::TypeInfo;111use sp_core::H160;112use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};113use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};114use up_data_structs::{115 AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec,116 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,117 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,118 PropertyScope, PropertyValue, TokenId, TrySetProperty,119};120use frame_support::BoundedBTreeMap;121use derivative::Derivative;122123pub use pallet::*;124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod common;127pub mod erc;128pub mod erc_token;129pub mod weights;130131#[derive(Derivative, Clone)]132pub struct CreateItemData<CrossAccountId> {133 #[derivative(Debug(format_with = "bounded::map_debug"))]134 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,135 #[derivative(Debug(format_with = "bounded::vec_debug"))]136 pub properties: CollectionPropertiesVec,137}138pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;139140141142#[struct_versioning::versioned(version = 2, upper)]143#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]144#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]145pub struct ItemData {146 pub const_data: BoundedVec<u8, CustomDataLimit>,147148 #[version(..2)]149 pub variable_data: BoundedVec<u8, CustomDataLimit>,150}151152#[frame_support::pallet]153pub mod pallet {154 use super::*;155 use frame_support::{156 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,157 traits::StorageVersion,158 };159 use frame_system::pallet_prelude::*;160 use up_data_structs::{CollectionId, TokenId};161 use super::weights::WeightInfo;162163 #[pallet::error]164 pub enum Error<T> {165 166 NotRefungibleDataUsedToMintFungibleCollectionToken,167 168 WrongRefungiblePieces,169 170 RepartitionWhileNotOwningAllPieces,171 172 RefungibleDisallowsNesting,173 174 SettingPropertiesNotAllowed,175 }176177 #[pallet::config]178 pub trait Config:179 frame_system::Config + pallet_common::Config + pallet_structure::Config180 {181 type WeightInfo: WeightInfo;182 }183184 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);185186 #[pallet::pallet]187 #[pallet::storage_version(STORAGE_VERSION)]188 #[pallet::generate_store(pub(super) trait Store)]189 pub struct Pallet<T>(_);190191 192 #[pallet::storage]193 pub type TokensMinted<T: Config> =194 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;195196 197 #[pallet::storage]198 pub type TokensBurnt<T: Config> =199 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;200201 202 203 #[pallet::storage]204 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]205 pub type TokenData<T: Config> = StorageNMap<206 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),207 Value = ItemData,208 QueryKind = ValueQuery,209 >;210211 212 #[pallet::storage]213 #[pallet::getter(fn token_properties)]214 pub type TokenProperties<T: Config> = StorageNMap<215 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),216 Value = up_data_structs::Properties,217 QueryKind = ValueQuery,218 OnEmpty = up_data_structs::TokenProperties,219 >;220221 222 #[pallet::storage]223 pub type TotalSupply<T: Config> = StorageNMap<224 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),225 Value = u128,226 QueryKind = ValueQuery,227 >;228229 230 #[pallet::storage]231 pub type Owned<T: Config> = StorageNMap<232 Key = (233 Key<Twox64Concat, CollectionId>,234 Key<Blake2_128Concat, T::CrossAccountId>,235 Key<Twox64Concat, TokenId>,236 ),237 Value = bool,238 QueryKind = ValueQuery,239 >;240241 242 #[pallet::storage]243 pub type AccountBalance<T: Config> = StorageNMap<244 Key = (245 Key<Twox64Concat, CollectionId>,246 247 Key<Blake2_128Concat, T::CrossAccountId>,248 ),249 Value = u32,250 QueryKind = ValueQuery,251 >;252253 254 #[pallet::storage]255 pub type Balance<T: Config> = StorageNMap<256 Key = (257 Key<Twox64Concat, CollectionId>,258 Key<Twox64Concat, TokenId>,259 260 Key<Blake2_128Concat, T::CrossAccountId>,261 ),262 Value = u128,263 QueryKind = ValueQuery,264 >;265266 267 #[pallet::storage]268 pub type Allowance<T: Config> = StorageNMap<269 Key = (270 Key<Twox64Concat, CollectionId>,271 Key<Twox64Concat, TokenId>,272 273 Key<Blake2_128, T::CrossAccountId>,274 275 Key<Blake2_128Concat, T::CrossAccountId>,276 ),277 Value = u128,278 QueryKind = ValueQuery,279 >;280281 #[pallet::hooks]282 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {283 fn on_runtime_upgrade() -> Weight {284 let storage_version = StorageVersion::get::<Pallet<T>>();285 if storage_version < StorageVersion::new(2) {286 <TokenData<T>>::remove_all(None);287 }288 StorageVersion::new(2).put::<Pallet<T>>();289290 0291 }292 }293}294295pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);296impl<T: Config> RefungibleHandle<T> {297 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {298 Self(inner)299 }300 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {301 self.0302 }303 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {304 &mut self.0305 }306}307308impl<T: Config> Deref for RefungibleHandle<T> {309 type Target = pallet_common::CollectionHandle<T>;310311 fn deref(&self) -> &Self::Target {312 &self.0313 }314}315316impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {317 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {318 self.0.recorder()319 }320 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {321 self.0.into_recorder()322 }323}324325impl<T: Config> Pallet<T> {326 327 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {328 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)329 }330331 332 333 334 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {335 <TotalSupply<T>>::contains_key((collection.id, token))336 }337338 pub fn set_scoped_token_property(339 collection_id: CollectionId,340 token_id: TokenId,341 scope: PropertyScope,342 property: Property,343 ) -> DispatchResult {344 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {345 properties.try_scoped_set(scope, property.key, property.value)346 })347 .map_err(<CommonError<T>>::from)?;348349 Ok(())350 }351352 pub fn set_scoped_token_properties(353 collection_id: CollectionId,354 token_id: TokenId,355 scope: PropertyScope,356 properties: impl Iterator<Item = Property>,357 ) -> DispatchResult {358 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {359 stored_properties.try_scoped_set_from_iter(scope, properties)360 })361 .map_err(<CommonError<T>>::from)?;362363 Ok(())364 }365}366367368impl<T: Config> Pallet<T> {369 370 371 372 373 374 pub fn init_collection(375 owner: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 ) -> Result<CollectionId, DispatchError> {378 <PalletCommon<T>>::init_collection(owner, data, false)379 }380381 382 383 384 385 pub fn destroy_collection(386 collection: RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 ) -> DispatchResult {389 let id = collection.id;390391 if Self::collection_has_tokens(id) {392 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());393 }394395 396397 PalletCommon::destroy_collection(collection.0, sender)?;398399 <TokensMinted<T>>::remove(id);400 <TokensBurnt<T>>::remove(id);401 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);402 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);403 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);404 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);405 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);406 Ok(())407 }408409 fn collection_has_tokens(collection_id: CollectionId) -> bool {410 <TotalSupply<T>>::iter_prefix((collection_id,))411 .next()412 .is_some()413 }414415 pub fn burn_token_unchecked(416 collection: &RefungibleHandle<T>,417 owner: &T::CrossAccountId,418 token_id: TokenId,419 ) -> DispatchResult {420 let burnt = <TokensBurnt<T>>::get(collection.id)421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423424 <TokensBurnt<T>>::insert(collection.id, burnt);425 <TokenProperties<T>>::remove((collection.id, token_id));426 <TotalSupply<T>>::remove((collection.id, token_id));427 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);428 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);429 <PalletEvm<T>>::deposit_log(430 ERC721Events::Transfer {431 from: *owner.as_eth(),432 to: H160::default(),433 token_id: token_id.into(),434 }435 .to_log(collection_id_to_address(collection.id)),436 );437 Ok(())438 }439440 441 442 443 444 445 446 447 448 449 450 451 pub fn burn(452 collection: &RefungibleHandle<T>,453 owner: &T::CrossAccountId,454 token: TokenId,455 amount: u128,456 ) -> DispatchResult {457 let total_supply = <TotalSupply<T>>::get((collection.id, token))458 .checked_sub(amount)459 .ok_or(<CommonError<T>>::TokenValueTooLow)?;460461 462 if total_supply == 0 {463 464 ensure!(465 <Balance<T>>::get((collection.id, token, owner)) == amount,466 <CommonError<T>>::TokenValueTooLow467 );468 let account_balance = <AccountBalance<T>>::get((collection.id, owner))469 .checked_sub(1)470 471 .ok_or(ArithmeticError::Underflow)?;472473 474475 <Owned<T>>::remove((collection.id, owner, token));476 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);477 <AccountBalance<T>>::insert((collection.id, owner), account_balance);478 Self::burn_token_unchecked(collection, owner, token)?;479 <PalletEvm<T>>::deposit_log(480 ERC20Events::Transfer {481 from: *owner.as_eth(),482 to: H160::default(),483 value: amount.into(),484 }485 .to_log(collection_id_to_address(collection.id)),486 );487 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(488 collection.id,489 token,490 owner.clone(),491 amount,492 ));493 return Ok(());494 }495496 let balance = <Balance<T>>::get((collection.id, token, owner))497 .checked_sub(amount)498 .ok_or(<CommonError<T>>::TokenValueTooLow)?;499 let account_balance = if balance == 0 {500 <AccountBalance<T>>::get((collection.id, owner))501 .checked_sub(1)502 503 .ok_or(ArithmeticError::Underflow)?504 } else {505 0506 };507508 509510 if balance == 0 {511 <Owned<T>>::remove((collection.id, owner, token));512 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);513 <Balance<T>>::remove((collection.id, token, owner));514 <AccountBalance<T>>::insert((collection.id, owner), account_balance);515516 if let Some(user) = Self::token_owner(collection.id, token) {517 <PalletEvm<T>>::deposit_log(518 ERC721Events::Transfer {519 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,520 to: *user.as_eth(),521 token_id: token.into(),522 }523 .to_log(collection_id_to_address(collection.id)),524 );525 }526 } else {527 <Balance<T>>::insert((collection.id, token, owner), balance);528 }529 <TotalSupply<T>>::insert((collection.id, token), total_supply);530531 <PalletEvm<T>>::deposit_log(532 ERC20Events::Transfer {533 from: *owner.as_eth(),534 to: H160::default(),535 value: amount.into(),536 }537 .to_log(T::EvmTokenAddressMapping::token_to_address(538 collection.id,539 token,540 )),541 );542 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(543 collection.id,544 token,545 owner.clone(),546 amount,547 ));548 Ok(())549 }550551 #[transactional]552 fn modify_token_properties(553 collection: &RefungibleHandle<T>,554 sender: &T::CrossAccountId,555 token_id: TokenId,556 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,557 is_token_create: bool,558 nesting_budget: &dyn Budget,559 ) -> DispatchResult {560 let is_collection_admin = || collection.is_owner_or_admin(sender);561 let is_token_owner = || -> Result<bool, DispatchError> {562 let balance = collection.balance(sender.clone(), token_id);563 let total_pieces: u128 =564 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);565 if balance != total_pieces {566 return Ok(false);567 }568569 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(570 sender.clone(),571 collection.id,572 token_id,573 None,574 nesting_budget,575 )?;576577 Ok(is_bundle_owner)578 };579580 for (key, value) in properties {581 let permission = <PalletCommon<T>>::property_permissions(collection.id)582 .get(&key)583 .cloned()584 .unwrap_or_else(PropertyPermission::none);585586 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))587 .get(&key)588 .is_some();589590 match permission {591 PropertyPermission { mutable: false, .. } if is_property_exists => {592 return Err(<CommonError<T>>::NoPermission.into());593 }594595 PropertyPermission {596 collection_admin,597 token_owner,598 ..599 } => {600 601 let is_token_create =602 is_token_create && (collection_admin || token_owner) && value.is_some();603 if !(is_token_create604 || (collection_admin && is_collection_admin())605 || (token_owner && is_token_owner()?))606 {607 fail!(<CommonError<T>>::NoPermission);608 }609 }610 }611612 match value {613 Some(value) => {614 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {615 properties.try_set(key.clone(), value)616 })617 .map_err(<CommonError<T>>::from)?;618619 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(620 collection.id,621 token_id,622 key,623 ));624 }625 None => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.remove(&key)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(632 collection.id,633 token_id,634 key,635 ));636 }637 }638 }639640 Ok(())641 }642643 pub fn set_token_properties(644 collection: &RefungibleHandle<T>,645 sender: &T::CrossAccountId,646 token_id: TokenId,647 properties: impl Iterator<Item = Property>,648 is_token_create: bool,649 nesting_budget: &dyn Budget,650 ) -> DispatchResult {651 Self::modify_token_properties(652 collection,653 sender,654 token_id,655 properties.map(|p| (p.key, Some(p.value))),656 is_token_create,657 nesting_budget,658 )659 }660661 pub fn set_token_property(662 collection: &RefungibleHandle<T>,663 sender: &T::CrossAccountId,664 token_id: TokenId,665 property: Property,666 nesting_budget: &dyn Budget,667 ) -> DispatchResult {668 let is_token_create = false;669670 Self::set_token_properties(671 collection,672 sender,673 token_id,674 [property].into_iter(),675 is_token_create,676 nesting_budget,677 )678 }679680 pub fn delete_token_properties(681 collection: &RefungibleHandle<T>,682 sender: &T::CrossAccountId,683 token_id: TokenId,684 property_keys: impl Iterator<Item = PropertyKey>,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 let is_token_create = false;688689 Self::modify_token_properties(690 collection,691 sender,692 token_id,693 property_keys.into_iter().map(|key| (key, None)),694 is_token_create,695 nesting_budget,696 )697 }698699 pub fn delete_token_property(700 collection: &RefungibleHandle<T>,701 sender: &T::CrossAccountId,702 token_id: TokenId,703 property_key: PropertyKey,704 nesting_budget: &dyn Budget,705 ) -> DispatchResult {706 Self::delete_token_properties(707 collection,708 sender,709 token_id,710 [property_key].into_iter(),711 nesting_budget,712 )713 }714715 716 717 718 719 720 721 722 723 724 pub fn transfer(725 collection: &RefungibleHandle<T>,726 from: &T::CrossAccountId,727 to: &T::CrossAccountId,728 token: TokenId,729 amount: u128,730 nesting_budget: &dyn Budget,731 ) -> DispatchResult {732 ensure!(733 collection.limits.transfers_enabled(),734 <CommonError<T>>::TransferNotAllowed735 );736737 if collection.permissions.access() == AccessMode::AllowList {738 collection.check_allowlist(from)?;739 collection.check_allowlist(to)?;740 }741 <PalletCommon<T>>::ensure_correct_receiver(to)?;742743 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));744 let updated_balance_from = initial_balance_from745 .checked_sub(amount)746 .ok_or(<CommonError<T>>::TokenValueTooLow)?;747 let mut create_target = false;748 let from_to_differ = from != to;749 let updated_balance_to = if from != to {750 let old_balance = <Balance<T>>::get((collection.id, token, to));751 if old_balance == 0 {752 create_target = true;753 }754 Some(755 old_balance756 .checked_add(amount)757 .ok_or(ArithmeticError::Overflow)?,758 )759 } else {760 None761 };762763 let account_balance_from = if updated_balance_from == 0 {764 Some(765 <AccountBalance<T>>::get((collection.id, from))766 .checked_sub(1)767 768 .ok_or(ArithmeticError::Underflow)?,769 )770 } else {771 None772 };773 774 775 let account_balance_to = if create_target && from_to_differ {776 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))777 .checked_add(1)778 .ok_or(ArithmeticError::Overflow)?;779 ensure!(780 account_balance_to < collection.limits.account_token_ownership_limit(),781 <CommonError<T>>::AccountTokenLimitExceeded,782 );783784 Some(account_balance_to)785 } else {786 None787 };788789 790791 <PalletStructure<T>>::nest_if_sent_to_token(792 from.clone(),793 to,794 collection.id,795 token,796 nesting_budget,797 )?;798799 if let Some(updated_balance_to) = updated_balance_to {800 801 if updated_balance_from == 0 {802 <Balance<T>>::remove((collection.id, token, from));803 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);804 } else {805 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);806 }807 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);808 if let Some(account_balance_from) = account_balance_from {809 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);810 <Owned<T>>::remove((collection.id, from, token));811 }812 if let Some(account_balance_to) = account_balance_to {813 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);814 <Owned<T>>::insert((collection.id, to, token), true);815 }816 }817818 <PalletEvm<T>>::deposit_log(819 ERC20Events::Transfer {820 from: *from.as_eth(),821 to: *to.as_eth(),822 value: amount.into(),823 }824 .to_log(T::EvmTokenAddressMapping::token_to_address(825 collection.id,826 token,827 )),828 );829830 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(831 collection.id,832 token,833 from.clone(),834 to.clone(),835 amount,836 ));837838 let total_supply = <TotalSupply<T>>::get((collection.id, token));839840 if amount == total_supply {841 842 <PalletEvm<T>>::deposit_log(843 ERC721Events::Transfer {844 from: *from.as_eth(),845 to: *to.as_eth(),846 token_id: token.into(),847 }848 .to_log(collection_id_to_address(collection.id)),849 );850 } else if let Some(updated_balance_to) = updated_balance_to {851 852 853 if initial_balance_from == total_supply {854 855 856 <PalletEvm<T>>::deposit_log(857 ERC721Events::Transfer {858 from: *from.as_eth(),859 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,860 token_id: token.into(),861 }862 .to_log(collection_id_to_address(collection.id)),863 );864 } else if updated_balance_to == total_supply {865 866 <PalletEvm<T>>::deposit_log(867 ERC721Events::Transfer {868 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,869 to: *to.as_eth(),870 token_id: token.into(),871 }872 .to_log(collection_id_to_address(collection.id)),873 );874 }875 }876877 Ok(())878 }879880 881 882 883 884 885 pub fn create_multiple_items(886 collection: &RefungibleHandle<T>,887 sender: &T::CrossAccountId,888 data: Vec<CreateItemData<T::CrossAccountId>>,889 nesting_budget: &dyn Budget,890 ) -> DispatchResult {891 if !collection.is_owner_or_admin(sender) {892 ensure!(893 collection.permissions.mint_mode(),894 <CommonError<T>>::PublicMintingNotAllowed895 );896 collection.check_allowlist(sender)?;897898 for item in data.iter() {899 for user in item.users.keys() {900 collection.check_allowlist(user)?;901 }902 }903 }904905 for item in data.iter() {906 for (owner, _) in item.users.iter() {907 <PalletCommon<T>>::ensure_correct_receiver(owner)?;908 }909 }910911 912 let totals = data913 .iter()914 .map(|data| {915 Ok(data916 .users917 .iter()918 .map(|u| u.1)919 .try_fold(0u128, |acc, v| acc.checked_add(*v))920 .ok_or(ArithmeticError::Overflow)?)921 })922 .collect::<Result<Vec<_>, DispatchError>>()?;923 for total in &totals {924 ensure!(925 *total <= MAX_REFUNGIBLE_PIECES,926 <Error<T>>::WrongRefungiblePieces927 );928 }929930 let first_token_id = <TokensMinted<T>>::get(collection.id);931 let tokens_minted = first_token_id932 .checked_add(data.len() as u32)933 .ok_or(ArithmeticError::Overflow)?;934 ensure!(935 tokens_minted < collection.limits.token_limit(),936 <CommonError<T>>::CollectionTokenLimitExceeded937 );938939 let mut balances = BTreeMap::new();940 for data in &data {941 for owner in data.users.keys() {942 let balance = balances943 .entry(owner)944 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));945 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;946947 ensure!(948 *balance <= collection.limits.account_token_ownership_limit(),949 <CommonError<T>>::AccountTokenLimitExceeded,950 );951 }952 }953954 for (i, token) in data.iter().enumerate() {955 let token_id = TokenId(first_token_id + i as u32 + 1);956 for (to, _) in token.users.iter() {957 <PalletStructure<T>>::check_nesting(958 sender.clone(),959 to,960 collection.id,961 token_id,962 nesting_budget,963 )?;964 }965 }966967 968969 with_transaction(|| {970 for (i, data) in data.iter().enumerate() {971 let token_id = first_token_id + i as u32 + 1;972 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);973974 for (user, amount) in data.users.iter() {975 if *amount == 0 {976 continue;977 }978 <Balance<T>>::insert((collection.id, token_id, &user), amount);979 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);980 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(981 user,982 collection.id,983 TokenId(token_id),984 );985 }986987 if let Err(e) = Self::set_token_properties(988 collection,989 sender,990 TokenId(token_id),991 data.properties.clone().into_iter(),992 true,993 nesting_budget,994 ) {995 return TransactionOutcome::Rollback(Err(e));996 }997 }998 TransactionOutcome::Commit(Ok(()))999 })?;10001001 <TokensMinted<T>>::insert(collection.id, tokens_minted);10021003 for (account, balance) in balances {1004 <AccountBalance<T>>::insert((collection.id, account), balance);1005 }10061007 for (i, token) in data.into_iter().enumerate() {1008 let token_id = first_token_id + i as u32 + 1;10091010 let receivers = token1011 .users1012 .into_iter()1013 .filter(|(_, amount)| *amount > 0)1014 .collect::<Vec<_>>();10151016 if let [(user, _)] = receivers.as_slice() {1017 1018 <PalletEvm<T>>::deposit_log(1019 ERC721Events::Transfer {1020 from: H160::default(),1021 to: *user.as_eth(),1022 token_id: token_id.into(),1023 }1024 .to_log(collection_id_to_address(collection.id)),1025 );1026 } else if let [_, ..] = receivers.as_slice() {1027 1028 <PalletEvm<T>>::deposit_log(1029 ERC721Events::Transfer {1030 from: H160::default(),1031 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1032 token_id: token_id.into(),1033 }1034 .to_log(collection_id_to_address(collection.id)),1035 );1036 }10371038 for (user, amount) in receivers.into_iter() {1039 <PalletEvm<T>>::deposit_log(1040 ERC20Events::Transfer {1041 from: H160::default(),1042 to: *user.as_eth(),1043 value: amount.into(),1044 }1045 .to_log(T::EvmTokenAddressMapping::token_to_address(1046 collection.id,1047 TokenId(token_id),1048 )),1049 );1050 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1051 collection.id,1052 TokenId(token_id),1053 user,1054 amount,1055 ));1056 }1057 }1058 Ok(())1059 }10601061 pub fn set_allowance_unchecked(1062 collection: &RefungibleHandle<T>,1063 sender: &T::CrossAccountId,1064 spender: &T::CrossAccountId,1065 token: TokenId,1066 amount: u128,1067 ) {1068 if amount == 0 {1069 <Allowance<T>>::remove((collection.id, token, sender, spender));1070 } else {1071 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1072 }10731074 <PalletEvm<T>>::deposit_log(1075 ERC20Events::Approval {1076 owner: *sender.as_eth(),1077 spender: *spender.as_eth(),1078 value: amount.into(),1079 }1080 .to_log(T::EvmTokenAddressMapping::token_to_address(1081 collection.id,1082 token,1083 )),1084 );1085 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1086 collection.id,1087 token,1088 sender.clone(),1089 spender.clone(),1090 amount,1091 ))1092 }10931094 1095 1096 1097 pub fn set_allowance(1098 collection: &RefungibleHandle<T>,1099 sender: &T::CrossAccountId,1100 spender: &T::CrossAccountId,1101 token: TokenId,1102 amount: u128,1103 ) -> DispatchResult {1104 if collection.permissions.access() == AccessMode::AllowList {1105 collection.check_allowlist(sender)?;1106 collection.check_allowlist(spender)?;1107 }11081109 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11101111 if <Balance<T>>::get((collection.id, token, sender)) < amount {1112 ensure!(1113 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1114 <CommonError<T>>::CantApproveMoreThanOwned1115 );1116 }11171118 11191120 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1121 Ok(())1122 }11231124 1125 fn check_allowed(1126 collection: &RefungibleHandle<T>,1127 spender: &T::CrossAccountId,1128 from: &T::CrossAccountId,1129 token: TokenId,1130 amount: u128,1131 nesting_budget: &dyn Budget,1132 ) -> Result<Option<u128>, DispatchError> {1133 if spender.conv_eq(from) {1134 return Ok(None);1135 }1136 if collection.permissions.access() == AccessMode::AllowList {1137 1138 collection.check_allowlist(spender)?;1139 }1140 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1141 1142 ensure!(1143 <PalletStructure<T>>::check_indirectly_owned(1144 spender.clone(),1145 source.0,1146 source.1,1147 None,1148 nesting_budget1149 )?,1150 <CommonError<T>>::ApprovedValueTooLow,1151 );1152 return Ok(None);1153 }1154 let allowance =1155 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1156 if allowance.is_none() {1157 ensure!(1158 collection.ignores_allowance(spender),1159 <CommonError<T>>::ApprovedValueTooLow1160 );1161 }1162 Ok(allowance)1163 }11641165 1166 1167 1168 1169 1170 1171 pub fn transfer_from(1172 collection: &RefungibleHandle<T>,1173 spender: &T::CrossAccountId,1174 from: &T::CrossAccountId,1175 to: &T::CrossAccountId,1176 token: TokenId,1177 amount: u128,1178 nesting_budget: &dyn Budget,1179 ) -> DispatchResult {1180 let allowance =1181 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11821183 11841185 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1186 if let Some(allowance) = allowance {1187 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1188 }1189 Ok(())1190 }11911192 1193 1194 1195 1196 1197 1198 pub fn burn_from(1199 collection: &RefungibleHandle<T>,1200 spender: &T::CrossAccountId,1201 from: &T::CrossAccountId,1202 token: TokenId,1203 amount: u128,1204 nesting_budget: &dyn Budget,1205 ) -> DispatchResult {1206 let allowance =1207 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12081209 12101211 Self::burn(collection, from, token, amount)?;1212 if let Some(allowance) = allowance {1213 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1214 }1215 Ok(())1216 }12171218 1219 1220 1221 1222 1223 1224 1225 pub fn create_item(1226 collection: &RefungibleHandle<T>,1227 sender: &T::CrossAccountId,1228 data: CreateItemData<T::CrossAccountId>,1229 nesting_budget: &dyn Budget,1230 ) -> DispatchResult {1231 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1232 }12331234 1235 1236 1237 1238 1239 1240 1241 pub fn repartition(1242 collection: &RefungibleHandle<T>,1243 owner: &T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 ) -> DispatchResult {1247 ensure!(1248 amount <= MAX_REFUNGIBLE_PIECES,1249 <Error<T>>::WrongRefungiblePieces1250 );1251 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1252 1253 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1254 let balance = <Balance<T>>::get((collection.id, token, owner));1255 ensure!(1256 total_pieces == balance,1257 <Error<T>>::RepartitionWhileNotOwningAllPieces1258 );12591260 <Balance<T>>::insert((collection.id, token, owner), amount);1261 <TotalSupply<T>>::insert((collection.id, token), amount);12621263 if amount > total_pieces {1264 let mint_amount = amount - total_pieces;1265 <PalletEvm<T>>::deposit_log(1266 ERC20Events::Transfer {1267 from: H160::default(),1268 to: *owner.as_eth(),1269 value: mint_amount.into(),1270 }1271 .to_log(T::EvmTokenAddressMapping::token_to_address(1272 collection.id,1273 token,1274 )),1275 );1276 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1277 collection.id,1278 token,1279 owner.clone(),1280 mint_amount,1281 ));1282 } else if total_pieces > amount {1283 let burn_amount = total_pieces - amount;1284 <PalletEvm<T>>::deposit_log(1285 ERC20Events::Transfer {1286 from: *owner.as_eth(),1287 to: H160::default(),1288 value: burn_amount.into(),1289 }1290 .to_log(T::EvmTokenAddressMapping::token_to_address(1291 collection.id,1292 token,1293 )),1294 );1295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1296 collection.id,1297 token,1298 owner.clone(),1299 burn_amount,1300 ));1301 }13021303 Ok(())1304 }13051306 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1307 let mut owner = None;1308 let mut count = 0;1309 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1310 count += 1;1311 if count > 1 {1312 return None;1313 }1314 owner = Some(key);1315 }1316 owner1317 }13181319 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1320 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1321 }13221323 pub fn set_collection_properties(1324 collection: &RefungibleHandle<T>,1325 sender: &T::CrossAccountId,1326 properties: Vec<Property>,1327 ) -> DispatchResult {1328 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1329 }13301331 pub fn delete_collection_properties(1332 collection: &RefungibleHandle<T>,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResult {1336 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1337 }13381339 pub fn set_token_property_permissions(1340 collection: &RefungibleHandle<T>,1341 sender: &T::CrossAccountId,1342 property_permissions: Vec<PropertyKeyPermission>,1343 ) -> DispatchResult {1344 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1345 }13461347 pub fn set_scoped_token_property_permissions(1348 collection: &RefungibleHandle<T>,1349 sender: &T::CrossAccountId,1350 scope: PropertyScope,1351 property_permissions: Vec<PropertyKeyPermission>,1352 ) -> DispatchResult {1353 <PalletCommon<T>>::set_scoped_token_property_permissions(1354 collection,1355 sender,1356 scope,1357 property_permissions,1358 )1359 }13601361 1362 1363 1364 1365 1366 1367 pub fn token_owners(1368 collection_id: CollectionId,1369 token: TokenId,1370 ) -> Option<Vec<T::CrossAccountId>> {1371 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1372 .map(|(owner, _amount)| owner)1373 .take(10)1374 .collect();13751376 if res.is_empty() {1377 None1378 } else {1379 Some(res)1380 }1381 }1382}