12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091#![cfg_attr(not(feature = "std"), no_std)]9293use core::ops::Deref;9495use erc::ERC721Events;96use evm_coder::ToLog;97use frame_support::{98 dispatch::{Pays, PostDispatchInfo},99 ensure, fail,100 pallet_prelude::*,101 storage::with_transaction,102 transactional, BoundedVec,103};104pub use pallet::*;105use pallet_common::{106 eth::collection_id_to_address, helpers::add_weight_to_post_info,107 weights::WeightInfo as CommonWeightInfo, CollectionHandle, Error as CommonError,108 Event as CommonEvent, Pallet as PalletCommon, SelfWeightOf as PalletCommonWeightOf,109};110use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};111use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};112use pallet_structure::{Error as StructureError, Pallet as PalletStructure};113use parity_scale_codec::{Decode, Encode, MaxEncodedLen};114use scale_info::TypeInfo;115use sp_core::{Get, H160};116use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};117use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};118use up_data_structs::{119 budget::Budget, mapping::TokenAddressMapping, AccessMode, AuxPropertyValue, CollectionId,120 CreateCollectionData, CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property,121 PropertyKey, PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,122 TokenProperties as TokenPropertiesT,123};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 frame_support::{151 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat, Twox64Concat,152 };153 use up_data_structs::{CollectionId, TokenId};154155 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 = OptionQuery,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 impl<T: Config> Default for GenesisConfig<T> {289 fn default() -> Self {290 Self(Default::default())291 }292 }293294 #[pallet::genesis_build]295 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {296 fn build(&self) {297 StorageVersion::new(1).put::<Pallet<T>>();298 }299 }300}301302pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);303impl<T: Config> NonfungibleHandle<T> {304 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {305 Self(inner)306 }307 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {308 self.0309 }310 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {311 &mut self.0312 }313}314315impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {316 fn recorder(&self) -> &SubstrateRecorder<T> {317 self.0.recorder()318 }319 fn into_recorder(self) -> SubstrateRecorder<T> {320 self.0.into_recorder()321 }322}323impl<T: Config> Deref for NonfungibleHandle<T> {324 type Target = pallet_common::CollectionHandle<T>;325326 fn deref(&self) -> &Self::Target {327 &self.0328 }329}330331impl<T: Config> Pallet<T> {332 333 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {334 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)335 }336337 338 339 340 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {341 <TokenData<T>>::contains_key((collection.id, token))342 }343344 345 346 347 pub fn try_mutate_token_aux_property<R, E>(348 collection_id: CollectionId,349 token_id: TokenId,350 scope: PropertyScope,351 key: PropertyKey,352 f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,353 ) -> Result<R, E> {354 <TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)355 }356357 358 pub fn remove_token_aux_property(359 collection_id: CollectionId,360 token_id: TokenId,361 scope: PropertyScope,362 key: PropertyKey,363 ) {364 <TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));365 }366367 368 369 370 pub fn iterate_token_aux_properties(371 collection_id: CollectionId,372 token_id: TokenId,373 scope: PropertyScope,374 ) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {375 <TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))376 }377378 379 pub fn current_token_id(collection_id: CollectionId) -> TokenId {380 TokenId(<TokensMinted<T>>::get(collection_id))381 }382}383384385impl<T: Config> Pallet<T> {386 387 388 389 390 391 pub fn init_collection(392 owner: T::CrossAccountId,393 payer: T::CrossAccountId,394 data: CreateCollectionData<T::CrossAccountId>,395 ) -> Result<CollectionId, DispatchError> {396 <PalletCommon<T>>::init_collection(owner, payer, data)397 }398399 400 401 402 403 pub fn destroy_collection(404 collection: NonfungibleHandle<T>,405 sender: &T::CrossAccountId,406 ) -> DispatchResult {407 let id = collection.id;408409 if Self::collection_has_tokens(id) {410 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());411 }412413 414415 PalletCommon::destroy_collection(collection.0, sender)?;416417 let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);418 let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);419 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);420 <TokensMinted<T>>::remove(id);421 <TokensBurnt<T>>::remove(id);422 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);423 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);424 let _ = <CollectionAllowance<T>>::clear_prefix((id,), u32::MAX, None);425 Ok(())426 }427428 429 430 431 432 433 434 435 436 437 pub fn burn(438 collection: &NonfungibleHandle<T>,439 sender: &T::CrossAccountId,440 token: TokenId,441 ) -> DispatchResult {442 let token_data =443 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;444 ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);445446 if collection.permissions.access() == AccessMode::AllowList {447 collection.check_allowlist(sender)?;448 }449450 if Self::token_has_children(collection.id, token) {451 return Err(<Error<T>>::CantBurnNftWithChildren.into());452 }453454 let burnt = <TokensBurnt<T>>::get(collection.id)455 .checked_add(1)456 .ok_or(ArithmeticError::Overflow)?;457458 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))459 .checked_sub(1)460 .ok_or(ArithmeticError::Overflow)?;461462 463464 if balance == 0 {465 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));466 } else {467 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);468 }469470 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);471472 <Owned<T>>::remove((collection.id, &token_data.owner, token));473 <TokensBurnt<T>>::insert(collection.id, burnt);474 <TokenData<T>>::remove((collection.id, token));475 <TokenProperties<T>>::remove((collection.id, token));476 let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);477 let old_spender = <Allowance<T>>::take((collection.id, token));478479 if let Some(old_spender) = old_spender {480 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(481 collection.id,482 token,483 token_data.owner.clone(),484 old_spender,485 0,486 ));487 }488489 <PalletEvm<T>>::deposit_log(490 ERC721Events::Transfer {491 from: *token_data.owner.as_eth(),492 to: H160::default(),493 token_id: token.into(),494 }495 .to_log(collection_id_to_address(collection.id)),496 );497 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(498 collection.id,499 token,500 token_data.owner,501 1,502 ));503 Ok(())504 }505506 507 508 509 510 511 512 #[transactional]513 pub fn burn_recursively(514 collection: &NonfungibleHandle<T>,515 sender: &T::CrossAccountId,516 token: TokenId,517 self_budget: &dyn Budget,518 breadth_budget: &dyn Budget,519 ) -> DispatchResultWithPostInfo {520 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);521522 let current_token_account =523 T::CrossTokenAddressMapping::token_to_address(collection.id, token);524525 let mut weight = Weight::zero();526527 528 529 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {530 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);531 let PostDispatchInfo { actual_weight, .. } =532 <PalletStructure<T>>::burn_item_recursively(533 current_token_account.clone(),534 collection,535 token,536 self_budget,537 breadth_budget,538 )?;539 if let Some(actual_weight) = actual_weight {540 weight = weight.saturating_add(actual_weight);541 }542 }543544 Self::burn(collection, sender, token)?;545 DispatchResultWithPostInfo::Ok(PostDispatchInfo {546 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),547 pays_fee: Pays::Yes,548 })549 }550551 552 553 554 555 556 557 558 559 560 561 562 #[transactional]563 fn modify_token_properties(564 collection: &NonfungibleHandle<T>,565 sender: &T::CrossAccountId,566 token_id: TokenId,567 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,568 nesting_budget: &dyn Budget,569 ) -> DispatchResult {570 let mut property_writer =571 pallet_common::property_writer_for_existing_token(collection, sender);572573 property_writer.write_token_properties(574 sender,575 token_id,576 properties_updates,577 nesting_budget,578 erc::ERC721TokenEvent::TokenChanged {579 token_id: token_id.into(),580 }581 .to_log(T::ContractAddress::get()),582 )583 }584585 pub fn next_token_id(collection: &NonfungibleHandle<T>) -> Result<TokenId, DispatchError> {586 let next_token_id = <TokensMinted<T>>::get(collection.id)587 .checked_add(1)588 .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;589590 ensure!(591 collection.limits.token_limit() >= next_token_id,592 <CommonError<T>>::CollectionTokenLimitExceeded593 );594595 Ok(TokenId(next_token_id))596 }597598 599 600 601 602 603 pub fn set_token_properties(604 collection: &NonfungibleHandle<T>,605 sender: &T::CrossAccountId,606 token_id: TokenId,607 properties: impl Iterator<Item = Property>,608 nesting_budget: &dyn Budget,609 ) -> DispatchResult {610 Self::modify_token_properties(611 collection,612 sender,613 token_id,614 properties.map(|p| (p.key, Some(p.value))),615 nesting_budget,616 )617 }618619 620 621 622 623 624 pub fn set_token_property(625 collection: &NonfungibleHandle<T>,626 sender: &T::CrossAccountId,627 token_id: TokenId,628 property: Property,629 nesting_budget: &dyn Budget,630 ) -> DispatchResult {631 Self::set_token_properties(632 collection,633 sender,634 token_id,635 [property].into_iter(),636 nesting_budget,637 )638 }639640 641 642 643 644 645 pub fn delete_token_properties(646 collection: &NonfungibleHandle<T>,647 sender: &T::CrossAccountId,648 token_id: TokenId,649 property_keys: impl Iterator<Item = PropertyKey>,650 nesting_budget: &dyn Budget,651 ) -> DispatchResult {652 Self::modify_token_properties(653 collection,654 sender,655 token_id,656 property_keys.into_iter().map(|key| (key, None)),657 nesting_budget,658 )659 }660661 662 663 664 665 666 pub fn delete_token_property(667 collection: &NonfungibleHandle<T>,668 sender: &T::CrossAccountId,669 token_id: TokenId,670 property_key: PropertyKey,671 nesting_budget: &dyn Budget,672 ) -> DispatchResult {673 Self::delete_token_properties(674 collection,675 sender,676 token_id,677 [property_key].into_iter(),678 nesting_budget,679 )680 }681682 683 pub fn set_collection_properties(684 collection: &NonfungibleHandle<T>,685 sender: &T::CrossAccountId,686 properties: Vec<Property>,687 ) -> DispatchResult {688 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())689 }690691 692 pub fn delete_collection_properties(693 collection: &CollectionHandle<T>,694 sender: &T::CrossAccountId,695 property_keys: Vec<PropertyKey>,696 ) -> DispatchResult {697 <PalletCommon<T>>::delete_collection_properties(698 collection,699 sender,700 property_keys.into_iter(),701 )702 }703704 705 706 707 pub fn set_token_property_permissions(708 collection: &CollectionHandle<T>,709 sender: &T::CrossAccountId,710 property_permissions: Vec<PropertyKeyPermission>,711 ) -> DispatchResult {712 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)713 }714715 716 717 718 pub fn set_scoped_token_property_permissions(719 collection: &CollectionHandle<T>,720 sender: &T::CrossAccountId,721 scope: PropertyScope,722 property_permissions: Vec<PropertyKeyPermission>,723 ) -> DispatchResult {724 <PalletCommon<T>>::set_scoped_token_property_permissions(725 collection,726 sender,727 scope,728 property_permissions,729 )730 }731732 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {733 <PalletCommon<T>>::property_permissions(collection_id)734 }735736 pub fn check_token_immediate_ownership(737 collection: &NonfungibleHandle<T>,738 token: TokenId,739 possible_owner: &T::CrossAccountId,740 ) -> DispatchResult {741 let token_data =742 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;743 ensure!(744 &token_data.owner == possible_owner,745 <CommonError<T>>::NoPermission746 );747 Ok(())748 }749750 751 752 753 754 755 756 757 758 759 pub fn transfer(760 collection: &NonfungibleHandle<T>,761 from: &T::CrossAccountId,762 to: &T::CrossAccountId,763 token: TokenId,764 nesting_budget: &dyn Budget,765 ) -> DispatchResultWithPostInfo {766 ensure!(767 collection.limits.transfers_enabled(),768 <CommonError<T>>::TransferNotAllowed769 );770771 let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();772 let token_data =773 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;774 ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);775776 if collection.permissions.access() == AccessMode::AllowList {777 collection.check_allowlist(from)?;778 collection.check_allowlist(to)?;779 actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;780 }781 <PalletCommon<T>>::ensure_correct_receiver(to)?;782783 let balance_from = <AccountBalance<T>>::get((collection.id, from))784 .checked_sub(1)785 .ok_or(<CommonError<T>>::TokenValueTooLow)?;786 let balance_to = if from != to {787 let balance_to = <AccountBalance<T>>::get((collection.id, to))788 .checked_add(1)789 .ok_or(ArithmeticError::Overflow)?;790791 ensure!(792 balance_to < collection.limits.account_token_ownership_limit(),793 <CommonError<T>>::AccountTokenLimitExceeded,794 );795796 Some(balance_to)797 } else {798 None799 };800801 <PalletStructure<T>>::nest_if_sent_to_token(802 from.clone(),803 to,804 collection.id,805 token,806 nesting_budget,807 )?;808809 810811 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);812813 <TokenData<T>>::insert((collection.id, token), ItemData { owner: to.clone() });814815 if let Some(balance_to) = balance_to {816 817 if balance_from == 0 {818 <AccountBalance<T>>::remove((collection.id, from));819 } else {820 <AccountBalance<T>>::insert((collection.id, from), balance_from);821 }822 <AccountBalance<T>>::insert((collection.id, to), balance_to);823 <Owned<T>>::remove((collection.id, from, token));824 <Owned<T>>::insert((collection.id, to, token), true);825 }826 Self::set_allowance_unchecked(collection, from, token, None, true);827828 <PalletEvm<T>>::deposit_log(829 ERC721Events::Transfer {830 from: *from.as_eth(),831 to: *to.as_eth(),832 token_id: token.into(),833 }834 .to_log(collection_id_to_address(collection.id)),835 );836 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(837 collection.id,838 token,839 from.clone(),840 to.clone(),841 1,842 ));843844 Ok(PostDispatchInfo {845 actual_weight: Some(actual_weight),846 pays_fee: Pays::Yes,847 })848 }849850 851 852 853 854 855 856 857 858 859 860 pub fn create_multiple_items(861 collection: &NonfungibleHandle<T>,862 sender: &T::CrossAccountId,863 data: Vec<CreateItemData<T>>,864 nesting_budget: &dyn Budget,865 ) -> DispatchResult {866 if !collection.is_owner_or_admin(sender) {867 ensure!(868 collection.permissions.mint_mode(),869 <CommonError<T>>::PublicMintingNotAllowed870 );871 collection.check_allowlist(sender)?;872873 for item in data.iter() {874 collection.check_allowlist(&item.owner)?;875 }876 }877878 for data in data.iter() {879 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;880 }881882 let first_token = <TokensMinted<T>>::get(collection.id);883 let tokens_minted = first_token884 .checked_add(data.len() as u32)885 .ok_or(ArithmeticError::Overflow)?;886 ensure!(887 tokens_minted <= collection.limits.token_limit(),888 <CommonError<T>>::CollectionTokenLimitExceeded889 );890891 let mut balances = BTreeMap::new();892 for data in &data {893 let balance = balances894 .entry(&data.owner)895 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));896 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;897898 ensure!(899 *balance <= collection.limits.account_token_ownership_limit(),900 <CommonError<T>>::AccountTokenLimitExceeded,901 );902 }903904 for (i, data) in data.iter().enumerate() {905 let token = TokenId(first_token + i as u32 + 1);906907 <PalletStructure<T>>::check_nesting(908 sender.clone(),909 &data.owner,910 collection.id,911 token,912 nesting_budget,913 )?;914 }915916 917918 let mut property_writer = pallet_common::property_writer_for_new_token(collection, sender);919920 with_transaction(|| {921 for (i, data) in data.iter().enumerate() {922 let token = first_token + i as u32 + 1;923924 <TokenData<T>>::insert(925 (collection.id, token),926 ItemData {927 928 owner: data.owner.clone(),929 },930 );931932 let token = TokenId(token);933934 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(935 &data.owner,936 collection.id,937 token,938 );939940 if let Err(e) = property_writer.write_token_properties(941 sender.conv_eq(&data.owner),942 token,943 data.properties.clone().into_iter(),944 erc::ERC721TokenEvent::TokenChanged {945 token_id: token.into(),946 }947 .to_log(T::ContractAddress::get()),948 ) {949 return TransactionOutcome::Rollback(Err(e));950 }951 }952 TransactionOutcome::Commit(Ok(()))953 })?;954955 <TokensMinted<T>>::insert(collection.id, tokens_minted);956 for (account, balance) in balances {957 <AccountBalance<T>>::insert((collection.id, account), balance);958 }959 for (i, data) in data.into_iter().enumerate() {960 let token = first_token + i as u32 + 1;961 <Owned<T>>::insert((collection.id, &data.owner, token), true);962963 <PalletEvm<T>>::deposit_log(964 ERC721Events::Transfer {965 from: H160::default(),966 to: *data.owner.as_eth(),967 token_id: token.into(),968 }969 .to_log(collection_id_to_address(collection.id)),970 );971 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(972 collection.id,973 TokenId(token),974 data.owner.clone(),975 1,976 ));977 }978 Ok(())979 }980981 pub fn set_allowance_unchecked(982 collection: &NonfungibleHandle<T>,983 sender: &T::CrossAccountId,984 token: TokenId,985 spender: Option<&T::CrossAccountId>,986 assume_implicit_eth: bool,987 ) {988 if let Some(spender) = spender {989 let old_spender = <Allowance<T>>::get((collection.id, token));990 <Allowance<T>>::insert((collection.id, token), spender);991 992 993 <PalletEvm<T>>::deposit_log(994 ERC721Events::Approval {995 owner: *sender.as_eth(),996 approved: *spender.as_eth(),997 token_id: token.into(),998 }999 .to_log(collection_id_to_address(collection.id)),1000 );1001 1002 1003 if old_spender.as_ref() != Some(spender) {1004 if let Some(old_owner) = old_spender {1005 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1006 collection.id,1007 token,1008 sender.clone(),1009 old_owner,1010 0,1011 ));1012 }1013 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1014 collection.id,1015 token,1016 sender.clone(),1017 spender.clone(),1018 1,1019 ));1020 }1021 } else {1022 let old_spender = <Allowance<T>>::take((collection.id, token));1023 if !assume_implicit_eth {1024 1025 1026 <PalletEvm<T>>::deposit_log(1027 ERC721Events::Approval {1028 owner: *sender.as_eth(),1029 approved: H160::default(),1030 token_id: token.into(),1031 }1032 .to_log(collection_id_to_address(collection.id)),1033 );1034 }1035 1036 1037 if let Some(old_spender) = old_spender {1038 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1039 collection.id,1040 token,1041 sender.clone(),1042 old_spender,1043 0,1044 ));1045 }1046 }1047 }10481049 pub fn get_allowance(1050 collection: &NonfungibleHandle<T>,1051 token_id: TokenId,1052 ) -> Result<Option<T::CrossAccountId>, DispatchError> {1053 ensure!(1054 <TokenData<T>>::get((collection.id, token_id)).is_some(),1055 <CommonError<T>>::TokenNotFound1056 );1057 Ok(<Allowance<T>>::get((collection.id, token_id)))1058 }10591060 1061 1062 1063 pub fn set_allowance(1064 collection: &NonfungibleHandle<T>,1065 sender: &T::CrossAccountId,1066 token: TokenId,1067 spender: Option<&T::CrossAccountId>,1068 ) -> DispatchResult {1069 if collection.permissions.access() == AccessMode::AllowList {1070 collection.check_allowlist(sender)?;1071 if let Some(spender) = spender {1072 collection.check_allowlist(spender)?;1073 }1074 }10751076 if let Some(spender) = spender {1077 <PalletCommon<T>>::ensure_correct_receiver(spender)?;1078 }10791080 let token_data =1081 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1082 if &token_data.owner != sender {1083 ensure!(1084 collection.ignores_owned_amount(sender),1085 <CommonError<T>>::CantApproveMoreThanOwned1086 );1087 }10881089 10901091 Self::set_allowance_unchecked(collection, sender, token, spender, false);1092 Ok(())1093 }10941095 1096 1097 1098 1099 1100 pub fn set_allowance_from(1101 collection: &NonfungibleHandle<T>,1102 sender: &T::CrossAccountId,1103 from: &T::CrossAccountId,1104 token: TokenId,1105 to: Option<&T::CrossAccountId>,1106 ) -> DispatchResult {1107 if collection.permissions.access() == AccessMode::AllowList {1108 collection.check_allowlist(sender)?;1109 collection.check_allowlist(from)?;1110 if let Some(to) = to {1111 collection.check_allowlist(to)?;1112 }1113 }11141115 if let Some(to) = to {1116 <PalletCommon<T>>::ensure_correct_receiver(to)?;1117 }11181119 ensure!(1120 sender.conv_eq(from),1121 <CommonError<T>>::AddressIsNotEthMirror1122 );11231124 let token_data =1125 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1126 if token_data.owner != *from {1127 ensure!(1128 collection.limits.owner_can_transfer()1129 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),1130 <CommonError<T>>::CantApproveMoreThanOwned1131 );1132 }11331134 11351136 Self::set_allowance_unchecked(collection, from, token, to, false);1137 Ok(())1138 }11391140 1141 fn check_allowed(1142 collection: &NonfungibleHandle<T>,1143 spender: &T::CrossAccountId,1144 from: &T::CrossAccountId,1145 token: TokenId,1146 nesting_budget: &dyn Budget,1147 ) -> DispatchResult {1148 if spender.conv_eq(from) {1149 return Ok(());1150 }1151 if collection.permissions.access() == AccessMode::AllowList {1152 1153 collection.check_allowlist(spender)?;1154 }11551156 if collection.ignores_token_restrictions(spender) {1157 return Ok(());1158 }11591160 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {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(());1172 }1173 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1174 return Ok(());1175 }1176 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1177 return Ok(());1178 }11791180 Err(<CommonError<T>>::ApprovedValueTooLow.into())1181 }11821183 1184 1185 1186 1187 1188 1189 pub fn transfer_from(1190 collection: &NonfungibleHandle<T>,1191 spender: &T::CrossAccountId,1192 from: &T::CrossAccountId,1193 to: &T::CrossAccountId,1194 token: TokenId,1195 nesting_budget: &dyn Budget,1196 ) -> DispatchResultWithPostInfo {1197 Self::check_allowed(collection, spender, from, token, nesting_budget)?;11981199 12001201 1202 let mut result = Self::transfer(collection, from, to, token, nesting_budget);1203 add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());1204 result1205 }12061207 1208 1209 1210 1211 1212 1213 pub fn burn_from(1214 collection: &NonfungibleHandle<T>,1215 spender: &T::CrossAccountId,1216 from: &T::CrossAccountId,1217 token: TokenId,1218 nesting_budget: &dyn Budget,1219 ) -> DispatchResult {1220 Self::check_allowed(collection, spender, from, token, nesting_budget)?;12211222 12231224 Self::burn(collection, from, token)1225 }12261227 1228 1229 pub fn check_nesting(1230 handle: &NonfungibleHandle<T>,1231 sender: T::CrossAccountId,1232 from: (CollectionId, TokenId),1233 under: TokenId,1234 nesting_budget: &dyn Budget,1235 ) -> DispatchResult {1236 let nesting = handle.permissions.nesting();12371238 #[cfg(not(feature = "runtime-benchmarks"))]1239 let permissive = false;1240 #[cfg(feature = "runtime-benchmarks")]1241 let permissive = nesting.permissive;12421243 if permissive {1244 ensure!(1245 <TokenData<T>>::contains_key((handle.id, under)),1246 <CommonError<T>>::TokenNotFound1247 );1248 } else if nesting.token_owner1249 && <PalletStructure<T>>::check_indirectly_owned(1250 sender.clone(),1251 handle.id,1252 under,1253 Some(from),1254 nesting_budget,1255 )? {1256 1257 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1258 1259 let _ = <PalletStructure<T>>::get_checked_topmost_owner(1260 handle.id,1261 under,1262 Some(from),1263 nesting_budget,1264 )?1265 .ok_or(<CommonError<T>>::TokenNotFound)?;1266 } else {1267 fail!(<CommonError<T>>::UserIsNotAllowedToNest);1268 }12691270 if let Some(whitelist) = &nesting.restricted {1271 ensure!(1272 whitelist.contains(&from.0),1273 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1274 );1275 }1276 Ok(())1277 }12781279 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1280 if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1281 <TokenChildren<T>>::insert((under.0, under.1, to_nest), true);1282 }1283 }12841285 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1286 if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1287 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1288 }1289 }12901291 fn collection_has_tokens(collection_id: CollectionId) -> bool {1292 <TokenData<T>>::iter_prefix((collection_id,))1293 .next()1294 .is_some()1295 }12961297 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1298 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1299 .next()1300 .is_some()1301 }13021303 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1304 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1305 .map(|((child_collection_id, child_id), _)| TokenChild {1306 collection: child_collection_id,1307 token: child_id,1308 })1309 .collect()1310 }13111312 1313 1314 1315 1316 1317 pub fn create_item(1318 collection: &NonfungibleHandle<T>,1319 sender: &T::CrossAccountId,1320 data: CreateItemData<T>,1321 nesting_budget: &dyn Budget,1322 ) -> DispatchResult {1323 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1324 }13251326 1327 1328 1329 1330 1331 1332 pub fn set_allowance_for_all(1333 collection: &NonfungibleHandle<T>,1334 owner: &T::CrossAccountId,1335 operator: &T::CrossAccountId,1336 approve: bool,1337 ) -> DispatchResult {1338 <PalletCommon<T>>::set_allowance_for_all(1339 collection,1340 owner,1341 operator,1342 approve,1343 || <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),1344 ERC721Events::ApprovalForAll {1345 owner: *owner.as_eth(),1346 operator: *operator.as_eth(),1347 approved: approve,1348 }1349 .to_log(collection_id_to_address(collection.id)),1350 )1351 }13521353 1354 pub fn allowance_for_all(1355 collection: &NonfungibleHandle<T>,1356 owner: &T::CrossAccountId,1357 operator: &T::CrossAccountId,1358 ) -> bool {1359 <CollectionAllowance<T>>::get((collection.id, owner, operator))1360 }13611362 pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {1363 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1364 if let Some(properties) = properties {1365 properties.recompute_consumed_space();1366 }1367 });13681369 Ok(())1370 }1371}