From 77688fbffa89292b00235d3ad4d02becc27ca3de Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 26 May 2022 21:35:51 +0000 Subject: [PATCH] feat: burn children when destroying a collection --- --- a/pallets/common/src/dispatch.rs +++ b/pallets/common/src/dispatch.rs @@ -6,7 +6,7 @@ weights::Pays, traits::Get, }; -use up_data_structs::{CollectionId, CreateCollectionData}; +use up_data_structs::{CollectionId, CreateCollectionData, budget::Budget}; use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle}; @@ -57,7 +57,11 @@ pub trait CollectionDispatch { fn create(sender: T::AccountId, data: CreateCollectionData) -> DispatchResult; - fn destroy(sender: T::CrossAccountId, handle: CollectionHandle) -> DispatchResult; + fn destroy( + sender: T::CrossAccountId, + handle: CollectionHandle, + nesting_budget: &dyn Budget, + ) -> DispatchResult; fn dispatch(handle: CollectionHandle) -> Self; fn into_inner(self) -> CollectionHandle; --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -1169,6 +1169,12 @@ token: TokenId, amount: u128, ) -> DispatchResultWithPostInfo; + fn burn_item_unchecked( + &self, + owner: &T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResult; fn set_collection_properties( &self, sender: T::CrossAccountId, --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -170,6 +170,17 @@ ) } + fn burn_item_unchecked( + &self, + owner: &T::CrossAccountId, + _token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + >::burn_item_unchecked(self, owner, amount)?; + + Ok(()) + } + fn transfer( &self, from: T::CrossAccountId, --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -160,6 +160,36 @@ owner: &T::CrossAccountId, amount: u128, ) -> DispatchResult { + if collection.access == AccessMode::AllowList { + collection.check_allowlist(owner)?; + } + + // ========= + + Self::burn_item_unchecked(collection, owner, amount)?; + + >::deposit_log( + ERC20Events::Transfer { + from: *owner.as_eth(), + to: H160::default(), + value: amount.into(), + } + .to_log(collection_id_to_address(collection.id)), + ); + >::deposit_event(CommonEvent::ItemDestroyed( + collection.id, + TokenId::default(), + owner.clone(), + amount, + )); + Ok(()) + } + + pub fn burn_item_unchecked( + collection: &FungibleHandle, + owner: &T::CrossAccountId, + amount: u128, + ) -> DispatchResult { let total_supply = >::get(collection.id) .checked_sub(amount) .ok_or(>::TokenValueTooLow)?; @@ -186,20 +216,6 @@ } >::insert(collection.id, total_supply); - >::deposit_log( - ERC20Events::Transfer { - from: *owner.as_eth(), - to: H160::default(), - value: amount.into(), - } - .to_log(collection_id_to_address(collection.id)), - ); - >::deposit_event(CommonEvent::ItemDestroyed( - collection.id, - TokenId::default(), - owner.clone(), - amount, - )); Ok(()) } --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -264,6 +264,19 @@ } } + fn burn_item_unchecked( + &self, + owner:& T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + if amount == 1 { + >::burn_item_unchecked(self, owner, token) + } else { + Ok(()) + } + } + fn transfer( &self, from: T::CrossAccountId, --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -27,6 +27,7 @@ use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_common::{ Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle, + dispatch::CollectionDispatch, eth::collection_id_to_address, }; use pallet_structure::Pallet as PalletStructure; @@ -79,6 +80,8 @@ NonfungibleItemsHaveNoAmount, /// Unable to burn NFT with children CantBurnNftWithChildren, + /// Too many children to burn when destroying a collection + TooManyChildrenToBurn, } #[pallet::config] @@ -290,13 +293,14 @@ pub fn destroy_collection( collection: NonfungibleHandle, sender: &T::CrossAccountId, + nesting_budget: &dyn Budget, ) -> DispatchResult { let id = collection.id; // ========= + Self::burn_children_in_collection(id, nesting_budget)?; PalletCommon::destroy_collection(collection.0, sender)?; - >::remove_prefix((id,), None); >::remove_prefix((id,), None); >::remove_prefix((id,), None); @@ -307,6 +311,44 @@ Ok(()) } + #[transactional] + fn burn_children_in_collection(collection_id: CollectionId, nesting_budget: &dyn Budget) -> DispatchResult { + for (parent_id, child) in >::drain_prefix((collection_id,)) + .map(|((parent_id, child), _)| (parent_id, child)) { + + let parent_address = T::CrossTokenAddressMapping::token_to_address(collection_id, parent_id); + Self::burn_tree(parent_address, child.0, child.1, nesting_budget)?; + } + + Ok(()) + } + + fn burn_tree( + parent: T::CrossAccountId, + collection_id: CollectionId, + token_id: TokenId, + nesting_budget: &dyn Budget + ) -> DispatchResult { + if !nesting_budget.consume() { + return Err(>::TooManyChildrenToBurn.into()); + } + + let handle = >::try_get(collection_id)?; + let handle = T::CollectionDispatch::dispatch(handle); + let handle = handle.as_dyn(); + + let amount = handle.balance(parent.clone(), token_id); + + handle.burn_item_unchecked(&parent, token_id, amount)?; + + for child in >::drain_prefix((collection_id, token_id)).map(|(child, _)| child) { + let parent = T::CrossTokenAddressMapping::token_to_address(collection_id, token_id); + Self::burn_tree(parent, child.0, child.1, nesting_budget)?; + } + + Ok(()) + } + pub fn burn( collection: &NonfungibleHandle, sender: &T::CrossAccountId, @@ -328,31 +370,11 @@ return Err(>::CantBurnNftWithChildren.into()); } - let burnt = >::get(collection.id) - .checked_add(1) - .ok_or(ArithmeticError::Overflow)?; - - let balance = >::get((collection.id, token_data.owner.clone())) - .checked_sub(1) - .ok_or(ArithmeticError::Overflow)?; - - if balance == 0 { - >::remove((collection.id, token_data.owner.clone())); - } else { - >::insert((collection.id, token_data.owner.clone()), balance); - } + let old_spender = >::get((collection.id, token)); - if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(&token_data.owner) { - Self::unnest(owner, (collection.id, token)); - } - // ========= - >::remove((collection.id, &token_data.owner, token)); - >::insert(collection.id, burnt); - >::remove((collection.id, token)); - >::remove((collection.id, token)); - let old_spender = >::take((collection.id, token)); + Self::burn_item_unchecked(collection, &token_data.owner, token)?; if let Some(old_spender) = old_spender { >::deposit_event(CommonEvent::Approved( @@ -381,6 +403,40 @@ Ok(()) } + pub fn burn_item_unchecked( + collection: &NonfungibleHandle, + owner: &T::CrossAccountId, + token: TokenId, + ) -> DispatchResult { + let burnt = >::get(collection.id) + .checked_add(1) + .ok_or(ArithmeticError::Overflow)?; + + let balance = >::get((collection.id, owner.clone())) + .checked_sub(1) + .ok_or(ArithmeticError::Overflow)?; + + // ========= + + if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(owner) { + Self::unnest(owner, (collection.id, token)); + } + + if balance == 0 { + >::remove((collection.id, owner.clone())); + } else { + >::insert((collection.id, owner.clone()), balance); + } + + >::remove((collection.id, owner, token)); + >::insert(collection.id, burnt); + >::remove((collection.id, token)); + >::remove((collection.id, token)); + >::remove((collection.id, token)); + + Ok(()) + } + pub fn set_token_property( collection: &NonfungibleHandle, sender: &T::CrossAccountId, --- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -179,7 +179,8 @@ ensure!(collection.total_supply() == 0, >::CollectionNotEmpty); - >::destroy_collection(collection, &cross_sender) + let empty_budget = budget::Value::new(0); + >::destroy_collection(collection, &cross_sender, &empty_budget) .map_err(Self::map_common_err_to_proxy)?; Self::deposit_event(Event::CollectionDestroyed { issuer: sender, collection_id }); --- a/pallets/refungible/src/common.rs +++ b/pallets/refungible/src/common.rs @@ -205,6 +205,15 @@ ) } + fn burn_item_unchecked( + &self, + owner: &T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + >::burn_item_unchecked(self, owner, token, amount) + } + fn transfer( &self, from: T::CrossAccountId, --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -245,6 +245,25 @@ token: TokenId, amount: u128, ) -> DispatchResult { + Self::burn_item_unchecked(collection, owner, token, amount)?; + + // TODO: ERC20 transfer event + >::deposit_event(CommonEvent::ItemDestroyed( + collection.id, + token, + owner.clone(), + amount, + )); + + Ok(()) + } + + pub fn burn_item_unchecked( + collection: &RefungibleHandle, + owner: &T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResult { let total_supply = >::get((collection.id, token)) .checked_sub(amount) .ok_or(>::TokenValueTooLow)?; @@ -299,13 +318,6 @@ >::insert((collection.id, token, owner), balance); } >::insert((collection.id, token), total_supply); - // TODO: ERC20 transfer event - >::deposit_event(CommonEvent::ItemDestroyed( - collection.id, - token, - owner.clone(), - amount, - )); Ok(()) } --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -332,15 +332,24 @@ /// # Arguments /// /// * collection_id: collection to destroy. - #[weight = >::destroy_collection()] + #[weight = + >::destroy_collection() + + >::burn_children_in_collection(*max_children_to_burn) + ] #[transactional] - pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult { + pub fn destroy_collection( + origin, + collection_id: CollectionId, + max_children_to_burn: u32, + ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let collection = >::try_get(collection_id)?; + let budget = budget::Value::new(max_children_to_burn); + // ========= - T::CollectionDispatch::destroy(sender, collection)?; + T::CollectionDispatch::destroy(sender, collection, &budget)?; >::remove_prefix(collection_id, None); >::remove_prefix(collection_id, None); --- a/pallets/unique/src/weights.rs +++ b/pallets/unique/src/weights.rs @@ -34,6 +34,7 @@ pub trait WeightInfo { fn create_collection() -> Weight; fn destroy_collection() -> Weight; + fn burn_children_in_collection(max: u32) -> Weight; fn add_to_allow_list() -> Weight; fn remove_from_allow_list() -> Weight; fn set_public_access_mode() -> Weight; @@ -73,6 +74,12 @@ .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + + fn burn_children_in_collection(max: u32) -> Weight { + // TODO + (50_000_000 as Weight).saturating_mul(max as Weight) + } + // Storage: Common CollectionById (r:1 w:0) // Storage: Common Allowlist (r:0 w:1) fn add_to_allow_list() -> Weight { @@ -192,6 +199,12 @@ .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + + fn burn_children_in_collection(max: u32) -> Weight { + // TODO + (50_000_000 as Weight).saturating_mul(max as Weight) + } + // Storage: Common CollectionById (r:1 w:0) // Storage: Common Allowlist (r:0 w:1) fn add_to_allow_list() -> Weight { --- a/runtime/common/src/dispatch.rs +++ b/runtime/common/src/dispatch.rs @@ -1,4 +1,4 @@ -use frame_support::{dispatch::DispatchResult, ensure}; +use frame_support::{dispatch::{DispatchResult}, ensure}; use pallet_evm::PrecompileResult; use sp_core::{H160, U256}; use sp_std::{borrow::ToOwned, vec::Vec}; @@ -12,6 +12,7 @@ use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc::RefungibleTokenHandle}; use up_data_structs::{ CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping, + budget::Budget, }; pub enum CollectionDispatchT @@ -46,7 +47,11 @@ Ok(()) } - fn destroy(sender: T::CrossAccountId, collection: CollectionHandle) -> DispatchResult { + fn destroy( + sender: T::CrossAccountId, + collection: CollectionHandle, + nesting_budget: &dyn Budget, + ) -> DispatchResult { match collection.mode { CollectionMode::ReFungible => { PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)? @@ -55,7 +60,11 @@ PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)? } CollectionMode::NFT => { - PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)? + PalletNonfungible::destroy_collection( + NonfungibleHandle::cast(collection), + &sender, + nesting_budget, + )? } } Ok(()) -- gitstuff