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 dispatch::{PostDispatchInfo, Pays},101};102use up_data_structs::{103 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,104 mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey, PropertyValue,105 PropertyKeyPermission, PropertyScope, TrySetProperty, TokenChild, AuxPropertyValue,106 PropertiesPermissionMap, TokenProperties as TokenPropertiesT,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, SelfWeightOf as PalletCommonWeightOf,112 weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info, SetPropertyMode,113};114use pallet_structure::{Pallet as PalletStructure, Error as StructureError};115use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};116use sp_core::{Get, H160};117use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};118use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};119use core::ops::Deref;120use codec::{Encode, Decode, MaxEncodedLen};121use scale_info::TypeInfo;122123pub use pallet::*;124use weights::WeightInfo;125#[cfg(feature = "runtime-benchmarks")]126pub mod benchmarking;127pub mod common;128pub mod erc;129pub mod weights;130131pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::Config>::CrossAccountId>;132pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;133134135136#[struct_versioning::versioned(version = 2, upper)]137#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]138pub struct ItemData<CrossAccountId> {139 #[version(..2)]140 pub const_data: BoundedVec<u8, CustomDataLimit>,141142 #[version(..2)]143 pub variable_data: BoundedVec<u8, CustomDataLimit>,144145 pub owner: CrossAccountId,146}147148#[frame_support::pallet]149pub mod pallet {150 use super::*;151 use frame_support::{152 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,153 };154 use up_data_structs::{CollectionId, TokenId};155 use super::weights::WeightInfo;156157 #[pallet::error]158 pub enum Error<T> {159 160 NotNonfungibleDataUsedToMintFungibleCollectionToken,161 162 NonfungibleItemsHaveNoAmount,163 164 CantBurnNftWithChildren,165 }166167 #[pallet::config]168 pub trait Config:169 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config170 {171 type WeightInfo: WeightInfo;172 }173174 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);175176 #[pallet::pallet]177 #[pallet::storage_version(STORAGE_VERSION)]178 pub struct Pallet<T>(_);179180 181 #[pallet::storage]182 pub type TokensMinted<T: Config> =183 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;184185 186 #[pallet::storage]187 pub type TokensBurnt<T: Config> =188 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;189190 191 #[pallet::storage]192 pub type TokenData<T: Config> = StorageNMap<193 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),194 Value = ItemData<T::CrossAccountId>,195 QueryKind = OptionQuery,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 = TokenPropertiesT,204 QueryKind = ValueQuery,205 >;206207 208 209 210 211 212 213 214 215 216 #[pallet::storage]217 #[pallet::getter(fn token_aux_property)]218 pub type TokenAuxProperties<T: Config> = StorageNMap<219 Key = (220 Key<Twox64Concat, CollectionId>,221 Key<Twox64Concat, TokenId>,222 Key<Twox64Concat, PropertyScope>,223 Key<Twox64Concat, PropertyKey>,224 ),225 Value = AuxPropertyValue,226 QueryKind = OptionQuery,227 >;228229 230 #[pallet::storage]231 pub type Owned<T: Config> = StorageNMap<232 Key = (233 Key<Twox64Concat, CollectionId>,234 Key<Blake2_128Concat, T::CrossAccountId>,235 Key<Twox64Concat, TokenId>,236 ),237 Value = bool,238 QueryKind = ValueQuery,239 >;240241 242 #[pallet::storage]243 #[pallet::getter(fn token_children)]244 pub type TokenChildren<T: Config> = StorageNMap<245 Key = (246 Key<Twox64Concat, CollectionId>,247 Key<Twox64Concat, TokenId>,248 Key<Twox64Concat, (CollectionId, TokenId)>,249 ),250 Value = bool,251 QueryKind = ValueQuery,252 >;253254 255 #[pallet::storage]256 pub type AccountBalance<T: Config> = StorageNMap<257 Key = (258 Key<Twox64Concat, CollectionId>,259 Key<Blake2_128Concat, T::CrossAccountId>,260 ),261 Value = u32,262 QueryKind = ValueQuery,263 >;264265 266 #[pallet::storage]267 pub type Allowance<T: Config> = StorageNMap<268 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),269 Value = T::CrossAccountId,270 QueryKind = OptionQuery,271 >;272273 274 #[pallet::storage]275 pub type CollectionAllowance<T: Config> = StorageNMap<276 Key = (277 Key<Twox64Concat, CollectionId>,278 Key<Blake2_128Concat, T::CrossAccountId>,279 Key<Blake2_128Concat, T::CrossAccountId>,280 ),281 Value = bool,282 QueryKind = ValueQuery,283 >;284285 #[pallet::genesis_config]286 pub struct GenesisConfig<T>(PhantomData<T>);287288 #[cfg(feature = "std")]289 impl<T: Config> Default for GenesisConfig<T> {290 fn default() -> Self {291 Self(Default::default())292 }293 }294295 #[pallet::genesis_build]296 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {297 fn build(&self) {298 StorageVersion::new(1).put::<Pallet<T>>();299 }300 }301}302303pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);304impl<T: Config> NonfungibleHandle<T> {305 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {306 Self(inner)307 }308 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {309 self.0310 }311 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {312 &mut self.0313 }314}315316impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {317 fn recorder(&self) -> &SubstrateRecorder<T> {318 self.0.recorder()319 }320 fn into_recorder(self) -> SubstrateRecorder<T> {321 self.0.into_recorder()322 }323}324impl<T: Config> Deref for NonfungibleHandle<T> {325 type Target = pallet_common::CollectionHandle<T>;326327 fn deref(&self) -> &Self::Target {328 &self.0329 }330}331332impl<T: Config> Pallet<T> {333 334 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {335 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)336 }337338 339 340 341 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {342 <TokenData<T>>::contains_key((collection.id, token))343 }344345 346 347 348 pub fn set_scoped_token_property(349 collection_id: CollectionId,350 token_id: TokenId,351 scope: PropertyScope,352 property: Property,353 ) -> DispatchResult {354 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {355 properties.try_scoped_set(scope, property.key, property.value)356 })357 .map_err(<CommonError<T>>::from)?;358359 Ok(())360 }361362 363 pub fn set_scoped_token_properties(364 collection_id: CollectionId,365 token_id: TokenId,366 scope: PropertyScope,367 properties: impl Iterator<Item = Property>,368 ) -> DispatchResult {369 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {370 stored_properties.try_scoped_set_from_iter(scope, properties)371 })372 .map_err(<CommonError<T>>::from)?;373374 Ok(())375 }376377 378 379 380 pub fn try_mutate_token_aux_property<R, E>(381 collection_id: CollectionId,382 token_id: TokenId,383 scope: PropertyScope,384 key: PropertyKey,385 f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,386 ) -> Result<R, E> {387 <TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)388 }389390 391 pub fn remove_token_aux_property(392 collection_id: CollectionId,393 token_id: TokenId,394 scope: PropertyScope,395 key: PropertyKey,396 ) {397 <TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));398 }399400 401 402 403 pub fn iterate_token_aux_properties(404 collection_id: CollectionId,405 token_id: TokenId,406 scope: PropertyScope,407 ) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {408 <TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))409 }410411 412 pub fn current_token_id(collection_id: CollectionId) -> TokenId {413 TokenId(<TokensMinted<T>>::get(collection_id))414 }415}416417418impl<T: Config> Pallet<T> {419 420 421 422 423 424 pub fn init_collection(425 owner: T::CrossAccountId,426 payer: T::CrossAccountId,427 data: CreateCollectionData<T::CrossAccountId>,428 ) -> Result<CollectionId, DispatchError> {429 <PalletCommon<T>>::init_collection(owner, payer, data)430 }431432 433 434 435 436 pub fn destroy_collection(437 collection: NonfungibleHandle<T>,438 sender: &T::CrossAccountId,439 ) -> DispatchResult {440 let id = collection.id;441442 if Self::collection_has_tokens(id) {443 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());444 }445446 447448 PalletCommon::destroy_collection(collection.0, sender)?;449450 let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);451 let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);452 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);453 <TokensMinted<T>>::remove(id);454 <TokensBurnt<T>>::remove(id);455 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);456 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);457 let _ = <CollectionAllowance<T>>::clear_prefix((id,), u32::MAX, None);458 Ok(())459 }460461 462 463 464 465 466 467 468 469 470 pub fn burn(471 collection: &NonfungibleHandle<T>,472 sender: &T::CrossAccountId,473 token: TokenId,474 ) -> DispatchResult {475 let token_data =476 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;477 ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);478479 if collection.permissions.access() == AccessMode::AllowList {480 collection.check_allowlist(sender)?;481 }482483 if Self::token_has_children(collection.id, token) {484 return Err(<Error<T>>::CantBurnNftWithChildren.into());485 }486487 let burnt = <TokensBurnt<T>>::get(collection.id)488 .checked_add(1)489 .ok_or(ArithmeticError::Overflow)?;490491 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))492 .checked_sub(1)493 .ok_or(ArithmeticError::Overflow)?;494495 496497 if balance == 0 {498 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));499 } else {500 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);501 }502503 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);504505 <Owned<T>>::remove((collection.id, &token_data.owner, token));506 <TokensBurnt<T>>::insert(collection.id, burnt);507 <TokenData<T>>::remove((collection.id, token));508 <TokenProperties<T>>::remove((collection.id, token));509 let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);510 let old_spender = <Allowance<T>>::take((collection.id, token));511512 if let Some(old_spender) = old_spender {513 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(514 collection.id,515 token,516 token_data.owner.clone(),517 old_spender,518 0,519 ));520 }521522 <PalletEvm<T>>::deposit_log(523 ERC721Events::Transfer {524 from: *token_data.owner.as_eth(),525 to: H160::default(),526 token_id: token.into(),527 }528 .to_log(collection_id_to_address(collection.id)),529 );530 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(531 collection.id,532 token,533 token_data.owner,534 1,535 ));536 Ok(())537 }538539 540 541 542 543 544 545 #[transactional]546 pub fn burn_recursively(547 collection: &NonfungibleHandle<T>,548 sender: &T::CrossAccountId,549 token: TokenId,550 self_budget: &dyn Budget,551 breadth_budget: &dyn Budget,552 ) -> DispatchResultWithPostInfo {553 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);554555 let current_token_account =556 T::CrossTokenAddressMapping::token_to_address(collection.id, token);557558 let mut weight = Weight::zero();559560 561 562 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {563 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);564 let PostDispatchInfo { actual_weight, .. } =565 <PalletStructure<T>>::burn_item_recursively(566 current_token_account.clone(),567 collection,568 token,569 self_budget,570 breadth_budget,571 )?;572 if let Some(actual_weight) = actual_weight {573 weight = weight.saturating_add(actual_weight);574 }575 }576577 Self::burn(collection, sender, token)?;578 DispatchResultWithPostInfo::Ok(PostDispatchInfo {579 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),580 pays_fee: Pays::Yes,581 })582 }583584 585 586 587 588 589 590 591 592 593 594 595 #[transactional]596 fn modify_token_properties(597 collection: &NonfungibleHandle<T>,598 sender: &T::CrossAccountId,599 token_id: TokenId,600 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,601 mode: SetPropertyMode,602 nesting_budget: &dyn Budget,603 ) -> DispatchResult {604 let mut is_token_owner = pallet_common::LazyValue::new(|| {605 if let SetPropertyMode::NewToken {606 mint_target_is_sender,607 } = mode608 {609 return Ok(mint_target_is_sender);610 }611612 let is_owned = <PalletStructure<T>>::check_indirectly_owned(613 sender.clone(),614 collection.id,615 token_id,616 None,617 nesting_budget,618 )?;619620 Ok(is_owned)621 });622623 let mut is_token_exist =624 pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));625626 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));627628 <PalletCommon<T>>::modify_token_properties(629 collection,630 sender,631 token_id,632 &mut is_token_exist,633 properties_updates,634 stored_properties,635 &mut is_token_owner,636 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),637 erc::ERC721TokenEvent::TokenChanged {638 token_id: token_id.into(),639 }640 .to_log(T::ContractAddress::get()),641 )642 }643644 pub fn next_token_id(collection: &NonfungibleHandle<T>) -> Result<TokenId, DispatchError> {645 let next_token_id = <TokensMinted<T>>::get(collection.id)646 .checked_add(1)647 .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;648649 ensure!(650 collection.limits.token_limit() >= next_token_id,651 <CommonError<T>>::CollectionTokenLimitExceeded652 );653654 Ok(TokenId(next_token_id))655 }656657 658 659 660 661 662 pub fn set_token_properties(663 collection: &NonfungibleHandle<T>,664 sender: &T::CrossAccountId,665 token_id: TokenId,666 properties: impl Iterator<Item = Property>,667 mode: SetPropertyMode,668 nesting_budget: &dyn Budget,669 ) -> DispatchResult {670 Self::modify_token_properties(671 collection,672 sender,673 token_id,674 properties.map(|p| (p.key, Some(p.value))),675 mode,676 nesting_budget,677 )678 }679680 681 682 683 684 685 pub fn set_token_property(686 collection: &NonfungibleHandle<T>,687 sender: &T::CrossAccountId,688 token_id: TokenId,689 property: Property,690 nesting_budget: &dyn Budget,691 ) -> DispatchResult {692 Self::set_token_properties(693 collection,694 sender,695 token_id,696 [property].into_iter(),697 SetPropertyMode::ExistingToken,698 nesting_budget,699 )700 }701702 703 704 705 706 707 pub fn delete_token_properties(708 collection: &NonfungibleHandle<T>,709 sender: &T::CrossAccountId,710 token_id: TokenId,711 property_keys: impl Iterator<Item = PropertyKey>,712 nesting_budget: &dyn Budget,713 ) -> DispatchResult {714 Self::modify_token_properties(715 collection,716 sender,717 token_id,718 property_keys.into_iter().map(|key| (key, None)),719 SetPropertyMode::ExistingToken,720 nesting_budget,721 )722 }723724 725 726 727 728 729 pub fn delete_token_property(730 collection: &NonfungibleHandle<T>,731 sender: &T::CrossAccountId,732 token_id: TokenId,733 property_key: PropertyKey,734 nesting_budget: &dyn Budget,735 ) -> DispatchResult {736 Self::delete_token_properties(737 collection,738 sender,739 token_id,740 [property_key].into_iter(),741 nesting_budget,742 )743 }744745 746 pub fn set_collection_properties(747 collection: &NonfungibleHandle<T>,748 sender: &T::CrossAccountId,749 properties: Vec<Property>,750 ) -> DispatchResult {751 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())752 }753754 755 pub fn delete_collection_properties(756 collection: &CollectionHandle<T>,757 sender: &T::CrossAccountId,758 property_keys: Vec<PropertyKey>,759 ) -> DispatchResult {760 <PalletCommon<T>>::delete_collection_properties(761 collection,762 sender,763 property_keys.into_iter(),764 )765 }766767 768 769 770 pub fn set_token_property_permissions(771 collection: &CollectionHandle<T>,772 sender: &T::CrossAccountId,773 property_permissions: Vec<PropertyKeyPermission>,774 ) -> DispatchResult {775 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)776 }777778 779 780 781 pub fn set_scoped_token_property_permissions(782 collection: &CollectionHandle<T>,783 sender: &T::CrossAccountId,784 scope: PropertyScope,785 property_permissions: Vec<PropertyKeyPermission>,786 ) -> DispatchResult {787 <PalletCommon<T>>::set_scoped_token_property_permissions(788 collection,789 sender,790 scope,791 property_permissions,792 )793 }794795 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {796 <PalletCommon<T>>::property_permissions(collection_id)797 }798799 pub fn check_token_immediate_ownership(800 collection: &NonfungibleHandle<T>,801 token: TokenId,802 possible_owner: &T::CrossAccountId,803 ) -> DispatchResult {804 let token_data =805 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;806 ensure!(807 &token_data.owner == possible_owner,808 <CommonError<T>>::NoPermission809 );810 Ok(())811 }812813 814 815 816 817 818 819 820 821 822 pub fn transfer(823 collection: &NonfungibleHandle<T>,824 from: &T::CrossAccountId,825 to: &T::CrossAccountId,826 token: TokenId,827 nesting_budget: &dyn Budget,828 ) -> DispatchResultWithPostInfo {829 ensure!(830 collection.limits.transfers_enabled(),831 <CommonError<T>>::TransferNotAllowed832 );833834 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();835 let token_data =836 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;837 ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);838839 if collection.permissions.access() == AccessMode::AllowList {840 collection.check_allowlist(from)?;841 collection.check_allowlist(to)?;842 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;843 }844 <PalletCommon<T>>::ensure_correct_receiver(to)?;845846 let balance_from = <AccountBalance<T>>::get((collection.id, from))847 .checked_sub(1)848 .ok_or(<CommonError<T>>::TokenValueTooLow)?;849 let balance_to = if from != to {850 let balance_to = <AccountBalance<T>>::get((collection.id, to))851 .checked_add(1)852 .ok_or(ArithmeticError::Overflow)?;853854 ensure!(855 balance_to < collection.limits.account_token_ownership_limit(),856 <CommonError<T>>::AccountTokenLimitExceeded,857 );858859 Some(balance_to)860 } else {861 None862 };863864 <PalletStructure<T>>::nest_if_sent_to_token(865 from.clone(),866 to,867 collection.id,868 token,869 nesting_budget,870 )?;871872 873874 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);875876 <TokenData<T>>::insert((collection.id, token), ItemData { owner: to.clone() });877878 if let Some(balance_to) = balance_to {879 880 if balance_from == 0 {881 <AccountBalance<T>>::remove((collection.id, from));882 } else {883 <AccountBalance<T>>::insert((collection.id, from), balance_from);884 }885 <AccountBalance<T>>::insert((collection.id, to), balance_to);886 <Owned<T>>::remove((collection.id, from, token));887 <Owned<T>>::insert((collection.id, to, token), true);888 }889 Self::set_allowance_unchecked(collection, from, token, None, true);890891 <PalletEvm<T>>::deposit_log(892 ERC721Events::Transfer {893 from: *from.as_eth(),894 to: *to.as_eth(),895 token_id: token.into(),896 }897 .to_log(collection_id_to_address(collection.id)),898 );899 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(900 collection.id,901 token,902 from.clone(),903 to.clone(),904 1,905 ));906907 Ok(PostDispatchInfo {908 actual_weight: Some(actual_weight),909 pays_fee: Pays::Yes,910 })911 }912913 914 915 916 917 918 919 920 921 922 923 pub fn create_multiple_items(924 collection: &NonfungibleHandle<T>,925 sender: &T::CrossAccountId,926 data: Vec<CreateItemData<T>>,927 nesting_budget: &dyn Budget,928 ) -> DispatchResult {929 if !collection.is_owner_or_admin(sender) {930 ensure!(931 collection.permissions.mint_mode(),932 <CommonError<T>>::PublicMintingNotAllowed933 );934 collection.check_allowlist(sender)?;935936 for item in data.iter() {937 collection.check_allowlist(&item.owner)?;938 }939 }940941 for data in data.iter() {942 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;943 }944945 let first_token = <TokensMinted<T>>::get(collection.id);946 let tokens_minted = first_token947 .checked_add(data.len() as u32)948 .ok_or(ArithmeticError::Overflow)?;949 ensure!(950 tokens_minted <= collection.limits.token_limit(),951 <CommonError<T>>::CollectionTokenLimitExceeded952 );953954 let mut balances = BTreeMap::new();955 for data in &data {956 let balance = balances957 .entry(&data.owner)958 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));959 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;960961 ensure!(962 *balance <= collection.limits.account_token_ownership_limit(),963 <CommonError<T>>::AccountTokenLimitExceeded,964 );965 }966967 for (i, data) in data.iter().enumerate() {968 let token = TokenId(first_token + i as u32 + 1);969970 <PalletStructure<T>>::check_nesting(971 sender.clone(),972 &data.owner,973 collection.id,974 token,975 nesting_budget,976 )?;977 }978979 980981 with_transaction(|| {982 for (i, data) in data.iter().enumerate() {983 let token = first_token + i as u32 + 1;984985 <TokenData<T>>::insert(986 (collection.id, token),987 ItemData {988 989 owner: data.owner.clone(),990 },991 );992993 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(994 &data.owner,995 collection.id,996 TokenId(token),997 );998999 if let Err(e) = Self::set_token_properties(1000 collection,1001 sender,1002 TokenId(token),1003 data.properties.clone().into_iter(),1004 SetPropertyMode::NewToken {1005 mint_target_is_sender: sender.conv_eq(&data.owner),1006 },1007 nesting_budget,1008 ) {1009 return TransactionOutcome::Rollback(Err(e));1010 }1011 }1012 TransactionOutcome::Commit(Ok(()))1013 })?;10141015 <TokensMinted<T>>::insert(collection.id, tokens_minted);1016 for (account, balance) in balances {1017 <AccountBalance<T>>::insert((collection.id, account), balance);1018 }1019 for (i, data) in data.into_iter().enumerate() {1020 let token = first_token + i as u32 + 1;1021 <Owned<T>>::insert((collection.id, &data.owner, token), true);10221023 <PalletEvm<T>>::deposit_log(1024 ERC721Events::Transfer {1025 from: H160::default(),1026 to: *data.owner.as_eth(),1027 token_id: token.into(),1028 }1029 .to_log(collection_id_to_address(collection.id)),1030 );1031 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1032 collection.id,1033 TokenId(token),1034 data.owner.clone(),1035 1,1036 ));1037 }1038 Ok(())1039 }10401041 pub fn set_allowance_unchecked(1042 collection: &NonfungibleHandle<T>,1043 sender: &T::CrossAccountId,1044 token: TokenId,1045 spender: Option<&T::CrossAccountId>,1046 assume_implicit_eth: bool,1047 ) {1048 if let Some(spender) = spender {1049 let old_spender = <Allowance<T>>::get((collection.id, token));1050 <Allowance<T>>::insert((collection.id, token), spender);1051 1052 1053 <PalletEvm<T>>::deposit_log(1054 ERC721Events::Approval {1055 owner: *sender.as_eth(),1056 approved: *spender.as_eth(),1057 token_id: token.into(),1058 }1059 .to_log(collection_id_to_address(collection.id)),1060 );1061 1062 1063 if old_spender.as_ref() != Some(spender) {1064 if let Some(old_owner) = old_spender {1065 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1066 collection.id,1067 token,1068 sender.clone(),1069 old_owner,1070 0,1071 ));1072 }1073 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1074 collection.id,1075 token,1076 sender.clone(),1077 spender.clone(),1078 1,1079 ));1080 }1081 } else {1082 let old_spender = <Allowance<T>>::take((collection.id, token));1083 if !assume_implicit_eth {1084 1085 1086 <PalletEvm<T>>::deposit_log(1087 ERC721Events::Approval {1088 owner: *sender.as_eth(),1089 approved: H160::default(),1090 token_id: token.into(),1091 }1092 .to_log(collection_id_to_address(collection.id)),1093 );1094 }1095 1096 1097 if let Some(old_spender) = old_spender {1098 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1099 collection.id,1100 token,1101 sender.clone(),1102 old_spender,1103 0,1104 ));1105 }1106 }1107 }11081109 pub fn get_allowance(1110 collection: &NonfungibleHandle<T>,1111 token_id: TokenId,1112 ) -> Result<Option<T::CrossAccountId>, DispatchError> {1113 ensure!(1114 <TokenData<T>>::get((collection.id, token_id)).is_some(),1115 <CommonError<T>>::TokenNotFound1116 );1117 Ok(<Allowance<T>>::get((collection.id, token_id)))1118 }11191120 1121 1122 1123 pub fn set_allowance(1124 collection: &NonfungibleHandle<T>,1125 sender: &T::CrossAccountId,1126 token: TokenId,1127 spender: Option<&T::CrossAccountId>,1128 ) -> DispatchResult {1129 if collection.permissions.access() == AccessMode::AllowList {1130 collection.check_allowlist(sender)?;1131 if let Some(spender) = spender {1132 collection.check_allowlist(spender)?;1133 }1134 }11351136 if let Some(spender) = spender {1137 <PalletCommon<T>>::ensure_correct_receiver(spender)?;1138 }11391140 let token_data =1141 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1142 if &token_data.owner != sender {1143 ensure!(1144 collection.ignores_owned_amount(sender),1145 <CommonError<T>>::CantApproveMoreThanOwned1146 );1147 }11481149 11501151 Self::set_allowance_unchecked(collection, sender, token, spender, false);1152 Ok(())1153 }11541155 1156 1157 1158 1159 1160 pub fn set_allowance_from(1161 collection: &NonfungibleHandle<T>,1162 sender: &T::CrossAccountId,1163 from: &T::CrossAccountId,1164 token: TokenId,1165 to: Option<&T::CrossAccountId>,1166 ) -> DispatchResult {1167 if collection.permissions.access() == AccessMode::AllowList {1168 collection.check_allowlist(sender)?;1169 collection.check_allowlist(from)?;1170 if let Some(to) = to {1171 collection.check_allowlist(to)?;1172 }1173 }11741175 if let Some(to) = to {1176 <PalletCommon<T>>::ensure_correct_receiver(to)?;1177 }11781179 ensure!(1180 sender.conv_eq(from),1181 <CommonError<T>>::AddressIsNotEthMirror1182 );11831184 let token_data =1185 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1186 if token_data.owner != *from {1187 ensure!(1188 collection.limits.owner_can_transfer()1189 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),1190 <CommonError<T>>::CantApproveMoreThanOwned1191 );1192 }11931194 11951196 Self::set_allowance_unchecked(collection, from, token, to, false);1197 Ok(())1198 }11991200 1201 fn check_allowed(1202 collection: &NonfungibleHandle<T>,1203 spender: &T::CrossAccountId,1204 from: &T::CrossAccountId,1205 token: TokenId,1206 nesting_budget: &dyn Budget,1207 ) -> DispatchResult {1208 if spender.conv_eq(from) {1209 return Ok(());1210 }1211 if collection.permissions.access() == AccessMode::AllowList {1212 1213 collection.check_allowlist(spender)?;1214 }12151216 if collection.ignores_token_restrictions(spender) {1217 return Ok(());1218 }12191220 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1221 ensure!(1222 <PalletStructure<T>>::check_indirectly_owned(1223 spender.clone(),1224 source.0,1225 source.1,1226 None,1227 nesting_budget1228 )?,1229 <CommonError<T>>::ApprovedValueTooLow,1230 );1231 return Ok(());1232 }1233 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1234 return Ok(());1235 }1236 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1237 return Ok(());1238 }12391240 Err(<CommonError<T>>::ApprovedValueTooLow.into())1241 }12421243 1244 1245 1246 1247 1248 1249 pub fn transfer_from(1250 collection: &NonfungibleHandle<T>,1251 spender: &T::CrossAccountId,1252 from: &T::CrossAccountId,1253 to: &T::CrossAccountId,1254 token: TokenId,1255 nesting_budget: &dyn Budget,1256 ) -> DispatchResultWithPostInfo {1257 Self::check_allowed(collection, spender, from, token, nesting_budget)?;12581259 12601261 1262 let mut result = Self::transfer(collection, from, to, token, nesting_budget);1263 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());1264 result1265 }12661267 1268 1269 1270 1271 1272 1273 pub fn burn_from(1274 collection: &NonfungibleHandle<T>,1275 spender: &T::CrossAccountId,1276 from: &T::CrossAccountId,1277 token: TokenId,1278 nesting_budget: &dyn Budget,1279 ) -> DispatchResult {1280 Self::check_allowed(collection, spender, from, token, nesting_budget)?;12811282 12831284 Self::burn(collection, from, token)1285 }12861287 1288 1289 pub fn check_nesting(1290 handle: &NonfungibleHandle<T>,1291 sender: T::CrossAccountId,1292 from: (CollectionId, TokenId),1293 under: TokenId,1294 nesting_budget: &dyn Budget,1295 ) -> DispatchResult {1296 let nesting = handle.permissions.nesting();12971298 #[cfg(not(feature = "runtime-benchmarks"))]1299 let permissive = false;1300 #[cfg(feature = "runtime-benchmarks")]1301 let permissive = nesting.permissive;13021303 if permissive {1304 ensure!(1305 <TokenData<T>>::contains_key((handle.id, under)),1306 <CommonError<T>>::TokenNotFound1307 );1308 } else if nesting.token_owner1309 && <PalletStructure<T>>::check_indirectly_owned(1310 sender.clone(),1311 handle.id,1312 under,1313 Some(from),1314 nesting_budget,1315 )? {1316 1317 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1318 1319 let _ = <PalletStructure<T>>::get_checked_topmost_owner(1320 handle.id,1321 under,1322 Some(from),1323 nesting_budget,1324 )?1325 .ok_or(<CommonError<T>>::TokenNotFound)?;1326 } else {1327 fail!(<CommonError<T>>::UserIsNotAllowedToNest);1328 }13291330 if let Some(whitelist) = &nesting.restricted {1331 ensure!(1332 whitelist.contains(&from.0),1333 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1334 );1335 }1336 Ok(())1337 }13381339 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1340 if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1341 <TokenChildren<T>>::insert((under.0, under.1, to_nest), true);1342 }1343 }13441345 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1346 if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1347 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1348 }1349 }13501351 fn collection_has_tokens(collection_id: CollectionId) -> bool {1352 <TokenData<T>>::iter_prefix((collection_id,))1353 .next()1354 .is_some()1355 }13561357 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1358 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1359 .next()1360 .is_some()1361 }13621363 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1364 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1365 .map(|((child_collection_id, child_id), _)| TokenChild {1366 collection: child_collection_id,1367 token: child_id,1368 })1369 .collect()1370 }13711372 1373 1374 1375 1376 1377 pub fn create_item(1378 collection: &NonfungibleHandle<T>,1379 sender: &T::CrossAccountId,1380 data: CreateItemData<T>,1381 nesting_budget: &dyn Budget,1382 ) -> DispatchResult {1383 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1384 }13851386 1387 1388 1389 1390 1391 1392 pub fn set_allowance_for_all(1393 collection: &NonfungibleHandle<T>,1394 owner: &T::CrossAccountId,1395 operator: &T::CrossAccountId,1396 approve: bool,1397 ) -> DispatchResult {1398 <PalletCommon<T>>::set_allowance_for_all(1399 collection,1400 owner,1401 operator,1402 approve,1403 || <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),1404 ERC721Events::ApprovalForAll {1405 owner: *owner.as_eth(),1406 operator: *operator.as_eth(),1407 approved: approve,1408 }1409 .to_log(collection_id_to_address(collection.id)),1410 )1411 }14121413 1414 pub fn allowance_for_all(1415 collection: &NonfungibleHandle<T>,1416 owner: &T::CrossAccountId,1417 operator: &T::CrossAccountId,1418 ) -> bool {1419 <CollectionAllowance<T>>::get((collection.id, owner, operator))1420 }14211422 pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {1423 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1424 properties.recompute_consumed_space();1425 });14261427 Ok(())1428 }1429}