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::{BoundedVec, ensure, fail, storage::with_transaction, transactional};97use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};98use pallet_evm_coder_substrate::WithRecorder;99use pallet_common::{100 CommonCollectionOperations, Error as CommonError, Event as CommonEvent,101 eth::collection_id_to_address, Pallet as PalletCommon,102};103use pallet_structure::Pallet as PalletStructure;104use scale_info::TypeInfo;105use sp_core::H160;106use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};107use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};108use up_data_structs::{109 AccessMode, budget::Budget, CollectionId, CreateCollectionData, CreateRefungibleExData,110 CustomDataLimit, mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, TokenId, Property,111 PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,112 TrySetProperty,113};114115pub use pallet::*;116#[cfg(feature = "runtime-benchmarks")]117pub mod benchmarking;118pub mod common;119pub mod erc;120pub mod erc_token;121pub mod weights;122123pub type CreateItemData<T> =124 CreateRefungibleExData<<T as pallet_evm::account::Config>::CrossAccountId>;125pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;126127128129#[struct_versioning::versioned(version = 2, upper)]130#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]131#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]132pub struct ItemData {133 pub const_data: BoundedVec<u8, CustomDataLimit>,134135 #[version(..2)]136 pub variable_data: BoundedVec<u8, CustomDataLimit>,137}138139#[frame_support::pallet]140pub mod pallet {141 use super::*;142 use frame_support::{143 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,144 traits::StorageVersion,145 };146 use frame_system::pallet_prelude::*;147 use up_data_structs::{CollectionId, TokenId};148 use super::weights::WeightInfo;149150 #[pallet::error]151 pub enum Error<T> {152 153 NotRefungibleDataUsedToMintFungibleCollectionToken,154 155 WrongRefungiblePieces,156 157 RepartitionWhileNotOwningAllPieces,158 159 RefungibleDisallowsNesting,160 161 SettingPropertiesNotAllowed,162 }163164 #[pallet::config]165 pub trait Config:166 frame_system::Config + pallet_common::Config + pallet_structure::Config167 {168 type WeightInfo: WeightInfo;169 }170171 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);172173 #[pallet::pallet]174 #[pallet::storage_version(STORAGE_VERSION)]175 #[pallet::generate_store(pub(super) trait Store)]176 pub struct Pallet<T>(_);177178 179 #[pallet::storage]180 pub type TokensMinted<T: Config> =181 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;182183 184 #[pallet::storage]185 pub type TokensBurnt<T: Config> =186 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;187188 189 190 #[pallet::storage]191 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]192 pub type TokenData<T: Config> = StorageNMap<193 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),194 Value = ItemData,195 QueryKind = ValueQuery,196 >;197198 199 #[pallet::storage]200 #[pallet::getter(fn token_properties)]201 pub type TokenProperties<T: Config> = StorageNMap<202 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),203 Value = up_data_structs::Properties,204 QueryKind = ValueQuery,205 OnEmpty = up_data_structs::TokenProperties,206 >;207208 209 #[pallet::storage]210 pub type TotalSupply<T: Config> = StorageNMap<211 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),212 Value = u128,213 QueryKind = ValueQuery,214 >;215216 217 #[pallet::storage]218 pub type Owned<T: Config> = StorageNMap<219 Key = (220 Key<Twox64Concat, CollectionId>,221 Key<Blake2_128Concat, T::CrossAccountId>,222 Key<Twox64Concat, TokenId>,223 ),224 Value = bool,225 QueryKind = ValueQuery,226 >;227228 229 #[pallet::storage]230 pub type AccountBalance<T: Config> = StorageNMap<231 Key = (232 Key<Twox64Concat, CollectionId>,233 234 Key<Blake2_128Concat, T::CrossAccountId>,235 ),236 Value = u32,237 QueryKind = ValueQuery,238 >;239240 241 #[pallet::storage]242 pub type Balance<T: Config> = StorageNMap<243 Key = (244 Key<Twox64Concat, CollectionId>,245 Key<Twox64Concat, TokenId>,246 247 Key<Blake2_128Concat, T::CrossAccountId>,248 ),249 Value = u128,250 QueryKind = ValueQuery,251 >;252253 254 #[pallet::storage]255 pub type Allowance<T: Config> = StorageNMap<256 Key = (257 Key<Twox64Concat, CollectionId>,258 Key<Twox64Concat, TokenId>,259 260 Key<Blake2_128, T::CrossAccountId>,261 262 Key<Blake2_128Concat, T::CrossAccountId>,263 ),264 Value = u128,265 QueryKind = ValueQuery,266 >;267268 #[pallet::hooks]269 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {270 fn on_runtime_upgrade() -> Weight {271 let storage_version = StorageVersion::get::<Pallet<T>>();272 if storage_version < StorageVersion::new(2) {273 <TokenData<T>>::remove_all(None);274 }275 StorageVersion::new(2).put::<Pallet<T>>();276277 0278 }279 }280}281282pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);283impl<T: Config> RefungibleHandle<T> {284 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {285 Self(inner)286 }287 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {288 self.0289 }290 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {291 &mut self.0292 }293}294295impl<T: Config> Deref for RefungibleHandle<T> {296 type Target = pallet_common::CollectionHandle<T>;297298 fn deref(&self) -> &Self::Target {299 &self.0300 }301}302303impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {304 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {305 self.0.recorder()306 }307 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {308 self.0.into_recorder()309 }310}311312impl<T: Config> Pallet<T> {313 314 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {315 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)316 }317318 319 320 321 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {322 <TotalSupply<T>>::contains_key((collection.id, token))323 }324325 pub fn set_scoped_token_property(326 collection_id: CollectionId,327 token_id: TokenId,328 scope: PropertyScope,329 property: Property,330 ) -> DispatchResult {331 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {332 properties.try_scoped_set(scope, property.key, property.value)333 })334 .map_err(<CommonError<T>>::from)?;335336 Ok(())337 }338339 pub fn set_scoped_token_properties(340 collection_id: CollectionId,341 token_id: TokenId,342 scope: PropertyScope,343 properties: impl Iterator<Item = Property>,344 ) -> DispatchResult {345 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {346 stored_properties.try_scoped_set_from_iter(scope, properties)347 })348 .map_err(<CommonError<T>>::from)?;349350 Ok(())351 }352}353354355impl<T: Config> Pallet<T> {356 357 358 359 360 361 pub fn init_collection(362 owner: T::CrossAccountId,363 data: CreateCollectionData<T::AccountId>,364 ) -> Result<CollectionId, DispatchError> {365 <PalletCommon<T>>::init_collection(owner, data, false)366 }367368 369 370 371 372 pub fn destroy_collection(373 collection: RefungibleHandle<T>,374 sender: &T::CrossAccountId,375 ) -> DispatchResult {376 let id = collection.id;377378 if Self::collection_has_tokens(id) {379 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());380 }381382 383384 PalletCommon::destroy_collection(collection.0, sender)?;385386 <TokensMinted<T>>::remove(id);387 <TokensBurnt<T>>::remove(id);388 <TotalSupply<T>>::remove_prefix((id,), None);389 <Balance<T>>::remove_prefix((id,), None);390 <Allowance<T>>::remove_prefix((id,), None);391 <Owned<T>>::remove_prefix((id,), None);392 <AccountBalance<T>>::remove_prefix((id,), None);393 Ok(())394 }395396 fn collection_has_tokens(collection_id: CollectionId) -> bool {397 <TotalSupply<T>>::iter_prefix((collection_id,))398 .next()399 .is_some()400 }401402 pub fn burn_token_unchecked(403 collection: &RefungibleHandle<T>,404 owner: &T::CrossAccountId,405 token_id: TokenId,406 ) -> DispatchResult {407 let burnt = <TokensBurnt<T>>::get(collection.id)408 .checked_add(1)409 .ok_or(ArithmeticError::Overflow)?;410411 <TokensBurnt<T>>::insert(collection.id, burnt);412 <TokenProperties<T>>::remove((collection.id, token_id));413 <TotalSupply<T>>::remove((collection.id, token_id));414 <Balance<T>>::remove_prefix((collection.id, token_id), None);415 <Allowance<T>>::remove_prefix((collection.id, token_id), None);416417 <PalletEvm<T>>::deposit_log(418 ERC721Events::Transfer {419 from: *owner.as_eth(),420 to: H160::default(),421 token_id: token_id.into(),422 }423 .to_log(collection_id_to_address(collection.id)),424 );425 Ok(())426 }427428 429 430 431 432 433 434 435 436 437 438 439 pub fn burn(440 collection: &RefungibleHandle<T>,441 owner: &T::CrossAccountId,442 token: TokenId,443 amount: u128,444 ) -> DispatchResult {445 let total_supply = <TotalSupply<T>>::get((collection.id, token))446 .checked_sub(amount)447 .ok_or(<CommonError<T>>::TokenValueTooLow)?;448449 450 if total_supply == 0 {451 452 ensure!(453 <Balance<T>>::get((collection.id, token, owner)) == amount,454 <CommonError<T>>::TokenValueTooLow455 );456 let account_balance = <AccountBalance<T>>::get((collection.id, owner))457 .checked_sub(1)458 459 .ok_or(ArithmeticError::Underflow)?;460461 462463 <Owned<T>>::remove((collection.id, owner, token));464 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);465 <AccountBalance<T>>::insert((collection.id, owner), account_balance);466 Self::burn_token_unchecked(collection, owner, token)?;467 <PalletEvm<T>>::deposit_log(468 ERC20Events::Transfer {469 from: *owner.as_eth(),470 to: H160::default(),471 value: amount.into(),472 }473 .to_log(collection_id_to_address(collection.id)),474 );475 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(476 collection.id,477 token,478 owner.clone(),479 amount,480 ));481 return Ok(());482 }483484 let balance = <Balance<T>>::get((collection.id, token, owner))485 .checked_sub(amount)486 .ok_or(<CommonError<T>>::TokenValueTooLow)?;487 let account_balance = if balance == 0 {488 <AccountBalance<T>>::get((collection.id, owner))489 .checked_sub(1)490 491 .ok_or(ArithmeticError::Underflow)?492 } else {493 0494 };495496 497498 if balance == 0 {499 <Owned<T>>::remove((collection.id, owner, token));500 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);501 <Balance<T>>::remove((collection.id, token, owner));502 <AccountBalance<T>>::insert((collection.id, owner), account_balance);503504 if let Some(user) = Self::token_owner(collection.id, token) {505 <PalletEvm<T>>::deposit_log(506 ERC721Events::Transfer {507 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,508 to: *user.as_eth(),509 token_id: token.into(),510 }511 .to_log(collection_id_to_address(collection.id)),512 );513 }514 } else {515 <Balance<T>>::insert((collection.id, token, owner), balance);516 }517 <TotalSupply<T>>::insert((collection.id, token), total_supply);518519 <PalletEvm<T>>::deposit_log(520 ERC20Events::Transfer {521 from: *owner.as_eth(),522 to: H160::default(),523 value: amount.into(),524 }525 .to_log(T::EvmTokenAddressMapping::token_to_address(526 collection.id,527 token,528 )),529 );530 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(531 collection.id,532 token,533 owner.clone(),534 amount,535 ));536 Ok(())537 }538539 #[transactional]540 fn modify_token_properties(541 collection: &RefungibleHandle<T>,542 sender: &T::CrossAccountId,543 token_id: TokenId,544 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,545 is_token_create: bool,546 nesting_budget: &dyn Budget,547 ) -> DispatchResult {548 let is_collection_admin = || collection.is_owner_or_admin(sender);549 let is_token_owner = || -> Result<bool, DispatchError> {550 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 for (key, value) in properties {569 let permission = <PalletCommon<T>>::property_permissions(collection.id)570 .get(&key)571 .cloned()572 .unwrap_or_else(PropertyPermission::none);573574 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))575 .get(&key)576 .is_some();577578 match permission {579 PropertyPermission { mutable: false, .. } if is_property_exists => {580 return Err(<CommonError<T>>::NoPermission.into());581 }582583 PropertyPermission {584 collection_admin,585 token_owner,586 ..587 } => {588 589 let is_token_create =590 is_token_create && (collection_admin || token_owner) && value.is_some();591 if !(is_token_create592 || (collection_admin && is_collection_admin())593 || (token_owner && is_token_owner()?))594 {595 fail!(<CommonError<T>>::NoPermission);596 }597 }598 }599600 match value {601 Some(value) => {602 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {603 properties.try_set(key.clone(), value)604 })605 .map_err(<CommonError<T>>::from)?;606607 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(608 collection.id,609 token_id,610 key,611 ));612 }613 None => {614 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {615 properties.remove(&key)616 })617 .map_err(<CommonError<T>>::from)?;618619 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(620 collection.id,621 token_id,622 key,623 ));624 }625 }626 }627628 Ok(())629 }630631 pub fn set_token_properties(632 collection: &RefungibleHandle<T>,633 sender: &T::CrossAccountId,634 token_id: TokenId,635 properties: impl Iterator<Item = Property>,636 is_token_create: bool,637 nesting_budget: &dyn Budget,638 ) -> DispatchResult {639 Self::modify_token_properties(640 collection,641 sender,642 token_id,643 properties.map(|p| (p.key, Some(p.value))),644 is_token_create,645 nesting_budget,646 )647 }648649 pub fn set_token_property(650 collection: &RefungibleHandle<T>,651 sender: &T::CrossAccountId,652 token_id: TokenId,653 property: Property,654 nesting_budget: &dyn Budget,655 ) -> DispatchResult {656 let is_token_create = false;657658 Self::set_token_properties(659 collection,660 sender,661 token_id,662 [property].into_iter(),663 is_token_create,664 nesting_budget,665 )666 }667668 pub fn delete_token_properties(669 collection: &RefungibleHandle<T>,670 sender: &T::CrossAccountId,671 token_id: TokenId,672 property_keys: impl Iterator<Item = PropertyKey>,673 nesting_budget: &dyn Budget,674 ) -> DispatchResult {675 let is_token_create = false;676677 Self::modify_token_properties(678 collection,679 sender,680 token_id,681 property_keys.into_iter().map(|key| (key, None)),682 is_token_create,683 nesting_budget,684 )685 }686687 pub fn delete_token_property(688 collection: &RefungibleHandle<T>,689 sender: &T::CrossAccountId,690 token_id: TokenId,691 property_key: PropertyKey,692 nesting_budget: &dyn Budget,693 ) -> DispatchResult {694 Self::delete_token_properties(695 collection,696 sender,697 token_id,698 [property_key].into_iter(),699 nesting_budget,700 )701 }702703 704 705 706 707 708 709 710 711 712 pub fn transfer(713 collection: &RefungibleHandle<T>,714 from: &T::CrossAccountId,715 to: &T::CrossAccountId,716 token: TokenId,717 amount: u128,718 nesting_budget: &dyn Budget,719 ) -> DispatchResult {720 ensure!(721 collection.limits.transfers_enabled(),722 <CommonError<T>>::TransferNotAllowed723 );724725 if collection.permissions.access() == AccessMode::AllowList {726 collection.check_allowlist(from)?;727 collection.check_allowlist(to)?;728 }729 <PalletCommon<T>>::ensure_correct_receiver(to)?;730731 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));732 let updated_balance_from = initial_balance_from733 .checked_sub(amount)734 .ok_or(<CommonError<T>>::TokenValueTooLow)?;735 let mut create_target = false;736 let from_to_differ = from != to;737 let updated_balance_to = if from != to {738 let old_balance = <Balance<T>>::get((collection.id, token, to));739 if old_balance == 0 {740 create_target = true;741 }742 Some(743 old_balance744 .checked_add(amount)745 .ok_or(ArithmeticError::Overflow)?,746 )747 } else {748 None749 };750751 let account_balance_from = if updated_balance_from == 0 {752 Some(753 <AccountBalance<T>>::get((collection.id, from))754 .checked_sub(1)755 756 .ok_or(ArithmeticError::Underflow)?,757 )758 } else {759 None760 };761 762 763 let account_balance_to = if create_target && from_to_differ {764 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))765 .checked_add(1)766 .ok_or(ArithmeticError::Overflow)?;767 ensure!(768 account_balance_to < collection.limits.account_token_ownership_limit(),769 <CommonError<T>>::AccountTokenLimitExceeded,770 );771772 Some(account_balance_to)773 } else {774 None775 };776777 778779 <PalletStructure<T>>::nest_if_sent_to_token(780 from.clone(),781 to,782 collection.id,783 token,784 nesting_budget,785 )?;786787 if let Some(updated_balance_to) = updated_balance_to {788 789 if updated_balance_from == 0 {790 <Balance<T>>::remove((collection.id, token, from));791 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);792 } else {793 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);794 }795 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);796 if let Some(account_balance_from) = account_balance_from {797 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);798 <Owned<T>>::remove((collection.id, from, token));799 }800 if let Some(account_balance_to) = account_balance_to {801 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);802 <Owned<T>>::insert((collection.id, to, token), true);803 }804 }805806 <PalletEvm<T>>::deposit_log(807 ERC20Events::Transfer {808 from: *from.as_eth(),809 to: *to.as_eth(),810 value: amount.into(),811 }812 .to_log(T::EvmTokenAddressMapping::token_to_address(813 collection.id,814 token,815 )),816 );817818 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(819 collection.id,820 token,821 from.clone(),822 to.clone(),823 amount,824 ));825826 let total_supply = <TotalSupply<T>>::get((collection.id, token));827828 if amount == total_supply {829 830 <PalletEvm<T>>::deposit_log(831 ERC721Events::Transfer {832 from: *from.as_eth(),833 to: *to.as_eth(),834 token_id: token.into(),835 }836 .to_log(collection_id_to_address(collection.id)),837 );838 } else if let Some(updated_balance_to) = updated_balance_to {839 840 841 if initial_balance_from == total_supply {842 843 844 <PalletEvm<T>>::deposit_log(845 ERC721Events::Transfer {846 from: *from.as_eth(),847 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,848 token_id: token.into(),849 }850 .to_log(collection_id_to_address(collection.id)),851 );852 } else if updated_balance_to == total_supply {853 854 <PalletEvm<T>>::deposit_log(855 ERC721Events::Transfer {856 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,857 to: *to.as_eth(),858 token_id: token.into(),859 }860 .to_log(collection_id_to_address(collection.id)),861 );862 }863 }864865 Ok(())866 }867868 869 870 871 872 873 pub fn create_multiple_items(874 collection: &RefungibleHandle<T>,875 sender: &T::CrossAccountId,876 data: Vec<CreateItemData<T>>,877 nesting_budget: &dyn Budget,878 ) -> DispatchResult {879 if !collection.is_owner_or_admin(sender) {880 ensure!(881 collection.permissions.mint_mode(),882 <CommonError<T>>::PublicMintingNotAllowed883 );884 collection.check_allowlist(sender)?;885886 for item in data.iter() {887 for user in item.users.keys() {888 collection.check_allowlist(user)?;889 }890 }891 }892893 for item in data.iter() {894 for (owner, _) in item.users.iter() {895 <PalletCommon<T>>::ensure_correct_receiver(owner)?;896 }897 }898899 900 let totals = data901 .iter()902 .map(|data| {903 Ok(data904 .users905 .iter()906 .map(|u| u.1)907 .try_fold(0u128, |acc, v| acc.checked_add(*v))908 .ok_or(ArithmeticError::Overflow)?)909 })910 .collect::<Result<Vec<_>, DispatchError>>()?;911 for total in &totals {912 ensure!(913 *total <= MAX_REFUNGIBLE_PIECES,914 <Error<T>>::WrongRefungiblePieces915 );916 }917918 let first_token_id = <TokensMinted<T>>::get(collection.id);919 let tokens_minted = first_token_id920 .checked_add(data.len() as u32)921 .ok_or(ArithmeticError::Overflow)?;922 ensure!(923 tokens_minted < collection.limits.token_limit(),924 <CommonError<T>>::CollectionTokenLimitExceeded925 );926927 let mut balances = BTreeMap::new();928 for data in &data {929 for owner in data.users.keys() {930 let balance = balances931 .entry(owner)932 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));933 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;934935 ensure!(936 *balance <= collection.limits.account_token_ownership_limit(),937 <CommonError<T>>::AccountTokenLimitExceeded,938 );939 }940 }941942 for (i, token) in data.iter().enumerate() {943 let token_id = TokenId(first_token_id + i as u32 + 1);944 for (to, _) in token.users.iter() {945 <PalletStructure<T>>::check_nesting(946 sender.clone(),947 to,948 collection.id,949 token_id,950 nesting_budget,951 )?;952 }953 }954955 956957 with_transaction(|| {958 for (i, data) in data.iter().enumerate() {959 let token_id = first_token_id + i as u32 + 1;960 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);961962 for (user, amount) in data.users.iter() {963 if *amount == 0 {964 continue;965 }966 <Balance<T>>::insert((collection.id, token_id, &user), amount);967 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);968 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(969 user,970 collection.id,971 TokenId(token_id),972 );973 }974975 if let Err(e) = Self::set_token_properties(976 collection,977 sender,978 TokenId(token_id),979 data.properties.clone().into_iter(),980 true,981 nesting_budget,982 ) {983 return TransactionOutcome::Rollback(Err(e));984 }985 }986 TransactionOutcome::Commit(Ok(()))987 })?;988989 <TokensMinted<T>>::insert(collection.id, tokens_minted);990991 for (account, balance) in balances {992 <AccountBalance<T>>::insert((collection.id, account), balance);993 }994995 for (i, token) in data.into_iter().enumerate() {996 let token_id = first_token_id + i as u32 + 1;997998 let receivers = token999 .users1000 .into_iter()1001 .filter(|(_, amount)| *amount > 0)1002 .collect::<Vec<_>>();10031004 if let [(user, _)] = receivers.as_slice() {1005 1006 <PalletEvm<T>>::deposit_log(1007 ERC721Events::Transfer {1008 from: H160::default(),1009 to: *user.as_eth(),1010 token_id: token_id.into(),1011 }1012 .to_log(collection_id_to_address(collection.id)),1013 );1014 } else if let [_, ..] = receivers.as_slice() {1015 1016 <PalletEvm<T>>::deposit_log(1017 ERC721Events::Transfer {1018 from: H160::default(),1019 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1020 token_id: token_id.into(),1021 }1022 .to_log(collection_id_to_address(collection.id)),1023 );1024 }10251026 for (user, amount) in receivers.into_iter() {1027 <PalletEvm<T>>::deposit_log(1028 ERC20Events::Transfer {1029 from: H160::default(),1030 to: *user.as_eth(),1031 value: amount.into(),1032 }1033 .to_log(T::EvmTokenAddressMapping::token_to_address(1034 collection.id,1035 TokenId(token_id),1036 )),1037 );1038 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1039 collection.id,1040 TokenId(token_id),1041 user,1042 amount,1043 ));1044 }1045 }1046 Ok(())1047 }10481049 pub fn set_allowance_unchecked(1050 collection: &RefungibleHandle<T>,1051 sender: &T::CrossAccountId,1052 spender: &T::CrossAccountId,1053 token: TokenId,1054 amount: u128,1055 ) {1056 if amount == 0 {1057 <Allowance<T>>::remove((collection.id, token, sender, spender));1058 } else {1059 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1060 }10611062 <PalletEvm<T>>::deposit_log(1063 ERC20Events::Approval {1064 owner: *sender.as_eth(),1065 spender: *spender.as_eth(),1066 value: amount.into(),1067 }1068 .to_log(T::EvmTokenAddressMapping::token_to_address(1069 collection.id,1070 token,1071 )),1072 );1073 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1074 collection.id,1075 token,1076 sender.clone(),1077 spender.clone(),1078 amount,1079 ))1080 }10811082 1083 1084 1085 pub fn set_allowance(1086 collection: &RefungibleHandle<T>,1087 sender: &T::CrossAccountId,1088 spender: &T::CrossAccountId,1089 token: TokenId,1090 amount: u128,1091 ) -> DispatchResult {1092 if collection.permissions.access() == AccessMode::AllowList {1093 collection.check_allowlist(sender)?;1094 collection.check_allowlist(spender)?;1095 }10961097 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10981099 if <Balance<T>>::get((collection.id, token, sender)) < amount {1100 ensure!(1101 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1102 <CommonError<T>>::CantApproveMoreThanOwned1103 );1104 }11051106 11071108 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1109 Ok(())1110 }11111112 1113 fn check_allowed(1114 collection: &RefungibleHandle<T>,1115 spender: &T::CrossAccountId,1116 from: &T::CrossAccountId,1117 token: TokenId,1118 amount: u128,1119 nesting_budget: &dyn Budget,1120 ) -> Result<Option<u128>, DispatchError> {1121 if spender.conv_eq(from) {1122 return Ok(None);1123 }1124 if collection.permissions.access() == AccessMode::AllowList {1125 1126 collection.check_allowlist(spender)?;1127 }1128 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1129 1130 ensure!(1131 <PalletStructure<T>>::check_indirectly_owned(1132 spender.clone(),1133 source.0,1134 source.1,1135 None,1136 nesting_budget1137 )?,1138 <CommonError<T>>::ApprovedValueTooLow,1139 );1140 return Ok(None);1141 }1142 let allowance =1143 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1144 if allowance.is_none() {1145 ensure!(1146 collection.ignores_allowance(spender),1147 <CommonError<T>>::ApprovedValueTooLow1148 );1149 }1150 Ok(allowance)1151 }11521153 1154 1155 1156 1157 1158 1159 pub fn transfer_from(1160 collection: &RefungibleHandle<T>,1161 spender: &T::CrossAccountId,1162 from: &T::CrossAccountId,1163 to: &T::CrossAccountId,1164 token: TokenId,1165 amount: u128,1166 nesting_budget: &dyn Budget,1167 ) -> DispatchResult {1168 let allowance =1169 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11701171 11721173 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1174 if let Some(allowance) = allowance {1175 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1176 }1177 Ok(())1178 }11791180 1181 1182 1183 1184 1185 1186 pub fn burn_from(1187 collection: &RefungibleHandle<T>,1188 spender: &T::CrossAccountId,1189 from: &T::CrossAccountId,1190 token: TokenId,1191 amount: u128,1192 nesting_budget: &dyn Budget,1193 ) -> DispatchResult {1194 let allowance =1195 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11961197 11981199 Self::burn(collection, from, token, amount)?;1200 if let Some(allowance) = allowance {1201 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1202 }1203 Ok(())1204 }12051206 1207 1208 1209 1210 1211 1212 1213 pub fn create_item(1214 collection: &RefungibleHandle<T>,1215 sender: &T::CrossAccountId,1216 data: CreateItemData<T>,1217 nesting_budget: &dyn Budget,1218 ) -> DispatchResult {1219 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1220 }12211222 1223 1224 1225 1226 1227 1228 1229 pub fn repartition(1230 collection: &RefungibleHandle<T>,1231 owner: &T::CrossAccountId,1232 token: TokenId,1233 amount: u128,1234 ) -> DispatchResult {1235 ensure!(1236 amount <= MAX_REFUNGIBLE_PIECES,1237 <Error<T>>::WrongRefungiblePieces1238 );1239 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1240 1241 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1242 let balance = <Balance<T>>::get((collection.id, token, owner));1243 ensure!(1244 total_pieces == balance,1245 <Error<T>>::RepartitionWhileNotOwningAllPieces1246 );12471248 <Balance<T>>::insert((collection.id, token, owner), amount);1249 <TotalSupply<T>>::insert((collection.id, token), amount);12501251 if amount > total_pieces {1252 let mint_amount = amount - total_pieces;1253 <PalletEvm<T>>::deposit_log(1254 ERC20Events::Transfer {1255 from: H160::default(),1256 to: *owner.as_eth(),1257 value: mint_amount.into(),1258 }1259 .to_log(T::EvmTokenAddressMapping::token_to_address(1260 collection.id,1261 token,1262 )),1263 );1264 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1265 collection.id,1266 token,1267 owner.clone(),1268 mint_amount,1269 ));1270 } else if total_pieces > amount {1271 let burn_amount = total_pieces - amount;1272 <PalletEvm<T>>::deposit_log(1273 ERC20Events::Transfer {1274 from: *owner.as_eth(),1275 to: H160::default(),1276 value: burn_amount.into(),1277 }1278 .to_log(T::EvmTokenAddressMapping::token_to_address(1279 collection.id,1280 token,1281 )),1282 );1283 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1284 collection.id,1285 token,1286 owner.clone(),1287 burn_amount,1288 ));1289 }12901291 Ok(())1292 }12931294 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1295 let mut owner = None;1296 let mut count = 0;1297 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1298 count += 1;1299 if count > 1 {1300 return None;1301 }1302 owner = Some(key);1303 }1304 owner1305 }13061307 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1308 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1309 }13101311 pub fn set_collection_properties(1312 collection: &RefungibleHandle<T>,1313 sender: &T::CrossAccountId,1314 properties: Vec<Property>,1315 ) -> DispatchResult {1316 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1317 }13181319 pub fn delete_collection_properties(1320 collection: &RefungibleHandle<T>,1321 sender: &T::CrossAccountId,1322 property_keys: Vec<PropertyKey>,1323 ) -> DispatchResult {1324 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1325 }13261327 pub fn set_token_property_permissions(1328 collection: &RefungibleHandle<T>,1329 sender: &T::CrossAccountId,1330 property_permissions: Vec<PropertyKeyPermission>,1331 ) -> DispatchResult {1332 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1333 }13341335 1336 1337 1338 1339 1340 1341 pub fn token_owners(1342 collection_id: CollectionId,1343 token: TokenId,1344 ) -> Option<Vec<T::CrossAccountId>> {1345 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1346 .map(|(owner, _amount)| owner)1347 .take(10)1348 .collect();13491350 if res.is_empty() {1351 None1352 } else {1353 Some(res)1354 }1355 }1356}