difftreelog
Merge pull request #355 from UniqueNetwork/feature/nft-children
in: master
Structure children map
12 files changed
pallets/common/src/lib.rsdiffbeforeafterboth353 MustBeTokenOwner,353 MustBeTokenOwner,354 /// No permission to perform action354 /// No permission to perform action355 NoPermission,355 NoPermission,356 /// Destroying only empty collections is allowed357 CantDestroyNotEmptyCollection,356 /// Collection is not in mint mode.358 /// Collection is not in mint mode.357 PublicMintingNotAllowed,359 PublicMintingNotAllowed,358 /// Address is not in allow list.360 /// Address is not in allow list.1268 budget: &dyn Budget,1270 budget: &dyn Budget,1269 ) -> DispatchResult;1271 ) -> DispatchResult;12721273 fn nest(1274 &self,1275 under: TokenId,1276 to_nest: (CollectionId, TokenId)1277 );12781279 fn unnest(1280 &self,1281 under: TokenId,1282 to_nest: (CollectionId, TokenId)1283 );127012841271 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1285 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1272 fn collection_tokens(&self) -> Vec<TokenId>;1286 fn collection_tokens(&self) -> Vec<TokenId>;pallets/fungible/src/common.rsdiffbeforeafterboth298 fail!(<Error<T>>::FungibleDisallowsNesting)298 fail!(<Error<T>>::FungibleDisallowsNesting)299 }299 }300301 fn nest(302 &self,303 _under: TokenId,304 _to_nest: (CollectionId, TokenId)305 ) {}306307 fn unnest(308 &self,309 _under: TokenId,310 _to_nest: (CollectionId, TokenId)311 ) {}300312301 fn collection_tokens(&self) -> Vec<TokenId> {313 fn collection_tokens(&self) -> Vec<TokenId> {302 vec![TokenId::default()]314 vec![TokenId::default()]pallets/fungible/src/lib.rsdiffbeforeafterboth25 budget::Budget,25 budget::Budget,26};26};27use pallet_common::{27use pallet_common::{28 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,28 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,29 dispatch::CollectionDispatch, eth::collection_id_to_address,29 eth::collection_id_to_address,30};30};31use pallet_evm::Pallet as PalletEvm;31use pallet_evm::Pallet as PalletEvm;32use pallet_structure::Pallet as PalletStructure;32use pallet_structure::Pallet as PalletStructure;145 ) -> DispatchResult {145 ) -> DispatchResult {146 let id = collection.id;146 let id = collection.id;147148 if Self::collection_has_tokens(id) {149 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());150 }147151148 // =========152 // =========149153155 Ok(())159 Ok(())156 }160 }161162 fn collection_has_tokens(collection_id: CollectionId) -> bool {163 <TotalSupply<T>>::get(collection_id) != 0164 }157165158 pub fn burn(166 pub fn burn(159 collection: &FungibleHandle<T>,167 collection: &FungibleHandle<T>,176184177 if balance == 0 {185 if balance == 0 {178 <Balance<T>>::remove((collection.id, owner));186 <Balance<T>>::remove((collection.id, owner));187 <PalletStructure<T>>::unnest_if_nested(188 owner,189 collection.id,190 TokenId::default()191 );179 } else {192 } else {180 <Balance<T>>::insert((collection.id, owner), balance);193 <Balance<T>>::insert((collection.id, owner), balance);181 }194 }229 None242 None230 };243 };231244232 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {245 // =========233 let handle = <CollectionHandle<T>>::try_get(target.0)?;234 let dispatch = T::CollectionDispatch::dispatch(handle);235 let dispatch = dispatch.as_dyn();236246237 dispatch.check_nesting(247 <PalletStructure<T>>::nest_if_sent_to_token(238 from.clone(),248 from.clone(),249 to,239 (collection.id, TokenId::default()),250 collection.id,240 target.1,251 TokenId::default(),241 nesting_budget,252 nesting_budget242 )?;253 )?;243 }244245 // =========246254247 if let Some(balance_to) = balance_to {255 if let Some(balance_to) = balance_to {248 // from != to256 // from != to249 if balance_from == 0 {257 if balance_from == 0 {250 <Balance<T>>::remove((collection.id, from));258 <Balance<T>>::remove((collection.id, from));259 <PalletStructure<T>>::unnest_if_nested(260 from,261 collection.id,262 TokenId::default()263 );251 } else {264 } else {252 <Balance<T>>::insert((collection.id, from), balance_from);265 <Balance<T>>::insert((collection.id, from), balance_from);253 }266 }306 }319 }307320308 for (to, _) in balances.iter() {321 for (to, _) in balances.iter() {309 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {310 let handle = <CollectionHandle<T>>::try_get(target.0)?;311 let dispatch = T::CollectionDispatch::dispatch(handle);312 let dispatch = dispatch.as_dyn();313314 dispatch.check_nesting(322 <PalletStructure<T>>::check_nesting(315 sender.clone(),323 sender.clone(),324 to,316 (collection.id, TokenId::default()),325 collection.id,317 target.1,326 TokenId::default(),318 nesting_budget,327 nesting_budget,319 )?;328 )?;320 }321 }329 }322330323 // =========331 // =========324332325 <TotalSupply<T>>::insert(collection.id, total_supply);333 <TotalSupply<T>>::insert(collection.id, total_supply);326 for (user, amount) in balances {334 for (user, amount) in balances {327 <Balance<T>>::insert((collection.id, &user), amount);335 <Balance<T>>::insert((collection.id, &user), amount);328336 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(&user, collection.id, TokenId::default());329 <PalletEvm<T>>::deposit_log(337 <PalletEvm<T>>::deposit_log(330 ERC20Events::Transfer {338 ERC20Events::Transfer {331 from: H160::default(),339 from: H160::default(),pallets/nonfungible/src/common.rsdiffbeforeafterboth353 <Pallet<T>>::check_nesting(self, sender, from, under, budget)353 <Pallet<T>>::check_nesting(self, sender, from, under, budget)354 }354 }355356 fn nest(357 &self,358 under: TokenId,359 to_nest: (CollectionId, TokenId)360 ) {361 <Pallet<T>>::nest((self.id, under), to_nest);362 }363364 fn unnest(365 &self,366 under: TokenId,367 to_unnest: (CollectionId, TokenId)368 ) {369 <Pallet<T>>::unnest((self.id, under), to_unnest);370 }355371356 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {372 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {357 <Owned<T>>::iter_prefix((self.id, account))373 <Owned<T>>::iter_prefix((self.id, account))pallets/nonfungible/src/lib.rsdiffbeforeafterboth27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};28use pallet_common::{28use pallet_common::{29 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,29 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,30 dispatch::CollectionDispatch, eth::collection_id_to_address,30 eth::collection_id_to_address,31};31};32use pallet_structure::Pallet as PalletStructure;32use pallet_structure::Pallet as PalletStructure;33use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};33use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};76 NotNonfungibleDataUsedToMintFungibleCollectionToken,76 NotNonfungibleDataUsedToMintFungibleCollectionToken,77 /// Used amount > 1 with NFT77 /// Used amount > 1 with NFT78 NonfungibleItemsHaveNoAmount,78 NonfungibleItemsHaveNoAmount,79 /// Unable to burn NFT with children80 CantBurnNftWithChildren,79 }81 }808281 #[pallet::config]83 #[pallet::config]127 QueryKind = ValueQuery,129 QueryKind = ValueQuery,128 >;130 >;131132 /// Used to enumerate token's children133 #[pallet::storage]134 #[pallet::getter(fn token_children)]135 pub type TokenChildren<T: Config> = StorageNMap<136 Key = (137 Key<Twox64Concat, CollectionId>,138 Key<Twox64Concat, TokenId>,139 Key<Twox64Concat, (CollectionId, TokenId)>,140 ),141 Value = bool,142 QueryKind = ValueQuery,143 >;129144130 #[pallet::storage]145 #[pallet::storage]131 pub type AccountBalance<T: Config> = StorageNMap<146 pub type AccountBalance<T: Config> = StorageNMap<277 ) -> DispatchResult {292 ) -> DispatchResult {278 let id = collection.id;293 let id = collection.id;294295 if Self::collection_has_tokens(id) {296 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());297 }279298280 // =========299 // =========281300282 PalletCommon::destroy_collection(collection.0, sender)?;301 PalletCommon::destroy_collection(collection.0, sender)?;283302284 <TokenData<T>>::remove_prefix((id,), None);303 <TokenData<T>>::remove_prefix((id,), None);304 <TokenChildren<T>>::remove_prefix((id,), None);285 <Owned<T>>::remove_prefix((id,), None);305 <Owned<T>>::remove_prefix((id,), None);286 <TokensMinted<T>>::remove(id);306 <TokensMinted<T>>::remove(id);287 <TokensBurnt<T>>::remove(id);307 <TokensBurnt<T>>::remove(id);307 collection.check_allowlist(sender)?;327 collection.check_allowlist(sender)?;308 }328 }329330 if Self::token_has_children(collection.id, token) {331 return Err(<Error<T>>::CantBurnNftWithChildren.into());332 }309333310 let burnt = <TokensBurnt<T>>::get(collection.id)334 let burnt = <TokensBurnt<T>>::get(collection.id)311 .checked_add(1)335 .checked_add(1)315 .checked_sub(1)339 .checked_sub(1)316 .ok_or(ArithmeticError::Overflow)?;340 .ok_or(ArithmeticError::Overflow)?;341342 // =========317343318 if balance == 0 {344 if balance == 0 {319 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));345 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));320 } else {346 } else {321 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);347 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);322 }348 }323 // =========349350 <PalletStructure<T>>::unnest_if_nested(351 &token_data.owner,352 collection.id,353 token354 );324355325 <Owned<T>>::remove((collection.id, &token_data.owner, token));356 <Owned<T>>::remove((collection.id, &token_data.owner, token));326 <TokensBurnt<T>>::insert(collection.id, burnt);357 <TokensBurnt<T>>::insert(collection.id, burnt);553 None584 None554 };585 };555556 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {557 let handle = <CollectionHandle<T>>::try_get(target.0)?;558 let dispatch = T::CollectionDispatch::dispatch(handle);559 let dispatch = dispatch.as_dyn();560586561 dispatch.check_nesting(587 <PalletStructure<T>>::nest_if_sent_to_token(562 from.clone(),588 from.clone(),589 to,563 (collection.id, token),590 collection.id,564 target.1,591 token,565 nesting_budget,592 nesting_budget566 )?;593 )?;567 }568594569 // =========595 // =========596597 <PalletStructure<T>>::unnest_if_nested(598 from,599 collection.id,600 token601 );570602571 <TokenData<T>>::insert(603 <TokenData<T>>::insert(572 (collection.id, token),604 (collection.id, token),653685654 for (i, data) in data.iter().enumerate() {686 for (i, data) in data.iter().enumerate() {655 let token = TokenId(first_token + i as u32 + 1);687 let token = TokenId(first_token + i as u32 + 1);656 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {688657 let handle = <CollectionHandle<T>>::try_get(target.0)?;658 let dispatch = T::CollectionDispatch::dispatch(handle);659 let dispatch = dispatch.as_dyn();660 dispatch.check_nesting(689 <PalletStructure<T>>::check_nesting(661 sender.clone(),690 sender.clone(),691 &data.owner,662 (collection.id, token),692 collection.id,663 target.1,693 token,664 nesting_budget,694 nesting_budget,665 )?;695 )?;666 }667 }696 }668697669 // =========698 // =========680 },709 },681 );710 );711712 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(&data.owner, collection.id, TokenId(token));682713683 if let Err(e) = Self::set_token_properties(714 if let Err(e) = Self::set_token_properties(684 collection,715 collection,927 Ok(())958 Ok(())928 }959 }960961 fn nest(962 under: (CollectionId, TokenId),963 to_nest: (CollectionId, TokenId),964 ) {965 <TokenChildren<T>>::insert(966 (under.0, under.1, (to_nest.0, to_nest.1)),967 true968 );969 }970971 fn unnest(972 under: (CollectionId, TokenId),973 to_unnest: (CollectionId, TokenId),974 ) {975 <TokenChildren<T>>::remove(976 (under.0, under.1, to_unnest)977 );978 }979980 fn collection_has_tokens(collection_id: CollectionId) -> bool {981 <TokenData<T>>::iter_prefix((collection_id,)).next().is_some()982 }983984 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {985 <TokenChildren<T>>::iter_prefix((collection_id, token_id)).next().is_some()986 }929987930 /// Delegated to `create_multiple_items`988 /// Delegated to `create_multiple_items`931 pub fn create_item(989 pub fn create_item(pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth26 }26 }27}27}2829pub trait RmrkRebind<T, S> {30 fn rebind(&self) -> BoundedVec<u8, S>;31}3233impl<T, S> RmrkRebind<T, S> for BoundedVec<u8, T> where BoundedVec<u8, S>: TryFrom<Vec<u8>> {34 fn rebind(&self) -> BoundedVec<u8, S> {35 BoundedVec::<u8, S>::try_from(36 self.clone().into_inner()37 ).unwrap_or_default()38 }39}284029#[derive(Encode, Decode, PartialEq, Eq)]41#[derive(Encode, Decode, PartialEq, Eq)]30pub enum CollectionType {42pub enum CollectionType {pallets/refungible/src/common.rsdiffbeforeafterboth313 fail!(<Error<T>>::RefungibleDisallowsNesting)313 fail!(<Error<T>>::RefungibleDisallowsNesting)314 }314 }315316 fn nest(317 &self,318 _under: TokenId,319 _to_nest: (CollectionId, TokenId)320 ) {}321322 fn unnest(323 &self,324 _under: TokenId,325 _to_nest: (CollectionId, TokenId)326 ) {}315327316 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {328 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {317 <Owned<T>>::iter_prefix((self.id, account))329 <Owned<T>>::iter_prefix((self.id, account))pallets/refungible/src/lib.rsdiffbeforeafterboth23};23};24use pallet_evm::account::CrossAccountId;24use pallet_evm::account::CrossAccountId;25use pallet_common::{25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 dispatch::CollectionDispatch,28};27};29use pallet_structure::Pallet as PalletStructure;28use pallet_structure::Pallet as PalletStructure;30use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};29use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};211 ) -> DispatchResult {210 ) -> DispatchResult {212 let id = collection.id;211 let id = collection.id;212213 if Self::collection_has_tokens(id) {214 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());215 }213216214 // =========217 // =========215218226 Ok(())229 Ok(())227 }230 }231232 fn collection_has_tokens(collection_id: CollectionId) -> bool {233 <TokenData<T>>::iter_prefix((collection_id,)).next().is_some()234 }228235229 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {236 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {230 let burnt = <TokensBurnt<T>>::get(collection.id)237 let burnt = <TokensBurnt<T>>::get(collection.id)265 // =========272 // =========266273267 <Owned<T>>::remove((collection.id, owner, token));274 <Owned<T>>::remove((collection.id, owner, token));275 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);268 <AccountBalance<T>>::insert((collection.id, owner), account_balance);276 <AccountBalance<T>>::insert((collection.id, owner), account_balance);269 Self::burn_token(collection, token)?;277 Self::burn_token(collection, token)?;270 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(278 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(292300293 if balance == 0 {301 if balance == 0 {294 <Owned<T>>::remove((collection.id, owner, token));302 <Owned<T>>::remove((collection.id, owner, token));303 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);295 <Balance<T>>::remove((collection.id, token, owner));304 <Balance<T>>::remove((collection.id, token, owner));296 <AccountBalance<T>>::insert((collection.id, owner), account_balance);305 <AccountBalance<T>>::insert((collection.id, owner), account_balance);297 } else {306 } else {372 None381 None373 };382 };374383375 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {384 // =========376 let handle = <CollectionHandle<T>>::try_get(target.0)?;377 let dispatch = T::CollectionDispatch::dispatch(handle);378 let dispatch = dispatch.as_dyn();379385380 dispatch.check_nesting(386 <PalletStructure<T>>::nest_if_sent_to_token(381 from.clone(),387 from.clone(),388 to,382 (collection.id, token),389 collection.id,383 target.1,390 token,384 nesting_budget,391 nesting_budget385 )?;392 )?;386 }387388 // =========389393390 if let Some(balance_to) = balance_to {394 if let Some(balance_to) = balance_to {391 // from != to395 // from != to392 if balance_from == 0 {396 if balance_from == 0 {393 <Balance<T>>::remove((collection.id, token, from));397 <Balance<T>>::remove((collection.id, token, from));398 <PalletStructure<T>>::unnest_if_nested(399 from,400 collection.id,401 token402 );394 } else {403 } else {395 <Balance<T>>::insert((collection.id, token, from), balance_from);404 <Balance<T>>::insert((collection.id, token, from), balance_from);396 }405 }488 for (i, token) in data.iter().enumerate() {497 for (i, token) in data.iter().enumerate() {489 let token_id = TokenId(first_token_id + i as u32 + 1);498 let token_id = TokenId(first_token_id + i as u32 + 1);490 for (to, _) in token.users.iter() {499 for (to, _) in token.users.iter() {491 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {492 let handle = <CollectionHandle<T>>::try_get(target.0)?;493 let dispatch = T::CollectionDispatch::dispatch(handle);494 let dispatch = dispatch.as_dyn();495500496 dispatch.check_nesting(501 <PalletStructure<T>>::check_nesting(497 sender.clone(),502 sender.clone(),503 to,498 (collection.id, token_id),504 collection.id,499 target.1,505 token_id,500 nesting_budget,506 nesting_budget,501 )?;507 )?;502 }503 }508 }504 }509 }505510525 }531 }526 <Balance<T>>::insert((collection.id, token_id, &user), amount);532 <Balance<T>>::insert((collection.id, token_id, &user), amount);527 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);533 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);534 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(&user, collection.id, TokenId(token_id));535528 // TODO: ERC20 transfer event536 // TODO: ERC20 transfer event529 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(537 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(pallets/structure/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]1#![cfg_attr(not(feature = "std"), no_std)]223use pallet_common::CommonCollectionOperations;3use sp_std::collections::btree_set::BTreeSet;4use sp_std::collections::btree_set::BTreeSet;455use frame_support::dispatch::DispatchError;6use frame_support::dispatch::{DispatchError, DispatchResult};6use frame_support::fail;7use frame_support::fail;7pub use pallet::*;8pub use pallet::*;8use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};9use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};155 budget: &dyn Budget,156 budget: &dyn Budget,156 ) -> Result<bool, DispatchError> {157 ) -> Result<bool, DispatchError> {157 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {158 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {158 Some((collection, token)) => Parent::Token(collection, token),159 Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,159 None => Parent::User(user),160 None => user,160 };161 };161162162 // Tried to nest token in itself163 // Tried to nest token in itself171 return Err(<Error<T>>::OuroborosDetected.into())172 return Err(<Error<T>>::OuroborosDetected.into())172 }173 }173 // Found needed parent, token is indirecty owned174 // Found needed parent, token is indirecty owned174 v if v == target_parent => return Ok(true),175 Parent::User(user) if user == target_parent => return Ok(true),175 // Token is owned by other user176 // Token is owned by other user176 Parent::User(_) => return Ok(false),177 Parent::User(_) => return Ok(false),177 Parent::TokenNotFound => return Ok(false),178 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),178 // Continue parent chain179 // Continue parent chain179 Parent::Token(_, _) => {}180 Parent::Token(_, _) => {}180 }181 }183 Err(<Error<T>>::DepthLimit.into())184 Err(<Error<T>>::DepthLimit.into())184 }185 }186187 pub fn check_nesting(188 from: T::CrossAccountId,189 under: &T::CrossAccountId,190 collection_id: CollectionId,191 token_id: TokenId,192 nesting_budget: &dyn Budget193 ) -> DispatchResult {194 Self::try_exec_if_owner_is_valid_nft(195 under,196 |d, parent_id| d.check_nesting(197 from,198 (collection_id, token_id),199 parent_id,200 nesting_budget201 )202 )203 }204205 pub fn nest_if_sent_to_token(206 from: T::CrossAccountId,207 under: &T::CrossAccountId,208 collection_id: CollectionId,209 token_id: TokenId,210 nesting_budget: &dyn Budget211 ) -> DispatchResult {212 Self::try_exec_if_owner_is_valid_nft(213 under,214 |d, parent_id| {215 d.check_nesting(216 from,217 (collection_id, token_id),218 parent_id,219 nesting_budget220 )?;221222 d.nest(parent_id, (collection_id, token_id));223224 Ok(())225 }226 )227 }228229 pub fn nest_if_sent_to_token_unchecked(230 owner: &T::CrossAccountId,231 collection_id: CollectionId,232 token_id: TokenId233 ) {234 Self::exec_if_owner_is_valid_nft(235 owner,236 |d, parent_id| d.nest(237 parent_id,238 (collection_id, token_id)239 )240 );241 }242243 pub fn unnest_if_nested(244 owner: &T::CrossAccountId,245 collection_id: CollectionId,246 token_id: TokenId247 ) {248 Self::exec_if_owner_is_valid_nft(249 owner,250 |d, parent_id| d.unnest(251 parent_id,252 (collection_id, token_id)253 )254 );255 }256257 fn exec_if_owner_is_valid_nft(258 account: &T::CrossAccountId,259 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId)260 ) {261 Self::try_exec_if_owner_is_valid_nft(262 account,263 |d, id| {264 action(d, id);265 Ok(())266 }267 ).unwrap();268 }269270 fn try_exec_if_owner_is_valid_nft(271 account: &T::CrossAccountId,272 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult273 ) -> DispatchResult {274 let account = T::CrossTokenAddressMapping::address_to_token(account);275276 if account.is_none() {277 return Ok(());278 }279280 let account = account.unwrap();281282 let handle = <CollectionHandle<T>>::try_get(account.0);283284 if handle.is_err() {285 return Ok(());286 }287288 let handle = handle.unwrap();289290 let dispatch = T::CollectionDispatch::dispatch(handle);291 let dispatch = dispatch.as_dyn();292293 action(dispatch, account.1)294 }185}295}186296pallets/unique/src/lib.rsdiffbeforeafterboth55pub mod weights;55pub mod weights;56use weights::WeightInfo;56use weights::WeightInfo;5758const NESTING_BUDGET: u32 = 5;575958decl_error! {60decl_error! {59 /// Error for non-fungible-token module.61 /// Error for non-fungible-token module.569 #[transactional]571 #[transactional]570 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {572 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {571 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);573 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);572 let budget = budget::Value::new(2);574 let budget = budget::Value::new(NESTING_BUDGET);573575574 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))576 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))575 }577 }597 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {599 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {598 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);600 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);599 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);600 let budget = budget::Value::new(2);602 let budget = budget::Value::new(NESTING_BUDGET);601603602 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))604 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))603 }605 }678 #[transactional]680 #[transactional]679 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {681 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {680 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681 let budget = budget::Value::new(2);683 let budget = budget::Value::new(NESTING_BUDGET);682684683 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))685 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))684 }686 }758 #[transactional]760 #[transactional]759 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {761 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {760 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);761 let budget = budget::Value::new(2);763 let budget = budget::Value::new(NESTING_BUDGET);762764763 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))765 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))764 }766 }790 #[transactional]792 #[transactional]791 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {793 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {792 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);794 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793 let budget = budget::Value::new(2);795 let budget = budget::Value::new(NESTING_BUDGET);794796795 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))797 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))796 }798 }841 #[transactional]843 #[transactional]842 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {844 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {843 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);845 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);844 let budget = budget::Value::new(2);846 let budget = budget::Value::new(NESTING_BUDGET);845847846 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))848 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))847 }849 }runtime/common/src/runtime_apis.rsdiffbeforeafterboth25 dispatch_unique_runtime!(collection.token_owner(token))25 dispatch_unique_runtime!(collection.token_owner(token))26 }26 }27 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {27 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {28 let budget = up_data_structs::budget::Value::new(5);28 let budget = up_data_structs::budget::Value::new(10);292930 Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))30 Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))31 }31 }142 }142 }143143144 fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {144 fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {145 use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode}};145 use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind}};146146147 let collection_id = CollectionId(collection_id);147 let collection_id = CollectionId(collection_id);148 let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {148 let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {156 issuer: collection.owner.clone(),156 issuer: collection.owner.clone(),157 metadata: RmrkCore::get_collection_property(collection_id, RmrkProperty::Metadata)?.decode_or_default(),157 metadata: RmrkCore::get_collection_property(collection_id, RmrkProperty::Metadata)?.decode_or_default(),158 max: collection.limits.token_limit,158 max: collection.limits.token_limit,159 symbol: collection.token_prefix.decode_or_default(),159 symbol: collection.token_prefix.rebind(),160 nfts_count160 nfts_count161 }))161 }))162 }162 }204 }204 }205205206 fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {206 fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {207 use up_data_structs::mapping::TokenAddressMapping;208209 let collection_id = CollectionId(collection_id);207 let collection_id = CollectionId(collection_id);210 let nft_id = TokenId(nft_id);208 let nft_id = TokenId(nft_id);211 if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }209 if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }212213 let cross_account_id = CrossAccountId::from_eth(214 EvmTokenAddressMapping::token_to_address(collection_id, nft_id)215 );216210217 Ok(211 Ok(218 pallet_nonfungible::Owned::<Runtime>::iter_prefix((collection_id, cross_account_id))212 pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))219 .map(|(child_id, _)| RmrkNftChild {213 .filter_map(|(child_id, is_child)|214 match is_child {215 true => Some(RmrkNftChild {220 collection_id: collection_id.0, // todo make sure they're always from this collection // spoiler: they're not216 collection_id: child_id.0.0,221 nft_id: child_id.0,217 nft_id: child_id.1.0,222 }).collect()218 }),219 false => None,220 }221 ).collect()223 )222 )224 }223 }332331333 fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {332 fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {334 use pallet_proxy_rmrk_core::{333 use pallet_proxy_rmrk_core::{335 RmrkProperty, misc::{CollectionType, RmrkDecode},334 RmrkProperty, misc::{CollectionType, RmrkDecode, RmrkRebind},336 };335 };337336338 let collection_id = CollectionId(base_id);337 let collection_id = CollectionId(base_id);344 Ok(Some(RmrkBaseInfo {343 Ok(Some(RmrkBaseInfo {345 issuer: collection.owner.clone(),344 issuer: collection.owner.clone(),346 base_type: RmrkCore::get_collection_property(collection_id, RmrkProperty::BaseType)?.decode_or_default(),345 base_type: RmrkCore::get_collection_property(collection_id, RmrkProperty::BaseType)?.decode_or_default(),347 symbol: collection.token_prefix.decode_or_default(),346 symbol: collection.token_prefix.rebind(),348 }))347 }))349 }348 }350349tests/src/nesting/nest.test.tsdiffbeforeafterboth207 await setCollectionPermissionsExceptSuccess(alice, collection, {nesting: 'Owner'});207 await setCollectionPermissionsExceptSuccess(alice, collection, {nesting: 'Owner'});208 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');208 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');209209210 // Create a nested-token matryoshka211 const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});210 const maxNestingLevel = 5;211 let prevToken = targetToken;212213 // Create a nested-token matryoshka214 for (let i = 0; i < maxNestingLevel; i++) {212 const nestedToken2 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken1)});215 const nestedToken = await createItemExpectSuccess(216 alice,217 collection,218 'NFT',219 {Ethereum: tokenIdToAddress(collection, prevToken)},220 );221222 prevToken = nestedToken;223 }224213 // The nesting depth is limited by 2225 // The nesting depth is limited by `maxNestingLevel`214 await expect(executeTransaction(api, alice, api.tx.unique.createItem(226 await expect(executeTransaction(api, alice, api.tx.unique.createItem(215 collection, 227 collection,216 {Ethereum: tokenIdToAddress(collection, nestedToken2)}, 228 {Ethereum: tokenIdToAddress(collection, prevToken)},217 {nft: {const_data: [], variable_data: []}} as any,229 {nft: {const_data: [], variable_data: []}} as any,218 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);230 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);219231220 expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});232 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});221 });233 });222 });234 });223235