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, eth::collection_id_to_address,101 Event as CommonEvent, Pallet as PalletCommon, erc::CollectionHelpersEvents,102};103use pallet_structure::Pallet as PalletStructure;104use scale_info::TypeInfo;105use sp_core::{Get, 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, CollectionFlags, CreateCollectionData,110 CustomDataLimit, mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,111 PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, TokenId,112 TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners,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 CreateRefungibleExMultipleOwners<<T as pallet_evm::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)]131pub struct ItemData {132 pub const_data: BoundedVec<u8, CustomDataLimit>,133134 #[version(..2)]135 pub variable_data: BoundedVec<u8, CustomDataLimit>,136}137138#[frame_support::pallet]139pub mod pallet {140 use super::*;141 use frame_support::{142 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,143 traits::StorageVersion,144 };145 use frame_system::pallet_prelude::*;146 use up_data_structs::{CollectionId, TokenId};147 use super::weights::WeightInfo;148149 #[pallet::error]150 pub enum Error<T> {151 152 NotRefungibleDataUsedToMintFungibleCollectionToken,153 154 WrongRefungiblePieces,155 156 RepartitionWhileNotOwningAllPieces,157 158 RefungibleDisallowsNesting,159 160 SettingPropertiesNotAllowed,161 }162163 #[pallet::config]164 pub trait Config:165 frame_system::Config + pallet_common::Config + pallet_structure::Config166 {167 type WeightInfo: WeightInfo;168 }169170 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);171172 #[pallet::pallet]173 #[pallet::storage_version(STORAGE_VERSION)]174 #[pallet::generate_store(pub(super) trait Store)]175 pub struct Pallet<T>(_);176177 178 #[pallet::storage]179 pub type TokensMinted<T: Config> =180 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;181182 183 #[pallet::storage]184 pub type TokensBurnt<T: Config> =185 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;186187 188 189 #[pallet::storage]190 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]191 pub type TokenData<T: Config> = StorageNMap<192 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),193 Value = ItemData,194 QueryKind = ValueQuery,195 >;196197 198 #[pallet::storage]199 #[pallet::getter(fn token_properties)]200 pub type TokenProperties<T: Config> = StorageNMap<201 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),202 Value = up_data_structs::Properties,203 QueryKind = ValueQuery,204 OnEmpty = up_data_structs::TokenProperties,205 >;206207 208 #[pallet::storage]209 pub type TotalSupply<T: Config> = StorageNMap<210 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),211 Value = u128,212 QueryKind = ValueQuery,213 >;214215 216 #[pallet::storage]217 pub type Owned<T: Config> = StorageNMap<218 Key = (219 Key<Twox64Concat, CollectionId>,220 Key<Blake2_128Concat, T::CrossAccountId>,221 Key<Twox64Concat, TokenId>,222 ),223 Value = bool,224 QueryKind = ValueQuery,225 >;226227 228 #[pallet::storage]229 pub type AccountBalance<T: Config> = StorageNMap<230 Key = (231 Key<Twox64Concat, CollectionId>,232 233 Key<Blake2_128Concat, T::CrossAccountId>,234 ),235 Value = u32,236 QueryKind = ValueQuery,237 >;238239 240 #[pallet::storage]241 pub type Balance<T: Config> = StorageNMap<242 Key = (243 Key<Twox64Concat, CollectionId>,244 Key<Twox64Concat, TokenId>,245 246 Key<Blake2_128Concat, T::CrossAccountId>,247 ),248 Value = u128,249 QueryKind = ValueQuery,250 >;251252 253 #[pallet::storage]254 pub type Allowance<T: Config> = StorageNMap<255 Key = (256 Key<Twox64Concat, CollectionId>,257 Key<Twox64Concat, TokenId>,258 259 Key<Blake2_128, T::CrossAccountId>,260 261 Key<Blake2_128Concat, T::CrossAccountId>,262 ),263 Value = u128,264 QueryKind = ValueQuery,265 >;266267 268 #[pallet::storage]269 pub type CollectionAllowance<T: Config> = StorageNMap<270 Key = (271 Key<Twox64Concat, CollectionId>,272 Key<Blake2_128Concat, T::CrossAccountId>,273 Key<Blake2_128Concat, T::CrossAccountId>,274 ),275 Value = bool,276 QueryKind = ValueQuery,277 >;278279 #[pallet::hooks]280 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {281 fn on_runtime_upgrade() -> Weight {282 let storage_version = StorageVersion::get::<Pallet<T>>();283 if storage_version < StorageVersion::new(2) {284 #[allow(deprecated)]285 let _ = <TokenData<T>>::clear(u32::MAX, None);286 }287 StorageVersion::new(2).put::<Pallet<T>>();288289 Weight::zero()290 }291 }292}293294pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);295impl<T: Config> RefungibleHandle<T> {296 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {297 Self(inner)298 }299 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {300 self.0301 }302 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {303 &mut self.0304 }305}306307impl<T: Config> Deref for RefungibleHandle<T> {308 type Target = pallet_common::CollectionHandle<T>;309310 fn deref(&self) -> &Self::Target {311 &self.0312 }313}314315impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {316 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {317 self.0.recorder()318 }319 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {320 self.0.into_recorder()321 }322}323324impl<T: Config> Pallet<T> {325 326 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {327 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)328 }329330 331 332 333 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {334 <TotalSupply<T>>::contains_key((collection.id, token))335 }336337 pub fn set_scoped_token_property(338 collection_id: CollectionId,339 token_id: TokenId,340 scope: PropertyScope,341 property: Property,342 ) -> DispatchResult {343 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {344 properties.try_scoped_set(scope, property.key, property.value)345 })346 .map_err(<CommonError<T>>::from)?;347348 Ok(())349 }350351 pub fn set_scoped_token_properties(352 collection_id: CollectionId,353 token_id: TokenId,354 scope: PropertyScope,355 properties: impl Iterator<Item = Property>,356 ) -> DispatchResult {357 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {358 stored_properties.try_scoped_set_from_iter(scope, properties)359 })360 .map_err(<CommonError<T>>::from)?;361362 Ok(())363 }364}365366367impl<T: Config> Pallet<T> {368 369 370 371 372 373 pub fn init_collection(374 owner: T::CrossAccountId,375 payer: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 flags: CollectionFlags,378 ) -> Result<CollectionId, DispatchError> {379 <PalletCommon<T>>::init_collection(owner, payer, data, flags)380 }381382 383 384 385 386 pub fn destroy_collection(387 collection: RefungibleHandle<T>,388 sender: &T::CrossAccountId,389 ) -> DispatchResult {390 let id = collection.id;391392 if Self::collection_has_tokens(id) {393 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());394 }395396 397398 PalletCommon::destroy_collection(collection.0, sender)?;399400 <TokensMinted<T>>::remove(id);401 <TokensBurnt<T>>::remove(id);402 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);403 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);404 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);405 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);406 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);407 Ok(())408 }409410 fn collection_has_tokens(collection_id: CollectionId) -> bool {411 <TotalSupply<T>>::iter_prefix((collection_id,))412 .next()413 .is_some()414 }415416 pub fn burn_token_unchecked(417 collection: &RefungibleHandle<T>,418 owner: &T::CrossAccountId,419 token_id: TokenId,420 ) -> DispatchResult {421 let burnt = <TokensBurnt<T>>::get(collection.id)422 .checked_add(1)423 .ok_or(ArithmeticError::Overflow)?;424425 <TokensBurnt<T>>::insert(collection.id, burnt);426 <TokenProperties<T>>::remove((collection.id, token_id));427 <TotalSupply<T>>::remove((collection.id, token_id));428 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);429 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);430 <PalletEvm<T>>::deposit_log(431 ERC721Events::Transfer {432 from: *owner.as_eth(),433 to: H160::default(),434 token_id: token_id.into(),435 }436 .to_log(collection_id_to_address(collection.id)),437 );438 Ok(())439 }440441 442 443 444 445 446 447 448 449 450 451 452 pub fn burn(453 collection: &RefungibleHandle<T>,454 owner: &T::CrossAccountId,455 token: TokenId,456 amount: u128,457 ) -> DispatchResult {458 if <Balance<T>>::get((collection.id, token, owner)) == 0 {459 return Err(<CommonError<T>>::TokenValueTooLow.into());460 }461462 let total_supply = <TotalSupply<T>>::get((collection.id, token))463 .checked_sub(amount)464 .ok_or(<CommonError<T>>::TokenValueTooLow)?;465466 467 if total_supply == 0 {468 469 ensure!(470 <Balance<T>>::get((collection.id, token, owner)) == amount,471 <CommonError<T>>::TokenValueTooLow472 );473 let account_balance = <AccountBalance<T>>::get((collection.id, owner))474 .checked_sub(1)475 476 .ok_or(ArithmeticError::Underflow)?;477478 479480 <Owned<T>>::remove((collection.id, owner, token));481 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);482 <AccountBalance<T>>::insert((collection.id, owner), account_balance);483 Self::burn_token_unchecked(collection, owner, token)?;484 <PalletEvm<T>>::deposit_log(485 ERC20Events::Transfer {486 from: *owner.as_eth(),487 to: H160::default(),488 value: amount.into(),489 }490 .to_log(collection_id_to_address(collection.id)),491 );492 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(493 collection.id,494 token,495 owner.clone(),496 amount,497 ));498 return Ok(());499 }500501 let balance = <Balance<T>>::get((collection.id, token, owner))502 .checked_sub(amount)503 .ok_or(<CommonError<T>>::TokenValueTooLow)?;504 let account_balance = if balance == 0 {505 <AccountBalance<T>>::get((collection.id, owner))506 .checked_sub(1)507 508 .ok_or(ArithmeticError::Underflow)?509 } else {510 0511 };512513 514515 if balance == 0 {516 <Owned<T>>::remove((collection.id, owner, token));517 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);518 <Balance<T>>::remove((collection.id, token, owner));519 <AccountBalance<T>>::insert((collection.id, owner), account_balance);520521 if let Some(user) = Self::token_owner(collection.id, token) {522 <PalletEvm<T>>::deposit_log(523 ERC721Events::Transfer {524 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,525 to: *user.as_eth(),526 token_id: token.into(),527 }528 .to_log(collection_id_to_address(collection.id)),529 );530 }531 } else {532 <Balance<T>>::insert((collection.id, token, owner), balance);533 }534 <TotalSupply<T>>::insert((collection.id, token), total_supply);535536 <PalletEvm<T>>::deposit_log(537 ERC20Events::Transfer {538 from: *owner.as_eth(),539 to: H160::default(),540 value: amount.into(),541 }542 .to_log(T::EvmTokenAddressMapping::token_to_address(543 collection.id,544 token,545 )),546 );547 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(548 collection.id,549 token,550 owner.clone(),551 amount,552 ));553 Ok(())554 }555556 #[transactional]557 fn modify_token_properties(558 collection: &RefungibleHandle<T>,559 sender: &T::CrossAccountId,560 token_id: TokenId,561 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,562 is_token_create: bool,563 nesting_budget: &dyn Budget,564 ) -> DispatchResult {565 let is_collection_admin = || collection.is_owner_or_admin(sender);566 let is_token_owner = || -> Result<bool, DispatchError> {567 let balance = collection.balance(sender.clone(), token_id);568 let total_pieces: u128 =569 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);570 if balance != total_pieces {571 return Ok(false);572 }573574 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(575 sender.clone(),576 collection.id,577 token_id,578 None,579 nesting_budget,580 )?;581582 Ok(is_bundle_owner)583 };584585 for (key, value) in properties {586 let permission = <PalletCommon<T>>::property_permissions(collection.id)587 .get(&key)588 .cloned()589 .unwrap_or_else(PropertyPermission::none);590591 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))592 .get(&key)593 .is_some();594595 match permission {596 PropertyPermission { mutable: false, .. } if is_property_exists => {597 return Err(<CommonError<T>>::NoPermission.into());598 }599600 PropertyPermission {601 collection_admin,602 token_owner,603 ..604 } => {605 606 let is_token_create =607 is_token_create && (collection_admin || token_owner) && value.is_some();608 if !(is_token_create609 || (collection_admin && is_collection_admin())610 || (token_owner && is_token_owner()?))611 {612 fail!(<CommonError<T>>::NoPermission);613 }614 }615 }616617 match value {618 Some(value) => {619 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {620 properties.try_set(key.clone(), value)621 })622 .map_err(<CommonError<T>>::from)?;623624 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(625 collection.id,626 token_id,627 key,628 ));629 }630 None => {631 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {632 properties.remove(&key)633 })634 .map_err(<CommonError<T>>::from)?;635636 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(637 collection.id,638 token_id,639 key,640 ));641 }642 }643644 <PalletEvm<T>>::deposit_log(645 CollectionHelpersEvents::TokenChanged {646 collection_id: collection_id_to_address(collection.id),647 token_id: token_id.into(),648 }649 .to_log(T::ContractAddress::get()),650 );651 }652653 Ok(())654 }655656 pub fn set_token_properties(657 collection: &RefungibleHandle<T>,658 sender: &T::CrossAccountId,659 token_id: TokenId,660 properties: impl Iterator<Item = Property>,661 is_token_create: bool,662 nesting_budget: &dyn Budget,663 ) -> DispatchResult {664 Self::modify_token_properties(665 collection,666 sender,667 token_id,668 properties.map(|p| (p.key, Some(p.value))),669 is_token_create,670 nesting_budget,671 )672 }673674 pub fn set_token_property(675 collection: &RefungibleHandle<T>,676 sender: &T::CrossAccountId,677 token_id: TokenId,678 property: Property,679 nesting_budget: &dyn Budget,680 ) -> DispatchResult {681 let is_token_create = false;682683 Self::set_token_properties(684 collection,685 sender,686 token_id,687 [property].into_iter(),688 is_token_create,689 nesting_budget,690 )691 }692693 pub fn delete_token_properties(694 collection: &RefungibleHandle<T>,695 sender: &T::CrossAccountId,696 token_id: TokenId,697 property_keys: impl Iterator<Item = PropertyKey>,698 nesting_budget: &dyn Budget,699 ) -> DispatchResult {700 let is_token_create = false;701702 Self::modify_token_properties(703 collection,704 sender,705 token_id,706 property_keys.into_iter().map(|key| (key, None)),707 is_token_create,708 nesting_budget,709 )710 }711712 pub fn delete_token_property(713 collection: &RefungibleHandle<T>,714 sender: &T::CrossAccountId,715 token_id: TokenId,716 property_key: PropertyKey,717 nesting_budget: &dyn Budget,718 ) -> DispatchResult {719 Self::delete_token_properties(720 collection,721 sender,722 token_id,723 [property_key].into_iter(),724 nesting_budget,725 )726 }727728 729 730 731 732 733 734 735 736 737 pub fn transfer(738 collection: &RefungibleHandle<T>,739 from: &T::CrossAccountId,740 to: &T::CrossAccountId,741 token: TokenId,742 amount: u128,743 nesting_budget: &dyn Budget,744 ) -> DispatchResult {745 ensure!(746 collection.limits.transfers_enabled(),747 <CommonError<T>>::TransferNotAllowed748 );749750 if collection.permissions.access() == AccessMode::AllowList {751 collection.check_allowlist(from)?;752 collection.check_allowlist(to)?;753 }754 <PalletCommon<T>>::ensure_correct_receiver(to)?;755756 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));757758 if initial_balance_from == 0 {759 return Err(<CommonError<T>>::TokenValueTooLow.into());760 }761762 let updated_balance_from = initial_balance_from763 .checked_sub(amount)764 .ok_or(<CommonError<T>>::TokenValueTooLow)?;765 let mut create_target = false;766 let from_to_differ = from != to;767 let updated_balance_to = if from != to && amount != 0 {768 let old_balance = <Balance<T>>::get((collection.id, token, to));769 if old_balance == 0 {770 create_target = true;771 }772 Some(773 old_balance774 .checked_add(amount)775 .ok_or(ArithmeticError::Overflow)?,776 )777 } else {778 None779 };780781 let account_balance_from = if updated_balance_from == 0 {782 Some(783 <AccountBalance<T>>::get((collection.id, from))784 .checked_sub(1)785 786 .ok_or(ArithmeticError::Underflow)?,787 )788 } else {789 None790 };791 792 793 let account_balance_to = if create_target && from_to_differ {794 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))795 .checked_add(1)796 .ok_or(ArithmeticError::Overflow)?;797 ensure!(798 account_balance_to < collection.limits.account_token_ownership_limit(),799 <CommonError<T>>::AccountTokenLimitExceeded,800 );801802 Some(account_balance_to)803 } else {804 None805 };806807 808809 if let Some(updated_balance_to) = updated_balance_to {810 811812 <PalletStructure<T>>::nest_if_sent_to_token(813 from.clone(),814 to,815 collection.id,816 token,817 nesting_budget,818 )?;819820 if updated_balance_from == 0 {821 <Balance<T>>::remove((collection.id, token, from));822 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);823 } else {824 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);825 }826 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);827 if let Some(account_balance_from) = account_balance_from {828 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);829 <Owned<T>>::remove((collection.id, from, token));830 }831 if let Some(account_balance_to) = account_balance_to {832 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);833 <Owned<T>>::insert((collection.id, to, token), true);834 }835 }836837 <PalletEvm<T>>::deposit_log(838 ERC20Events::Transfer {839 from: *from.as_eth(),840 to: *to.as_eth(),841 value: amount.into(),842 }843 .to_log(T::EvmTokenAddressMapping::token_to_address(844 collection.id,845 token,846 )),847 );848849 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(850 collection.id,851 token,852 from.clone(),853 to.clone(),854 amount,855 ));856857 let total_supply = <TotalSupply<T>>::get((collection.id, token));858859 if amount == total_supply {860 861 <PalletEvm<T>>::deposit_log(862 ERC721Events::Transfer {863 from: *from.as_eth(),864 to: *to.as_eth(),865 token_id: token.into(),866 }867 .to_log(collection_id_to_address(collection.id)),868 );869 } else if let Some(updated_balance_to) = updated_balance_to {870 871 872 if initial_balance_from == total_supply {873 874 875 <PalletEvm<T>>::deposit_log(876 ERC721Events::Transfer {877 from: *from.as_eth(),878 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,879 token_id: token.into(),880 }881 .to_log(collection_id_to_address(collection.id)),882 );883 } else if updated_balance_to == total_supply {884 885 <PalletEvm<T>>::deposit_log(886 ERC721Events::Transfer {887 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,888 to: *to.as_eth(),889 token_id: token.into(),890 }891 .to_log(collection_id_to_address(collection.id)),892 );893 }894 }895896 Ok(())897 }898899 900 901 902 903 904 pub fn create_multiple_items(905 collection: &RefungibleHandle<T>,906 sender: &T::CrossAccountId,907 data: Vec<CreateItemData<T>>,908 nesting_budget: &dyn Budget,909 ) -> DispatchResult {910 if !collection.is_owner_or_admin(sender) {911 ensure!(912 collection.permissions.mint_mode(),913 <CommonError<T>>::PublicMintingNotAllowed914 );915 collection.check_allowlist(sender)?;916917 for item in data.iter() {918 for user in item.users.keys() {919 collection.check_allowlist(user)?;920 }921 }922 }923924 for item in data.iter() {925 for (owner, _) in item.users.iter() {926 <PalletCommon<T>>::ensure_correct_receiver(owner)?;927 }928 }929930 931 let totals = data932 .iter()933 .map(|data| {934 Ok(data935 .users936 .iter()937 .map(|u| u.1)938 .try_fold(0u128, |acc, v| acc.checked_add(*v))939 .ok_or(ArithmeticError::Overflow)?)940 })941 .collect::<Result<Vec<_>, DispatchError>>()?;942 for total in &totals {943 ensure!(944 *total <= MAX_REFUNGIBLE_PIECES,945 <Error<T>>::WrongRefungiblePieces946 );947 }948949 let first_token_id = <TokensMinted<T>>::get(collection.id);950 let tokens_minted = first_token_id951 .checked_add(data.len() as u32)952 .ok_or(ArithmeticError::Overflow)?;953 ensure!(954 tokens_minted < collection.limits.token_limit(),955 <CommonError<T>>::CollectionTokenLimitExceeded956 );957958 let mut balances = BTreeMap::new();959 for data in &data {960 for owner in data.users.keys() {961 let balance = balances962 .entry(owner)963 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));964 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;965966 ensure!(967 *balance <= collection.limits.account_token_ownership_limit(),968 <CommonError<T>>::AccountTokenLimitExceeded,969 );970 }971 }972973 for (i, token) in data.iter().enumerate() {974 let token_id = TokenId(first_token_id + i as u32 + 1);975 for (to, _) in token.users.iter() {976 <PalletStructure<T>>::check_nesting(977 sender.clone(),978 to,979 collection.id,980 token_id,981 nesting_budget,982 )?;983 }984 }985986 987988 with_transaction(|| {989 for (i, data) in data.iter().enumerate() {990 let token_id = first_token_id + i as u32 + 1;991 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);992993 for (user, amount) in data.users.iter() {994 if *amount == 0 {995 continue;996 }997 <Balance<T>>::insert((collection.id, token_id, &user), amount);998 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);999 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(1000 user,1001 collection.id,1002 TokenId(token_id),1003 );1004 }10051006 if let Err(e) = Self::set_token_properties(1007 collection,1008 sender,1009 TokenId(token_id),1010 data.properties.clone().into_iter(),1011 true,1012 nesting_budget,1013 ) {1014 return TransactionOutcome::Rollback(Err(e));1015 }1016 }1017 TransactionOutcome::Commit(Ok(()))1018 })?;10191020 <TokensMinted<T>>::insert(collection.id, tokens_minted);10211022 for (account, balance) in balances {1023 <AccountBalance<T>>::insert((collection.id, account), balance);1024 }10251026 for (i, token) in data.into_iter().enumerate() {1027 let token_id = first_token_id + i as u32 + 1;10281029 let receivers = token1030 .users1031 .into_iter()1032 .filter(|(_, amount)| *amount > 0)1033 .collect::<Vec<_>>();10341035 if let [(user, _)] = receivers.as_slice() {1036 1037 <PalletEvm<T>>::deposit_log(1038 ERC721Events::Transfer {1039 from: H160::default(),1040 to: *user.as_eth(),1041 token_id: token_id.into(),1042 }1043 .to_log(collection_id_to_address(collection.id)),1044 );1045 } else if let [_, ..] = receivers.as_slice() {1046 1047 <PalletEvm<T>>::deposit_log(1048 ERC721Events::Transfer {1049 from: H160::default(),1050 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1051 token_id: token_id.into(),1052 }1053 .to_log(collection_id_to_address(collection.id)),1054 );1055 }10561057 for (user, amount) in receivers.into_iter() {1058 <PalletEvm<T>>::deposit_log(1059 ERC20Events::Transfer {1060 from: H160::default(),1061 to: *user.as_eth(),1062 value: amount.into(),1063 }1064 .to_log(T::EvmTokenAddressMapping::token_to_address(1065 collection.id,1066 TokenId(token_id),1067 )),1068 );1069 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1070 collection.id,1071 TokenId(token_id),1072 user,1073 amount,1074 ));1075 }1076 }1077 Ok(())1078 }10791080 pub fn set_allowance_unchecked(1081 collection: &RefungibleHandle<T>,1082 sender: &T::CrossAccountId,1083 spender: &T::CrossAccountId,1084 token: TokenId,1085 amount: u128,1086 ) {1087 if amount == 0 {1088 <Allowance<T>>::remove((collection.id, token, sender, spender));1089 } else {1090 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1091 }10921093 <PalletEvm<T>>::deposit_log(1094 ERC20Events::Approval {1095 owner: *sender.as_eth(),1096 spender: *spender.as_eth(),1097 value: amount.into(),1098 }1099 .to_log(T::EvmTokenAddressMapping::token_to_address(1100 collection.id,1101 token,1102 )),1103 );1104 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1105 collection.id,1106 token,1107 sender.clone(),1108 spender.clone(),1109 amount,1110 ))1111 }11121113 1114 1115 1116 pub fn set_allowance(1117 collection: &RefungibleHandle<T>,1118 sender: &T::CrossAccountId,1119 spender: &T::CrossAccountId,1120 token: TokenId,1121 amount: u128,1122 ) -> DispatchResult {1123 if collection.permissions.access() == AccessMode::AllowList {1124 collection.check_allowlist(sender)?;1125 collection.check_allowlist(spender)?;1126 }11271128 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11291130 if <Balance<T>>::get((collection.id, token, sender)) < amount {1131 ensure!(1132 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1133 <CommonError<T>>::CantApproveMoreThanOwned1134 );1135 }11361137 11381139 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1140 Ok(())1141 }11421143 1144 fn check_allowed(1145 collection: &RefungibleHandle<T>,1146 spender: &T::CrossAccountId,1147 from: &T::CrossAccountId,1148 token: TokenId,1149 amount: u128,1150 nesting_budget: &dyn Budget,1151 ) -> Result<Option<u128>, DispatchError> {1152 if spender.conv_eq(from) {1153 return Ok(None);1154 }1155 if collection.permissions.access() == AccessMode::AllowList {1156 1157 collection.check_allowlist(spender)?;1158 }1159 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1160 1161 ensure!(1162 <PalletStructure<T>>::check_indirectly_owned(1163 spender.clone(),1164 source.0,1165 source.1,1166 None,1167 nesting_budget1168 )?,1169 <CommonError<T>>::ApprovedValueTooLow,1170 );1171 return Ok(None);1172 }1173 let allowance =1174 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);11751176 1177 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1178 return Ok(allowance);1179 }11801181 if allowance.is_none() {1182 ensure!(1183 collection.ignores_allowance(spender),1184 <CommonError<T>>::ApprovedValueTooLow1185 );1186 }1187 Ok(allowance)1188 }11891190 1191 1192 1193 1194 1195 1196 pub fn transfer_from(1197 collection: &RefungibleHandle<T>,1198 spender: &T::CrossAccountId,1199 from: &T::CrossAccountId,1200 to: &T::CrossAccountId,1201 token: TokenId,1202 amount: u128,1203 nesting_budget: &dyn Budget,1204 ) -> DispatchResult {1205 let allowance =1206 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12071208 12091210 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1211 if let Some(allowance) = allowance {1212 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1213 }1214 Ok(())1215 }12161217 1218 1219 1220 1221 1222 1223 pub fn burn_from(1224 collection: &RefungibleHandle<T>,1225 spender: &T::CrossAccountId,1226 from: &T::CrossAccountId,1227 token: TokenId,1228 amount: u128,1229 nesting_budget: &dyn Budget,1230 ) -> DispatchResult {1231 let allowance =1232 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12331234 12351236 Self::burn(collection, from, token, amount)?;1237 if let Some(allowance) = allowance {1238 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1239 }1240 Ok(())1241 }12421243 1244 1245 1246 1247 1248 1249 1250 pub fn create_item(1251 collection: &RefungibleHandle<T>,1252 sender: &T::CrossAccountId,1253 data: CreateItemData<T>,1254 nesting_budget: &dyn Budget,1255 ) -> DispatchResult {1256 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1257 }12581259 1260 1261 1262 1263 1264 1265 1266 pub fn repartition(1267 collection: &RefungibleHandle<T>,1268 owner: &T::CrossAccountId,1269 token: TokenId,1270 amount: u128,1271 ) -> DispatchResult {1272 ensure!(1273 amount <= MAX_REFUNGIBLE_PIECES,1274 <Error<T>>::WrongRefungiblePieces1275 );1276 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1277 1278 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1279 let balance = <Balance<T>>::get((collection.id, token, owner));1280 ensure!(1281 total_pieces == balance,1282 <Error<T>>::RepartitionWhileNotOwningAllPieces1283 );12841285 <Balance<T>>::insert((collection.id, token, owner), amount);1286 <TotalSupply<T>>::insert((collection.id, token), amount);12871288 if amount > total_pieces {1289 let mint_amount = amount - total_pieces;1290 <PalletEvm<T>>::deposit_log(1291 ERC20Events::Transfer {1292 from: H160::default(),1293 to: *owner.as_eth(),1294 value: mint_amount.into(),1295 }1296 .to_log(T::EvmTokenAddressMapping::token_to_address(1297 collection.id,1298 token,1299 )),1300 );1301 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1302 collection.id,1303 token,1304 owner.clone(),1305 mint_amount,1306 ));1307 } else if total_pieces > amount {1308 let burn_amount = total_pieces - amount;1309 <PalletEvm<T>>::deposit_log(1310 ERC20Events::Transfer {1311 from: *owner.as_eth(),1312 to: H160::default(),1313 value: burn_amount.into(),1314 }1315 .to_log(T::EvmTokenAddressMapping::token_to_address(1316 collection.id,1317 token,1318 )),1319 );1320 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1321 collection.id,1322 token,1323 owner.clone(),1324 burn_amount,1325 ));1326 }13271328 Ok(())1329 }13301331 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1332 let mut owner = None;1333 let mut count = 0;1334 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1335 count += 1;1336 if count > 1 {1337 return None;1338 }1339 owner = Some(key);1340 }1341 owner1342 }13431344 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1345 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1346 }13471348 pub fn set_collection_properties(1349 collection: &RefungibleHandle<T>,1350 sender: &T::CrossAccountId,1351 properties: Vec<Property>,1352 ) -> DispatchResult {1353 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1354 }13551356 pub fn delete_collection_properties(1357 collection: &RefungibleHandle<T>,1358 sender: &T::CrossAccountId,1359 property_keys: Vec<PropertyKey>,1360 ) -> DispatchResult {1361 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1362 }13631364 pub fn set_token_property_permissions(1365 collection: &RefungibleHandle<T>,1366 sender: &T::CrossAccountId,1367 property_permissions: Vec<PropertyKeyPermission>,1368 ) -> DispatchResult {1369 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1370 }13711372 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1373 <PalletCommon<T>>::property_permissions(collection_id)1374 }13751376 pub fn set_scoped_token_property_permissions(1377 collection: &RefungibleHandle<T>,1378 sender: &T::CrossAccountId,1379 scope: PropertyScope,1380 property_permissions: Vec<PropertyKeyPermission>,1381 ) -> DispatchResult {1382 <PalletCommon<T>>::set_scoped_token_property_permissions(1383 collection,1384 sender,1385 scope,1386 property_permissions,1387 )1388 }13891390 1391 1392 1393 1394 1395 1396 pub fn token_owners(1397 collection_id: CollectionId,1398 token: TokenId,1399 ) -> Option<Vec<T::CrossAccountId>> {1400 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1401 .map(|(owner, _amount)| owner)1402 .take(10)1403 .collect();14041405 if res.is_empty() {1406 None1407 } else {1408 Some(res)1409 }1410 }14111412 1413 1414 1415 1416 1417 1418 pub fn set_allowance_for_all(1419 collection: &RefungibleHandle<T>,1420 owner: &T::CrossAccountId,1421 operator: &T::CrossAccountId,1422 approve: bool,1423 ) -> DispatchResult {1424 if collection.permissions.access() == AccessMode::AllowList {1425 collection.check_allowlist(owner)?;1426 collection.check_allowlist(operator)?;1427 }14281429 <PalletCommon<T>>::ensure_correct_receiver(operator)?;14301431 14321433 <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve);1434 <PalletEvm<T>>::deposit_log(1435 ERC721Events::ApprovalForAll {1436 owner: *owner.as_eth(),1437 operator: *operator.as_eth(),1438 approved: approve,1439 }1440 .to_log(collection_id_to_address(collection.id)),1441 );1442 <PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(1443 collection.id,1444 owner.clone(),1445 operator.clone(),1446 approve,1447 ));1448 Ok(())1449 }14501451 1452 pub fn allowance_for_all(1453 collection: &RefungibleHandle<T>,1454 owner: &T::CrossAccountId,1455 operator: &T::CrossAccountId,1456 ) -> bool {1457 <CollectionAllowance<T>>::get((collection.id, owner, operator))1458 }14591460 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1461 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1462 properties.recompute_consumed_space();1463 });14641465 Ok(())1466 }1467}