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
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -51,44 +51,15 @@
 	event MintingFinished();
 }
 
-// Selector: 0784ee64
-contract ERC721UniqueExtensions is Dummy, ERC165 {
-	// @notice Returns next free RFT ID.
-	//
-	// Selector: nextTokenId() 75794a3c
-	function nextTokenId() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-
-	// Selector: mintBulk(address,uint256[]) 44a9945e
-	function mintBulk(address to, uint256[] memory tokenIds)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokenIds;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
-	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokens;
-		dummy = 0;
-		return false;
-	}
-}
-
 // Selector: 41369377
 contract 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,
@@ -104,6 +75,12 @@
 		dummy = 0;
 	}
 
+	// @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,
@@ -117,6 +94,11 @@
 		dummy = 0;
 	}
 
+	// @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) public {
 		require(false, stub_error);
@@ -125,7 +107,11 @@
 		dummy = 0;
 	}
 
-	// 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)
@@ -143,7 +129,10 @@
 
 // Selector: 42966c68
 contract 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) public {
@@ -155,6 +144,12 @@
 
 // Selector: 58800161
 contract 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) public view returns (uint256) {
 		require(false, stub_error);
@@ -203,7 +198,17 @@
 		dummy = 0;
 	}
 
-	// @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(
@@ -266,6 +271,8 @@
 
 // Selector: 5b5e139f
 contract ERC721Metadata is Dummy, ERC165 {
+	// @notice A descriptive name for a collection of RFTs in this contract
+	//
 	// Selector: name() 06fdde03
 	function name() public view returns (string memory) {
 		require(false, stub_error);
@@ -273,6 +280,8 @@
 		return "";
 	}
 
+	// @notice An abbreviated name for RFTs in this contract
+	//
 	// Selector: symbol() 95d89b41
 	function symbol() public view returns (string memory) {
 		require(false, stub_error);
@@ -280,7 +289,15 @@
 		return "";
 	}
 
-	// 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) public view returns (string memory) {
@@ -300,8 +317,11 @@
 		return false;
 	}
 
-	// `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) public returns (bool) {
@@ -312,8 +332,12 @@
 		return false;
 	}
 
-	// `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(
@@ -341,6 +365,11 @@
 
 // Selector: 780e9d63
 contract 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) public view returns (uint256) {
 		require(false, stub_error);
@@ -364,6 +393,10 @@
 		return 0;
 	}
 
+	// @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() public view returns (uint256) {
 		require(false, stub_error);
@@ -599,6 +632,87 @@
 	}
 }
 
+// Selector: d74d154f
+contract 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) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	// @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) public {
+		require(false, stub_error);
+		from;
+		tokenId;
+		dummy = 0;
+	}
+
+	// @notice Returns next free RFT ID.
+	//
+	// Selector: nextTokenId() 75794a3c
+	function nextTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+
+	// @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)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokenIds;
+		dummy = 0;
+		return false;
+	}
+
+	// @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)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokens;
+		dummy = 0;
+		return false;
+	}
+}
+
 contract UniqueRefungible is
 	Dummy,
 	ERC165,
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
before · tests/src/eth/api/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 holder13interface Dummy {1415}1617interface ERC165 is Dummy {18	function supportsInterface(bytes4 interfaceID) external view returns (bool);19}2021// Inline22interface ERC721Events {23	event Transfer(24		address indexed from,25		address indexed to,26		uint256 indexed tokenId27	);28	event Approval(29		address indexed owner,30		address indexed approved,31		uint256 indexed tokenId32	);33	event ApprovalForAll(34		address indexed owner,35		address indexed operator,36		bool approved37	);38}3940// Inline41interface ERC721MintableEvents {42	event MintingFinished();43}4445// Selector: 0784ee6446interface ERC721UniqueExtensions is Dummy, ERC165 {47	// @notice Returns next free RFT ID.48	//49	// Selector: nextTokenId() 75794a3c50	function nextTokenId() external view returns (uint256);5152	// Selector: mintBulk(address,uint256[]) 44a9945e53	function mintBulk(address to, uint256[] memory tokenIds)54		external55		returns (bool);5657	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 3654300658	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)59		external60		returns (bool);61}6263// Selector: 4136937764interface TokenProperties is Dummy, ERC165 {65	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa66	function setTokenPropertyPermission(67		string memory key,68		bool isMutable,69		bool collectionAdmin,70		bool tokenOwner71	) external;7273	// Selector: setProperty(uint256,string,bytes) 1752d67b74	function setProperty(75		uint256 tokenId,76		string memory key,77		bytes memory value78	) external;7980	// Selector: deleteProperty(uint256,string) 066111d181	function deleteProperty(uint256 tokenId, string memory key) external;8283	// Throws error if key not found84	//85	// Selector: property(uint256,string) 7228c32786	function property(uint256 tokenId, string memory key)87		external88		view89		returns (bytes memory);90}9192// Selector: 42966c6893interface ERC721Burnable is Dummy, ERC165 {94	// @dev Not implemented95	//96	// Selector: burn(uint256) 42966c6897	function burn(uint256 tokenId) external;98}99100// Selector: 58800161101interface ERC721 is Dummy, ERC165, ERC721Events {102	// Selector: balanceOf(address) 70a08231103	function balanceOf(address owner) external view returns (uint256);104105	// Selector: ownerOf(uint256) 6352211e106	function ownerOf(uint256 tokenId) external view returns (address);107108	// @dev Not implemented109	//110	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672111	function safeTransferFromWithData(112		address from,113		address to,114		uint256 tokenId,115		bytes memory data116	) external;117118	// @dev Not implemented119	//120	// Selector: safeTransferFrom(address,address,uint256) 42842e0e121	function safeTransferFrom(122		address from,123		address to,124		uint256 tokenId125	) external;126127	// @dev Not implemented128	//129	// Selector: transferFrom(address,address,uint256) 23b872dd130	function transferFrom(131		address from,132		address to,133		uint256 tokenId134	) external;135136	// @dev Not implemented137	//138	// Selector: approve(address,uint256) 095ea7b3139	function approve(address approved, uint256 tokenId) external;140141	// @dev Not implemented142	//143	// Selector: setApprovalForAll(address,bool) a22cb465144	function setApprovalForAll(address operator, bool approved) external;145146	// @dev Not implemented147	//148	// Selector: getApproved(uint256) 081812fc149	function getApproved(uint256 tokenId) external view returns (address);150151	// @dev Not implemented152	//153	// Selector: isApprovedForAll(address,address) e985e9c5154	function isApprovedForAll(address owner, address operator)155		external156		view157		returns (address);158}159160// Selector: 5b5e139f161interface ERC721Metadata is Dummy, ERC165 {162	// Selector: name() 06fdde03163	function name() external view returns (string memory);164165	// Selector: symbol() 95d89b41166	function symbol() external view returns (string memory);167168	// Returns token's const_metadata169	//170	// Selector: tokenURI(uint256) c87b56dd171	function tokenURI(uint256 tokenId) external view returns (string memory);172}173174// Selector: 68ccfe89175interface ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {176	// Selector: mintingFinished() 05d2035b177	function mintingFinished() external view returns (bool);178179	// `token_id` should be obtained with `next_token_id` method,180	// unlike standard, you can't specify it manually181	//182	// Selector: mint(address,uint256) 40c10f19183	function mint(address to, uint256 tokenId) external returns (bool);184185	// `token_id` should be obtained with `next_token_id` method,186	// unlike standard, you can't specify it manually187	//188	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f189	function mintWithTokenURI(190		address to,191		uint256 tokenId,192		string memory tokenUri193	) external returns (bool);194195	// @dev Not implemented196	//197	// Selector: finishMinting() 7d64bcb4198	function finishMinting() external returns (bool);199}200201// Selector: 780e9d63202interface ERC721Enumerable is Dummy, ERC165 {203	// Selector: tokenByIndex(uint256) 4f6ccce7204	function tokenByIndex(uint256 index) external view returns (uint256);205206	// Not implemented207	//208	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59209	function tokenOfOwnerByIndex(address owner, uint256 index)210		external211		view212		returns (uint256);213214	// Selector: totalSupply() 18160ddd215	function totalSupply() external view returns (uint256);216}217218// Selector: 7d9262e6219interface Collection is Dummy, ERC165 {220	// Set collection property.221	//222	// @param key Property key.223	// @param value Propery value.224	//225	// Selector: setCollectionProperty(string,bytes) 2f073f66226	function setCollectionProperty(string memory key, bytes memory value)227		external;228229	// Delete collection property.230	//231	// @param key Property key.232	//233	// Selector: deleteCollectionProperty(string) 7b7debce234	function deleteCollectionProperty(string memory key) external;235236	// Get collection property.237	//238	// @dev Throws error if key not found.239	//240	// @param key Property key.241	// @return bytes The property corresponding to the key.242	//243	// Selector: collectionProperty(string) cf24fd6d244	function collectionProperty(string memory key)245		external246		view247		returns (bytes memory);248249	// Set the sponsor of the collection.250	//251	// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.252	//253	// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.254	//255	// Selector: setCollectionSponsor(address) 7623402e256	function setCollectionSponsor(address sponsor) external;257258	// Collection sponsorship confirmation.259	//260	// @dev After setting the sponsor for the collection, it must be confirmed with this function.261	//262	// Selector: confirmCollectionSponsorship() 3c50e97a263	function confirmCollectionSponsorship() external;264265	// Set limits for the collection.266	// @dev Throws error if limit not found.267	// @param limit Name of the limit. Valid names:268	// 	"accountTokenOwnershipLimit",269	// 	"sponsoredDataSize",270	// 	"sponsoredDataRateLimit",271	// 	"tokenLimit",272	// 	"sponsorTransferTimeout",273	// 	"sponsorApproveTimeout"274	// @param value Value of the limit.275	//276	// Selector: setCollectionLimit(string,uint32) 6a3841db277	function setCollectionLimit(string memory limit, uint32 value) external;278279	// Set limits for the collection.280	// @dev Throws error if limit not found.281	// @param limit Name of the limit. Valid names:282	// 	"ownerCanTransfer",283	// 	"ownerCanDestroy",284	// 	"transfersEnabled"285	// @param value Value of the limit.286	//287	// Selector: setCollectionLimit(string,bool) 993b7fba288	function setCollectionLimit(string memory limit, bool value) external;289290	// Get contract address.291	//292	// Selector: contractAddress() f6b4dfb4293	function contractAddress() external view returns (address);294295	// Add collection admin by substrate address.296	// @param new_admin Substrate administrator address.297	//298	// Selector: addCollectionAdminSubstrate(uint256) 5730062b299	function addCollectionAdminSubstrate(uint256 newAdmin) external;300301	// Remove collection admin by substrate address.302	// @param admin Substrate administrator address.303	//304	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9305	function removeCollectionAdminSubstrate(uint256 admin) external;306307	// Add collection admin.308	// @param new_admin Address of the added administrator.309	//310	// Selector: addCollectionAdmin(address) 92e462c7311	function addCollectionAdmin(address newAdmin) external;312313	// Remove collection admin.314	//315	// @param new_admin Address of the removed administrator.316	//317	// Selector: removeCollectionAdmin(address) fafd7b42318	function removeCollectionAdmin(address admin) external;319320	// Toggle accessibility of collection nesting.321	//322	// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'323	//324	// Selector: setCollectionNesting(bool) 112d4586325	function setCollectionNesting(bool enable) external;326327	// Toggle accessibility of collection nesting.328	//329	// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'330	// @param collections Addresses of collections that will be available for nesting.331	//332	// Selector: setCollectionNesting(bool,address[]) 64872396333	function setCollectionNesting(bool enable, address[] memory collections)334		external;335336	// Set the collection access method.337	// @param mode Access mode338	// 	0 for Normal339	// 	1 for AllowList340	//341	// Selector: setCollectionAccess(uint8) 41835d4c342	function setCollectionAccess(uint8 mode) external;343344	// Add the user to the allowed list.345	//346	// @param user Address of a trusted user.347	//348	// Selector: addToCollectionAllowList(address) 67844fe6349	function addToCollectionAllowList(address user) external;350351	// Remove the user from the allowed list.352	//353	// @param user Address of a removed user.354	//355	// Selector: removeFromCollectionAllowList(address) 85c51acb356	function removeFromCollectionAllowList(address user) external;357358	// Switch permission for minting.359	//360	// @param mode Enable if "true".361	//362	// Selector: setCollectionMintMode(bool) 00018e84363	function setCollectionMintMode(bool mode) external;364}365366interface UniqueRefungible is367	Dummy,368	ERC165,369	ERC721,370	ERC721Metadata,371	ERC721Enumerable,372	ERC721UniqueExtensions,373	ERC721Mintable,374	ERC721Burnable,375	Collection,376	TokenProperties377{}
after · tests/src/eth/api/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 holder13interface Dummy {1415}1617interface ERC165 is Dummy {18	function supportsInterface(bytes4 interfaceID) external view returns (bool);19}2021// Inline22interface ERC721Events {23	event Transfer(24		address indexed from,25		address indexed to,26		uint256 indexed tokenId27	);28	event Approval(29		address indexed owner,30		address indexed approved,31		uint256 indexed tokenId32	);33	event ApprovalForAll(34		address indexed owner,35		address indexed operator,36		bool approved37	);38}3940// Inline41interface ERC721MintableEvents {42	event MintingFinished();43}4445// Selector: 4136937746interface TokenProperties is Dummy, ERC165 {47	// @notice Set permissions for token property.48	// @dev Throws error if `msg.sender` is not admin or owner of the collection.49	// @param key Property key.50	// @param is_mutable Permission to mutate property.51	// @param collection_admin Permission to mutate property by collection admin if property is mutable.52	// @param token_owner Permission to mutate property by token owner if property is mutable.53	//54	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa55	function setTokenPropertyPermission(56		string memory key,57		bool isMutable,58		bool collectionAdmin,59		bool tokenOwner60	) external;6162	// @notice Set token property value.63	// @dev Throws error if `msg.sender` has no permission to edit the property.64	// @param tokenId ID of the token.65	// @param key Property key.66	// @param value Property value.67	//68	// Selector: setProperty(uint256,string,bytes) 1752d67b69	function setProperty(70		uint256 tokenId,71		string memory key,72		bytes memory value73	) external;7475	// @notice Delete token property value.76	// @dev Throws error if `msg.sender` has no permission to edit the property.77	// @param tokenId ID of the token.78	// @param key Property key.79	//80	// Selector: deleteProperty(uint256,string) 066111d181	function deleteProperty(uint256 tokenId, string memory key) external;8283	// @notice Get token property value.84	// @dev Throws error if key not found85	// @param tokenId ID of the token.86	// @param key Property key.87	// @return Property value bytes88	//89	// Selector: property(uint256,string) 7228c32790	function property(uint256 tokenId, string memory key)91		external92		view93		returns (bytes memory);94}9596// Selector: 42966c6897interface ERC721Burnable is Dummy, ERC165 {98	// @notice Burns a specific ERC721 token.99	// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized100	//  operator of the current owner.101	// @param tokenId The RFT to approve102	//103	// Selector: burn(uint256) 42966c68104	function burn(uint256 tokenId) external;105}106107// Selector: 58800161108interface ERC721 is Dummy, ERC165, ERC721Events {109	// @notice Count all RFTs assigned to an owner110	// @dev RFTs assigned to the zero address are considered invalid, and this111	//  function throws for queries about the zero address.112	// @param owner An address for whom to query the balance113	// @return The number of RFTs owned by `owner`, possibly zero114	//115	// Selector: balanceOf(address) 70a08231116	function balanceOf(address owner) external view returns (uint256);117118	// Selector: ownerOf(uint256) 6352211e119	function ownerOf(uint256 tokenId) external view returns (address);120121	// @dev Not implemented122	//123	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672124	function safeTransferFromWithData(125		address from,126		address to,127		uint256 tokenId,128		bytes memory data129	) external;130131	// @dev Not implemented132	//133	// Selector: safeTransferFrom(address,address,uint256) 42842e0e134	function safeTransferFrom(135		address from,136		address to,137		uint256 tokenId138	) external;139140	// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE141	//  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE142	//  THEY MAY BE PERMANENTLY LOST143	// @dev Throws unless `msg.sender` is the current owner or an authorized144	//  operator for this RFT. Throws if `from` is not the current owner. Throws145	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.146	//  Throws if RFT pieces have multiple owners.147	// @param from The current owner of the NFT148	// @param to The new owner149	// @param tokenId The NFT to transfer150	// @param _value Not used for an NFT151	//152	// Selector: transferFrom(address,address,uint256) 23b872dd153	function transferFrom(154		address from,155		address to,156		uint256 tokenId157	) external;158159	// @dev Not implemented160	//161	// Selector: approve(address,uint256) 095ea7b3162	function approve(address approved, uint256 tokenId) external;163164	// @dev Not implemented165	//166	// Selector: setApprovalForAll(address,bool) a22cb465167	function setApprovalForAll(address operator, bool approved) external;168169	// @dev Not implemented170	//171	// Selector: getApproved(uint256) 081812fc172	function getApproved(uint256 tokenId) external view returns (address);173174	// @dev Not implemented175	//176	// Selector: isApprovedForAll(address,address) e985e9c5177	function isApprovedForAll(address owner, address operator)178		external179		view180		returns (address);181}182183// Selector: 5b5e139f184interface ERC721Metadata is Dummy, ERC165 {185	// @notice A descriptive name for a collection of RFTs in this contract186	//187	// Selector: name() 06fdde03188	function name() external view returns (string memory);189190	// @notice An abbreviated name for RFTs in this contract191	//192	// Selector: symbol() 95d89b41193	function symbol() external view returns (string memory);194195	// @notice A distinct Uniform Resource Identifier (URI) for a given asset.196	//197	// @dev If the token has a `url` property and it is not empty, it is returned.198	//  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`.199	//  If the collection property `baseURI` is empty or absent, return "" (empty string)200	//  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix201	//  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).202	//203	// @return token's const_metadata204	//205	// Selector: tokenURI(uint256) c87b56dd206	function tokenURI(uint256 tokenId) external view returns (string memory);207}208209// Selector: 68ccfe89210interface ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {211	// Selector: mintingFinished() 05d2035b212	function mintingFinished() external view returns (bool);213214	// @notice Function to mint token.215	// @dev `tokenId` should be obtained with `nextTokenId` method,216	//  unlike standard, you can't specify it manually217	// @param to The new owner218	// @param tokenId ID of the minted RFT219	//220	// Selector: mint(address,uint256) 40c10f19221	function mint(address to, uint256 tokenId) external returns (bool);222223	// @notice Function to mint token with the given tokenUri.224	// @dev `tokenId` should be obtained with `nextTokenId` method,225	//  unlike standard, you can't specify it manually226	// @param to The new owner227	// @param tokenId ID of the minted RFT228	// @param tokenUri Token URI that would be stored in the RFT properties229	//230	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f231	function mintWithTokenURI(232		address to,233		uint256 tokenId,234		string memory tokenUri235	) external returns (bool);236237	// @dev Not implemented238	//239	// Selector: finishMinting() 7d64bcb4240	function finishMinting() external returns (bool);241}242243// Selector: 780e9d63244interface ERC721Enumerable is Dummy, ERC165 {245	// @notice Enumerate valid RFTs246	// @param index A counter less than `totalSupply()`247	// @return The token identifier for the `index`th NFT,248	//  (sort order not specified)249	//250	// Selector: tokenByIndex(uint256) 4f6ccce7251	function tokenByIndex(uint256 index) external view returns (uint256);252253	// Not implemented254	//255	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59256	function tokenOfOwnerByIndex(address owner, uint256 index)257		external258		view259		returns (uint256);260261	// @notice Count RFTs tracked by this contract262	// @return A count of valid RFTs tracked by this contract, where each one of263	//  them has an assigned and queryable owner not equal to the zero address264	//265	// Selector: totalSupply() 18160ddd266	function totalSupply() external view returns (uint256);267}268269// Selector: 7d9262e6270interface Collection is Dummy, ERC165 {271	// Set collection property.272	//273	// @param key Property key.274	// @param value Propery value.275	//276	// Selector: setCollectionProperty(string,bytes) 2f073f66277	function setCollectionProperty(string memory key, bytes memory value)278		external;279280	// Delete collection property.281	//282	// @param key Property key.283	//284	// Selector: deleteCollectionProperty(string) 7b7debce285	function deleteCollectionProperty(string memory key) external;286287	// Get collection property.288	//289	// @dev Throws error if key not found.290	//291	// @param key Property key.292	// @return bytes The property corresponding to the key.293	//294	// Selector: collectionProperty(string) cf24fd6d295	function collectionProperty(string memory key)296		external297		view298		returns (bytes memory);299300	// Set the sponsor of the collection.301	//302	// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.303	//304	// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.305	//306	// Selector: setCollectionSponsor(address) 7623402e307	function setCollectionSponsor(address sponsor) external;308309	// Collection sponsorship confirmation.310	//311	// @dev After setting the sponsor for the collection, it must be confirmed with this function.312	//313	// Selector: confirmCollectionSponsorship() 3c50e97a314	function confirmCollectionSponsorship() external;315316	// Set limits for the collection.317	// @dev Throws error if limit not found.318	// @param limit Name of the limit. Valid names:319	// 	"accountTokenOwnershipLimit",320	// 	"sponsoredDataSize",321	// 	"sponsoredDataRateLimit",322	// 	"tokenLimit",323	// 	"sponsorTransferTimeout",324	// 	"sponsorApproveTimeout"325	// @param value Value of the limit.326	//327	// Selector: setCollectionLimit(string,uint32) 6a3841db328	function setCollectionLimit(string memory limit, uint32 value) external;329330	// Set limits for the collection.331	// @dev Throws error if limit not found.332	// @param limit Name of the limit. Valid names:333	// 	"ownerCanTransfer",334	// 	"ownerCanDestroy",335	// 	"transfersEnabled"336	// @param value Value of the limit.337	//338	// Selector: setCollectionLimit(string,bool) 993b7fba339	function setCollectionLimit(string memory limit, bool value) external;340341	// Get contract address.342	//343	// Selector: contractAddress() f6b4dfb4344	function contractAddress() external view returns (address);345346	// Add collection admin by substrate address.347	// @param new_admin Substrate administrator address.348	//349	// Selector: addCollectionAdminSubstrate(uint256) 5730062b350	function addCollectionAdminSubstrate(uint256 newAdmin) external;351352	// Remove collection admin by substrate address.353	// @param admin Substrate administrator address.354	//355	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9356	function removeCollectionAdminSubstrate(uint256 admin) external;357358	// Add collection admin.359	// @param new_admin Address of the added administrator.360	//361	// Selector: addCollectionAdmin(address) 92e462c7362	function addCollectionAdmin(address newAdmin) external;363364	// Remove collection admin.365	//366	// @param new_admin Address of the removed administrator.367	//368	// Selector: removeCollectionAdmin(address) fafd7b42369	function removeCollectionAdmin(address admin) external;370371	// Toggle accessibility of collection nesting.372	//373	// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'374	//375	// Selector: setCollectionNesting(bool) 112d4586376	function setCollectionNesting(bool enable) external;377378	// Toggle accessibility of collection nesting.379	//380	// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'381	// @param collections Addresses of collections that will be available for nesting.382	//383	// Selector: setCollectionNesting(bool,address[]) 64872396384	function setCollectionNesting(bool enable, address[] memory collections)385		external;386387	// Set the collection access method.388	// @param mode Access mode389	// 	0 for Normal390	// 	1 for AllowList391	//392	// Selector: setCollectionAccess(uint8) 41835d4c393	function setCollectionAccess(uint8 mode) external;394395	// Add the user to the allowed list.396	//397	// @param user Address of a trusted user.398	//399	// Selector: addToCollectionAllowList(address) 67844fe6400	function addToCollectionAllowList(address user) external;401402	// Remove the user from the allowed list.403	//404	// @param user Address of a removed user.405	//406	// Selector: removeFromCollectionAllowList(address) 85c51acb407	function removeFromCollectionAllowList(address user) external;408409	// Switch permission for minting.410	//411	// @param mode Enable if "true".412	//413	// Selector: setCollectionMintMode(bool) 00018e84414	function setCollectionMintMode(bool mode) external;415}416417// Selector: d74d154f418interface ERC721UniqueExtensions is Dummy, ERC165 {419	// @notice Transfer ownership of an RFT420	// @dev Throws unless `msg.sender` is the current owner. Throws if `to`421	//  is the zero address. Throws if `tokenId` is not a valid RFT.422	//  Throws if RFT pieces have multiple owners.423	// @param to The new owner424	// @param tokenId The RFT to transfer425	// @param _value Not used for an RFT426	//427	// Selector: transfer(address,uint256) a9059cbb428	function transfer(address to, uint256 tokenId) external;429430	// @notice Burns a specific ERC721 token.431	// @dev Throws unless `msg.sender` is the current owner or an authorized432	//  operator for this RFT. Throws if `from` is not the current owner. Throws433	//  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.434	//  Throws if RFT pieces have multiple owners.435	// @param from The current owner of the RFT436	// @param tokenId The RFT to transfer437	// @param _value Not used for an RFT438	//439	// Selector: burnFrom(address,uint256) 79cc6790440	function burnFrom(address from, uint256 tokenId) external;441442	// @notice Returns next free RFT ID.443	//444	// Selector: nextTokenId() 75794a3c445	function nextTokenId() external view returns (uint256);446447	// @notice Function to mint multiple tokens.448	// @dev `tokenIds` should be an array of consecutive numbers and first number449	//  should be obtained with `nextTokenId` method450	// @param to The new owner451	// @param tokenIds IDs of the minted RFTs452	//453	// Selector: mintBulk(address,uint256[]) 44a9945e454	function mintBulk(address to, uint256[] memory tokenIds)455		external456		returns (bool);457458	// @notice Function to mint multiple tokens with the given tokenUris.459	// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive460	//  numbers and first number should be obtained with `nextTokenId` method461	// @param to The new owner462	// @param tokens array of pairs of token ID and token URI for minted tokens463	//464	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006465	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)466		external467		returns (bool);468}469470interface UniqueRefungible is471	Dummy,472	ERC165,473	ERC721,474	ERC721Metadata,475	ERC721Enumerable,476	ERC721UniqueExtensions,477	ERC721Mintable,478	ERC721Burnable,479	Collection,480	TokenProperties481{}
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" }