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

difftreelog

feat implement ERC721Burnable/Mintable interfaces

Yaroslav Bolyukin2021-06-28parent: #eef8c23.patch.diff
in: master

2 files changed

modifiedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
before · pallets/nft/src/eth/erc.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};3use core::convert::TryInto;4use alloc::format;5use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList};6use frame_support::storage::StorageDoubleMap;7use pallet_evm::AddressMapping;8use super::account::CrossAccountId;9use sp_std::vec::Vec;1011#[solidity_interface(name = "ERC165")]12impl<T: Config> CollectionHandle<T> {13	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {14		Ok(match self.mode {15			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),16			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),17			_ => false,18		})19	}20}2122#[solidity_interface(name = "InlineNameSymbol")]23impl<T: Config> CollectionHandle<T> {24	fn name(&self) -> Result<string> {25		Ok(decode_utf16(self.name.iter().copied())26			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))27			.collect::<string>())28	}2930	fn symbol(&self) -> Result<string> {31		Ok(string::from_utf8_lossy(&self.token_prefix).into())32	}33}3435#[solidity_interface(name = "InlineTotalSupply")]36impl<T: Config> CollectionHandle<T> {37	fn total_supply(&self) -> Result<uint256> {38		// TODO: we do not track total amount of all tokens39		Ok(0.into())40	}41}4243#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]44impl<T: Config> CollectionHandle<T> {45	fn token_uri(&self, token_id: uint256) -> Result<string> {46		// TODO: We should standartize url prefix, maybe via offchain schema?47		Ok(format!("unique.network/{}/{}", self.id, token_id))48	}49}5051#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]52impl<T: Config> CollectionHandle<T> {53	fn token_by_index(&self, index: uint256) -> Result<uint256> {54		Ok(index)55	}5657	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {58		// TODO: Not implemetable59		Err("not implemented".into())60	}61}6263#[derive(ToLog)]64pub enum ERC721Events {65	Transfer {66		#[indexed]67		from: address,68		#[indexed]69		to: address,70		#[indexed]71		token_id: uint256,72	},73	Approval {74		#[indexed]75		owner: address,76		#[indexed]77		approved: address,78		#[indexed]79		token_id: uint256,80	},81	#[allow(dead_code)]82	ApprovalForAll {83		#[indexed]84		owner: address,85		#[indexed]86		operator: address,87		approved: bool,88	},89}9091#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]92impl<T: Config> CollectionHandle<T> {93	#[solidity(rename_selector = "balanceOf")]94	fn balance_of_nft(&self, owner: address) -> Result<uint256> {95		let owner = T::EvmAddressMapping::into_account_id(owner);96		let balance = <Balance<T>>::get(self.id, owner);97		Ok(balance.into())98	}99	fn owner_of(&self, token_id: uint256) -> Result<address> {100		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;101		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;102		Ok(*token.owner.as_eth())103	}104	fn safe_transfer_from_with_data(105		&mut self,106		_from: address,107		_to: address,108		_token_id: uint256,109		_data: bytes,110		_value: value,111	) -> Result<void> {112		// TODO: Not implemetable113		Err("not implemented".into())114	}115	fn safe_transfer_from(116		&mut self,117		_from: address,118		_to: address,119		_token_id: uint256,120		_value: value,121	) -> Result<void> {122		// TODO: Not implemetable123		Err("not implemented".into())124	}125126	fn transfer_from(127		&mut self,128		caller: caller,129		from: address,130		to: address,131		token_id: uint256,132		_value: value,133	) -> Result<void> {134		let caller = T::CrossAccountId::from_eth(caller);135		let from = T::CrossAccountId::from_eth(from);136		let to = T::CrossAccountId::from_eth(to);137		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;138139		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)140			.map_err(|_| "transferFrom error")?;141		Ok(())142	}143144	fn approve(145		&mut self,146		caller: caller,147		approved: address,148		token_id: uint256,149		_value: value,150	) -> Result<void> {151		let caller = T::CrossAccountId::from_eth(caller);152		let approved = T::CrossAccountId::from_eth(approved);153		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;154155		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)156			.map_err(|_| "approve internal")?;157		Ok(())158	}159160	fn set_approval_for_all(161		&mut self,162		_caller: caller,163		_operator: address,164		_approved: bool,165	) -> Result<void> {166		// TODO: Not implemetable167		Err("not implemented".into())168	}169170	fn get_approved(&self, _token_id: uint256) -> Result<address> {171		// TODO: Not implemetable172		Err("not implemented".into())173	}174175	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {176		// TODO: Not implemetable177		Err("not implemented".into())178	}179}180181#[solidity_interface(name = "ERC721UniqueExtensions")]182impl<T: Config> CollectionHandle<T> {183	#[solidity(rename_selector = "transfer")]184	fn transfer_nft(185		&mut self,186		caller: caller,187		to: address,188		token_id: uint256,189		_value: value,190	) -> Result<void> {191		let caller = T::CrossAccountId::from_eth(caller);192		let to = T::CrossAccountId::from_eth(to);193		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;194195		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)196			.map_err(|_| "transfer error")?;197		Ok(())198	}199}200201#[solidity_interface(202	name = "UniqueNFT",203	is(204		ERC165,205		ERC721,206		ERC721Metadata,207		ERC721Enumerable,208		ERC721UniqueExtensions209	)210)]211impl<T: Config> CollectionHandle<T> {}212213#[derive(ToLog)]214pub enum ERC20Events {215	Transfer {216		#[indexed]217		from: address,218		#[indexed]219		to: address,220		value: uint256,221	},222	Approval {223		#[indexed]224		owner: address,225		#[indexed]226		spender: address,227		value: uint256,228	},229}230231#[solidity_interface(232	name = "ERC20",233	inline_is(InlineNameSymbol, InlineTotalSupply),234	events(ERC20Events)235)]236impl<T: Config> CollectionHandle<T> {237	fn decimals(&self) -> Result<uint8> {238		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {239			*decimals240		} else {241			unreachable!()242		})243	}244	fn balance_of(&self, owner: address) -> Result<uint256> {245		let owner = T::EvmAddressMapping::into_account_id(owner);246		let balance = <Balance<T>>::get(self.id, owner);247		Ok(balance.into())248	}249	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {250		let caller = T::CrossAccountId::from_eth(caller);251		let to = T::CrossAccountId::from_eth(to);252		let amount = amount.try_into().map_err(|_| "amount overflow")?;253254		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)255			.map_err(|_| "transfer error")?;256		Ok(true)257	}258	#[solidity(rename_selector = "transferFrom")]259	fn transfer_from_fungible(260		&mut self,261		caller: caller,262		from: address,263		to: address,264		amount: uint256,265	) -> Result<bool> {266		let caller = T::CrossAccountId::from_eth(caller);267		let from = T::CrossAccountId::from_eth(from);268		let to = T::CrossAccountId::from_eth(to);269		let amount = amount.try_into().map_err(|_| "amount overflow")?;270271		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)272			.map_err(|_| "transferFrom error")?;273		Ok(true)274	}275	#[solidity(rename_selector = "approve")]276	fn approve_fungible(277		&mut self,278		caller: caller,279		spender: address,280		amount: uint256,281	) -> Result<bool> {282		let caller = T::CrossAccountId::from_eth(caller);283		let spender = T::CrossAccountId::from_eth(spender);284		let amount = amount.try_into().map_err(|_| "amount overflow")?;285286		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)287			.map_err(|_| "approve internal")?;288		Ok(true)289	}290	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {291		let owner = T::CrossAccountId::from_eth(owner);292		let spender = T::CrossAccountId::from_eth(spender);293294		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())295	}296}297298#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]299impl<T: Config> CollectionHandle<T> {}
after · pallets/nft/src/eth/erc.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use alloc::format;6use crate::{7	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,8	ItemListIndex,9};10use frame_support::storage::{StorageMap, StorageDoubleMap};11use pallet_evm::AddressMapping;12use super::account::CrossAccountId;13use sp_std::{vec, vec::Vec};1415#[solidity_interface(name = "ERC165")]16impl<T: Config> CollectionHandle<T> {17	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {18		Ok(match self.mode {19			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),20			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),21			_ => false,22		})23	}24}2526#[solidity_interface(name = "InlineNameSymbol")]27impl<T: Config> CollectionHandle<T> {28	fn name(&self) -> Result<string> {29		Ok(decode_utf16(self.name.iter().copied())30			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))31			.collect::<string>())32	}3334	fn symbol(&self) -> Result<string> {35		Ok(string::from_utf8_lossy(&self.token_prefix).into())36	}37}3839#[solidity_interface(name = "InlineTotalSupply")]40impl<T: Config> CollectionHandle<T> {41	fn total_supply(&self) -> Result<uint256> {42		// TODO: we do not track total amount of all tokens43		Ok(0.into())44	}45}4647#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]48impl<T: Config> CollectionHandle<T> {49	fn token_uri(&self, token_id: uint256) -> Result<string> {50		// TODO: We should standartize url prefix, maybe via offchain schema?51		Ok(format!("unique.network/{}/{}", self.id, token_id))52	}53}5455#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]56impl<T: Config> CollectionHandle<T> {57	fn token_by_index(&self, index: uint256) -> Result<uint256> {58		Ok(index)59	}6061	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {62		// TODO: Not implemetable63		Err("not implemented".into())64	}65}6667#[derive(ToLog)]68pub enum ERC721Events {69	Transfer {70		#[indexed]71		from: address,72		#[indexed]73		to: address,74		#[indexed]75		token_id: uint256,76	},77	Approval {78		#[indexed]79		owner: address,80		#[indexed]81		approved: address,82		#[indexed]83		token_id: uint256,84	},85	#[allow(dead_code)]86	ApprovalForAll {87		#[indexed]88		owner: address,89		#[indexed]90		operator: address,91		approved: bool,92	},93}9495#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]96impl<T: Config> CollectionHandle<T> {97	#[solidity(rename_selector = "balanceOf")]98	fn balance_of_nft(&self, owner: address) -> Result<uint256> {99		let owner = T::EvmAddressMapping::into_account_id(owner);100		let balance = <Balance<T>>::get(self.id, owner);101		Ok(balance.into())102	}103	fn owner_of(&self, token_id: uint256) -> Result<address> {104		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;106		Ok(*token.owner.as_eth())107	}108	fn safe_transfer_from_with_data(109		&mut self,110		_from: address,111		_to: address,112		_token_id: uint256,113		_data: bytes,114		_value: value,115	) -> Result<void> {116		// TODO: Not implemetable117		Err("not implemented".into())118	}119	fn safe_transfer_from(120		&mut self,121		_from: address,122		_to: address,123		_token_id: uint256,124		_value: value,125	) -> Result<void> {126		// TODO: Not implemetable127		Err("not implemented".into())128	}129130	fn transfer_from(131		&mut self,132		caller: caller,133		from: address,134		to: address,135		token_id: uint256,136		_value: value,137	) -> Result<void> {138		let caller = T::CrossAccountId::from_eth(caller);139		let from = T::CrossAccountId::from_eth(from);140		let to = T::CrossAccountId::from_eth(to);141		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;142143		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)144			.map_err(|_| "transferFrom error")?;145		Ok(())146	}147148	fn approve(149		&mut self,150		caller: caller,151		approved: address,152		token_id: uint256,153		_value: value,154	) -> Result<void> {155		let caller = T::CrossAccountId::from_eth(caller);156		let approved = T::CrossAccountId::from_eth(approved);157		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;158159		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)160			.map_err(|_| "approve internal")?;161		Ok(())162	}163164	fn set_approval_for_all(165		&mut self,166		_caller: caller,167		_operator: address,168		_approved: bool,169	) -> Result<void> {170		// TODO: Not implemetable171		Err("not implemented".into())172	}173174	fn get_approved(&self, _token_id: uint256) -> Result<address> {175		// TODO: Not implemetable176		Err("not implemented".into())177	}178179	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {180		// TODO: Not implemetable181		Err("not implemented".into())182	}183}184185#[solidity_interface(name = "ERC721Burnable")]186impl<T: Config> CollectionHandle<T> {187	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {188		let caller = T::CrossAccountId::from_eth(caller);189		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;190191		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;192		Ok(())193	}194}195196#[derive(ToLog)]197pub enum ERC721MintableEvents {198	#[allow(dead_code)]199	MintingFinished {},200}201202#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]203impl<T: Config> CollectionHandle<T> {204	fn minting_finished(&self) -> Result<bool> {205		Ok(false)206	}207208	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {209		let caller = T::CrossAccountId::from_eth(caller);210		let to = T::CrossAccountId::from_eth(to);211		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;212		if <ItemListIndex>::get(self.id)213			.checked_add(1)214			.ok_or("item id overflow")?215			!= token_id216		{217			return Err("item id should be next".into());218		}219220		<Module<T>>::create_item_internal(221			&caller,222			&self,223			&to,224			CreateItemData::NFT(CreateNftData {225				const_data: vec![],226				variable_data: vec![],227			}),228		)229		.map_err(|_| "mint error")?;230		Ok(true)231	}232233	#[solidity(rename_selector = "mintWithTokenURI")]234	fn mint_with_token_uri(235		&mut self,236		caller: caller,237		to: address,238		token_id: uint256,239		token_uri: string,240	) -> Result<bool> {241		let caller = T::CrossAccountId::from_eth(caller);242		let to = T::CrossAccountId::from_eth(to);243		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;244		if <ItemListIndex>::get(self.id)245			.checked_add(1)246			.ok_or("item id overflow")?247			!= token_id248		{249			return Err("item id should be next".into());250		}251252		<Module<T>>::create_item_internal(253			&caller,254			&self,255			&to,256			CreateItemData::NFT(CreateNftData {257				const_data: token_uri.into(),258				variable_data: vec![],259			}),260		)261		.map_err(|_| "mint error")?;262		Ok(true)263	}264265	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {266		Err("not implementable".into())267	}268}269270#[solidity_interface(name = "ERC721UniqueExtensions")]271impl<T: Config> CollectionHandle<T> {272	#[solidity(rename_selector = "transfer")]273	fn transfer_nft(274		&mut self,275		caller: caller,276		to: address,277		token_id: uint256,278		_value: value,279	) -> Result<void> {280		let caller = T::CrossAccountId::from_eth(caller);281		let to = T::CrossAccountId::from_eth(to);282		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;283284		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)285			.map_err(|_| "transfer error")?;286		Ok(())287	}288289	fn next_token_id(&self) -> Result<uint256> {290		Ok(ItemListIndex::get(self.id)291			.checked_add(1)292			.ok_or("item id overflow")?293			.into())294	}295}296297#[solidity_interface(298	name = "UniqueNFT",299	is(300		ERC165,301		ERC721,302		ERC721Metadata,303		ERC721Enumerable,304		ERC721UniqueExtensions,305		ERC721Mintable,306		ERC721Burnable,307	)308)]309impl<T: Config> CollectionHandle<T> {}310311#[derive(ToLog)]312pub enum ERC20Events {313	Transfer {314		#[indexed]315		from: address,316		#[indexed]317		to: address,318		value: uint256,319	},320	Approval {321		#[indexed]322		owner: address,323		#[indexed]324		spender: address,325		value: uint256,326	},327}328329#[solidity_interface(330	name = "ERC20",331	inline_is(InlineNameSymbol, InlineTotalSupply),332	events(ERC20Events)333)]334impl<T: Config> CollectionHandle<T> {335	fn decimals(&self) -> Result<uint8> {336		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {337			*decimals338		} else {339			unreachable!()340		})341	}342	fn balance_of(&self, owner: address) -> Result<uint256> {343		let owner = T::EvmAddressMapping::into_account_id(owner);344		let balance = <Balance<T>>::get(self.id, owner);345		Ok(balance.into())346	}347	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {348		let caller = T::CrossAccountId::from_eth(caller);349		let to = T::CrossAccountId::from_eth(to);350		let amount = amount.try_into().map_err(|_| "amount overflow")?;351352		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)353			.map_err(|_| "transfer error")?;354		Ok(true)355	}356	#[solidity(rename_selector = "transferFrom")]357	fn transfer_from_fungible(358		&mut self,359		caller: caller,360		from: address,361		to: address,362		amount: uint256,363	) -> Result<bool> {364		let caller = T::CrossAccountId::from_eth(caller);365		let from = T::CrossAccountId::from_eth(from);366		let to = T::CrossAccountId::from_eth(to);367		let amount = amount.try_into().map_err(|_| "amount overflow")?;368369		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)370			.map_err(|_| "transferFrom error")?;371		Ok(true)372	}373	#[solidity(rename_selector = "approve")]374	fn approve_fungible(375		&mut self,376		caller: caller,377		spender: address,378		amount: uint256,379	) -> Result<bool> {380		let caller = T::CrossAccountId::from_eth(caller);381		let spender = T::CrossAccountId::from_eth(spender);382		let amount = amount.try_into().map_err(|_| "amount overflow")?;383384		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)385			.map_err(|_| "approve internal")?;386		Ok(true)387	}388	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {389		let owner = T::CrossAccountId::from_eth(owner);390		let spender = T::CrossAccountId::from_eth(spender);391392		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())393	}394}395396#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]397impl<T: Config> CollectionHandle<T> {}
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -1251,6 +1251,10 @@
 		Self::can_create_items_in_collection(collection, sender, owner, 1)?;
 		Self::validate_create_item_args(collection, &data)?;
 		Self::create_item_no_validation(collection, owner, data)?;
+
+		Ok(())
+	}
+
 	pub fn transfer_internal(
 		sender: &T::CrossAccountId,
 		recipient: &T::CrossAccountId,
@@ -1819,6 +1823,11 @@
 		<NftItemList<T>>::remove(collection_id, item_id);
 		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);
 
+		collection.log(ERC721Events::Transfer {
+			from: *item.owner.as_eth(),
+			to: H160::default(),
+			token_id: item_id.into(),
+		})?;
 		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));
 		Ok(())
 	}