difftreelog
cargo fmt
in: master
1 file changed
pallets/nonfungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22 BoundedVec, ensure, fail, transactional,23 storage::with_transaction,24 pallet_prelude::DispatchResultWithPostInfo,25 pallet_prelude::Weight,26 weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30 mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,31 PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32 AuxPropertyValue,33};34use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};35use pallet_common::{36 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,37 eth::collection_id_to_address,38};39use pallet_structure::{Pallet as PalletStructure, Error as StructureError};40use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};43use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};44use core::ops::Deref;45use codec::{Encode, Decode, MaxEncodedLen};46use scale_info::TypeInfo;4748pub use pallet::*;49use weights::WeightInfo;50#[cfg(feature = "runtime-benchmarks")]51pub mod benchmarking;52pub mod common;53pub mod erc;54pub mod weights;5556pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;57pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5859#[struct_versioning::versioned(version = 2, upper)]60#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]61pub struct ItemData<CrossAccountId> {62 #[version(..2)]63 pub const_data: BoundedVec<u8, CustomDataLimit>,6465 #[version(..2)]66 pub variable_data: BoundedVec<u8, CustomDataLimit>,6768 pub owner: CrossAccountId,69}7071#[frame_support::pallet]72pub mod pallet {73 use super::*;74 use frame_support::{75 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,76 };77 use frame_system::pallet_prelude::*;78 use up_data_structs::{CollectionId, TokenId};79 use super::weights::WeightInfo;8081 #[pallet::error]82 pub enum Error<T> {83 /// Not Nonfungible item data used to mint in Nonfungible collection.84 NotNonfungibleDataUsedToMintFungibleCollectionToken,85 /// Used amount > 1 with NFT86 NonfungibleItemsHaveNoAmount,87 /// Unable to burn NFT with children88 CantBurnNftWithChildren,89 }9091 #[pallet::config]92 pub trait Config:93 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config94 {95 type WeightInfo: WeightInfo;96 }9798 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);99100 #[pallet::pallet]101 #[pallet::storage_version(STORAGE_VERSION)]102 #[pallet::generate_store(pub(super) trait Store)]103 pub struct Pallet<T>(_);104105 #[pallet::storage]106 pub type TokensMinted<T: Config> =107 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;108 #[pallet::storage]109 pub type TokensBurnt<T: Config> =110 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;111112 #[pallet::storage]113 pub type TokenData<T: Config> = StorageNMap<114 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),115 Value = ItemData<T::CrossAccountId>,116 QueryKind = OptionQuery,117 >;118119 #[pallet::storage]120 #[pallet::getter(fn token_properties)]121 pub type TokenProperties<T: Config> = StorageNMap<122 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),123 Value = Properties,124 QueryKind = ValueQuery,125 OnEmpty = up_data_structs::TokenProperties,126 >;127128 #[pallet::storage]129 #[pallet::getter(fn token_aux_property)]130 pub type TokenAuxProperties<T: Config> = StorageNMap<131 Key = (132 Key<Twox64Concat, CollectionId>,133 Key<Twox64Concat, TokenId>,134 Key<Twox64Concat, PropertyScope>,135 Key<Twox64Concat, PropertyKey>,136 ),137 Value = AuxPropertyValue,138 QueryKind = OptionQuery,139 >;140141 /// Used to enumerate tokens owned by account142 #[pallet::storage]143 pub type Owned<T: Config> = StorageNMap<144 Key = (145 Key<Twox64Concat, CollectionId>,146 Key<Blake2_128Concat, T::CrossAccountId>,147 Key<Twox64Concat, TokenId>,148 ),149 Value = bool,150 QueryKind = ValueQuery,151 >;152153 /// Used to enumerate token's children154 #[pallet::storage]155 #[pallet::getter(fn token_children)]156 pub type TokenChildren<T: Config> = StorageNMap<157 Key = (158 Key<Twox64Concat, CollectionId>,159 Key<Twox64Concat, TokenId>,160 Key<Twox64Concat, (CollectionId, TokenId)>,161 ),162 Value = bool,163 QueryKind = ValueQuery,164 >;165166 #[pallet::storage]167 pub type AccountBalance<T: Config> = StorageNMap<168 Key = (169 Key<Twox64Concat, CollectionId>,170 Key<Blake2_128Concat, T::CrossAccountId>,171 ),172 Value = u32,173 QueryKind = ValueQuery,174 >;175176 #[pallet::storage]177 pub type Allowance<T: Config> = StorageNMap<178 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),179 Value = T::CrossAccountId,180 QueryKind = OptionQuery,181 >;182183 #[pallet::hooks]184 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {185 fn on_runtime_upgrade() -> Weight {186 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {187 let mut had_consts = BTreeSet::new();188 <TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(189 |(collection, token), v| {190 let mut props = vec![];191 if !v.const_data.is_empty() {192 props.push(Property {193 key: b"_old_constData".to_vec().try_into().unwrap(),194 value: v195 .const_data196 .clone()197 .into_inner()198 .try_into()199 .expect("const too long"),200 });201 had_consts.insert(collection);202 }203 if !v.variable_data.is_empty() {204 props.push(Property {205 key: b"_old_variableData".to_vec().try_into().unwrap(),206 value: v207 .variable_data208 .clone()209 .into_inner()210 .try_into()211 .expect("variable too long"),212 })213 }214 if !props.is_empty() {215 Self::set_scoped_token_properties(216 collection,217 token,218 PropertyScope::None,219 props.into_iter(),220 )221 .expect("existing token data exceeds property storage");222 }223 Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))224 },225 );226 for collection in had_consts {227 <PalletCommon<T>>::set_property_permission_unchecked(228 collection,229 PropertyKeyPermission {230 key: b"_old_constData".to_vec().try_into().unwrap(),231 permission: PropertyPermission {232 mutable: false,233 collection_admin: true,234 token_owner: false,235 },236 },237 )238 .expect("failed to configure permission");239 }240 }241242 0243 }244 }245}246247pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);248impl<T: Config> NonfungibleHandle<T> {249 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {250 Self(inner)251 }252 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {253 self.0254 }255 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {256 &mut self.0257 }258}259impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {260 fn recorder(&self) -> &SubstrateRecorder<T> {261 self.0.recorder()262 }263 fn into_recorder(self) -> SubstrateRecorder<T> {264 self.0.into_recorder()265 }266}267impl<T: Config> Deref for NonfungibleHandle<T> {268 type Target = pallet_common::CollectionHandle<T>;269270 fn deref(&self) -> &Self::Target {271 &self.0272 }273}274275impl<T: Config> Pallet<T> {276 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {277 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)278 }279 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {280 <TokenData<T>>::contains_key((collection.id, token))281 }282283 pub fn set_scoped_token_property(284 collection_id: CollectionId,285 token_id: TokenId,286 scope: PropertyScope,287 property: Property,288 ) -> DispatchResult {289 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {290 properties.try_scoped_set(scope, property.key, property.value)291 })292 .map_err(<CommonError<T>>::from)?;293294 Ok(())295 }296297 pub fn set_scoped_token_properties(298 collection_id: CollectionId,299 token_id: TokenId,300 scope: PropertyScope,301 properties: impl Iterator<Item = Property>,302 ) -> DispatchResult {303 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {304 stored_properties.try_scoped_set_from_iter(scope, properties)305 })306 .map_err(<CommonError<T>>::from)?;307308 Ok(())309 }310311 pub fn try_mutate_token_aux_property<R, E>(312 collection_id: CollectionId,313 token_id: TokenId,314 scope: PropertyScope,315 key: PropertyKey,316 f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,317 ) -> Result<R, E> {318 <TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)319 }320321 pub fn remove_token_aux_property(322 collection_id: CollectionId,323 token_id: TokenId,324 scope: PropertyScope,325 key: PropertyKey,326 ) {327 <TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));328 }329330 pub fn iterate_token_aux_properties(331 collection_id: CollectionId,332 token_id: TokenId,333 scope: PropertyScope,334 ) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {335 <TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))336 }337338 pub fn current_token_id(collection_id: CollectionId) -> TokenId {339 TokenId(<TokensMinted<T>>::get(collection_id))340 }341}342343// unchecked calls skips any permission checks344impl<T: Config> Pallet<T> {345 pub fn init_collection(346 owner: T::CrossAccountId,347 data: CreateCollectionData<T::AccountId>,348 is_external: bool,349 ) -> Result<CollectionId, DispatchError> {350 <PalletCommon<T>>::init_collection(owner, data, is_external)351 }352 pub fn destroy_collection(353 collection: NonfungibleHandle<T>,354 sender: &T::CrossAccountId,355 ) -> DispatchResult {356 let id = collection.id;357358 if Self::collection_has_tokens(id) {359 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());360 }361362 // =========363364 PalletCommon::destroy_collection(collection.0, sender)?;365366 <TokenData<T>>::remove_prefix((id,), None);367 <TokenChildren<T>>::remove_prefix((id,), None);368 <Owned<T>>::remove_prefix((id,), None);369 <TokensMinted<T>>::remove(id);370 <TokensBurnt<T>>::remove(id);371 <Allowance<T>>::remove_prefix((id,), None);372 <AccountBalance<T>>::remove_prefix((id,), None);373 Ok(())374 }375376 pub fn burn(377 collection: &NonfungibleHandle<T>,378 sender: &T::CrossAccountId,379 token: TokenId,380 ) -> DispatchResult {381 let token_data =382 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;383 ensure!(384 &token_data.owner == sender385 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),386 <CommonError<T>>::NoPermission387 );388389 if collection.permissions.access() == AccessMode::AllowList {390 collection.check_allowlist(sender)?;391 }392393 if Self::token_has_children(collection.id, token) {394 return Err(<Error<T>>::CantBurnNftWithChildren.into());395 }396397 let burnt = <TokensBurnt<T>>::get(collection.id)398 .checked_add(1)399 .ok_or(ArithmeticError::Overflow)?;400401 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))402 .checked_sub(1)403 .ok_or(ArithmeticError::Overflow)?;404405 // =========406407 if balance == 0 {408 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));409 } else {410 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);411 }412413 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);414415 <Owned<T>>::remove((collection.id, &token_data.owner, token));416 <TokensBurnt<T>>::insert(collection.id, burnt);417 <TokenData<T>>::remove((collection.id, token));418 <TokenProperties<T>>::remove((collection.id, token));419 <TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);420 let old_spender = <Allowance<T>>::take((collection.id, token));421422 if let Some(old_spender) = old_spender {423 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(424 collection.id,425 token,426 token_data.owner.clone(),427 old_spender,428 0,429 ));430 }431432 <PalletEvm<T>>::deposit_log(433 ERC721Events::Transfer {434 from: *token_data.owner.as_eth(),435 to: H160::default(),436 token_id: token.into(),437 }438 .to_log(collection_id_to_address(collection.id)),439 );440 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(441 collection.id,442 token,443 token_data.owner,444 1,445 ));446 Ok(())447 }448449 #[transactional]450 pub fn burn_recursively(451 collection: &NonfungibleHandle<T>,452 sender: &T::CrossAccountId,453 token: TokenId,454 self_budget: &dyn Budget,455 breadth_budget: &dyn Budget,456 ) -> DispatchResultWithPostInfo {457 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);458459 let current_token_account =460 T::CrossTokenAddressMapping::token_to_address(collection.id, token);461462 let mut weight = 0 as Weight;463464 // This method is transactional, if user in fact doesn't have permissions to remove token -465 // tokens removed here will be restored after rejected transaction466 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {467 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);468 let PostDispatchInfo { actual_weight, .. } =469 <PalletStructure<T>>::burn_item_recursively(470 current_token_account.clone(),471 collection,472 token,473 self_budget,474 breadth_budget,475 )?;476 if let Some(actual_weight) = actual_weight {477 weight = weight.saturating_add(actual_weight);478 }479 }480481 Self::burn(collection, sender, token)?;482 DispatchResultWithPostInfo::Ok(PostDispatchInfo {483 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),484 pays_fee: Pays::Yes,485 })486 }487488 #[transactional]489 fn modify_token_properties(490 collection: &NonfungibleHandle<T>,491 sender: &T::CrossAccountId,492 token_id: TokenId,493 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,494 is_token_create: bool,495 nesting_budget: &dyn Budget,496 ) -> DispatchResult {497 let mut collection_admin_status = None;498 let mut token_owner_result = None;499500 let mut is_collection_admin = || {501 *collection_admin_status502 .get_or_insert_with(|| collection.is_owner_or_admin(sender))503 };504505 let mut is_token_owner = || {506 *token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {507 let is_owned = <PalletStructure<T>>::check_indirectly_owned(508 sender.clone(),509 collection.id,510 token_id,511 None,512 nesting_budget,513 )?;514515 Ok(is_owned)516 })517 };518519 for (key, value) in properties {520 let permission = <PalletCommon<T>>::property_permissions(collection.id)521 .get(&key)522 .cloned()523 .unwrap_or_else(PropertyPermission::none);524525 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))526 .get(&key)527 .is_some();528529 match permission {530 PropertyPermission { mutable: false, .. } if is_property_exists => {531 return Err(<CommonError<T>>::NoPermission.into());532 }533534 PropertyPermission {535 collection_admin,536 token_owner,537 ..538 } => {539 //TODO: investigate threats during public minting.540 if is_token_create && (collection_admin || token_owner) && value.is_some() {541 // Pass542 } else if collection_admin && is_collection_admin() {543 // Pass544 } else if token_owner && is_token_owner()? {545 // Pass546 } else {547 return Err(<CommonError<T>>::NoPermission.into());548 }549 }550 }551552 match value {553 Some(value) => {554 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {555 properties.try_set(key.clone(), value)556 })557 .map_err(<CommonError<T>>::from)?;558559 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(560 collection.id,561 token_id,562 key,563 ));564 }565 None => {566 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {567 properties.remove(&key)568 })569 .map_err(<CommonError<T>>::from)?;570571 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(572 collection.id,573 token_id,574 key,575 ));576 }577 }578 }579580 Ok(())581 }582583 pub fn set_token_properties(584 collection: &NonfungibleHandle<T>,585 sender: &T::CrossAccountId,586 token_id: TokenId,587 properties: impl Iterator<Item = Property>,588 is_token_create: bool,589 nesting_budget: &dyn Budget,590 ) -> DispatchResult {591 Self::modify_token_properties(592 collection,593 sender,594 token_id,595 properties.map(|p| (p.key, Some(p.value))),596 is_token_create,597 nesting_budget,598 )599 }600601 pub fn set_token_property(602 collection: &NonfungibleHandle<T>,603 sender: &T::CrossAccountId,604 token_id: TokenId,605 property: Property,606 nesting_budget: &dyn Budget,607 ) -> DispatchResult {608 let is_token_create = false;609610 Self::set_token_properties(611 collection,612 sender,613 token_id,614 [property].into_iter(),615 is_token_create,616 nesting_budget,617 )618 }619620 pub fn delete_token_properties(621 collection: &NonfungibleHandle<T>,622 sender: &T::CrossAccountId,623 token_id: TokenId,624 property_keys: impl Iterator<Item = PropertyKey>,625 nesting_budget: &dyn Budget,626 ) -> DispatchResult {627 let is_token_create = false;628629 Self::modify_token_properties(630 collection,631 sender,632 token_id,633 property_keys.into_iter().map(|key| (key, None)),634 is_token_create,635 nesting_budget,636 )637 }638639 pub fn delete_token_property(640 collection: &NonfungibleHandle<T>,641 sender: &T::CrossAccountId,642 token_id: TokenId,643 property_key: PropertyKey,644 nesting_budget: &dyn Budget,645 ) -> DispatchResult {646 Self::delete_token_properties(647 collection,648 sender,649 token_id,650 [property_key].into_iter(),651 nesting_budget,652 )653 }654655 pub fn set_collection_properties(656 collection: &NonfungibleHandle<T>,657 sender: &T::CrossAccountId,658 properties: Vec<Property>,659 ) -> DispatchResult {660 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)661 }662663 pub fn delete_collection_properties(664 collection: &CollectionHandle<T>,665 sender: &T::CrossAccountId,666 property_keys: Vec<PropertyKey>,667 ) -> DispatchResult {668 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)669 }670671 pub fn set_token_property_permissions(672 collection: &CollectionHandle<T>,673 sender: &T::CrossAccountId,674 property_permissions: Vec<PropertyKeyPermission>,675 ) -> DispatchResult {676 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)677 }678679 pub fn set_property_permission(680 collection: &CollectionHandle<T>,681 sender: &T::CrossAccountId,682 permission: PropertyKeyPermission,683 ) -> DispatchResult {684 <PalletCommon<T>>::set_property_permission(collection, sender, permission)685 }686687 pub fn transfer(688 collection: &NonfungibleHandle<T>,689 from: &T::CrossAccountId,690 to: &T::CrossAccountId,691 token: TokenId,692 nesting_budget: &dyn Budget,693 ) -> DispatchResult {694 ensure!(695 collection.limits.transfers_enabled(),696 <CommonError<T>>::TransferNotAllowed697 );698699 let token_data =700 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;701 // TODO: require sender to be token, owner, require admins to go through transfer_from702 ensure!(703 &token_data.owner == from704 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),705 <CommonError<T>>::NoPermission706 );707708 if collection.permissions.access() == AccessMode::AllowList {709 collection.check_allowlist(from)?;710 collection.check_allowlist(to)?;711 }712 <PalletCommon<T>>::ensure_correct_receiver(to)?;713714 let balance_from = <AccountBalance<T>>::get((collection.id, from))715 .checked_sub(1)716 .ok_or(<CommonError<T>>::TokenValueTooLow)?;717 let balance_to = if from != to {718 let balance_to = <AccountBalance<T>>::get((collection.id, to))719 .checked_add(1)720 .ok_or(ArithmeticError::Overflow)?;721722 ensure!(723 balance_to < collection.limits.account_token_ownership_limit(),724 <CommonError<T>>::AccountTokenLimitExceeded,725 );726727 Some(balance_to)728 } else {729 None730 };731732 <PalletStructure<T>>::nest_if_sent_to_token(733 from.clone(),734 to,735 collection.id,736 token,737 nesting_budget,738 )?;739740 // =========741742 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);743744 <TokenData<T>>::insert(745 (collection.id, token),746 ItemData {747 owner: to.clone(),748 ..token_data749 },750 );751752 if let Some(balance_to) = balance_to {753 // from != to754 if balance_from == 0 {755 <AccountBalance<T>>::remove((collection.id, from));756 } else {757 <AccountBalance<T>>::insert((collection.id, from), balance_from);758 }759 <AccountBalance<T>>::insert((collection.id, to), balance_to);760 <Owned<T>>::remove((collection.id, from, token));761 <Owned<T>>::insert((collection.id, to, token), true);762 }763 Self::set_allowance_unchecked(collection, from, token, None, true);764765 <PalletEvm<T>>::deposit_log(766 ERC721Events::Transfer {767 from: *from.as_eth(),768 to: *to.as_eth(),769 token_id: token.into(),770 }771 .to_log(collection_id_to_address(collection.id)),772 );773 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774 collection.id,775 token,776 from.clone(),777 to.clone(),778 1,779 ));780 Ok(())781 }782783 pub fn create_multiple_items(784 collection: &NonfungibleHandle<T>,785 sender: &T::CrossAccountId,786 data: Vec<CreateItemData<T>>,787 nesting_budget: &dyn Budget,788 ) -> DispatchResult {789 if !collection.is_owner_or_admin(sender) {790 ensure!(791 collection.permissions.mint_mode(),792 <CommonError<T>>::PublicMintingNotAllowed793 );794 collection.check_allowlist(sender)?;795796 for item in data.iter() {797 collection.check_allowlist(&item.owner)?;798 }799 }800801 for data in data.iter() {802 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;803 }804805 let first_token = <TokensMinted<T>>::get(collection.id);806 let tokens_minted = first_token807 .checked_add(data.len() as u32)808 .ok_or(ArithmeticError::Overflow)?;809 ensure!(810 tokens_minted <= collection.limits.token_limit(),811 <CommonError<T>>::CollectionTokenLimitExceeded812 );813814 let mut balances = BTreeMap::new();815 for data in &data {816 let balance = balances817 .entry(&data.owner)818 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));819 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;820821 ensure!(822 *balance <= collection.limits.account_token_ownership_limit(),823 <CommonError<T>>::AccountTokenLimitExceeded,824 );825 }826827 for (i, data) in data.iter().enumerate() {828 let token = TokenId(first_token + i as u32 + 1);829830 <PalletStructure<T>>::check_nesting(831 sender.clone(),832 &data.owner,833 collection.id,834 token,835 nesting_budget,836 )?;837 }838839 // =========840841 with_transaction(|| {842 for (i, data) in data.iter().enumerate() {843 let token = first_token + i as u32 + 1;844845 <TokenData<T>>::insert(846 (collection.id, token),847 ItemData {848 // const_data: data.const_data.clone(),849 owner: data.owner.clone(),850 },851 );852853 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(854 &data.owner,855 collection.id,856 TokenId(token),857 );858859 if let Err(e) = Self::set_token_properties(860 collection,861 sender,862 TokenId(token),863 data.properties.clone().into_iter(),864 true,865 nesting_budget,866 ) {867 return TransactionOutcome::Rollback(Err(e));868 }869 }870 TransactionOutcome::Commit(Ok(()))871 })?;872873 <TokensMinted<T>>::insert(collection.id, tokens_minted);874 for (account, balance) in balances {875 <AccountBalance<T>>::insert((collection.id, account), balance);876 }877 for (i, data) in data.into_iter().enumerate() {878 let token = first_token + i as u32 + 1;879 <Owned<T>>::insert((collection.id, &data.owner, token), true);880881 <PalletEvm<T>>::deposit_log(882 ERC721Events::Transfer {883 from: H160::default(),884 to: *data.owner.as_eth(),885 token_id: token.into(),886 }887 .to_log(collection_id_to_address(collection.id)),888 );889 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(890 collection.id,891 TokenId(token),892 data.owner.clone(),893 1,894 ));895 }896 Ok(())897 }898899 pub fn set_allowance_unchecked(900 collection: &NonfungibleHandle<T>,901 sender: &T::CrossAccountId,902 token: TokenId,903 spender: Option<&T::CrossAccountId>,904 assume_implicit_eth: bool,905 ) {906 if let Some(spender) = spender {907 let old_spender = <Allowance<T>>::get((collection.id, token));908 <Allowance<T>>::insert((collection.id, token), spender);909 // In ERC721 there is only one possible approved user of token, so we set910 // approved user to spender911 <PalletEvm<T>>::deposit_log(912 ERC721Events::Approval {913 owner: *sender.as_eth(),914 approved: *spender.as_eth(),915 token_id: token.into(),916 }917 .to_log(collection_id_to_address(collection.id)),918 );919 // In Unique chain, any token can have any amount of approved users, so we need to920 // set allowance of old owner to 0, and allowance of new owner to 1921 if old_spender.as_ref() != Some(spender) {922 if let Some(old_owner) = old_spender {923 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(924 collection.id,925 token,926 sender.clone(),927 old_owner,928 0,929 ));930 }931 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(932 collection.id,933 token,934 sender.clone(),935 spender.clone(),936 1,937 ));938 }939 } else {940 let old_spender = <Allowance<T>>::take((collection.id, token));941 if !assume_implicit_eth {942 // In ERC721 there is only one possible approved user of token, so we set943 // approved user to zero address944 <PalletEvm<T>>::deposit_log(945 ERC721Events::Approval {946 owner: *sender.as_eth(),947 approved: H160::default(),948 token_id: token.into(),949 }950 .to_log(collection_id_to_address(collection.id)),951 );952 }953 // In Unique chain, any token can have any amount of approved users, so we need to954 // set allowance of old owner to 0955 if let Some(old_spender) = old_spender {956 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(957 collection.id,958 token,959 sender.clone(),960 old_spender,961 0,962 ));963 }964 }965 }966967 pub fn set_allowance(968 collection: &NonfungibleHandle<T>,969 sender: &T::CrossAccountId,970 token: TokenId,971 spender: Option<&T::CrossAccountId>,972 ) -> DispatchResult {973 if collection.permissions.access() == AccessMode::AllowList {974 collection.check_allowlist(sender)?;975 if let Some(spender) = spender {976 collection.check_allowlist(spender)?;977 }978 }979980 if let Some(spender) = spender {981 <PalletCommon<T>>::ensure_correct_receiver(spender)?;982 }983984 let token_data =985 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;986 if &token_data.owner != sender {987 ensure!(988 collection.ignores_owned_amount(sender),989 <CommonError<T>>::CantApproveMoreThanOwned990 );991 }992993 // =========994995 Self::set_allowance_unchecked(collection, sender, token, spender, false);996 Ok(())997 }998999 fn check_allowed(1000 collection: &NonfungibleHandle<T>,1001 spender: &T::CrossAccountId,1002 from: &T::CrossAccountId,1003 token: TokenId,1004 nesting_budget: &dyn Budget,1005 ) -> DispatchResult {1006 if spender.conv_eq(from) {1007 return Ok(());1008 }1009 if collection.permissions.access() == AccessMode::AllowList {1010 // `from`, `to` checked in [`transfer`]1011 collection.check_allowlist(spender)?;1012 }1013 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1014 // TODO: should collection owner be allowed to perform this transfer?1015 ensure!(1016 <PalletStructure<T>>::check_indirectly_owned(1017 spender.clone(),1018 source.0,1019 source.1,1020 None,1021 nesting_budget1022 )?,1023 <CommonError<T>>::ApprovedValueTooLow,1024 );1025 return Ok(());1026 }1027 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1028 return Ok(());1029 }1030 ensure!(1031 collection.ignores_allowance(spender),1032 <CommonError<T>>::ApprovedValueTooLow1033 );1034 Ok(())1035 }10361037 pub fn transfer_from(1038 collection: &NonfungibleHandle<T>,1039 spender: &T::CrossAccountId,1040 from: &T::CrossAccountId,1041 to: &T::CrossAccountId,1042 token: TokenId,1043 nesting_budget: &dyn Budget,1044 ) -> DispatchResult {1045 Self::check_allowed(collection, spender, from, token, nesting_budget)?;10461047 // =========10481049 // Allowance is reset in [`transfer`]1050 Self::transfer(collection, from, to, token, nesting_budget)1051 }10521053 pub fn burn_from(1054 collection: &NonfungibleHandle<T>,1055 spender: &T::CrossAccountId,1056 from: &T::CrossAccountId,1057 token: TokenId,1058 nesting_budget: &dyn Budget,1059 ) -> DispatchResult {1060 Self::check_allowed(collection, spender, from, token, nesting_budget)?;10611062 // =========10631064 Self::burn(collection, from, token)1065 }10661067 pub fn check_nesting(1068 handle: &NonfungibleHandle<T>,1069 sender: T::CrossAccountId,1070 from: (CollectionId, TokenId),1071 under: TokenId,1072 nesting_budget: &dyn Budget,1073 ) -> DispatchResult {1074 let nesting = handle.permissions.nesting();10751076 #[cfg(not(feature = "runtime-benchmarks"))]1077 let permissive = false;1078 #[cfg(feature = "runtime-benchmarks")]1079 let permissive = nesting.permissive;10801081 if permissive {1082 // Pass1083 } else if nesting.token_owner1084 && <PalletStructure<T>>::check_indirectly_owned(1085 sender.clone(),1086 handle.id,1087 under,1088 Some(from),1089 nesting_budget,1090 )? {1091 // Pass1092 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1093 // Pass1094 } else {1095 fail!(<CommonError<T>>::UserIsNotAllowedToNest);1096 }10971098 if let Some(whitelist) = &nesting.restricted {1099 ensure!(1100 whitelist.contains(&from.0),1101 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1102 );1103 }1104 Ok(())1105 }11061107 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1108 <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1109 }11101111 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1112 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1113 }11141115 fn collection_has_tokens(collection_id: CollectionId) -> bool {1116 <TokenData<T>>::iter_prefix((collection_id,))1117 .next()1118 .is_some()1119 }11201121 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1122 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1123 .next()1124 .is_some()1125 }11261127 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1128 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1129 .map(|((child_collection_id, child_id), _)| TokenChild {1130 collection: child_collection_id,1131 token: child_id,1132 })1133 .collect()1134 }11351136 /// Delegated to `create_multiple_items`1137 pub fn create_item(1138 collection: &NonfungibleHandle<T>,1139 sender: &T::CrossAccountId,1140 data: CreateItemData<T>,1141 nesting_budget: &dyn Budget,1142 ) -> DispatchResult {1143 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1144 }1145}