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

difftreelog

chore implement transfer and burn for ERC-721

Grigoriy Simonov2022-07-28parent: #7d1859c.patch.diff
in: master

9 files changed

modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -41,10 +41,6 @@
 UniqueRefungible.sol:
 	PACKAGE=pallet-refungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
 	PACKAGE=pallet-refungible NAME=erc::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
-	
-UniqueRefungible.sol:
-	PACKAGE=pallet-refungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
-	PACKAGE=pallet-refungible NAME=erc::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
 
 UniqueRefungibleToken.sol:
 	PACKAGE=pallet-refungible NAME=erc_token::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
@@ -73,10 +69,6 @@
 UniqueRefungibleToken: UniqueRefungibleToken.sol
 	INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungibleToken.raw ./.maintain/scripts/compile_stub.sh
 	INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_TOKEN_EVM_ABI) ./.maintain/scripts/generate_abi.sh
-
-UniqueRefungible: UniqueRefungible.sol
-	INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungible.raw ./.maintain/scripts/compile_stub.sh
-	INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
 
 ContractHelpers: ContractHelpers.sol
 	INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw ./.maintain/scripts/compile_stub.sh
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -34,8 +34,9 @@
 		CommonEvmHandler, CollectionCall,
 		static_property::{key, value as property_value},
 	},
+	eth::collection_id_to_address,
 };
-use pallet_evm::{account::CrossAccountId, PrecompileHandle};
+use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
 use sp_core::H160;
@@ -46,8 +47,8 @@
 };
 
 use crate::{
-	AccountBalance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,
-	TokenProperties, TokensMinted, weights::WeightInfo,
+	AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,
+	TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,
 };
 
 /// @title A contract that allows to set and delete token properties and change token property permissions.
@@ -331,16 +332,49 @@
 		Err("not implemented".into())
 	}
 
-	/// @dev Not implemented
+	/// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE
+	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+	///  THEY MAY BE PERMANENTLY LOST
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this RFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param from The current owner of the NFT
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	/// @param _value Not used for an NFT
+	#[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]
 	fn transfer_from(
 		&mut self,
-		_caller: caller,
-		_from: address,
-		_to: address,
-		_token_id: uint256,
+		caller: caller,
+		from: address,
+		to: address,
+		token_id: uint256,
 		_value: value,
 	) -> Result<void> {
-		Err("not implemented".into())
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(&self, token, &from)?;
+		ensure_single_owner(&self, token, balance)?;
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		<PalletEvm<T>>::deposit_log(
+			ERC721Events::Transfer {
+				from: *from.as_eth(),
+				to: *to.as_eth(),
+				token_id: token_id.into(),
+			}
+			.to_log(collection_id_to_address(self.id)),
+		);
+		Ok(())
 	}
 
 	/// @dev Not implemented
@@ -378,12 +412,48 @@
 	}
 }
 
+/// Returns amount of pieces of `token` that `owner` have
+fn balance<T: Config>(
+	collection: &RefungibleHandle<T>,
+	token: TokenId,
+	owner: &T::CrossAccountId,
+) -> Result<u128> {
+	collection.consume_store_reads(1)?;
+	let balance = <Balance<T>>::get((collection.id, token, &owner));
+	Ok(balance)
+}
+
+/// Throws if `owner_balance` is lower than total amount of `token` pieces
+fn ensure_single_owner<T: Config>(
+	collection: &RefungibleHandle<T>,
+	token: TokenId,
+	owner_balance: u128,
+) -> Result<()> {
+	collection.consume_store_reads(1)?;
+	let total_supply = <TotalSupply<T>>::get((collection.id, token));
+	if total_supply != owner_balance {
+		return Err("token has multiple owners".into());
+	}
+	Ok(())
+}
+
 /// @title ERC721 Token that can be irreversibly burned (destroyed).
 #[solidity_interface(name = "ERC721Burnable")]
 impl<T: Config> RefungibleHandle<T> {
-	/// @dev Not implemented
-	fn burn(&mut self, _caller: caller, _token_id: uint256, _value: value) -> Result<void> {
-		Err("not implemented".into())
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized
+	///  operator of the current owner.
+	/// @param tokenId The RFT to approve
+	#[weight(<SelfWeightOf<T>>::burn_item_fully())]
+	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token = token_id.try_into()?;
+
+		let balance = balance(&self, token, &caller)?;
+		ensure_single_owner(&self, token, balance)?;
+
+		<Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
 	}
 }
 
@@ -555,6 +625,75 @@
 /// @title Unique extensions for ERC721.
 #[solidity_interface(name = "ERC721UniqueExtensions")]
 impl<T: Config> RefungibleHandle<T> {
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
+	/// @param _value Not used for an RFT
+	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
+	fn transfer(
+		&mut self,
+		caller: caller,
+		to: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(&self, token, &caller)?;
+		ensure_single_owner(&self, token, balance)?;
+
+		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		<PalletEvm<T>>::deposit_log(
+			ERC721Events::Transfer {
+				from: *caller.as_eth(),
+				to: *to.as_eth(),
+				token_id: token_id.into(),
+			}
+			.to_log(collection_id_to_address(self.id)),
+		);
+		Ok(())
+	}
+
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this RFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param from The current owner of the RFT
+	/// @param tokenId The RFT to transfer
+	/// @param _value Not used for an RFT
+	#[weight(<SelfWeightOf<T>>::burn_from())]
+	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()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(&self, token, &caller)?;
+		ensure_single_owner(&self, token, balance)?;
+
+		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
 	/// @notice Returns next free RFT ID.
 	fn next_token_id(&self) -> Result<uint256> {
 		self.consume_store_reads(1)?;
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -454,6 +454,14 @@
 			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
 			<AccountBalance<T>>::insert((collection.id, owner), account_balance);
 			Self::burn_token_unchecked(collection, token)?;
+			<PalletEvm<T>>::deposit_log(
+				ERC721Events::Transfer {
+					from: *owner.as_eth(),
+					to: H160::default(),
+					token_id: token.into(),
+				}
+				.to_log(collection_id_to_address(collection.id)),
+			);
 			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
 				collection.id,
 				token,
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
before · pallets/refungible/src/stubs/UniqueRefungible.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56// Anonymous struct7struct Tuple0 {8	uint256 field_0;9	string field_1;10}1112// Common stubs holder13contract Dummy {14	uint8 dummy;15	string stub_error = "this contract is implemented in native";16}1718contract ERC165 is Dummy {19	function supportsInterface(bytes4 interfaceID)20		external21		view22		returns (bool)23	{24		require(false, stub_error);25		interfaceID;26		return true;27	}28}2930// Inline31contract ERC721Events {32	event Transfer(33		address indexed from,34		address indexed to,35		uint256 indexed tokenId36	);37	event Approval(38		address indexed owner,39		address indexed approved,40		uint256 indexed tokenId41	);42	event ApprovalForAll(43		address indexed owner,44		address indexed operator,45		bool approved46	);47}4849// Inline50contract ERC721MintableEvents {51	event MintingFinished();52}5354// Selector: 0784ee6455contract ERC721UniqueExtensions is Dummy, ERC165 {56	// @notice Returns next free RFT ID.57	//58	// Selector: nextTokenId() 75794a3c59	function nextTokenId() public view returns (uint256) {60		require(false, stub_error);61		dummy;62		return 0;63	}6465	// Selector: mintBulk(address,uint256[]) 44a9945e66	function mintBulk(address to, uint256[] memory tokenIds)67		public68		returns (bool)69	{70		require(false, stub_error);71		to;72		tokenIds;73		dummy = 0;74		return false;75	}7677	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 3654300678	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)79		public80		returns (bool)81	{82		require(false, stub_error);83		to;84		tokens;85		dummy = 0;86		return false;87	}88}8990// Selector: 4136937791contract TokenProperties is Dummy, ERC165 {92	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa93	function setTokenPropertyPermission(94		string memory key,95		bool isMutable,96		bool collectionAdmin,97		bool tokenOwner98	) public {99		require(false, stub_error);100		key;101		isMutable;102		collectionAdmin;103		tokenOwner;104		dummy = 0;105	}106107	// Selector: setProperty(uint256,string,bytes) 1752d67b108	function setProperty(109		uint256 tokenId,110		string memory key,111		bytes memory value112	) public {113		require(false, stub_error);114		tokenId;115		key;116		value;117		dummy = 0;118	}119120	// Selector: deleteProperty(uint256,string) 066111d1121	function deleteProperty(uint256 tokenId, string memory key) public {122		require(false, stub_error);123		tokenId;124		key;125		dummy = 0;126	}127128	// Throws error if key not found129	//130	// Selector: property(uint256,string) 7228c327131	function property(uint256 tokenId, string memory key)132		public133		view134		returns (bytes memory)135	{136		require(false, stub_error);137		tokenId;138		key;139		dummy;140		return hex"";141	}142}143144// Selector: 42966c68145contract ERC721Burnable is Dummy, ERC165 {146	// @dev Not implemented147	//148	// Selector: burn(uint256) 42966c68149	function burn(uint256 tokenId) public {150		require(false, stub_error);151		tokenId;152		dummy = 0;153	}154}155156// Selector: 58800161157contract ERC721 is Dummy, ERC165, ERC721Events {158	// Selector: balanceOf(address) 70a08231159	function balanceOf(address owner) public view returns (uint256) {160		require(false, stub_error);161		owner;162		dummy;163		return 0;164	}165166	// Selector: ownerOf(uint256) 6352211e167	function ownerOf(uint256 tokenId) public view returns (address) {168		require(false, stub_error);169		tokenId;170		dummy;171		return 0x0000000000000000000000000000000000000000;172	}173174	// @dev Not implemented175	//176	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672177	function safeTransferFromWithData(178		address from,179		address to,180		uint256 tokenId,181		bytes memory data182	) public {183		require(false, stub_error);184		from;185		to;186		tokenId;187		data;188		dummy = 0;189	}190191	// @dev Not implemented192	//193	// Selector: safeTransferFrom(address,address,uint256) 42842e0e194	function safeTransferFrom(195		address from,196		address to,197		uint256 tokenId198	) public {199		require(false, stub_error);200		from;201		to;202		tokenId;203		dummy = 0;204	}205206	// @dev Not implemented207	//208	// Selector: transferFrom(address,address,uint256) 23b872dd209	function transferFrom(210		address from,211		address to,212		uint256 tokenId213	) public {214		require(false, stub_error);215		from;216		to;217		tokenId;218		dummy = 0;219	}220221	// @dev Not implemented222	//223	// Selector: approve(address,uint256) 095ea7b3224	function approve(address approved, uint256 tokenId) public {225		require(false, stub_error);226		approved;227		tokenId;228		dummy = 0;229	}230231	// @dev Not implemented232	//233	// Selector: setApprovalForAll(address,bool) a22cb465234	function setApprovalForAll(address operator, bool approved) public {235		require(false, stub_error);236		operator;237		approved;238		dummy = 0;239	}240241	// @dev Not implemented242	//243	// Selector: getApproved(uint256) 081812fc244	function getApproved(uint256 tokenId) public view returns (address) {245		require(false, stub_error);246		tokenId;247		dummy;248		return 0x0000000000000000000000000000000000000000;249	}250251	// @dev Not implemented252	//253	// Selector: isApprovedForAll(address,address) e985e9c5254	function isApprovedForAll(address owner, address operator)255		public256		view257		returns (address)258	{259		require(false, stub_error);260		owner;261		operator;262		dummy;263		return 0x0000000000000000000000000000000000000000;264	}265}266267// Selector: 5b5e139f268contract ERC721Metadata is Dummy, ERC165 {269	// Selector: name() 06fdde03270	function name() public view returns (string memory) {271		require(false, stub_error);272		dummy;273		return "";274	}275276	// Selector: symbol() 95d89b41277	function symbol() public view returns (string memory) {278		require(false, stub_error);279		dummy;280		return "";281	}282283	// Returns token's const_metadata284	//285	// Selector: tokenURI(uint256) c87b56dd286	function tokenURI(uint256 tokenId) public view returns (string memory) {287		require(false, stub_error);288		tokenId;289		dummy;290		return "";291	}292}293294// Selector: 68ccfe89295contract ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {296	// Selector: mintingFinished() 05d2035b297	function mintingFinished() public view returns (bool) {298		require(false, stub_error);299		dummy;300		return false;301	}302303	// `token_id` should be obtained with `next_token_id` method,304	// unlike standard, you can't specify it manually305	//306	// Selector: mint(address,uint256) 40c10f19307	function mint(address to, uint256 tokenId) public returns (bool) {308		require(false, stub_error);309		to;310		tokenId;311		dummy = 0;312		return false;313	}314315	// `token_id` should be obtained with `next_token_id` method,316	// unlike standard, you can't specify it manually317	//318	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f319	function mintWithTokenURI(320		address to,321		uint256 tokenId,322		string memory tokenUri323	) public returns (bool) {324		require(false, stub_error);325		to;326		tokenId;327		tokenUri;328		dummy = 0;329		return false;330	}331332	// @dev Not implemented333	//334	// Selector: finishMinting() 7d64bcb4335	function finishMinting() public returns (bool) {336		require(false, stub_error);337		dummy = 0;338		return false;339	}340}341342// Selector: 780e9d63343contract ERC721Enumerable is Dummy, ERC165 {344	// Selector: tokenByIndex(uint256) 4f6ccce7345	function tokenByIndex(uint256 index) public view returns (uint256) {346		require(false, stub_error);347		index;348		dummy;349		return 0;350	}351352	// Not implemented353	//354	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59355	function tokenOfOwnerByIndex(address owner, uint256 index)356		public357		view358		returns (uint256)359	{360		require(false, stub_error);361		owner;362		index;363		dummy;364		return 0;365	}366367	// Selector: totalSupply() 18160ddd368	function totalSupply() public view returns (uint256) {369		require(false, stub_error);370		dummy;371		return 0;372	}373}374375// Selector: 7d9262e6376contract Collection is Dummy, ERC165 {377	// Set collection property.378	//379	// @param key Property key.380	// @param value Propery value.381	//382	// Selector: setCollectionProperty(string,bytes) 2f073f66383	function setCollectionProperty(string memory key, bytes memory value)384		public385	{386		require(false, stub_error);387		key;388		value;389		dummy = 0;390	}391392	// Delete collection property.393	//394	// @param key Property key.395	//396	// Selector: deleteCollectionProperty(string) 7b7debce397	function deleteCollectionProperty(string memory key) public {398		require(false, stub_error);399		key;400		dummy = 0;401	}402403	// Get collection property.404	//405	// @dev Throws error if key not found.406	//407	// @param key Property key.408	// @return bytes The property corresponding to the key.409	//410	// Selector: collectionProperty(string) cf24fd6d411	function collectionProperty(string memory key)412		public413		view414		returns (bytes memory)415	{416		require(false, stub_error);417		key;418		dummy;419		return hex"";420	}421422	// Set the sponsor of the collection.423	//424	// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.425	//426	// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.427	//428	// Selector: setCollectionSponsor(address) 7623402e429	function setCollectionSponsor(address sponsor) public {430		require(false, stub_error);431		sponsor;432		dummy = 0;433	}434435	// Collection sponsorship confirmation.436	//437	// @dev After setting the sponsor for the collection, it must be confirmed with this function.438	//439	// Selector: confirmCollectionSponsorship() 3c50e97a440	function confirmCollectionSponsorship() public {441		require(false, stub_error);442		dummy = 0;443	}444445	// Set limits for the collection.446	// @dev Throws error if limit not found.447	// @param limit Name of the limit. Valid names:448	// 	"accountTokenOwnershipLimit",449	// 	"sponsoredDataSize",450	// 	"sponsoredDataRateLimit",451	// 	"tokenLimit",452	// 	"sponsorTransferTimeout",453	// 	"sponsorApproveTimeout"454	// @param value Value of the limit.455	//456	// Selector: setCollectionLimit(string,uint32) 6a3841db457	function setCollectionLimit(string memory limit, uint32 value) public {458		require(false, stub_error);459		limit;460		value;461		dummy = 0;462	}463464	// Set limits for the collection.465	// @dev Throws error if limit not found.466	// @param limit Name of the limit. Valid names:467	// 	"ownerCanTransfer",468	// 	"ownerCanDestroy",469	// 	"transfersEnabled"470	// @param value Value of the limit.471	//472	// Selector: setCollectionLimit(string,bool) 993b7fba473	function setCollectionLimit(string memory limit, bool value) public {474		require(false, stub_error);475		limit;476		value;477		dummy = 0;478	}479480	// Get contract address.481	//482	// Selector: contractAddress() f6b4dfb4483	function contractAddress() public view returns (address) {484		require(false, stub_error);485		dummy;486		return 0x0000000000000000000000000000000000000000;487	}488489	// Add collection admin by substrate address.490	// @param new_admin Substrate administrator address.491	//492	// Selector: addCollectionAdminSubstrate(uint256) 5730062b493	function addCollectionAdminSubstrate(uint256 newAdmin) public {494		require(false, stub_error);495		newAdmin;496		dummy = 0;497	}498499	// Remove collection admin by substrate address.500	// @param admin Substrate administrator address.501	//502	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9503	function removeCollectionAdminSubstrate(uint256 admin) public {504		require(false, stub_error);505		admin;506		dummy = 0;507	}508509	// Add collection admin.510	// @param new_admin Address of the added administrator.511	//512	// Selector: addCollectionAdmin(address) 92e462c7513	function addCollectionAdmin(address newAdmin) public {514		require(false, stub_error);515		newAdmin;516		dummy = 0;517	}518519	// Remove collection admin.520	//521	// @param new_admin Address of the removed administrator.522	//523	// Selector: removeCollectionAdmin(address) fafd7b42524	function removeCollectionAdmin(address admin) public {525		require(false, stub_error);526		admin;527		dummy = 0;528	}529530	// Toggle accessibility of collection nesting.531	//532	// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'533	//534	// Selector: setCollectionNesting(bool) 112d4586535	function setCollectionNesting(bool enable) public {536		require(false, stub_error);537		enable;538		dummy = 0;539	}540541	// Toggle accessibility of collection nesting.542	//543	// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'544	// @param collections Addresses of collections that will be available for nesting.545	//546	// Selector: setCollectionNesting(bool,address[]) 64872396547	function setCollectionNesting(bool enable, address[] memory collections)548		public549	{550		require(false, stub_error);551		enable;552		collections;553		dummy = 0;554	}555556	// Set the collection access method.557	// @param mode Access mode558	// 	0 for Normal559	// 	1 for AllowList560	//561	// Selector: setCollectionAccess(uint8) 41835d4c562	function setCollectionAccess(uint8 mode) public {563		require(false, stub_error);564		mode;565		dummy = 0;566	}567568	// Add the user to the allowed list.569	//570	// @param user Address of a trusted user.571	//572	// Selector: addToCollectionAllowList(address) 67844fe6573	function addToCollectionAllowList(address user) public {574		require(false, stub_error);575		user;576		dummy = 0;577	}578579	// Remove the user from the allowed list.580	//581	// @param user Address of a removed user.582	//583	// Selector: removeFromCollectionAllowList(address) 85c51acb584	function removeFromCollectionAllowList(address user) public {585		require(false, stub_error);586		user;587		dummy = 0;588	}589590	// Switch permission for minting.591	//592	// @param mode Enable if "true".593	//594	// Selector: setCollectionMintMode(bool) 00018e84595	function setCollectionMintMode(bool mode) public {596		require(false, stub_error);597		mode;598		dummy = 0;599	}600}601602contract UniqueRefungible is603	Dummy,604	ERC165,605	ERC721,606	ERC721Metadata,607	ERC721Enumerable,608	ERC721UniqueExtensions,609	ERC721Mintable,610	ERC721Burnable,611	Collection,612	TokenProperties613{}
after · pallets/refungible/src/stubs/UniqueRefungible.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56// Anonymous struct7struct Tuple0 {8	uint256 field_0;9	string field_1;10}1112// Common stubs holder13contract Dummy {14	uint8 dummy;15	string stub_error = "this contract is implemented in native";16}1718contract ERC165 is Dummy {19	function supportsInterface(bytes4 interfaceID)20		external21		view22		returns (bool)23	{24		require(false, stub_error);25		interfaceID;26		return true;27	}28}2930// Inline31contract ERC721Events {32	event Transfer(33		address indexed from,34		address indexed to,35		uint256 indexed tokenId36	);37	event Approval(38		address indexed owner,39		address indexed approved,40		uint256 indexed tokenId41	);42	event ApprovalForAll(43		address indexed owner,44		address indexed operator,45		bool approved46	);47}4849// Inline50contract ERC721MintableEvents {51	event MintingFinished();52}5354// Selector: 4136937755contract TokenProperties is Dummy, ERC165 {56	// @notice Set permissions for token property.57	// @dev Throws error if `msg.sender` is not admin or owner of the collection.58	// @param key Property key.59	// @param is_mutable Permission to mutate property.60	// @param collection_admin Permission to mutate property by collection admin if property is mutable.61	// @param token_owner Permission to mutate property by token owner if property is mutable.62	//63	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa64	function setTokenPropertyPermission(65		string memory key,66		bool isMutable,67		bool collectionAdmin,68		bool tokenOwner69	) public {70		require(false, stub_error);71		key;72		isMutable;73		collectionAdmin;74		tokenOwner;75		dummy = 0;76	}7778	// @notice Set token property value.79	// @dev Throws error if `msg.sender` has no permission to edit the property.80	// @param tokenId ID of the token.81	// @param key Property key.82	// @param value Property value.83	//84	// Selector: setProperty(uint256,string,bytes) 1752d67b85	function setProperty(86		uint256 tokenId,87		string memory key,88		bytes memory value89	) public {90		require(false, stub_error);91		tokenId;92		key;93		value;94		dummy = 0;95	}9697	// @notice Delete token property value.98	// @dev Throws error if `msg.sender` has no permission to edit the property.99	// @param tokenId ID of the token.100	// @param key Property key.101	//102	// Selector: deleteProperty(uint256,string) 066111d1103	function deleteProperty(uint256 tokenId, string memory key) public {104		require(false, stub_error);105		tokenId;106		key;107		dummy = 0;108	}109110	// @notice Get token property value.111	// @dev Throws error if key not found112	// @param tokenId ID of the token.113	// @param key Property key.114	// @return Property value bytes115	//116	// Selector: property(uint256,string) 7228c327117	function property(uint256 tokenId, string memory key)118		public119		view120		returns (bytes memory)121	{122		require(false, stub_error);123		tokenId;124		key;125		dummy;126		return hex"";127	}128}129130// Selector: 42966c68131contract ERC721Burnable is Dummy, ERC165 {132	// @notice Burns a specific ERC721 token.133	// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized134	//  operator of the current owner.135	// @param tokenId The RFT to approve136	//137	// Selector: burn(uint256) 42966c68138	function burn(uint256 tokenId) public {139		require(false, stub_error);140		tokenId;141		dummy = 0;142	}143}144145// Selector: 58800161146contract ERC721 is Dummy, ERC165, ERC721Events {147	// @notice Count all RFTs assigned to an owner148	// @dev RFTs assigned to the zero address are considered invalid, and this149	//  function throws for queries about the zero address.150	// @param owner An address for whom to query the balance151	// @return The number of RFTs owned by `owner`, possibly zero152	//153	// Selector: balanceOf(address) 70a08231154	function balanceOf(address owner) public view returns (uint256) {155		require(false, stub_error);156		owner;157		dummy;158		return 0;159	}160161	// Selector: ownerOf(uint256) 6352211e162	function ownerOf(uint256 tokenId) public view returns (address) {163		require(false, stub_error);164		tokenId;165		dummy;166		return 0x0000000000000000000000000000000000000000;167	}168169	// @dev Not implemented170	//171	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672172	function safeTransferFromWithData(173		address from,174		address to,175		uint256 tokenId,176		bytes memory data177	) public {178		require(false, stub_error);179		from;180		to;181		tokenId;182		data;183		dummy = 0;184	}185186	// @dev Not implemented187	//188	// Selector: safeTransferFrom(address,address,uint256) 42842e0e189	function safeTransferFrom(190		address from,191		address to,192		uint256 tokenId193	) public {194		require(false, stub_error);195		from;196		to;197		tokenId;198		dummy = 0;199	}200201	// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE202	//  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE203	//  THEY MAY BE PERMANENTLY LOST204	// @dev Throws unless `msg.sender` is the current owner or an authorized205	//  operator for this RFT. Throws if `from` is not the current owner. Throws206	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.207	//  Throws if RFT pieces have multiple owners.208	// @param from The current owner of the NFT209	// @param to The new owner210	// @param tokenId The NFT to transfer211	// @param _value Not used for an NFT212	//213	// Selector: transferFrom(address,address,uint256) 23b872dd214	function transferFrom(215		address from,216		address to,217		uint256 tokenId218	) public {219		require(false, stub_error);220		from;221		to;222		tokenId;223		dummy = 0;224	}225226	// @dev Not implemented227	//228	// Selector: approve(address,uint256) 095ea7b3229	function approve(address approved, uint256 tokenId) public {230		require(false, stub_error);231		approved;232		tokenId;233		dummy = 0;234	}235236	// @dev Not implemented237	//238	// Selector: setApprovalForAll(address,bool) a22cb465239	function setApprovalForAll(address operator, bool approved) public {240		require(false, stub_error);241		operator;242		approved;243		dummy = 0;244	}245246	// @dev Not implemented247	//248	// Selector: getApproved(uint256) 081812fc249	function getApproved(uint256 tokenId) public view returns (address) {250		require(false, stub_error);251		tokenId;252		dummy;253		return 0x0000000000000000000000000000000000000000;254	}255256	// @dev Not implemented257	//258	// Selector: isApprovedForAll(address,address) e985e9c5259	function isApprovedForAll(address owner, address operator)260		public261		view262		returns (address)263	{264		require(false, stub_error);265		owner;266		operator;267		dummy;268		return 0x0000000000000000000000000000000000000000;269	}270}271272// Selector: 5b5e139f273contract ERC721Metadata is Dummy, ERC165 {274	// @notice A descriptive name for a collection of RFTs in this contract275	//276	// Selector: name() 06fdde03277	function name() public view returns (string memory) {278		require(false, stub_error);279		dummy;280		return "";281	}282283	// @notice An abbreviated name for RFTs in this contract284	//285	// Selector: symbol() 95d89b41286	function symbol() public view returns (string memory) {287		require(false, stub_error);288		dummy;289		return "";290	}291292	// @notice A distinct Uniform Resource Identifier (URI) for a given asset.293	//294	// @dev If the token has a `url` property and it is not empty, it is returned.295	//  Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.296	//  If the collection property `baseURI` is empty or absent, return "" (empty string)297	//  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix298	//  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).299	//300	// @return token's const_metadata301	//302	// Selector: tokenURI(uint256) c87b56dd303	function tokenURI(uint256 tokenId) public view returns (string memory) {304		require(false, stub_error);305		tokenId;306		dummy;307		return "";308	}309}310311// Selector: 68ccfe89312contract ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {313	// Selector: mintingFinished() 05d2035b314	function mintingFinished() public view returns (bool) {315		require(false, stub_error);316		dummy;317		return false;318	}319320	// @notice Function to mint token.321	// @dev `tokenId` should be obtained with `nextTokenId` method,322	//  unlike standard, you can't specify it manually323	// @param to The new owner324	// @param tokenId ID of the minted RFT325	//326	// Selector: mint(address,uint256) 40c10f19327	function mint(address to, uint256 tokenId) public returns (bool) {328		require(false, stub_error);329		to;330		tokenId;331		dummy = 0;332		return false;333	}334335	// @notice Function to mint token with the given tokenUri.336	// @dev `tokenId` should be obtained with `nextTokenId` method,337	//  unlike standard, you can't specify it manually338	// @param to The new owner339	// @param tokenId ID of the minted RFT340	// @param tokenUri Token URI that would be stored in the RFT properties341	//342	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f343	function mintWithTokenURI(344		address to,345		uint256 tokenId,346		string memory tokenUri347	) public returns (bool) {348		require(false, stub_error);349		to;350		tokenId;351		tokenUri;352		dummy = 0;353		return false;354	}355356	// @dev Not implemented357	//358	// Selector: finishMinting() 7d64bcb4359	function finishMinting() public returns (bool) {360		require(false, stub_error);361		dummy = 0;362		return false;363	}364}365366// Selector: 780e9d63367contract ERC721Enumerable is Dummy, ERC165 {368	// @notice Enumerate valid RFTs369	// @param index A counter less than `totalSupply()`370	// @return The token identifier for the `index`th NFT,371	//  (sort order not specified)372	//373	// Selector: tokenByIndex(uint256) 4f6ccce7374	function tokenByIndex(uint256 index) public view returns (uint256) {375		require(false, stub_error);376		index;377		dummy;378		return 0;379	}380381	// Not implemented382	//383	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59384	function tokenOfOwnerByIndex(address owner, uint256 index)385		public386		view387		returns (uint256)388	{389		require(false, stub_error);390		owner;391		index;392		dummy;393		return 0;394	}395396	// @notice Count RFTs tracked by this contract397	// @return A count of valid RFTs tracked by this contract, where each one of398	//  them has an assigned and queryable owner not equal to the zero address399	//400	// Selector: totalSupply() 18160ddd401	function totalSupply() public view returns (uint256) {402		require(false, stub_error);403		dummy;404		return 0;405	}406}407408// Selector: 7d9262e6409contract Collection is Dummy, ERC165 {410	// Set collection property.411	//412	// @param key Property key.413	// @param value Propery value.414	//415	// Selector: setCollectionProperty(string,bytes) 2f073f66416	function setCollectionProperty(string memory key, bytes memory value)417		public418	{419		require(false, stub_error);420		key;421		value;422		dummy = 0;423	}424425	// Delete collection property.426	//427	// @param key Property key.428	//429	// Selector: deleteCollectionProperty(string) 7b7debce430	function deleteCollectionProperty(string memory key) public {431		require(false, stub_error);432		key;433		dummy = 0;434	}435436	// Get collection property.437	//438	// @dev Throws error if key not found.439	//440	// @param key Property key.441	// @return bytes The property corresponding to the key.442	//443	// Selector: collectionProperty(string) cf24fd6d444	function collectionProperty(string memory key)445		public446		view447		returns (bytes memory)448	{449		require(false, stub_error);450		key;451		dummy;452		return hex"";453	}454455	// Set the sponsor of the collection.456	//457	// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.458	//459	// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.460	//461	// Selector: setCollectionSponsor(address) 7623402e462	function setCollectionSponsor(address sponsor) public {463		require(false, stub_error);464		sponsor;465		dummy = 0;466	}467468	// Collection sponsorship confirmation.469	//470	// @dev After setting the sponsor for the collection, it must be confirmed with this function.471	//472	// Selector: confirmCollectionSponsorship() 3c50e97a473	function confirmCollectionSponsorship() public {474		require(false, stub_error);475		dummy = 0;476	}477478	// Set limits for the collection.479	// @dev Throws error if limit not found.480	// @param limit Name of the limit. Valid names:481	// 	"accountTokenOwnershipLimit",482	// 	"sponsoredDataSize",483	// 	"sponsoredDataRateLimit",484	// 	"tokenLimit",485	// 	"sponsorTransferTimeout",486	// 	"sponsorApproveTimeout"487	// @param value Value of the limit.488	//489	// Selector: setCollectionLimit(string,uint32) 6a3841db490	function setCollectionLimit(string memory limit, uint32 value) public {491		require(false, stub_error);492		limit;493		value;494		dummy = 0;495	}496497	// Set limits for the collection.498	// @dev Throws error if limit not found.499	// @param limit Name of the limit. Valid names:500	// 	"ownerCanTransfer",501	// 	"ownerCanDestroy",502	// 	"transfersEnabled"503	// @param value Value of the limit.504	//505	// Selector: setCollectionLimit(string,bool) 993b7fba506	function setCollectionLimit(string memory limit, bool value) public {507		require(false, stub_error);508		limit;509		value;510		dummy = 0;511	}512513	// Get contract address.514	//515	// Selector: contractAddress() f6b4dfb4516	function contractAddress() public view returns (address) {517		require(false, stub_error);518		dummy;519		return 0x0000000000000000000000000000000000000000;520	}521522	// Add collection admin by substrate address.523	// @param new_admin Substrate administrator address.524	//525	// Selector: addCollectionAdminSubstrate(uint256) 5730062b526	function addCollectionAdminSubstrate(uint256 newAdmin) public {527		require(false, stub_error);528		newAdmin;529		dummy = 0;530	}531532	// Remove collection admin by substrate address.533	// @param admin Substrate administrator address.534	//535	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9536	function removeCollectionAdminSubstrate(uint256 admin) public {537		require(false, stub_error);538		admin;539		dummy = 0;540	}541542	// Add collection admin.543	// @param new_admin Address of the added administrator.544	//545	// Selector: addCollectionAdmin(address) 92e462c7546	function addCollectionAdmin(address newAdmin) public {547		require(false, stub_error);548		newAdmin;549		dummy = 0;550	}551552	// Remove collection admin.553	//554	// @param new_admin Address of the removed administrator.555	//556	// Selector: removeCollectionAdmin(address) fafd7b42557	function removeCollectionAdmin(address admin) public {558		require(false, stub_error);559		admin;560		dummy = 0;561	}562563	// Toggle accessibility of collection nesting.564	//565	// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'566	//567	// Selector: setCollectionNesting(bool) 112d4586568	function setCollectionNesting(bool enable) public {569		require(false, stub_error);570		enable;571		dummy = 0;572	}573574	// Toggle accessibility of collection nesting.575	//576	// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'577	// @param collections Addresses of collections that will be available for nesting.578	//579	// Selector: setCollectionNesting(bool,address[]) 64872396580	function setCollectionNesting(bool enable, address[] memory collections)581		public582	{583		require(false, stub_error);584		enable;585		collections;586		dummy = 0;587	}588589	// Set the collection access method.590	// @param mode Access mode591	// 	0 for Normal592	// 	1 for AllowList593	//594	// Selector: setCollectionAccess(uint8) 41835d4c595	function setCollectionAccess(uint8 mode) public {596		require(false, stub_error);597		mode;598		dummy = 0;599	}600601	// Add the user to the allowed list.602	//603	// @param user Address of a trusted user.604	//605	// Selector: addToCollectionAllowList(address) 67844fe6606	function addToCollectionAllowList(address user) public {607		require(false, stub_error);608		user;609		dummy = 0;610	}611612	// Remove the user from the allowed list.613	//614	// @param user Address of a removed user.615	//616	// Selector: removeFromCollectionAllowList(address) 85c51acb617	function removeFromCollectionAllowList(address user) public {618		require(false, stub_error);619		user;620		dummy = 0;621	}622623	// Switch permission for minting.624	//625	// @param mode Enable if "true".626	//627	// Selector: setCollectionMintMode(bool) 00018e84628	function setCollectionMintMode(bool mode) public {629		require(false, stub_error);630		mode;631		dummy = 0;632	}633}634635// Selector: d74d154f636contract ERC721UniqueExtensions is Dummy, ERC165 {637	// @notice Transfer ownership of an RFT638	// @dev Throws unless `msg.sender` is the current owner. Throws if `to`639	//  is the zero address. Throws if `tokenId` is not a valid RFT.640	//  Throws if RFT pieces have multiple owners.641	// @param to The new owner642	// @param tokenId The RFT to transfer643	// @param _value Not used for an RFT644	//645	// Selector: transfer(address,uint256) a9059cbb646	function transfer(address to, uint256 tokenId) public {647		require(false, stub_error);648		to;649		tokenId;650		dummy = 0;651	}652653	// @notice Burns a specific ERC721 token.654	// @dev Throws unless `msg.sender` is the current owner or an authorized655	//  operator for this RFT. Throws if `from` is not the current owner. Throws656	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.657	//  Throws if RFT pieces have multiple owners.658	// @param from The current owner of the RFT659	// @param tokenId The RFT to transfer660	// @param _value Not used for an RFT661	//662	// Selector: burnFrom(address,uint256) 79cc6790663	function burnFrom(address from, uint256 tokenId) public {664		require(false, stub_error);665		from;666		tokenId;667		dummy = 0;668	}669670	// @notice Returns next free RFT ID.671	//672	// Selector: nextTokenId() 75794a3c673	function nextTokenId() public view returns (uint256) {674		require(false, stub_error);675		dummy;676		return 0;677	}678679	// @notice Function to mint multiple tokens.680	// @dev `tokenIds` should be an array of consecutive numbers and first number681	//  should be obtained with `nextTokenId` method682	// @param to The new owner683	// @param tokenIds IDs of the minted RFTs684	//685	// Selector: mintBulk(address,uint256[]) 44a9945e686	function mintBulk(address to, uint256[] memory tokenIds)687		public688		returns (bool)689	{690		require(false, stub_error);691		to;692		tokenIds;693		dummy = 0;694		return false;695	}696697	// @notice Function to mint multiple tokens with the given tokenUris.698	// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive699	//  numbers and first number should be obtained with `nextTokenId` method700	// @param to The new owner701	// @param tokens array of pairs of token ID and token URI for minted tokens702	//703	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006704	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)705		public706		returns (bool)707	{708		require(false, stub_error);709		to;710		tokens;711		dummy = 0;712		return false;713	}714}715716contract UniqueRefungible is717	Dummy,718	ERC165,719	ERC721,720	ERC721Metadata,721	ERC721Enumerable,722	ERC721UniqueExtensions,723	ERC721Mintable,724	ERC721Burnable,725	Collection,726	TokenProperties727{}
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -42,26 +42,15 @@
 	event MintingFinished();
 }
 
-// Selector: 0784ee64
-interface ERC721UniqueExtensions is Dummy, ERC165 {
-	// @notice Returns next free RFT ID.
-	//
-	// Selector: nextTokenId() 75794a3c
-	function nextTokenId() external view returns (uint256);
-
-	// Selector: mintBulk(address,uint256[]) 44a9945e
-	function mintBulk(address to, uint256[] memory tokenIds)
-		external
-		returns (bool);
-
-	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
-	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
-		external
-		returns (bool);
-}
-
 // Selector: 41369377
 interface TokenProperties is Dummy, ERC165 {
+	// @notice Set permissions for token property.
+	// @dev Throws error if `msg.sender` is not admin or owner of the collection.
+	// @param key Property key.
+	// @param is_mutable Permission to mutate property.
+	// @param collection_admin Permission to mutate property by collection admin if property is mutable.
+	// @param token_owner Permission to mutate property by token owner if property is mutable.
+	//
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
 	function setTokenPropertyPermission(
 		string memory key,
@@ -70,6 +59,12 @@
 		bool tokenOwner
 	) external;
 
+	// @notice Set token property value.
+	// @dev Throws error if `msg.sender` has no permission to edit the property.
+	// @param tokenId ID of the token.
+	// @param key Property key.
+	// @param value Property value.
+	//
 	// Selector: setProperty(uint256,string,bytes) 1752d67b
 	function setProperty(
 		uint256 tokenId,
@@ -77,10 +72,19 @@
 		bytes memory value
 	) external;
 
+	// @notice Delete token property value.
+	// @dev Throws error if `msg.sender` has no permission to edit the property.
+	// @param tokenId ID of the token.
+	// @param key Property key.
+	//
 	// Selector: deleteProperty(uint256,string) 066111d1
 	function deleteProperty(uint256 tokenId, string memory key) external;
 
-	// Throws error if key not found
+	// @notice Get token property value.
+	// @dev Throws error if key not found
+	// @param tokenId ID of the token.
+	// @param key Property key.
+	// @return Property value bytes
 	//
 	// Selector: property(uint256,string) 7228c327
 	function property(uint256 tokenId, string memory key)
@@ -91,7 +95,10 @@
 
 // Selector: 42966c68
 interface ERC721Burnable is Dummy, ERC165 {
-	// @dev Not implemented
+	// @notice Burns a specific ERC721 token.
+	// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized
+	//  operator of the current owner.
+	// @param tokenId The RFT to approve
 	//
 	// Selector: burn(uint256) 42966c68
 	function burn(uint256 tokenId) external;
@@ -99,6 +106,12 @@
 
 // Selector: 58800161
 interface ERC721 is Dummy, ERC165, ERC721Events {
+	// @notice Count all RFTs assigned to an owner
+	// @dev RFTs assigned to the zero address are considered invalid, and this
+	//  function throws for queries about the zero address.
+	// @param owner An address for whom to query the balance
+	// @return The number of RFTs owned by `owner`, possibly zero
+	//
 	// Selector: balanceOf(address) 70a08231
 	function balanceOf(address owner) external view returns (uint256);
 
@@ -124,7 +137,17 @@
 		uint256 tokenId
 	) external;
 
-	// @dev Not implemented
+	// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE
+	//  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+	//  THEY MAY BE PERMANENTLY LOST
+	// @dev Throws unless `msg.sender` is the current owner or an authorized
+	//  operator for this RFT. Throws if `from` is not the current owner. Throws
+	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	//  Throws if RFT pieces have multiple owners.
+	// @param from The current owner of the NFT
+	// @param to The new owner
+	// @param tokenId The NFT to transfer
+	// @param _value Not used for an NFT
 	//
 	// Selector: transferFrom(address,address,uint256) 23b872dd
 	function transferFrom(
@@ -159,13 +182,25 @@
 
 // Selector: 5b5e139f
 interface ERC721Metadata is Dummy, ERC165 {
+	// @notice A descriptive name for a collection of RFTs in this contract
+	//
 	// Selector: name() 06fdde03
 	function name() external view returns (string memory);
 
+	// @notice An abbreviated name for RFTs in this contract
+	//
 	// Selector: symbol() 95d89b41
 	function symbol() external view returns (string memory);
 
-	// Returns token's const_metadata
+	// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+	//
+	// @dev If the token has a `url` property and it is not empty, it is returned.
+	//  Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.
+	//  If the collection property `baseURI` is empty or absent, return "" (empty string)
+	//  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
+	//  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
+	//
+	// @return token's const_metadata
 	//
 	// Selector: tokenURI(uint256) c87b56dd
 	function tokenURI(uint256 tokenId) external view returns (string memory);
@@ -176,14 +211,21 @@
 	// Selector: mintingFinished() 05d2035b
 	function mintingFinished() external view returns (bool);
 
-	// `token_id` should be obtained with `next_token_id` method,
-	// unlike standard, you can't specify it manually
+	// @notice Function to mint token.
+	// @dev `tokenId` should be obtained with `nextTokenId` method,
+	//  unlike standard, you can't specify it manually
+	// @param to The new owner
+	// @param tokenId ID of the minted RFT
 	//
 	// Selector: mint(address,uint256) 40c10f19
 	function mint(address to, uint256 tokenId) external returns (bool);
 
-	// `token_id` should be obtained with `next_token_id` method,
-	// unlike standard, you can't specify it manually
+	// @notice Function to mint token with the given tokenUri.
+	// @dev `tokenId` should be obtained with `nextTokenId` method,
+	//  unlike standard, you can't specify it manually
+	// @param to The new owner
+	// @param tokenId ID of the minted RFT
+	// @param tokenUri Token URI that would be stored in the RFT properties
 	//
 	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
 	function mintWithTokenURI(
@@ -200,6 +242,11 @@
 
 // Selector: 780e9d63
 interface ERC721Enumerable is Dummy, ERC165 {
+	// @notice Enumerate valid RFTs
+	// @param index A counter less than `totalSupply()`
+	// @return The token identifier for the `index`th NFT,
+	//  (sort order not specified)
+	//
 	// Selector: tokenByIndex(uint256) 4f6ccce7
 	function tokenByIndex(uint256 index) external view returns (uint256);
 
@@ -211,6 +258,10 @@
 		view
 		returns (uint256);
 
+	// @notice Count RFTs tracked by this contract
+	// @return A count of valid RFTs tracked by this contract, where each one of
+	//  them has an assigned and queryable owner not equal to the zero address
+	//
 	// Selector: totalSupply() 18160ddd
 	function totalSupply() external view returns (uint256);
 }
@@ -363,6 +414,59 @@
 	function setCollectionMintMode(bool mode) external;
 }
 
+// Selector: d74d154f
+interface ERC721UniqueExtensions is Dummy, ERC165 {
+	// @notice Transfer ownership of an RFT
+	// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	//  is the zero address. Throws if `tokenId` is not a valid RFT.
+	//  Throws if RFT pieces have multiple owners.
+	// @param to The new owner
+	// @param tokenId The RFT to transfer
+	// @param _value Not used for an RFT
+	//
+	// Selector: transfer(address,uint256) a9059cbb
+	function transfer(address to, uint256 tokenId) external;
+
+	// @notice Burns a specific ERC721 token.
+	// @dev Throws unless `msg.sender` is the current owner or an authorized
+	//  operator for this RFT. Throws if `from` is not the current owner. Throws
+	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	//  Throws if RFT pieces have multiple owners.
+	// @param from The current owner of the RFT
+	// @param tokenId The RFT to transfer
+	// @param _value Not used for an RFT
+	//
+	// Selector: burnFrom(address,uint256) 79cc6790
+	function burnFrom(address from, uint256 tokenId) external;
+
+	// @notice Returns next free RFT ID.
+	//
+	// Selector: nextTokenId() 75794a3c
+	function nextTokenId() external view returns (uint256);
+
+	// @notice Function to mint multiple tokens.
+	// @dev `tokenIds` should be an array of consecutive numbers and first number
+	//  should be obtained with `nextTokenId` method
+	// @param to The new owner
+	// @param tokenIds IDs of the minted RFTs
+	//
+	// Selector: mintBulk(address,uint256[]) 44a9945e
+	function mintBulk(address to, uint256[] memory tokenIds)
+		external
+		returns (bool);
+
+	// @notice Function to mint multiple tokens with the given tokenUris.
+	// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+	//  numbers and first number should be obtained with `nextTokenId` method
+	// @param to The new owner
+	// @param tokens array of pairs of token ID and token URI for minted tokens
+	//
+	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+		external
+		returns (bool);
+}
+
 interface UniqueRefungible is
 	Dummy,
 	ERC165,
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -14,8 +14,8 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {createCollectionExpectSuccess} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, tokenIdToAddress} from './util/helpers';
+import {createCollectionExpectSuccess, UNIQUE} from '../util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, tokenIdToAddress} from './util/helpers';
 import reFungibleAbi from './reFungibleAbi.json';
 import reFungibleTokenAbi from './reFungibleTokenAbi.json';
 import {expect} from 'chai';
@@ -26,7 +26,7 @@
     const helper = evmCollectionHelpers(web3, caller);
     const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, collectionIdAddress, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
     const nextTokenId = await contract.methods.nextTokenId().call();
     await contract.methods.mint(caller, nextTokenId).send();
     const totalSupply = await contract.methods.totalSupply().call();
@@ -38,7 +38,7 @@
     const helper = evmCollectionHelpers(web3, caller);
     const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, collectionIdAddress, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
 
     {
       const nextTokenId = await contract.methods.nextTokenId().call();
@@ -63,7 +63,7 @@
     const helper = evmCollectionHelpers(web3, caller);
     const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, collectionIdAddress, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
 
     const tokenId = await contract.methods.nextTokenId().call();
     await contract.methods.mint(caller, tokenId).send();
@@ -79,7 +79,7 @@
     const helper = evmCollectionHelpers(web3, caller);
     const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, collectionIdAddress, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
 
     const tokenId = await contract.methods.nextTokenId().call();
     await contract.methods.mint(caller, tokenId).send();
@@ -105,7 +105,7 @@
     let result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
-    const contract = evmCollection(web3, owner, collectionIdAddress);
+    const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
     const nextTokenId = await contract.methods.nextTokenId().call();
 
     expect(nextTokenId).to.be.equal('1');
@@ -137,7 +137,7 @@
     const helper = evmCollectionHelpers(web3, caller);
     const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, collectionIdAddress, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
 
     const receiver = createEthAccount(web3);
 
@@ -189,8 +189,148 @@
       expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
     }
   });
+
+  itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+    {
+      const result = await contract.methods.burn(tokenId).send();
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: caller,
+            to: '0x0000000000000000000000000000000000000000',
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+  });
+
+  itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const receiver = createEthAccount(web3);
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+    {
+      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();
+      const events = normalizeEvents(result.events);
+      expect(events).to.include.deep.members([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: caller,
+            to: receiver,
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(caller).call();
+      expect(+balance).to.equal(0);
+    }
+  });
+
+  itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const receiver = createEthAccount(web3);
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    {
+      const result = await contract.methods.transfer(receiver, tokenId).send();
+      const events = normalizeEvents(result.events);
+      expect(events).to.include.deep.members([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: caller,
+            to: receiver,
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(caller).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+  });
 });
 
+describe('RFT: Fees', () => {
+  itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const receiver = createEthAccount(web3);
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    const cost = await recordEthFee(api, caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());
+    expect(cost < BigInt(0.2 * Number(UNIQUE)));
+    expect(cost > 0n);
+  });
+
+  itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, caller);
+    const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});
+
+    const receiver = createEthAccount(web3);
+
+    const tokenId = await contract.methods.nextTokenId().call();
+    await contract.methods.mint(caller, tokenId).send();
+
+    const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send());
+    expect(cost < BigInt(0.2 * Number(UNIQUE)));
+    expect(cost > 0n);
+  });
+});
+
 describe('Common metadata', () => {
   itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
@@ -200,7 +340,7 @@
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, address, {type: 'ReFungible'});
     const name = await contract.methods.name().call();
 
     expect(name).to.equal('token name');
@@ -214,7 +354,7 @@
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(reFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const contract = evmCollection(web3, caller, address, {type: 'ReFungible'});
     const symbol = await contract.methods.symbol().call();
 
     expect(symbol).to.equal('TOK');
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -136,6 +136,16 @@
     "type": "function"
   },
   {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "burnFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
     "name": "collectionProperty",
     "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
@@ -498,6 +508,16 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transfer",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }