git.delta.rocks / unique-network / refs/commits / 44992fca8f8c

difftreelog

feat burn_from

Yaroslav Bolyukin2021-11-02parent: #283713e.patch.diff
in: master

12 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -459,6 +459,7 @@
 	fn transfer() -> Weight;
 	fn approve() -> Weight;
 	fn transfer_from() -> Weight;
+	fn burn_from() -> Weight;
 	fn set_variable_metadata(bytes: u32) -> Weight;
 }
 
@@ -504,6 +505,13 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResultWithPostInfo;
+	fn burn_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo;
 
 	fn set_variable_metadata(
 		&self,
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -38,6 +38,10 @@
 		<SelfWeightOf<T>>::transfer_from()
 	}
 
+	fn burn_from() -> Weight {
+		0
+	}
+
 	fn set_variable_metadata(_bytes: u32) -> Weight {
 		// Error
 		0
@@ -156,6 +160,24 @@
 		)
 	}
 
+	fn burn_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(
+			token == TokenId::default(),
+			<Error<T>>::FungibleItemsHaveNoId
+		);
+
+		with_weight(
+			<Pallet<T>>::burn_from(&self, &sender, &from, amount),
+			<CommonWeights<T>>::burn_from(),
+		)
+	}
+
 	fn set_variable_metadata(
 		&self,
 		_sender: T::CrossAccountId,
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -96,6 +96,18 @@
 	}
 }
 
+#[solidity_interface(name = "ERC20UniqueExtensions")]
+impl<T: Config> FungibleHandle<T> {
+	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Pallet<T>>::burn_from(self, &caller, &from, amount).map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+}
+
 #[solidity_interface(name = "UniqueFungible", is(ERC20))]
 impl<T: Config> FungibleHandle<T> {}
 
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -356,6 +356,38 @@
 		Ok(())
 	}
 
+	pub fn burn_from(
+		collection: &FungibleHandle<T>,
+		spender: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		amount: u128,
+	) -> DispatchResult {
+		if spender == from {
+			return Self::burn(collection, from, amount);
+		}
+		if collection.access == AccessMode::WhiteList {
+			// `from` checked in [`burn`]
+			collection.check_allowlist(spender)?;
+		}
+
+		let allowance = <Allowance<T>>::get((collection.id, from.as_sub(), spender.as_sub()))
+			.checked_sub(amount);
+		if allowance.is_none() {
+			ensure!(
+				collection.ignores_allowance(spender)?,
+				<CommonError<T>>::TokenValueNotEnough
+			);
+		}
+
+		// =========
+
+		Self::burn(collection, from, amount)?;
+		if let Some(allowance) = allowance {
+			Self::set_allowance_unchecked(collection, from, spender, allowance);
+		}
+		Ok(())
+	}
+
 	/// Delegated to `create_multiple_items`
 	pub fn create_item(
 		collection: &FungibleHandle<T>,
modifiedpallets/nft/src/common.rsdiffbeforeafterboth
--- a/pallets/nft/src/common.rs
+++ b/pallets/nft/src/common.rs
@@ -45,4 +45,8 @@
 	fn set_variable_metadata(bytes: u32) -> nft_data_structs::Weight {
 		dispatch_weight::<T>() + max_weight_of!(set_variable_metadata(bytes))
 	}
+
+	fn burn_from() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(burn_from())
+	}
 }
modifiedpallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -35,9 +35,10 @@
 				.map_err(|_| AnyError)?
 				.ok_or(AnyError)?;
 			match call {
-				UniqueNFTCall::ERC721UniqueExtensions(
-					ERC721UniqueExtensionsCall::TransferNft { token_id, .. },
-				)
+				UniqueNFTCall::ERC721UniqueExtensions(ERC721UniqueExtensionsCall::Transfer {
+					token_id,
+					..
+				})
 				| UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => {
 					let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
 					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -623,14 +623,13 @@
 		/// * item_id: ID of NFT to burn.
 		///
 		/// * from: owner of item
-		// #[weight = 0]
-		// #[transactional]
-		// pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> PostDispatchInfo {
-		// 	let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
+		#[weight = <CommonWeights<T>>::burn_from()]
+		#[transactional]
+		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
+			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-		// 	// dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))
-		// 	todo!()
-		// }
+			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))
+		}
 
 		/// Change ownership of the token.
 		///
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -39,6 +39,10 @@
 		<SelfWeightOf<T>>::transfer_from()
 	}
 
+	fn burn_from() -> Weight {
+		0
+	}
+
 	fn set_variable_metadata(bytes: u32) -> Weight {
 		<SelfWeightOf<T>>::set_variable_metadata(bytes)
 	}
@@ -163,6 +167,25 @@
 		}
 	}
 
+	fn burn_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		if amount == 1 {
+			with_weight(
+				<Pallet<T>>::burn_from(&self, &sender, &from, token),
+				<CommonWeights<T>>::burn_from(),
+			)
+		} else {
+			Ok(().into())
+		}
+	}
+
 	fn set_variable_metadata(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -264,8 +264,7 @@
 
 #[solidity_interface(name = "ERC721UniqueExtensions")]
 impl<T: Config> NonfungibleHandle<T> {
-	#[solidity(rename_selector = "transfer")]
-	fn transfer_nft(
+	fn transfer(
 		&mut self,
 		caller: caller,
 		to: address,
@@ -280,6 +279,21 @@
 		Ok(())
 	}
 
+	fn burn_from(
+		&mut self,
+		caller: caller,
+		from: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
 	fn next_token_id(&self) -> Result<uint256> {
 		Ok(<TokensMinted<T>>::get(self.id)
 			.checked_add(1)
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};17use scale_info::TypeInfo;1819pub use pallet::*;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod common;23pub mod erc;24pub mod weights;2526pub struct CreateItemData<T: Config> {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29	pub owner: T::CrossAccountId,30}31pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3233#[derive(Encode, Decode, TypeInfo)]34pub struct ItemData<T: Config> {35	pub const_data: Vec<u8>,36	pub variable_data: Vec<u8>,37	pub owner: T::CrossAccountId,38}3940#[frame_support::pallet]41pub mod pallet {42	use super::*;43	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};44	use nft_data_structs::{CollectionId, TokenId};45	use super::weights::WeightInfo;4647	#[pallet::error]48	pub enum Error<T> {49		/// Not Nonfungible item data used to mint in Nonfungible collection.50		NotNonfungibleDataUsedToMintFungibleCollectionToken,51		/// Used amount > 1 with NFT52		NonfungibleItemsHaveNoAmount,53	}5455	#[pallet::config]56	pub trait Config: frame_system::Config + pallet_common::Config {57		type WeightInfo: WeightInfo;58	}5960	#[pallet::pallet]61	#[pallet::generate_store(pub(super) trait Store)]62	pub struct Pallet<T>(_);6364	#[pallet::storage]65	pub(super) type TokensMinted<T: Config> =66		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;67	#[pallet::storage]68	pub(super) type TokensBurnt<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub(super) type TokenData<T: Config> = StorageNMap<73		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),74		Value = ItemData<T>,75		QueryKind = OptionQuery,76	>;7778	/// Used to enumerate tokens owned by account79	#[pallet::storage]80	pub(super) type Owned<T: Config> = StorageNMap<81		Key = (82			Key<Twox64Concat, CollectionId>,83			Key<Blake2_128Concat, T::AccountId>,84			Key<Twox64Concat, TokenId>,85		),86		Value = bool,87		QueryKind = ValueQuery,88	>;8990	#[pallet::storage]91	pub(super) type AccountBalance<T: Config> = StorageNMap<92		Key = (93			Key<Twox64Concat, CollectionId>,94			Key<Blake2_128Concat, T::AccountId>,95		),96		Value = u32,97		QueryKind = ValueQuery,98	>;99100	#[pallet::storage]101	pub(super) type Allowance<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = T::CrossAccountId,104		QueryKind = OptionQuery,105	>;106}107108pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);109impl<T: Config> NonfungibleHandle<T> {110	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {111		Self(inner)112	}113	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {114		self.0115	}116}117impl<T: Config> Deref for NonfungibleHandle<T> {118	type Target = pallet_common::CollectionHandle<T>;119120	fn deref(&self) -> &Self::Target {121		&self.0122	}123}124125impl<T: Config> Pallet<T> {126	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {127		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)128	}129	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {130		<TokenData<T>>::contains_key((collection.id, token))131	}132}133134// unchecked calls skips any permission checks135impl<T: Config> Pallet<T> {136	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {137		PalletCommon::init_collection(data)138	}139	pub fn destroy_collection(140		collection: NonfungibleHandle<T>,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		let id = collection.id;144145		// =========146147		PalletCommon::destroy_collection(collection.0, sender)?;148149		<TokenData<T>>::remove_prefix((id,), None);150		<Owned<T>>::remove_prefix((id,), None);151		<TokensMinted<T>>::remove(id);152		<TokensBurnt<T>>::remove(id);153		<Allowance<T>>::remove_prefix((id,), None);154		<AccountBalance<T>>::remove_prefix((id,), None);155		Ok(())156	}157158	pub fn burn(159		collection: &NonfungibleHandle<T>,160		sender: &T::CrossAccountId,161		token: TokenId,162	) -> DispatchResult {163		let token_data = <TokenData<T>>::get((collection.id, token))164			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;165		ensure!(166			&token_data.owner == sender167				|| (collection.limits.owner_can_transfer168					&& collection.is_owner_or_admin(sender)?),169			<CommonError<T>>::NoPermission170		);171172		if collection.access == AccessMode::WhiteList {173			collection.check_allowlist(sender)?;174		}175176		let burnt = <TokensBurnt<T>>::get(collection.id)177			.checked_add(1)178			.ok_or(ArithmeticError::Overflow)?;179180		// =========181182		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));183		<TokensBurnt<T>>::insert(collection.id, burnt);184		<TokenData<T>>::remove((collection.id, token));185		let old_spender = <Allowance<T>>::take((collection.id, token));186187		if let Some(old_spender) = old_spender {188			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(189				collection.id,190				token,191				sender.clone(),192				old_spender.clone(),193				0,194			));195		}196197		collection.log_infallible(ERC721Events::Transfer {198			from: *token_data.owner.as_eth(),199			to: H160::default(),200			token_id: token.into(),201		});202		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(203			collection.id,204			token,205			token_data.owner,206			1,207		));208		return Ok(());209	}210211	pub fn transfer(212		collection: &NonfungibleHandle<T>,213		from: &T::CrossAccountId,214		to: &T::CrossAccountId,215		token: TokenId,216	) -> DispatchResult {217		ensure!(218			collection.transfers_enabled,219			<CommonError<T>>::TransferNotAllowed220		);221222		let token_data = <TokenData<T>>::get((collection.id, token))223			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;224		ensure!(225			&token_data.owner == from226				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),227			<CommonError<T>>::NoPermission228		);229230		if collection.access == AccessMode::WhiteList {231			collection.check_allowlist(from)?;232			collection.check_allowlist(to)?;233		}234		<PalletCommon<T>>::ensure_correct_receiver(to)?;235236		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))237			.checked_sub(1)238			.ok_or(<CommonError<T>>::TokenValueTooLow)?;239		let balance_to = if from != to {240			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))241				.checked_add(1)242				.ok_or(ArithmeticError::Overflow)?;243244			ensure!(245				balance_to < collection.limits.account_token_ownership_limit(),246				<CommonError<T>>::AccountTokenLimitExceeded,247			);248249			Some(balance_to)250		} else {251			None252		};253254		collection.consume_sstores(4)?;255		collection.consume_log(3, 0)?;256257		// =========258259		<TokenData<T>>::insert(260			(collection.id, token),261			ItemData {262				owner: to.clone(),263				..token_data264			},265		);266267		if let Some(balance_to) = balance_to {268			// from != to269			if balance_from == 0 {270				<AccountBalance<T>>::remove((collection.id, from.as_sub()));271			} else {272				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);273			}274			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);275			<Owned<T>>::remove((collection.id, from.as_sub(), token));276			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);277		}278		Self::set_allowance_unchecked(collection, from, token, None);279280		collection.log_infallible(ERC721Events::Transfer {281			from: *from.as_eth(),282			to: *to.as_eth(),283			token_id: token.into(),284		});285		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(286			collection.id,287			token,288			from.clone(),289			to.clone(),290			1,291		));292		Ok(())293	}294295	pub fn create_multiple_items(296		collection: &NonfungibleHandle<T>,297		sender: &T::CrossAccountId,298		data: Vec<CreateItemData<T>>,299	) -> DispatchResult {300		let unrestricted_minting = collection.is_owner_or_admin(sender)?;301		if !unrestricted_minting {302			ensure!(303				collection.mint_mode,304				<CommonError<T>>::PublicMintingNotAllowed305			);306			collection.check_allowlist(sender)?;307308			for item in data.iter() {309				collection.check_allowlist(&item.owner)?;310			}311		}312313		for data in data.iter() {314			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;315			if !data.const_data.is_empty() {316				collection.consume_sstore()?;317			}318			if !data.variable_data.is_empty() {319				collection.consume_sstore()?;320			}321			collection.consume_sstore()?;322			collection.consume_log(3, 0)?;323		}324325		let first_token = <TokensMinted<T>>::get(collection.id);326		let tokens_minted = first_token327			.checked_add(data.len() as u32)328			.ok_or(ArithmeticError::Overflow)?;329		ensure!(330			tokens_minted < collection.limits.token_limit,331			<CommonError<T>>::CollectionTokenLimitExceeded332		);333		collection.consume_sstore()?;334335		let mut balances = BTreeMap::new();336		for data in &data {337			let balance = balances338				.entry(data.owner.as_sub())339				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));340			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;341342			ensure!(343				*balance <= collection.limits.account_token_ownership_limit(),344				<CommonError<T>>::AccountTokenLimitExceeded,345			);346		}347		collection.consume_sstores(balances.len())?;348349		// =========350351		<TokensMinted<T>>::insert(collection.id, tokens_minted);352		for (account, balance) in balances {353			<AccountBalance<T>>::insert((collection.id, account), balance);354		}355		for (i, data) in data.into_iter().enumerate() {356			let token = first_token + i as u32 + 1;357358			<TokenData<T>>::insert(359				(collection.id, token),360				ItemData {361					const_data: data.const_data.into(),362					variable_data: data.variable_data.into(),363					owner: data.owner.clone(),364				},365			);366			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);367368			collection.log_infallible(ERC721Events::Transfer {369				from: H160::default(),370				to: *data.owner.as_eth(),371				token_id: token.into(),372			});373			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(374				collection.id,375				TokenId(token),376				data.owner.clone(),377				1,378			));379		}380		Ok(())381	}382383	pub fn set_allowance_unchecked(384		collection: &NonfungibleHandle<T>,385		sender: &T::CrossAccountId,386		token: TokenId,387		spender: Option<&T::CrossAccountId>,388	) {389		if let Some(spender) = spender {390			let old_spender = <Allowance<T>>::get((collection.id, token));391			<Allowance<T>>::insert((collection.id, token), spender);392			// In ERC721 there is only one possible approved user of token, so we set393			// approved user to spender394			collection.log_infallible(ERC721Events::Approval {395				owner: *sender.as_eth(),396				approved: *spender.as_eth(),397				token_id: token.into(),398			});399			// In Unique chain, any token can have any amount of approved users, so we need to400			// set allowance of old owner to 0, and allowance of new owner to 1401			if old_spender.as_ref() != Some(spender) {402				if let Some(old_owner) = old_spender {403					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(404						collection.id,405						token,406						sender.clone(),407						old_owner.clone(),408						0,409					));410				}411				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(412					collection.id,413					token,414					sender.clone(),415					spender.clone(),416					1,417				));418			}419		} else {420			let old_spender = <Allowance<T>>::take((collection.id, token));421			// In ERC721 there is only one possible approved user of token, so we set422			// approved user to zero address423			collection.log_infallible(ERC721Events::Approval {424				owner: *sender.as_eth(),425				approved: H160::default(),426				token_id: token.into(),427			});428			// In Unique chain, any token can have any amount of approved users, so we need to429			// set allowance of old owner to 0430			if let Some(old_spender) = old_spender {431				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(432					collection.id,433					token,434					sender.clone(),435					old_spender.clone(),436					0,437				));438			}439		}440	}441442	pub fn set_allowance(443		collection: &NonfungibleHandle<T>,444		sender: &T::CrossAccountId,445		token: TokenId,446		spender: Option<&T::CrossAccountId>,447	) -> DispatchResult {448		if collection.access == AccessMode::WhiteList {449			collection.check_allowlist(&sender)?;450			if let Some(spender) = spender {451				collection.check_allowlist(&spender)?;452			}453		}454455		if let Some(spender) = spender {456			<PalletCommon<T>>::ensure_correct_receiver(spender)?;457		}458		let token_data =459			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;460		if &token_data.owner != sender {461			ensure!(462				collection.ignores_owned_amount(sender)?,463				<CommonError<T>>::CantApproveMoreThanOwned464			);465		}466467		// =========468469		Self::set_allowance_unchecked(collection, sender, token, spender);470		Ok(())471	}472473	pub fn transfer_from(474		collection: &NonfungibleHandle<T>,475		spender: &T::CrossAccountId,476		from: &T::CrossAccountId,477		to: &T::CrossAccountId,478		token: TokenId,479	) -> DispatchResult {480		if spender == from {481			return Self::transfer(collection, from, to, token);482		}483		if collection.access == AccessMode::WhiteList {484			// `from`, `to` checked in [`transfer`]485			collection.check_allowlist(spender)?;486		}487488		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {489			ensure!(490				collection.ignores_allowance(spender)?,491				<CommonError<T>>::TokenValueNotEnough492			);493		}494495		// =========496497		Self::transfer(collection, &from, to, token)?;498		// Allowance is reset in [`transfer`]499		Ok(())500	}501502	pub fn set_variable_metadata(503		collection: &NonfungibleHandle<T>,504		sender: &T::CrossAccountId,505		token: TokenId,506		data: Vec<u8>,507	) -> DispatchResult {508		ensure!(509			data.len() as u32 <= CUSTOM_DATA_LIMIT,510			<CommonError<T>>::TokenVariableDataLimitExceeded511		);512		let token_data =513			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;514		collection.check_can_update_meta(sender, &token_data.owner)?;515516		collection.consume_sstore()?;517518		// =========519520		<TokenData<T>>::insert(521			(collection.id, token),522			ItemData {523				variable_data: data,524				..token_data525			},526		);527		Ok(())528	}529530	/// Delegated to `create_multiple_items`531	pub fn create_item(532		collection: &NonfungibleHandle<T>,533		sender: &T::CrossAccountId,534		data: CreateItemData<T>,535	) -> DispatchResult {536		Self::create_multiple_items(collection, sender, vec![data])537	}538}
after · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};17use scale_info::TypeInfo;1819pub use pallet::*;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod common;23pub mod erc;24pub mod weights;2526pub struct CreateItemData<T: Config> {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29	pub owner: T::CrossAccountId,30}31pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3233#[derive(Encode, Decode, TypeInfo)]34pub struct ItemData<T: Config> {35	pub const_data: Vec<u8>,36	pub variable_data: Vec<u8>,37	pub owner: T::CrossAccountId,38}3940#[frame_support::pallet]41pub mod pallet {42	use super::*;43	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};44	use nft_data_structs::{CollectionId, TokenId};45	use super::weights::WeightInfo;4647	#[pallet::error]48	pub enum Error<T> {49		/// Not Nonfungible item data used to mint in Nonfungible collection.50		NotNonfungibleDataUsedToMintFungibleCollectionToken,51		/// Used amount > 1 with NFT52		NonfungibleItemsHaveNoAmount,53	}5455	#[pallet::config]56	pub trait Config: frame_system::Config + pallet_common::Config {57		type WeightInfo: WeightInfo;58	}5960	#[pallet::pallet]61	#[pallet::generate_store(pub(super) trait Store)]62	pub struct Pallet<T>(_);6364	#[pallet::storage]65	pub(super) type TokensMinted<T: Config> =66		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;67	#[pallet::storage]68	pub(super) type TokensBurnt<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub(super) type TokenData<T: Config> = StorageNMap<73		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),74		Value = ItemData<T>,75		QueryKind = OptionQuery,76	>;7778	/// Used to enumerate tokens owned by account79	#[pallet::storage]80	pub(super) type Owned<T: Config> = StorageNMap<81		Key = (82			Key<Twox64Concat, CollectionId>,83			Key<Blake2_128Concat, T::AccountId>,84			Key<Twox64Concat, TokenId>,85		),86		Value = bool,87		QueryKind = ValueQuery,88	>;8990	#[pallet::storage]91	pub(super) type AccountBalance<T: Config> = StorageNMap<92		Key = (93			Key<Twox64Concat, CollectionId>,94			Key<Blake2_128Concat, T::AccountId>,95		),96		Value = u32,97		QueryKind = ValueQuery,98	>;99100	#[pallet::storage]101	pub(super) type Allowance<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = T::CrossAccountId,104		QueryKind = OptionQuery,105	>;106}107108pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);109impl<T: Config> NonfungibleHandle<T> {110	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {111		Self(inner)112	}113	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {114		self.0115	}116}117impl<T: Config> Deref for NonfungibleHandle<T> {118	type Target = pallet_common::CollectionHandle<T>;119120	fn deref(&self) -> &Self::Target {121		&self.0122	}123}124125impl<T: Config> Pallet<T> {126	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {127		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)128	}129	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {130		<TokenData<T>>::contains_key((collection.id, token))131	}132}133134// unchecked calls skips any permission checks135impl<T: Config> Pallet<T> {136	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {137		PalletCommon::init_collection(data)138	}139	pub fn destroy_collection(140		collection: NonfungibleHandle<T>,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		let id = collection.id;144145		// =========146147		PalletCommon::destroy_collection(collection.0, sender)?;148149		<TokenData<T>>::remove_prefix((id,), None);150		<Owned<T>>::remove_prefix((id,), None);151		<TokensMinted<T>>::remove(id);152		<TokensBurnt<T>>::remove(id);153		<Allowance<T>>::remove_prefix((id,), None);154		<AccountBalance<T>>::remove_prefix((id,), None);155		Ok(())156	}157158	pub fn burn(159		collection: &NonfungibleHandle<T>,160		sender: &T::CrossAccountId,161		token: TokenId,162	) -> DispatchResult {163		let token_data = <TokenData<T>>::get((collection.id, token))164			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;165		ensure!(166			&token_data.owner == sender167				|| (collection.limits.owner_can_transfer168					&& collection.is_owner_or_admin(sender)?),169			<CommonError<T>>::NoPermission170		);171172		if collection.access == AccessMode::WhiteList {173			collection.check_allowlist(sender)?;174		}175176		let burnt = <TokensBurnt<T>>::get(collection.id)177			.checked_add(1)178			.ok_or(ArithmeticError::Overflow)?;179180		// =========181182		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));183		<TokensBurnt<T>>::insert(collection.id, burnt);184		<TokenData<T>>::remove((collection.id, token));185		let old_spender = <Allowance<T>>::take((collection.id, token));186187		if let Some(old_spender) = old_spender {188			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(189				collection.id,190				token,191				sender.clone(),192				old_spender.clone(),193				0,194			));195		}196197		collection.log_infallible(ERC721Events::Transfer {198			from: *token_data.owner.as_eth(),199			to: H160::default(),200			token_id: token.into(),201		});202		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(203			collection.id,204			token,205			token_data.owner,206			1,207		));208		return Ok(());209	}210211	pub fn transfer(212		collection: &NonfungibleHandle<T>,213		from: &T::CrossAccountId,214		to: &T::CrossAccountId,215		token: TokenId,216	) -> DispatchResult {217		ensure!(218			collection.transfers_enabled,219			<CommonError<T>>::TransferNotAllowed220		);221222		let token_data = <TokenData<T>>::get((collection.id, token))223			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;224		ensure!(225			&token_data.owner == from226				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),227			<CommonError<T>>::NoPermission228		);229230		if collection.access == AccessMode::WhiteList {231			collection.check_allowlist(from)?;232			collection.check_allowlist(to)?;233		}234		<PalletCommon<T>>::ensure_correct_receiver(to)?;235236		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))237			.checked_sub(1)238			.ok_or(<CommonError<T>>::TokenValueTooLow)?;239		let balance_to = if from != to {240			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))241				.checked_add(1)242				.ok_or(ArithmeticError::Overflow)?;243244			ensure!(245				balance_to < collection.limits.account_token_ownership_limit(),246				<CommonError<T>>::AccountTokenLimitExceeded,247			);248249			Some(balance_to)250		} else {251			None252		};253254		collection.consume_sstores(4)?;255		collection.consume_log(3, 0)?;256257		// =========258259		<TokenData<T>>::insert(260			(collection.id, token),261			ItemData {262				owner: to.clone(),263				..token_data264			},265		);266267		if let Some(balance_to) = balance_to {268			// from != to269			if balance_from == 0 {270				<AccountBalance<T>>::remove((collection.id, from.as_sub()));271			} else {272				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);273			}274			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);275			<Owned<T>>::remove((collection.id, from.as_sub(), token));276			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);277		}278		Self::set_allowance_unchecked(collection, from, token, None);279280		collection.log_infallible(ERC721Events::Transfer {281			from: *from.as_eth(),282			to: *to.as_eth(),283			token_id: token.into(),284		});285		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(286			collection.id,287			token,288			from.clone(),289			to.clone(),290			1,291		));292		Ok(())293	}294295	pub fn create_multiple_items(296		collection: &NonfungibleHandle<T>,297		sender: &T::CrossAccountId,298		data: Vec<CreateItemData<T>>,299	) -> DispatchResult {300		let unrestricted_minting = collection.is_owner_or_admin(sender)?;301		if !unrestricted_minting {302			ensure!(303				collection.mint_mode,304				<CommonError<T>>::PublicMintingNotAllowed305			);306			collection.check_allowlist(sender)?;307308			for item in data.iter() {309				collection.check_allowlist(&item.owner)?;310			}311		}312313		for data in data.iter() {314			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;315			if !data.const_data.is_empty() {316				collection.consume_sstore()?;317			}318			if !data.variable_data.is_empty() {319				collection.consume_sstore()?;320			}321			collection.consume_sstore()?;322			collection.consume_log(3, 0)?;323		}324325		let first_token = <TokensMinted<T>>::get(collection.id);326		let tokens_minted = first_token327			.checked_add(data.len() as u32)328			.ok_or(ArithmeticError::Overflow)?;329		ensure!(330			tokens_minted < collection.limits.token_limit,331			<CommonError<T>>::CollectionTokenLimitExceeded332		);333		collection.consume_sstore()?;334335		let mut balances = BTreeMap::new();336		for data in &data {337			let balance = balances338				.entry(data.owner.as_sub())339				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));340			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;341342			ensure!(343				*balance <= collection.limits.account_token_ownership_limit(),344				<CommonError<T>>::AccountTokenLimitExceeded,345			);346		}347		collection.consume_sstores(balances.len())?;348349		// =========350351		<TokensMinted<T>>::insert(collection.id, tokens_minted);352		for (account, balance) in balances {353			<AccountBalance<T>>::insert((collection.id, account), balance);354		}355		for (i, data) in data.into_iter().enumerate() {356			let token = first_token + i as u32 + 1;357358			<TokenData<T>>::insert(359				(collection.id, token),360				ItemData {361					const_data: data.const_data.into(),362					variable_data: data.variable_data.into(),363					owner: data.owner.clone(),364				},365			);366			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);367368			collection.log_infallible(ERC721Events::Transfer {369				from: H160::default(),370				to: *data.owner.as_eth(),371				token_id: token.into(),372			});373			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(374				collection.id,375				TokenId(token),376				data.owner.clone(),377				1,378			));379		}380		Ok(())381	}382383	pub fn set_allowance_unchecked(384		collection: &NonfungibleHandle<T>,385		sender: &T::CrossAccountId,386		token: TokenId,387		spender: Option<&T::CrossAccountId>,388	) {389		if let Some(spender) = spender {390			let old_spender = <Allowance<T>>::get((collection.id, token));391			<Allowance<T>>::insert((collection.id, token), spender);392			// In ERC721 there is only one possible approved user of token, so we set393			// approved user to spender394			collection.log_infallible(ERC721Events::Approval {395				owner: *sender.as_eth(),396				approved: *spender.as_eth(),397				token_id: token.into(),398			});399			// In Unique chain, any token can have any amount of approved users, so we need to400			// set allowance of old owner to 0, and allowance of new owner to 1401			if old_spender.as_ref() != Some(spender) {402				if let Some(old_owner) = old_spender {403					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(404						collection.id,405						token,406						sender.clone(),407						old_owner.clone(),408						0,409					));410				}411				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(412					collection.id,413					token,414					sender.clone(),415					spender.clone(),416					1,417				));418			}419		} else {420			let old_spender = <Allowance<T>>::take((collection.id, token));421			// In ERC721 there is only one possible approved user of token, so we set422			// approved user to zero address423			collection.log_infallible(ERC721Events::Approval {424				owner: *sender.as_eth(),425				approved: H160::default(),426				token_id: token.into(),427			});428			// In Unique chain, any token can have any amount of approved users, so we need to429			// set allowance of old owner to 0430			if let Some(old_spender) = old_spender {431				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(432					collection.id,433					token,434					sender.clone(),435					old_spender.clone(),436					0,437				));438			}439		}440	}441442	pub fn set_allowance(443		collection: &NonfungibleHandle<T>,444		sender: &T::CrossAccountId,445		token: TokenId,446		spender: Option<&T::CrossAccountId>,447	) -> DispatchResult {448		if collection.access == AccessMode::WhiteList {449			collection.check_allowlist(&sender)?;450			if let Some(spender) = spender {451				collection.check_allowlist(&spender)?;452			}453		}454455		if let Some(spender) = spender {456			<PalletCommon<T>>::ensure_correct_receiver(spender)?;457		}458		let token_data =459			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;460		if &token_data.owner != sender {461			ensure!(462				collection.ignores_owned_amount(sender)?,463				<CommonError<T>>::CantApproveMoreThanOwned464			);465		}466467		// =========468469		Self::set_allowance_unchecked(collection, sender, token, spender);470		Ok(())471	}472473	pub fn transfer_from(474		collection: &NonfungibleHandle<T>,475		spender: &T::CrossAccountId,476		from: &T::CrossAccountId,477		to: &T::CrossAccountId,478		token: TokenId,479	) -> DispatchResult {480		if spender == from {481			return Self::transfer(collection, from, to, token);482		}483		if collection.access == AccessMode::WhiteList {484			// `from`, `to` checked in [`transfer`]485			collection.check_allowlist(spender)?;486		}487488		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {489			ensure!(490				collection.ignores_allowance(spender)?,491				<CommonError<T>>::TokenValueNotEnough492			);493		}494495		// =========496497		Self::transfer(collection, &from, to, token)?;498		// Allowance is reset in [`transfer`]499		Ok(())500	}501502	pub fn burn_from(503		collection: &NonfungibleHandle<T>,504		spender: &T::CrossAccountId,505		from: &T::CrossAccountId,506		token: TokenId,507	) -> DispatchResult {508		if spender == from {509			return Self::burn(collection, from, token);510		}511		if collection.access == AccessMode::WhiteList {512			// `from` checked in [`burn`]513			collection.check_allowlist(spender)?;514		}515516		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {517			ensure!(518				collection.ignores_allowance(spender)?,519				<CommonError<T>>::TokenValueNotEnough520			);521		}522523		// =========524525		Self::burn(collection, &from, token)526	}527528	pub fn set_variable_metadata(529		collection: &NonfungibleHandle<T>,530		sender: &T::CrossAccountId,531		token: TokenId,532		data: Vec<u8>,533	) -> DispatchResult {534		ensure!(535			data.len() as u32 <= CUSTOM_DATA_LIMIT,536			<CommonError<T>>::TokenVariableDataLimitExceeded537		);538		let token_data =539			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;540		collection.check_can_update_meta(sender, &token_data.owner)?;541542		collection.consume_sstore()?;543544		// =========545546		<TokenData<T>>::insert(547			(collection.id, token),548			ItemData {549				variable_data: data,550				..token_data551			},552		);553		Ok(())554	}555556	/// Delegated to `create_multiple_items`557	pub fn create_item(558		collection: &NonfungibleHandle<T>,559		sender: &T::CrossAccountId,560		data: CreateItemData<T>,561	) -> DispatchResult {562		Self::create_multiple_items(collection, sender, vec![data])563	}564}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -59,6 +59,10 @@
 		)
 	}
 
+	fn burn_from() -> Weight {
+		0
+	}
+
 	fn set_variable_metadata(bytes: u32) -> Weight {
 		<SelfWeightOf<T>>::set_variable_metadata(bytes)
 	}
@@ -161,7 +165,20 @@
 	) -> DispatchResultWithPostInfo {
 		with_weight(
 			<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token, amount),
-			<CommonWeights<T>>::approve(),
+			<CommonWeights<T>>::transfer_from(),
+		)
+	}
+
+	fn burn_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		with_weight(
+			<Pallet<T>>::burn_from(&self, &sender, &from, token, amount),
+			<CommonWeights<T>>::burn_from(),
 		)
 	}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -537,6 +537,39 @@
 		Ok(())
 	}
 
+	pub fn burn_from(
+		collection: &RefungibleHandle<T>,
+		spender: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResult {
+		if spender == from {
+			return Self::burn(collection, from, token, amount);
+		}
+		if collection.access == AccessMode::WhiteList {
+			// `from` checked in [`burn`]
+			collection.check_allowlist(spender)?;
+		}
+
+		let allowance = <Allowance<T>>::get((collection.id, token, from.as_sub(), &spender))
+			.checked_sub(amount);
+		if allowance.is_none() {
+			ensure!(
+				collection.ignores_allowance(spender)?,
+				<CommonError<T>>::TokenValueNotEnough
+			);
+		}
+
+		// =========
+
+		Self::burn(collection, from, token, amount)?;
+		if let Some(allowance) = allowance {
+			Self::set_allowance_unchecked(collection, from, spender, token, allowance);
+		}
+		Ok(())
+	}
+
 	pub fn set_variable_metadata(
 		collection: &RefungibleHandle<T>,
 		sender: &T::CrossAccountId,