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
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -499,6 +499,32 @@
 		Ok(())
 	}
 
+	pub fn burn_from(
+		collection: &NonfungibleHandle<T>,
+		spender: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		token: TokenId,
+	) -> DispatchResult {
+		if spender == from {
+			return Self::burn(collection, from, token);
+		}
+		if collection.access == AccessMode::WhiteList {
+			// `from` checked in [`burn`]
+			collection.check_allowlist(spender)?;
+		}
+
+		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
+			ensure!(
+				collection.ignores_allowance(spender)?,
+				<CommonError<T>>::TokenValueNotEnough
+			);
+		}
+
+		// =========
+
+		Self::burn(collection, &from, token)
+	}
+
 	pub fn set_variable_metadata(
 		collection: &NonfungibleHandle<T>,
 		sender: &T::CrossAccountId,
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
before · pallets/refungible/src/common.rs
1use core::marker::PhantomData;23use sp_std::collections::btree_map::BTreeMap;4use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};5use nft_data_structs::TokenId;6use pallet_common::{7	CommonCollectionOperations, CommonWeightInfo, account::CrossAccountId, with_weight,8};9use sp_runtime::DispatchError;10use sp_std::vec::Vec;1112use crate::{13	AccountBalance, Allowance, Balance, Config, CreateItemData, Error, Owned, Pallet,14	RefungibleHandle, SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,15};1617macro_rules! max_weight_of {18	($($method:ident ($($args:tt)*)),*) => {19		020		$(21			.max(<SelfWeightOf<T>>::$method($($args)*))22		)*23	};24}2526pub struct CommonWeights<T: Config>(PhantomData<T>);27impl<T: Config> CommonWeightInfo for CommonWeights<T> {28	fn create_item() -> Weight {29		<SelfWeightOf<T>>::create_item()30	}3132	fn create_multiple_items(amount: u32) -> Weight {33		<SelfWeightOf<T>>::create_multiple_items(amount)34	}3536	fn burn_item() -> Weight {37		max_weight_of!(burn_item_partial(), burn_item_fully())38	}3940	fn transfer() -> Weight {41		max_weight_of!(42			transfer_normal(),43			transfer_creating(),44			transfer_removing(),45			transfer_creating_removing()46		)47	}4849	fn approve() -> Weight {50		<SelfWeightOf<T>>::approve()51	}5253	fn transfer_from() -> Weight {54		max_weight_of!(55			transfer_from_normal(),56			transfer_from_creating(),57			transfer_from_removing(),58			transfer_from_creating_removing()59		)60	}6162	fn set_variable_metadata(bytes: u32) -> Weight {63		<SelfWeightOf<T>>::set_variable_metadata(bytes)64	}65}6667fn map_create_data<T: Config>(68	data: nft_data_structs::CreateItemData,69	to: &T::CrossAccountId,70) -> Result<CreateItemData<T>, DispatchError> {71	match data {72		nft_data_structs::CreateItemData::ReFungible(data) => Ok(CreateItemData {73			const_data: data.const_data,74			variable_data: data.variable_data,75			users: {76				let mut out = BTreeMap::new();77				out.insert(to.clone(), data.pieces);78				out79			},80		}),81		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),82	}83}8485impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {86	fn create_item(87		&self,88		sender: T::CrossAccountId,89		to: T::CrossAccountId,90		data: nft_data_structs::CreateItemData,91	) -> DispatchResultWithPostInfo {92		with_weight(93			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),94			<CommonWeights<T>>::create_item(),95		)96	}9798	fn create_multiple_items(99		&self,100		sender: T::CrossAccountId,101		to: T::CrossAccountId,102		data: Vec<nft_data_structs::CreateItemData>,103	) -> DispatchResultWithPostInfo {104		let data = data105			.into_iter()106			.map(|d| map_create_data::<T>(d, &to))107			.collect::<Result<Vec<_>, DispatchError>>()?;108109		let amount = data.len();110		with_weight(111			<Pallet<T>>::create_multiple_items(self, &sender, data),112			<CommonWeights<T>>::create_multiple_items(amount as u32),113		)114	}115116	fn burn_item(117		&self,118		sender: T::CrossAccountId,119		token: TokenId,120		amount: u128,121	) -> DispatchResultWithPostInfo {122		with_weight(123			<Pallet<T>>::burn(self, &sender, token, amount),124			<CommonWeights<T>>::burn_item(),125		)126	}127128	fn transfer(129		&self,130		from: T::CrossAccountId,131		to: T::CrossAccountId,132		token: TokenId,133		amount: u128,134	) -> DispatchResultWithPostInfo {135		with_weight(136			<Pallet<T>>::transfer(&self, &from, &to, token, amount),137			<CommonWeights<T>>::transfer(),138		)139	}140141	fn approve(142		&self,143		sender: T::CrossAccountId,144		spender: T::CrossAccountId,145		token: TokenId,146		amount: u128,147	) -> DispatchResultWithPostInfo {148		with_weight(149			<Pallet<T>>::set_allowance(&self, &sender, &spender, token, amount),150			<CommonWeights<T>>::approve(),151		)152	}153154	fn transfer_from(155		&self,156		sender: T::CrossAccountId,157		from: T::CrossAccountId,158		to: T::CrossAccountId,159		token: TokenId,160		amount: u128,161	) -> DispatchResultWithPostInfo {162		with_weight(163			<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token, amount),164			<CommonWeights<T>>::approve(),165		)166	}167168	fn set_variable_metadata(169		&self,170		sender: T::CrossAccountId,171		token: TokenId,172		data: Vec<u8>,173	) -> DispatchResultWithPostInfo {174		let len = data.len();175		with_weight(176			<Pallet<T>>::set_variable_metadata(&self, &sender, token, data),177			<CommonWeights<T>>::set_variable_metadata(len as u32),178		)179	}180181	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {182		<Owned<T>>::iter_prefix((self.id, account.as_sub()))183			.map(|(id, _)| id)184			.collect()185	}186187	fn token_exists(&self, token: TokenId) -> bool {188		<Pallet<T>>::token_exists(self, token)189	}190191	fn last_token_id(&self) -> TokenId {192		TokenId(<TokensMinted<T>>::get(self.id))193	}194195	fn token_owner(&self, _token: TokenId) -> T::CrossAccountId {196		T::CrossAccountId::default()197	}198	fn const_metadata(&self, token: TokenId) -> Vec<u8> {199		<TokenData<T>>::get((self.id, token)).const_data200	}201	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {202		<TokenData<T>>::get((self.id, token)).variable_data203	}204205	fn collection_tokens(&self) -> u32 {206		<Pallet<T>>::total_supply(self)207	}208209	fn account_balance(&self, account: T::CrossAccountId) -> u32 {210		<AccountBalance<T>>::get((self.id, account.as_sub()))211	}212213	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {214		<Balance<T>>::get((self.id, token, account.as_sub()))215	}216217	fn allowance(218		&self,219		sender: T::CrossAccountId,220		spender: T::CrossAccountId,221		token: TokenId,222	) -> u128 {223		<Allowance<T>>::get((self.id, token, sender.as_sub(), spender))224	}225}
after · pallets/refungible/src/common.rs
1use core::marker::PhantomData;23use sp_std::collections::btree_map::BTreeMap;4use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};5use nft_data_structs::TokenId;6use pallet_common::{7	CommonCollectionOperations, CommonWeightInfo, account::CrossAccountId, with_weight,8};9use sp_runtime::DispatchError;10use sp_std::vec::Vec;1112use crate::{13	AccountBalance, Allowance, Balance, Config, CreateItemData, Error, Owned, Pallet,14	RefungibleHandle, SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,15};1617macro_rules! max_weight_of {18	($($method:ident ($($args:tt)*)),*) => {19		020		$(21			.max(<SelfWeightOf<T>>::$method($($args)*))22		)*23	};24}2526pub struct CommonWeights<T: Config>(PhantomData<T>);27impl<T: Config> CommonWeightInfo for CommonWeights<T> {28	fn create_item() -> Weight {29		<SelfWeightOf<T>>::create_item()30	}3132	fn create_multiple_items(amount: u32) -> Weight {33		<SelfWeightOf<T>>::create_multiple_items(amount)34	}3536	fn burn_item() -> Weight {37		max_weight_of!(burn_item_partial(), burn_item_fully())38	}3940	fn transfer() -> Weight {41		max_weight_of!(42			transfer_normal(),43			transfer_creating(),44			transfer_removing(),45			transfer_creating_removing()46		)47	}4849	fn approve() -> Weight {50		<SelfWeightOf<T>>::approve()51	}5253	fn transfer_from() -> Weight {54		max_weight_of!(55			transfer_from_normal(),56			transfer_from_creating(),57			transfer_from_removing(),58			transfer_from_creating_removing()59		)60	}6162	fn burn_from() -> Weight {63		064	}6566	fn set_variable_metadata(bytes: u32) -> Weight {67		<SelfWeightOf<T>>::set_variable_metadata(bytes)68	}69}7071fn map_create_data<T: Config>(72	data: nft_data_structs::CreateItemData,73	to: &T::CrossAccountId,74) -> Result<CreateItemData<T>, DispatchError> {75	match data {76		nft_data_structs::CreateItemData::ReFungible(data) => Ok(CreateItemData {77			const_data: data.const_data,78			variable_data: data.variable_data,79			users: {80				let mut out = BTreeMap::new();81				out.insert(to.clone(), data.pieces);82				out83			},84		}),85		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),86	}87}8889impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {90	fn create_item(91		&self,92		sender: T::CrossAccountId,93		to: T::CrossAccountId,94		data: nft_data_structs::CreateItemData,95	) -> DispatchResultWithPostInfo {96		with_weight(97			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),98			<CommonWeights<T>>::create_item(),99		)100	}101102	fn create_multiple_items(103		&self,104		sender: T::CrossAccountId,105		to: T::CrossAccountId,106		data: Vec<nft_data_structs::CreateItemData>,107	) -> DispatchResultWithPostInfo {108		let data = data109			.into_iter()110			.map(|d| map_create_data::<T>(d, &to))111			.collect::<Result<Vec<_>, DispatchError>>()?;112113		let amount = data.len();114		with_weight(115			<Pallet<T>>::create_multiple_items(self, &sender, data),116			<CommonWeights<T>>::create_multiple_items(amount as u32),117		)118	}119120	fn burn_item(121		&self,122		sender: T::CrossAccountId,123		token: TokenId,124		amount: u128,125	) -> DispatchResultWithPostInfo {126		with_weight(127			<Pallet<T>>::burn(self, &sender, token, amount),128			<CommonWeights<T>>::burn_item(),129		)130	}131132	fn transfer(133		&self,134		from: T::CrossAccountId,135		to: T::CrossAccountId,136		token: TokenId,137		amount: u128,138	) -> DispatchResultWithPostInfo {139		with_weight(140			<Pallet<T>>::transfer(&self, &from, &to, token, amount),141			<CommonWeights<T>>::transfer(),142		)143	}144145	fn approve(146		&self,147		sender: T::CrossAccountId,148		spender: T::CrossAccountId,149		token: TokenId,150		amount: u128,151	) -> DispatchResultWithPostInfo {152		with_weight(153			<Pallet<T>>::set_allowance(&self, &sender, &spender, token, amount),154			<CommonWeights<T>>::approve(),155		)156	}157158	fn transfer_from(159		&self,160		sender: T::CrossAccountId,161		from: T::CrossAccountId,162		to: T::CrossAccountId,163		token: TokenId,164		amount: u128,165	) -> DispatchResultWithPostInfo {166		with_weight(167			<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token, amount),168			<CommonWeights<T>>::transfer_from(),169		)170	}171172	fn burn_from(173		&self,174		sender: T::CrossAccountId,175		from: T::CrossAccountId,176		token: TokenId,177		amount: u128,178	) -> DispatchResultWithPostInfo {179		with_weight(180			<Pallet<T>>::burn_from(&self, &sender, &from, token, amount),181			<CommonWeights<T>>::burn_from(),182		)183	}184185	fn set_variable_metadata(186		&self,187		sender: T::CrossAccountId,188		token: TokenId,189		data: Vec<u8>,190	) -> DispatchResultWithPostInfo {191		let len = data.len();192		with_weight(193			<Pallet<T>>::set_variable_metadata(&self, &sender, token, data),194			<CommonWeights<T>>::set_variable_metadata(len as u32),195		)196	}197198	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {199		<Owned<T>>::iter_prefix((self.id, account.as_sub()))200			.map(|(id, _)| id)201			.collect()202	}203204	fn token_exists(&self, token: TokenId) -> bool {205		<Pallet<T>>::token_exists(self, token)206	}207208	fn last_token_id(&self) -> TokenId {209		TokenId(<TokensMinted<T>>::get(self.id))210	}211212	fn token_owner(&self, _token: TokenId) -> T::CrossAccountId {213		T::CrossAccountId::default()214	}215	fn const_metadata(&self, token: TokenId) -> Vec<u8> {216		<TokenData<T>>::get((self.id, token)).const_data217	}218	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {219		<TokenData<T>>::get((self.id, token)).variable_data220	}221222	fn collection_tokens(&self) -> u32 {223		<Pallet<T>>::total_supply(self)224	}225226	fn account_balance(&self, account: T::CrossAccountId) -> u32 {227		<AccountBalance<T>>::get((self.id, account.as_sub()))228	}229230	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {231		<Balance<T>>::get((self.id, token, account.as_sub()))232	}233234	fn allowance(235		&self,236		sender: T::CrossAccountId,237		spender: T::CrossAccountId,238		token: TokenId,239	) -> u128 {240		<Allowance<T>>::get((self.id, token, sender.as_sub(), spender))241	}242}
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,