git.delta.rocks / unique-network / refs/commits / b08cb26894de

difftreelog

Merge pull request #355 from UniqueNetwork/feature/nft-children

Yaroslav Bolyukin2022-05-30parents: #764dfd5 #8cbbfc6.patch.diff
in: master
Structure children map

12 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
353 MustBeTokenOwner,353 MustBeTokenOwner,
354 /// No permission to perform action354 /// No permission to perform action
355 NoPermission,355 NoPermission,
356 /// Destroying only empty collections is allowed
357 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;
1272
1273 fn nest(
1274 &self,
1275 under: TokenId,
1276 to_nest: (CollectionId, TokenId)
1277 );
1278
1279 fn unnest(
1280 &self,
1281 under: TokenId,
1282 to_nest: (CollectionId, TokenId)
1283 );
12701284
1271 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>;
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
298 fail!(<Error<T>>::FungibleDisallowsNesting)298 fail!(<Error<T>>::FungibleDisallowsNesting)
299 }299 }
300
301 fn nest(
302 &self,
303 _under: TokenId,
304 _to_nest: (CollectionId, TokenId)
305 ) {}
306
307 fn unnest(
308 &self,
309 _under: TokenId,
310 _to_nest: (CollectionId, TokenId)
311 ) {}
300312
301 fn collection_tokens(&self) -> Vec<TokenId> {313 fn collection_tokens(&self) -> Vec<TokenId> {
302 vec![TokenId::default()]314 vec![TokenId::default()]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
25 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;
147
148 if Self::collection_has_tokens(id) {
149 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());
150 }
147151
148 // =========152 // =========
149153
155 Ok(())159 Ok(())
156 }160 }
161
162 fn collection_has_tokens(collection_id: CollectionId) -> bool {
163 <TotalSupply<T>>::get(collection_id) != 0
164 }
157165
158 pub fn burn(166 pub fn burn(
159 collection: &FungibleHandle<T>,167 collection: &FungibleHandle<T>,
176184
177 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 None
230 };243 };
231244
232 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();
236246
237 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_budget
242 )?;253 )?;
243 }
244
245 // =========
246254
247 if let Some(balance_to) = balance_to {255 if let Some(balance_to) = balance_to {
248 // from != to256 // from != to
249 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 }
307320
308 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();
313
314 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 }
322330
323 // =========331 // =========
324332
325 <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(),
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
353 <Pallet<T>>::check_nesting(self, sender, from, under, budget)353 <Pallet<T>>::check_nesting(self, sender, from, under, budget)
354 }354 }
355
356 fn nest(
357 &self,
358 under: TokenId,
359 to_nest: (CollectionId, TokenId)
360 ) {
361 <Pallet<T>>::nest((self.id, under), to_nest);
362 }
363
364 fn unnest(
365 &self,
366 under: TokenId,
367 to_unnest: (CollectionId, TokenId)
368 ) {
369 <Pallet<T>>::unnest((self.id, under), to_unnest);
370 }
355371
356 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))
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
27use 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 NFT
78 NonfungibleItemsHaveNoAmount,78 NonfungibleItemsHaveNoAmount,
79 /// Unable to burn NFT with children
80 CantBurnNftWithChildren,
79 }81 }
8082
81 #[pallet::config]83 #[pallet::config]
127 QueryKind = ValueQuery,129 QueryKind = ValueQuery,
128 >;130 >;
131
132 /// Used to enumerate token's children
133 #[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 >;
129144
130 #[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;
294
295 if Self::collection_has_tokens(id) {
296 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());
297 }
279298
280 // =========299 // =========
281300
282 PalletCommon::destroy_collection(collection.0, sender)?;301 PalletCommon::destroy_collection(collection.0, sender)?;
283302
284 <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 }
329
330 if Self::token_has_children(collection.id, token) {
331 return Err(<Error<T>>::CantBurnNftWithChildren.into());
332 }
309333
310 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)?;
341
342 // =========
317343
318 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 // =========349
350 <PalletStructure<T>>::unnest_if_nested(
351 &token_data.owner,
352 collection.id,
353 token
354 );
324355
325 <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 None
554 };585 };
555
556 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();
560586
561 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_budget
566 )?;593 )?;
567 }
568594
569 // =========595 // =========
596
597 <PalletStructure<T>>::unnest_if_nested(
598 from,
599 collection.id,
600 token
601 );
570602
571 <TokenData<T>>::insert(603 <TokenData<T>>::insert(
572 (collection.id, token),604 (collection.id, token),
653685
654 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) {688
657 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 }
668697
669 // =========698 // =========
680 },709 },
681 );710 );
711
712 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(&data.owner, collection.id, TokenId(token));
682713
683 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 }
960
961 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 true
968 );
969 }
970
971 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 }
979
980 fn collection_has_tokens(collection_id: CollectionId) -> bool {
981 <TokenData<T>>::iter_prefix((collection_id,)).next().is_some()
982 }
983
984 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {
985 <TokenChildren<T>>::iter_prefix((collection_id, token_id)).next().is_some()
986 }
929987
930 /// Delegated to `create_multiple_items`988 /// Delegated to `create_multiple_items`
931 pub fn create_item(989 pub fn create_item(
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
26 }26 }
27}27}
28
29pub trait RmrkRebind<T, S> {
30 fn rebind(&self) -> BoundedVec<u8, S>;
31}
32
33impl<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}
2840
29#[derive(Encode, Decode, PartialEq, Eq)]41#[derive(Encode, Decode, PartialEq, Eq)]
30pub enum CollectionType {42pub enum CollectionType {
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
313 fail!(<Error<T>>::RefungibleDisallowsNesting)313 fail!(<Error<T>>::RefungibleDisallowsNesting)
314 }314 }
315
316 fn nest(
317 &self,
318 _under: TokenId,
319 _to_nest: (CollectionId, TokenId)
320 ) {}
321
322 fn unnest(
323 &self,
324 _under: TokenId,
325 _to_nest: (CollectionId, TokenId)
326 ) {}
315327
316 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))
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
23};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;
212
213 if Self::collection_has_tokens(id) {
214 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());
215 }
213216
214 // =========217 // =========
215218
226 Ok(())229 Ok(())
227 }230 }
231
232 fn collection_has_tokens(collection_id: CollectionId) -> bool {
233 <TokenData<T>>::iter_prefix((collection_id,)).next().is_some()
234 }
228235
229 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 // =========
266273
267 <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(
292300
293 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 None
373 };382 };
374383
375 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();
379385
380 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_budget
385 )?;392 )?;
386 }
387
388 // =========
389393
390 if let Some(balance_to) = balance_to {394 if let Some(balance_to) = balance_to {
391 // from != to395 // from != to
392 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 token
402 );
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();
495500
496 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 }
505510
525 }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));
535
528 // TODO: ERC20 transfer event536 // TODO: ERC20 transfer event
529 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(537 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
1#![cfg_attr(not(feature = "std"), no_std)]1#![cfg_attr(not(feature = "std"), no_std)]
22
3use pallet_common::CommonCollectionOperations;
3use sp_std::collections::btree_set::BTreeSet;4use sp_std::collections::btree_set::BTreeSet;
45
5use 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 };
161162
162 // Tried to nest token in itself163 // Tried to nest token in itself
171 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 owned
174 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 user
176 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 chain
179 Parent::Token(_, _) => {}180 Parent::Token(_, _) => {}
180 }181 }
183 Err(<Error<T>>::DepthLimit.into())184 Err(<Error<T>>::DepthLimit.into())
184 }185 }
186
187 pub fn check_nesting(
188 from: T::CrossAccountId,
189 under: &T::CrossAccountId,
190 collection_id: CollectionId,
191 token_id: TokenId,
192 nesting_budget: &dyn Budget
193 ) -> 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_budget
201 )
202 )
203 }
204
205 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 Budget
211 ) -> 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_budget
220 )?;
221
222 d.nest(parent_id, (collection_id, token_id));
223
224 Ok(())
225 }
226 )
227 }
228
229 pub fn nest_if_sent_to_token_unchecked(
230 owner: &T::CrossAccountId,
231 collection_id: CollectionId,
232 token_id: TokenId
233 ) {
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 }
242
243 pub fn unnest_if_nested(
244 owner: &T::CrossAccountId,
245 collection_id: CollectionId,
246 token_id: TokenId
247 ) {
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 }
256
257 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 }
269
270 fn try_exec_if_owner_is_valid_nft(
271 account: &T::CrossAccountId,
272 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult
273 ) -> DispatchResult {
274 let account = T::CrossTokenAddressMapping::address_to_token(account);
275
276 if account.is_none() {
277 return Ok(());
278 }
279
280 let account = account.unwrap();
281
282 let handle = <CollectionHandle<T>>::try_get(account.0);
283
284 if handle.is_err() {
285 return Ok(());
286 }
287
288 let handle = handle.unwrap();
289
290 let dispatch = T::CollectionDispatch::dispatch(handle);
291 let dispatch = dispatch.as_dyn();
292
293 action(dispatch, account.1)
294 }
185}295}
186296
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
55pub mod weights;55pub mod weights;
56use weights::WeightInfo;56use weights::WeightInfo;
57
58const NESTING_BUDGET: u32 = 5;
5759
58decl_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);
573575
574 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);
601603
602 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);
682684
683 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);
762764
763 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);
794796
795 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);
845847
846 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 }
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
25 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);
2929
30 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 }
143143
144 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}};
146146
147 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_count
161 }))161 }))
162 }162 }
204 }204 }
205205
206 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;
208
209 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()); }
212
213 let cross_account_id = CrossAccountId::from_eth(
214 EvmTokenAddressMapping::token_to_address(collection_id, nft_id)
215 );
216210
217 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 }
332331
333 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 };
337336
338 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 }
350349
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
207 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');
209209
210 // Create a nested-token matryoshka
211 const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});210 const maxNestingLevel = 5;
211 let prevToken = targetToken;
212
213 // Create a nested-token matryoshka
214 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 );
221
222 prevToken = nestedToken;
223 }
224
213 // 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$/);
219231
220 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