From 390a1544bccfffddcf0202ab88de0b1ed74bdaa7 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 06 Oct 2021 10:54:39 +0000 Subject: [PATCH] feat: burn_from --- --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -987,16 +987,44 @@ /// * item_id: ID of NFT to burn. #[weight = >::burn_item()] #[transactional] - pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, item_owner: T::CrossAccountId, value: u128) -> DispatchResult { + pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let target_collection = Self::get_collection(collection_id)?; - Self::burn_item_internal(&sender, &target_collection, item_id, &item_owner, value)?; + Self::burn_item_internal(&sender, &target_collection, item_id, value, true)?; target_collection.submit_logs() } + /// Destroys a concrete instance of NFT on behalf of the owner + /// See also: [`approve`] + /// + /// # Permissions + /// + /// * Collection Owner. + /// * Collection Admin. + /// * Current NFT Owner. + /// + /// # Arguments + /// + /// * collection_id: ID of the collection. + /// + /// * item_id: ID of NFT to burn. + /// + /// * from: owner of item + #[weight = >::burn_item()] + #[transactional] + pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResult { + + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + let target_collection = Self::get_collection(collection_id)?; + + Self::burn_from_internal(&sender, &target_collection, &from, item_id, value)?; + + target_collection.submit_logs() + } + /// Change ownership of the token. /// /// # Permissions @@ -1593,21 +1621,22 @@ } pub fn burn_item_internal( - sender: &T::CrossAccountId, + owner: &T::CrossAccountId, collection: &CollectionHandle, item_id: TokenId, - item_owner: &T::CrossAccountId, value: u128, + allow_escalation: bool, ) -> DispatchResult { ensure!( - Self::is_item_owner(sender, collection, item_id)? - || (collection.limits.owner_can_transfer - && Self::is_owner_or_admin_permissions(collection, sender)?), + Self::is_item_owner(owner, collection, item_id)? + || (allow_escalation + && collection.limits.owner_can_transfer + && Self::is_owner_or_admin_permissions(collection, owner)?), Error::::NoPermission ); if collection.access == AccessMode::WhiteList { - Self::check_white_list(collection, sender)?; + Self::check_white_list(collection, owner)?; } match collection.mode { @@ -1617,13 +1646,71 @@ _ => fail!(>::TokenValueTooLow), }, CollectionMode::Fungible(_) => Self::burn_fungible_item(collection, owner, value)?, - CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, owner, value)?, + CollectionMode::ReFungible => { + Self::burn_refungible_item(collection, item_id, owner, value)? + } _ => (), }; Ok(()) } + pub fn burn_from_internal( + sender: &T::CrossAccountId, + collection: &CollectionHandle, + from: &T::CrossAccountId, + item_id: TokenId, + amount: u128, + ) -> DispatchResult { + if sender == from { + // Transfer by `from`, because it is either equal to sender, or derived from him + return Self::burn_item_internal(from, collection, item_id, amount, true); + } + + // Check approval + collection.consume_sload()?; + let approval: u128 = + >::get(collection.id, (item_id, from.as_sub(), sender.as_sub())); + + // Transfer permissions check + ensure!( + approval >= amount + || (collection.limits.owner_can_transfer + && Self::is_owner_or_admin_permissions(collection, sender)?), + Error::::NoPermission + ); + + if collection.access == AccessMode::WhiteList { + Self::check_white_list(collection, sender)?; + } + + // Reduce approval by transferred amount or remove if remaining approval drops to 0 + let allowance = approval.saturating_sub(amount); + collection.consume_sstore()?; + if allowance > 0 { + >::insert( + collection.id, + (item_id, from.as_sub(), sender.as_sub()), + allowance, + ); + } else { + >::remove(collection.id, (item_id, from.as_sub(), sender.as_sub())); + } + + // Escalation is disallowed here, because we need to be sure that passed owner is real + Self::burn_item_internal(from, collection, item_id, amount, false)?; + + if matches!(collection.mode, CollectionMode::Fungible(_)) { + collection.log(ERC20Events::Approval { + owner: *from.as_eth(), + spender: *sender.as_eth(), + value: allowance.into(), + })?; + } + + Ok(()) + } + pub fn toggle_white_list_internal( sender: &T::CrossAccountId, collection: &CollectionHandle, -- gitstuff