12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091#![cfg_attr(not(feature = "std"), no_std)]9293use erc::ERC721Events;94use evm_coder::ToLog;95use frame_support::{96 BoundedVec, ensure, fail, transactional,97 storage::with_transaction,98 pallet_prelude::DispatchResultWithPostInfo,99 pallet_prelude::Weight,100 weights::{PostDispatchInfo, Pays},101};102use up_data_structs::{103 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,104 mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,105 PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,106 AuxPropertyValue,107};108use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};109use pallet_common::{110 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,111 eth::collection_id_to_address,112};113use pallet_structure::{Pallet as PalletStructure, Error as StructureError};114use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};115use sp_core::H160;116use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};117use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};118use core::ops::Deref;119use codec::{Encode, Decode, MaxEncodedLen};120use scale_info::TypeInfo;121122pub use pallet::*;123use weights::WeightInfo;124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod common;127pub mod erc;128pub mod weights;129130pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;131pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;132133#[struct_versioning::versioned(version = 2, upper)]134#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]135pub struct ItemData<CrossAccountId> {136 #[version(..2)]137 pub const_data: BoundedVec<u8, CustomDataLimit>,138139 #[version(..2)]140 pub variable_data: BoundedVec<u8, CustomDataLimit>,141142 pub owner: CrossAccountId,143}144145#[frame_support::pallet]146pub mod pallet {147 use super::*;148 use frame_support::{149 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,150 };151 use frame_system::pallet_prelude::*;152 use up_data_structs::{CollectionId, TokenId};153 use super::weights::WeightInfo;154155 #[pallet::error]156 pub enum Error<T> {157 158 NotNonfungibleDataUsedToMintFungibleCollectionToken,159 160 NonfungibleItemsHaveNoAmount,161 162 CantBurnNftWithChildren,163 }164165 #[pallet::config]166 pub trait Config:167 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config168 {169 type WeightInfo: WeightInfo;170 }171172 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);173174 #[pallet::pallet]175 #[pallet::storage_version(STORAGE_VERSION)]176 #[pallet::generate_store(pub(super) trait Store)]177 pub struct Pallet<T>(_);178179 180 #[pallet::storage]181 pub type TokensMinted<T: Config> =182 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;183184 185 #[pallet::storage]186 pub type TokensBurnt<T: Config> =187 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;188189 190 #[pallet::storage]191 pub type TokenData<T: Config> = StorageNMap<192 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),193 Value = ItemData<T::CrossAccountId>,194 QueryKind = OptionQuery,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 = Properties,203 QueryKind = ValueQuery,204 OnEmpty = up_data_structs::TokenProperties,205 >;206207 208 #[pallet::storage]209 #[pallet::getter(fn token_aux_property)]210 pub type TokenAuxProperties<T: Config> = StorageNMap<211 Key = (212 Key<Twox64Concat, CollectionId>,213 Key<Twox64Concat, TokenId>,214 Key<Twox64Concat, PropertyScope>,215 Key<Twox64Concat, PropertyKey>,216 ),217 Value = AuxPropertyValue,218 QueryKind = OptionQuery,219 >;220221 222 #[pallet::storage]223 pub type Owned<T: Config> = StorageNMap<224 Key = (225 Key<Twox64Concat, CollectionId>,226 Key<Blake2_128Concat, T::CrossAccountId>,227 Key<Twox64Concat, TokenId>,228 ),229 Value = bool,230 QueryKind = ValueQuery,231 >;232233 234 #[pallet::storage]235 #[pallet::getter(fn token_children)]236 pub type TokenChildren<T: Config> = StorageNMap<237 Key = (238 Key<Twox64Concat, CollectionId>,239 Key<Twox64Concat, TokenId>,240 Key<Twox64Concat, (CollectionId, TokenId)>,241 ),242 Value = bool,243 QueryKind = ValueQuery,244 >;245246 247 #[pallet::storage]248 pub type AccountBalance<T: Config> = StorageNMap<249 Key = (250 Key<Twox64Concat, CollectionId>,251 Key<Blake2_128Concat, T::CrossAccountId>,252 ),253 Value = u32,254 QueryKind = ValueQuery,255 >;256257 258 #[pallet::storage]259 pub type Allowance<T: Config> = StorageNMap<260 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),261 Value = T::CrossAccountId,262 QueryKind = OptionQuery,263 >;264265 #[pallet::hooks]266 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {267 fn on_runtime_upgrade() -> Weight {268 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {269 let mut had_consts = BTreeSet::new();270 <TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(271 |(collection, token), v| {272 let mut props = vec![];273 if !v.const_data.is_empty() {274 props.push(Property {275 key: b"_old_constData".to_vec().try_into().unwrap(),276 value: v277 .const_data278 .clone()279 .into_inner()280 .try_into()281 .expect("const too long"),282 });283 had_consts.insert(collection);284 }285 if !v.variable_data.is_empty() {286 props.push(Property {287 key: b"_old_variableData".to_vec().try_into().unwrap(),288 value: v289 .variable_data290 .clone()291 .into_inner()292 .try_into()293 .expect("variable too long"),294 })295 }296 if !props.is_empty() {297 Self::set_scoped_token_properties(298 collection,299 token,300 PropertyScope::None,301 props.into_iter(),302 )303 .expect("existing token data exceeds property storage");304 }305 Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))306 },307 );308 for collection in had_consts {309 <PalletCommon<T>>::set_property_permission_unchecked(310 collection,311 PropertyKeyPermission {312 key: b"_old_constData".to_vec().try_into().unwrap(),313 permission: PropertyPermission {314 mutable: false,315 collection_admin: true,316 token_owner: false,317 },318 },319 )320 .expect("failed to configure permission");321 }322 }323324 0325 }326 }327}328329pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);330impl<T: Config> NonfungibleHandle<T> {331 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {332 Self(inner)333 }334 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {335 self.0336 }337 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {338 &mut self.0339 }340}341impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {342 fn recorder(&self) -> &SubstrateRecorder<T> {343 self.0.recorder()344 }345 fn into_recorder(self) -> SubstrateRecorder<T> {346 self.0.into_recorder()347 }348}349impl<T: Config> Deref for NonfungibleHandle<T> {350 type Target = pallet_common::CollectionHandle<T>;351352 fn deref(&self) -> &Self::Target {353 &self.0354 }355}356357impl<T: Config> Pallet<T> {358 359 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {360 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)361 }362363 364 365 366 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {367 <TokenData<T>>::contains_key((collection.id, token))368 }369370 371 372 373 pub fn set_scoped_token_property(374 collection_id: CollectionId,375 token_id: TokenId,376 scope: PropertyScope,377 property: Property,378 ) -> DispatchResult {379 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {380 properties.try_scoped_set(scope, property.key, property.value)381 })382 .map_err(<CommonError<T>>::from)?;383384 Ok(())385 }386387 388 pub fn set_scoped_token_properties(389 collection_id: CollectionId,390 token_id: TokenId,391 scope: PropertyScope,392 properties: impl Iterator<Item = Property>,393 ) -> DispatchResult {394 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {395 stored_properties.try_scoped_set_from_iter(scope, properties)396 })397 .map_err(<CommonError<T>>::from)?;398399 Ok(())400 }401402 403 404 405 pub fn try_mutate_token_aux_property<R, E>(406 collection_id: CollectionId,407 token_id: TokenId,408 scope: PropertyScope,409 key: PropertyKey,410 f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,411 ) -> Result<R, E> {412 <TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)413 }414415 416 pub fn remove_token_aux_property(417 collection_id: CollectionId,418 token_id: TokenId,419 scope: PropertyScope,420 key: PropertyKey,421 ) {422 <TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));423 }424425 426 427 428 pub fn iterate_token_aux_properties(429 collection_id: CollectionId,430 token_id: TokenId,431 scope: PropertyScope,432 ) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {433 <TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))434 }435436 437 pub fn current_token_id(collection_id: CollectionId) -> TokenId {438 TokenId(<TokensMinted<T>>::get(collection_id))439 }440}441442443impl<T: Config> Pallet<T> {444 445 446 447 448 449 pub fn init_collection(450 owner: T::CrossAccountId,451 data: CreateCollectionData<T::AccountId>,452 is_external: bool,453 ) -> Result<CollectionId, DispatchError> {454 <PalletCommon<T>>::init_collection(owner, data, is_external)455 }456457 458 459 460 461 pub fn destroy_collection(462 collection: NonfungibleHandle<T>,463 sender: &T::CrossAccountId,464 ) -> DispatchResult {465 let id = collection.id;466467 if Self::collection_has_tokens(id) {468 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());469 }470471 472473 PalletCommon::destroy_collection(collection.0, sender)?;474475 <TokenData<T>>::remove_prefix((id,), None);476 <TokenChildren<T>>::remove_prefix((id,), None);477 <Owned<T>>::remove_prefix((id,), None);478 <TokensMinted<T>>::remove(id);479 <TokensBurnt<T>>::remove(id);480 <Allowance<T>>::remove_prefix((id,), None);481 <AccountBalance<T>>::remove_prefix((id,), None);482 Ok(())483 }484485 486 487 488 489 490 491 492 493 494 pub fn burn(495 collection: &NonfungibleHandle<T>,496 sender: &T::CrossAccountId,497 token: TokenId,498 ) -> DispatchResult {499 let token_data =500 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;501 ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);502503 if collection.permissions.access() == AccessMode::AllowList {504 collection.check_allowlist(sender)?;505 }506507 if Self::token_has_children(collection.id, token) {508 return Err(<Error<T>>::CantBurnNftWithChildren.into());509 }510511 let burnt = <TokensBurnt<T>>::get(collection.id)512 .checked_add(1)513 .ok_or(ArithmeticError::Overflow)?;514515 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))516 .checked_sub(1)517 .ok_or(ArithmeticError::Overflow)?;518519 520521 if balance == 0 {522 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));523 } else {524 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);525 }526527 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);528529 <Owned<T>>::remove((collection.id, &token_data.owner, token));530 <TokensBurnt<T>>::insert(collection.id, burnt);531 <TokenData<T>>::remove((collection.id, token));532 <TokenProperties<T>>::remove((collection.id, token));533 <TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);534 let old_spender = <Allowance<T>>::take((collection.id, token));535536 if let Some(old_spender) = old_spender {537 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(538 collection.id,539 token,540 token_data.owner.clone(),541 old_spender,542 0,543 ));544 }545546 <PalletEvm<T>>::deposit_log(547 ERC721Events::Transfer {548 from: *token_data.owner.as_eth(),549 to: H160::default(),550 token_id: token.into(),551 }552 .to_log(collection_id_to_address(collection.id)),553 );554 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(555 collection.id,556 token,557 token_data.owner,558 1,559 ));560 Ok(())561 }562563 564 565 566 567 568 569 #[transactional]570 pub fn burn_recursively(571 collection: &NonfungibleHandle<T>,572 sender: &T::CrossAccountId,573 token: TokenId,574 self_budget: &dyn Budget,575 breadth_budget: &dyn Budget,576 ) -> DispatchResultWithPostInfo {577 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);578579 let current_token_account =580 T::CrossTokenAddressMapping::token_to_address(collection.id, token);581582 let mut weight = 0 as Weight;583584 585 586 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {587 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);588 let PostDispatchInfo { actual_weight, .. } =589 <PalletStructure<T>>::burn_item_recursively(590 current_token_account.clone(),591 collection,592 token,593 self_budget,594 breadth_budget,595 )?;596 if let Some(actual_weight) = actual_weight {597 weight = weight.saturating_add(actual_weight);598 }599 }600601 Self::burn(collection, sender, token)?;602 DispatchResultWithPostInfo::Ok(PostDispatchInfo {603 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),604 pays_fee: Pays::Yes,605 })606 }607608 609 610 611 612 613 614 615 616 #[transactional]617 fn modify_token_properties(618 collection: &NonfungibleHandle<T>,619 sender: &T::CrossAccountId,620 token_id: TokenId,621 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,622 is_token_create: bool,623 nesting_budget: &dyn Budget,624 ) -> DispatchResult {625 let mut collection_admin_status = None;626 let mut token_owner_result = None;627628 let mut is_collection_admin =629 || *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));630631 let mut is_token_owner = || {632 *token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {633 let is_owned = <PalletStructure<T>>::check_indirectly_owned(634 sender.clone(),635 collection.id,636 token_id,637 None,638 nesting_budget,639 )?;640641 Ok(is_owned)642 })643 };644645 for (key, value) in properties {646 let permission = <PalletCommon<T>>::property_permissions(collection.id)647 .get(&key)648 .cloned()649 .unwrap_or_else(PropertyPermission::none);650651 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))652 .get(&key)653 .is_some();654655 match permission {656 PropertyPermission { mutable: false, .. } if is_property_exists => {657 return Err(<CommonError<T>>::NoPermission.into());658 }659660 PropertyPermission {661 collection_admin,662 token_owner,663 ..664 } => {665 666 if is_token_create && (collection_admin || token_owner) && value.is_some() {667 668 } else if collection_admin && is_collection_admin() {669 670 } else if token_owner && is_token_owner()? {671 672 } else {673 fail!(<CommonError<T>>::NoPermission);674 }675 }676 }677678 match value {679 Some(value) => {680 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {681 properties.try_set(key.clone(), value)682 })683 .map_err(<CommonError<T>>::from)?;684685 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(686 collection.id,687 token_id,688 key,689 ));690 }691 None => {692 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {693 properties.remove(&key)694 })695 .map_err(<CommonError<T>>::from)?;696697 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(698 collection.id,699 token_id,700 key,701 ));702 }703 }704 }705706 Ok(())707 }708709 710 711 712 713 714 pub fn set_token_properties(715 collection: &NonfungibleHandle<T>,716 sender: &T::CrossAccountId,717 token_id: TokenId,718 properties: impl Iterator<Item = Property>,719 is_token_create: bool,720 nesting_budget: &dyn Budget,721 ) -> DispatchResult {722 Self::modify_token_properties(723 collection,724 sender,725 token_id,726 properties.map(|p| (p.key, Some(p.value))),727 is_token_create,728 nesting_budget,729 )730 }731732 733 734 735 736 737 pub fn set_token_property(738 collection: &NonfungibleHandle<T>,739 sender: &T::CrossAccountId,740 token_id: TokenId,741 property: Property,742 nesting_budget: &dyn Budget,743 ) -> DispatchResult {744 let is_token_create = false;745746 Self::set_token_properties(747 collection,748 sender,749 token_id,750 [property].into_iter(),751 is_token_create,752 nesting_budget,753 )754 }755756 757 758 759 760 761 pub fn delete_token_properties(762 collection: &NonfungibleHandle<T>,763 sender: &T::CrossAccountId,764 token_id: TokenId,765 property_keys: impl Iterator<Item = PropertyKey>,766 nesting_budget: &dyn Budget,767 ) -> DispatchResult {768 let is_token_create = false;769770 Self::modify_token_properties(771 collection,772 sender,773 token_id,774 property_keys.into_iter().map(|key| (key, None)),775 is_token_create,776 nesting_budget,777 )778 }779780 781 782 783 784 785 pub fn delete_token_property(786 collection: &NonfungibleHandle<T>,787 sender: &T::CrossAccountId,788 token_id: TokenId,789 property_key: PropertyKey,790 nesting_budget: &dyn Budget,791 ) -> DispatchResult {792 Self::delete_token_properties(793 collection,794 sender,795 token_id,796 [property_key].into_iter(),797 nesting_budget,798 )799 }800801 802 pub fn set_collection_properties(803 collection: &NonfungibleHandle<T>,804 sender: &T::CrossAccountId,805 properties: Vec<Property>,806 ) -> DispatchResult {807 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)808 }809810 811 pub fn delete_collection_properties(812 collection: &CollectionHandle<T>,813 sender: &T::CrossAccountId,814 property_keys: Vec<PropertyKey>,815 ) -> DispatchResult {816 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)817 }818819 820 821 822 pub fn set_token_property_permissions(823 collection: &CollectionHandle<T>,824 sender: &T::CrossAccountId,825 property_permissions: Vec<PropertyKeyPermission>,826 ) -> DispatchResult {827 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)828 }829830 831 832 833 pub fn set_property_permission(834 collection: &CollectionHandle<T>,835 sender: &T::CrossAccountId,836 permission: PropertyKeyPermission,837 ) -> DispatchResult {838 <PalletCommon<T>>::set_property_permission(collection, sender, permission)839 }840841 842 843 844 845 846 847 848 849 850 pub fn transfer(851 collection: &NonfungibleHandle<T>,852 from: &T::CrossAccountId,853 to: &T::CrossAccountId,854 token: TokenId,855 nesting_budget: &dyn Budget,856 ) -> DispatchResult {857 ensure!(858 collection.limits.transfers_enabled(),859 <CommonError<T>>::TransferNotAllowed860 );861862 let token_data =863 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;864 ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);865866 if collection.permissions.access() == AccessMode::AllowList {867 collection.check_allowlist(from)?;868 collection.check_allowlist(to)?;869 }870 <PalletCommon<T>>::ensure_correct_receiver(to)?;871872 let balance_from = <AccountBalance<T>>::get((collection.id, from))873 .checked_sub(1)874 .ok_or(<CommonError<T>>::TokenValueTooLow)?;875 let balance_to = if from != to {876 let balance_to = <AccountBalance<T>>::get((collection.id, to))877 .checked_add(1)878 .ok_or(ArithmeticError::Overflow)?;879880 ensure!(881 balance_to < collection.limits.account_token_ownership_limit(),882 <CommonError<T>>::AccountTokenLimitExceeded,883 );884885 Some(balance_to)886 } else {887 None888 };889890 <PalletStructure<T>>::nest_if_sent_to_token(891 from.clone(),892 to,893 collection.id,894 token,895 nesting_budget,896 )?;897898 899900 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);901902 <TokenData<T>>::insert(903 (collection.id, token),904 ItemData {905 owner: to.clone(),906 ..token_data907 },908 );909910 if let Some(balance_to) = balance_to {911 912 if balance_from == 0 {913 <AccountBalance<T>>::remove((collection.id, from));914 } else {915 <AccountBalance<T>>::insert((collection.id, from), balance_from);916 }917 <AccountBalance<T>>::insert((collection.id, to), balance_to);918 <Owned<T>>::remove((collection.id, from, token));919 <Owned<T>>::insert((collection.id, to, token), true);920 }921 Self::set_allowance_unchecked(collection, from, token, None, true);922923 <PalletEvm<T>>::deposit_log(924 ERC721Events::Transfer {925 from: *from.as_eth(),926 to: *to.as_eth(),927 token_id: token.into(),928 }929 .to_log(collection_id_to_address(collection.id)),930 );931 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(932 collection.id,933 token,934 from.clone(),935 to.clone(),936 1,937 ));938 Ok(())939 }940941 942 943 944 945 946 947 948 949 950 951 pub fn create_multiple_items(952 collection: &NonfungibleHandle<T>,953 sender: &T::CrossAccountId,954 data: Vec<CreateItemData<T>>,955 nesting_budget: &dyn Budget,956 ) -> DispatchResult {957 if !collection.is_owner_or_admin(sender) {958 ensure!(959 collection.permissions.mint_mode(),960 <CommonError<T>>::PublicMintingNotAllowed961 );962 collection.check_allowlist(sender)?;963964 for item in data.iter() {965 collection.check_allowlist(&item.owner)?;966 }967 }968969 for data in data.iter() {970 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;971 }972973 let first_token = <TokensMinted<T>>::get(collection.id);974 let tokens_minted = first_token975 .checked_add(data.len() as u32)976 .ok_or(ArithmeticError::Overflow)?;977 ensure!(978 tokens_minted <= collection.limits.token_limit(),979 <CommonError<T>>::CollectionTokenLimitExceeded980 );981982 let mut balances = BTreeMap::new();983 for data in &data {984 let balance = balances985 .entry(&data.owner)986 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));987 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;988989 ensure!(990 *balance <= collection.limits.account_token_ownership_limit(),991 <CommonError<T>>::AccountTokenLimitExceeded,992 );993 }994995 for (i, data) in data.iter().enumerate() {996 let token = TokenId(first_token + i as u32 + 1);997998 <PalletStructure<T>>::check_nesting(999 sender.clone(),1000 &data.owner,1001 collection.id,1002 token,1003 nesting_budget,1004 )?;1005 }10061007 10081009 with_transaction(|| {1010 for (i, data) in data.iter().enumerate() {1011 let token = first_token + i as u32 + 1;10121013 <TokenData<T>>::insert(1014 (collection.id, token),1015 ItemData {1016 1017 owner: data.owner.clone(),1018 },1019 );10201021 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(1022 &data.owner,1023 collection.id,1024 TokenId(token),1025 );10261027 if let Err(e) = Self::set_token_properties(1028 collection,1029 sender,1030 TokenId(token),1031 data.properties.clone().into_iter(),1032 true,1033 nesting_budget,1034 ) {1035 return TransactionOutcome::Rollback(Err(e));1036 }1037 }1038 TransactionOutcome::Commit(Ok(()))1039 })?;10401041 <TokensMinted<T>>::insert(collection.id, tokens_minted);1042 for (account, balance) in balances {1043 <AccountBalance<T>>::insert((collection.id, account), balance);1044 }1045 for (i, data) in data.into_iter().enumerate() {1046 let token = first_token + i as u32 + 1;1047 <Owned<T>>::insert((collection.id, &data.owner, token), true);10481049 <PalletEvm<T>>::deposit_log(1050 ERC721Events::Transfer {1051 from: H160::default(),1052 to: *data.owner.as_eth(),1053 token_id: token.into(),1054 }1055 .to_log(collection_id_to_address(collection.id)),1056 );1057 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1058 collection.id,1059 TokenId(token),1060 data.owner.clone(),1061 1,1062 ));1063 }1064 Ok(())1065 }10661067 pub fn set_allowance_unchecked(1068 collection: &NonfungibleHandle<T>,1069 sender: &T::CrossAccountId,1070 token: TokenId,1071 spender: Option<&T::CrossAccountId>,1072 assume_implicit_eth: bool,1073 ) {1074 if let Some(spender) = spender {1075 let old_spender = <Allowance<T>>::get((collection.id, token));1076 <Allowance<T>>::insert((collection.id, token), spender);1077 1078 1079 <PalletEvm<T>>::deposit_log(1080 ERC721Events::Approval {1081 owner: *sender.as_eth(),1082 approved: *spender.as_eth(),1083 token_id: token.into(),1084 }1085 .to_log(collection_id_to_address(collection.id)),1086 );1087 1088 1089 if old_spender.as_ref() != Some(spender) {1090 if let Some(old_owner) = old_spender {1091 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1092 collection.id,1093 token,1094 sender.clone(),1095 old_owner,1096 0,1097 ));1098 }1099 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1100 collection.id,1101 token,1102 sender.clone(),1103 spender.clone(),1104 1,1105 ));1106 }1107 } else {1108 let old_spender = <Allowance<T>>::take((collection.id, token));1109 if !assume_implicit_eth {1110 1111 1112 <PalletEvm<T>>::deposit_log(1113 ERC721Events::Approval {1114 owner: *sender.as_eth(),1115 approved: H160::default(),1116 token_id: token.into(),1117 }1118 .to_log(collection_id_to_address(collection.id)),1119 );1120 }1121 1122 1123 if let Some(old_spender) = old_spender {1124 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1125 collection.id,1126 token,1127 sender.clone(),1128 old_spender,1129 0,1130 ));1131 }1132 }1133 }11341135 1136 1137 1138 pub fn set_allowance(1139 collection: &NonfungibleHandle<T>,1140 sender: &T::CrossAccountId,1141 token: TokenId,1142 spender: Option<&T::CrossAccountId>,1143 ) -> DispatchResult {1144 if collection.permissions.access() == AccessMode::AllowList {1145 collection.check_allowlist(sender)?;1146 if let Some(spender) = spender {1147 collection.check_allowlist(spender)?;1148 }1149 }11501151 if let Some(spender) = spender {1152 <PalletCommon<T>>::ensure_correct_receiver(spender)?;1153 }11541155 let token_data =1156 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1157 if &token_data.owner != sender {1158 ensure!(1159 collection.ignores_owned_amount(sender),1160 <CommonError<T>>::CantApproveMoreThanOwned1161 );1162 }11631164 11651166 Self::set_allowance_unchecked(collection, sender, token, spender, false);1167 Ok(())1168 }11691170 1171 fn check_allowed(1172 collection: &NonfungibleHandle<T>,1173 spender: &T::CrossAccountId,1174 from: &T::CrossAccountId,1175 token: TokenId,1176 nesting_budget: &dyn Budget,1177 ) -> DispatchResult {1178 if spender.conv_eq(from) {1179 return Ok(());1180 }1181 if collection.permissions.access() == AccessMode::AllowList {1182 1183 collection.check_allowlist(spender)?;1184 }11851186 if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {1187 return Ok(());1188 }11891190 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1191 ensure!(1192 <PalletStructure<T>>::check_indirectly_owned(1193 spender.clone(),1194 source.0,1195 source.1,1196 None,1197 nesting_budget1198 )?,1199 <CommonError<T>>::ApprovedValueTooLow,1200 );1201 return Ok(());1202 }1203 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1204 return Ok(());1205 }1206 ensure!(1207 collection.ignores_allowance(spender),1208 <CommonError<T>>::ApprovedValueTooLow1209 );1210 Ok(())1211 }12121213 1214 1215 1216 1217 1218 1219 pub fn transfer_from(1220 collection: &NonfungibleHandle<T>,1221 spender: &T::CrossAccountId,1222 from: &T::CrossAccountId,1223 to: &T::CrossAccountId,1224 token: TokenId,1225 nesting_budget: &dyn Budget,1226 ) -> DispatchResult {1227 Self::check_allowed(collection, spender, from, token, nesting_budget)?;12281229 12301231 1232 Self::transfer(collection, from, to, token, nesting_budget)1233 }12341235 1236 1237 1238 1239 1240 1241 pub fn burn_from(1242 collection: &NonfungibleHandle<T>,1243 spender: &T::CrossAccountId,1244 from: &T::CrossAccountId,1245 token: TokenId,1246 nesting_budget: &dyn Budget,1247 ) -> DispatchResult {1248 Self::check_allowed(collection, spender, from, token, nesting_budget)?;12491250 12511252 Self::burn(collection, from, token)1253 }12541255 1256 1257 pub fn check_nesting(1258 handle: &NonfungibleHandle<T>,1259 sender: T::CrossAccountId,1260 from: (CollectionId, TokenId),1261 under: TokenId,1262 nesting_budget: &dyn Budget,1263 ) -> DispatchResult {1264 let nesting = handle.permissions.nesting();12651266 #[cfg(not(feature = "runtime-benchmarks"))]1267 let permissive = false;1268 #[cfg(feature = "runtime-benchmarks")]1269 let permissive = nesting.permissive;12701271 if permissive {1272 1273 } else if nesting.token_owner1274 && <PalletStructure<T>>::check_indirectly_owned(1275 sender.clone(),1276 handle.id,1277 under,1278 Some(from),1279 nesting_budget,1280 )? {1281 1282 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1283 1284 } else {1285 fail!(<CommonError<T>>::UserIsNotAllowedToNest);1286 }12871288 if let Some(whitelist) = &nesting.restricted {1289 ensure!(1290 whitelist.contains(&from.0),1291 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1292 );1293 }1294 Ok(())1295 }12961297 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1298 <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1299 }13001301 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1302 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1303 }13041305 fn collection_has_tokens(collection_id: CollectionId) -> bool {1306 <TokenData<T>>::iter_prefix((collection_id,))1307 .next()1308 .is_some()1309 }13101311 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1312 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1313 .next()1314 .is_some()1315 }13161317 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1318 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1319 .map(|((child_collection_id, child_id), _)| TokenChild {1320 collection: child_collection_id,1321 token: child_id,1322 })1323 .collect()1324 }13251326 1327 1328 1329 1330 1331 pub fn create_item(1332 collection: &NonfungibleHandle<T>,1333 sender: &T::CrossAccountId,1334 data: CreateItemData<T>,1335 nesting_budget: &dyn Budget,1336 ) -> DispatchResult {1337 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1338 }1339}