git.delta.rocks / unique-network / refs/commits / 36fc5abfd9d0

difftreelog

feat add delete properties

Trubnikov Sergey2022-10-24parent: #ec90529.patch.diff
in: master

17 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -117,8 +117,6 @@
 	/// @param key Property key.
 	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]
 	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {
-		self.consume_store_reads_and_writes(1, 1)?;
-
 		let caller = T::CrossAccountId::from_eth(caller);
 		let key = <Vec<u8>>::from(key)
 			.try_into()
@@ -127,6 +125,24 @@
 		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)
 	}
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]
+	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let keys = keys
+			.into_iter()
+			.map(|key| {
+				<Vec<u8>>::from(key)
+					.try_into()
+					.map_err(|_| Error::Revert("key too large".into()))
+			})
+			.collect::<Result<Vec<_>>>()?;
+
+		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)
+	}
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -146,28 +162,34 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {
-		let mut keys_ = Vec::<PropertyKey>::with_capacity(keys.len());
-		for key in keys {
-			keys_.push(
+		let keys = keys
+			.into_iter()
+			.map(|key| {
 				<Vec<u8>>::from(key)
 					.try_into()
-					.map_err(|_| Error::Revert("key too large".into()))?,
-			)
-		}
-		let properties = Pallet::<T>::filter_collection_properties(self.id, Some(keys_))
-			.map_err(dispatch_to_evm::<T>)?;
+					.map_err(|_| Error::Revert("key too large".into()))
+			})
+			.collect::<Result<Vec<_>>>()?;
 
-		let mut properties_ = Vec::<(string, bytes)>::with_capacity(properties.len());
-		for p in properties {
-			let key =
-				string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;
-			let value = bytes(p.value.to_vec());
-			properties_.push((key, value));
-		}
-		Ok(properties_)
+		let properties = Pallet::<T>::filter_collection_properties(
+			self.id,
+			if keys.is_empty() { None } else { Some(keys) },
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		let properties = properties
+			.into_iter()
+			.map(|p| {
+				let key =
+					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;
+				let value = bytes(p.value.to_vec());
+				Ok((key, value))
+			})
+			.collect::<Result<Vec<_>>>()?;
+		Ok(properties)
 	}
 
 	/// Set the sponsor of the collection.
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -55,6 +55,17 @@
 		dummy = 0;
 	}
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) public {
+		require(false, stub_error);
+		keys;
+		dummy = 0;
+	}
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -72,7 +83,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -128,6 +128,17 @@
 		dummy = 0;
 	}
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) public {
+		require(false, stub_error);
+		keys;
+		dummy = 0;
+	}
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -145,7 +156,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
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
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -128,6 +128,17 @@
 		dummy = 0;
 	}
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) public {
+		require(false, stub_error);
+		keys;
+		dummy = 0;
+	}
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -145,7 +156,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -37,6 +37,13 @@
 	///  or in textual repr: deleteCollectionProperty(string)
 	function deleteCollectionProperty(string memory key) external;
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) external;
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -49,7 +56,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -86,6 +86,13 @@
 	///  or in textual repr: deleteCollectionProperty(string)
 	function deleteCollectionProperty(string memory key) external;
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) external;
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -98,7 +105,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x5d354410
+/// @dev the ERC-165 identifier for this interface is 0xb3152af3
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -86,6 +86,13 @@
 	///  or in textual repr: deleteCollectionProperty(string)
 	function deleteCollectionProperty(string memory key) external;
 
+	/// Delete collection properties.
+	///
+	/// @param keys Properties keys.
+	/// @dev EVM selector for this function is: 0xee206ee3,
+	///  or in textual repr: deleteCollectionProperties(string[])
+	function deleteCollectionProperties(string[] memory keys) external;
+
 	/// Get collection property.
 	///
 	/// @dev Throws error if key not found.
@@ -98,7 +105,7 @@
 
 	/// Get collection properties.
 	///
-	/// @param keys Properties keys.
+	/// @param keys Properties keys. Empty keys for all propertyes.
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
modifiedtests/src/eth/collectionProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -16,7 +16,7 @@
 
 import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';
 import {Pallets} from '../util';
-import {IProperty, ITokenPropertyPermission} from '../util/playgrounds/types';
+import {IProperty, ITokenPropertyPermission, TCollectionMode} from '../util/playgrounds/types';
 import {IKeyringPair} from '@polkadot/types/types';
 
 describe('EVM collection properties', () => {
@@ -163,29 +163,89 @@
 });
 
 describe('EVM collection property', () => {
-  itEth('Set/read properties', async ({helper, privateKey}) => {
-    const alice = await privateKey('//Alice');
-    const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+  let alice: IKeyringPair;
+
+  before(() => {
+    usingEthPlaygrounds(async (_helper, privateKey) => {
+      alice = await privateKey('//Alice');
+    });
+  });
+
+  async function testSetReadProperties(helper: EthUniqueHelper, mode: TCollectionMode) {
+    const collection = await helper[mode].mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
 
     const sender = await helper.eth.createAccountWithBalance(alice, 100n);
     await collection.addAdmin(alice, {Ethereum: sender});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
-    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);
+    const contract = helper.ethNativeContract.collection(collectionAddress, mode, sender);
 
-    const key1 = 'key1';
-    const value1 = Buffer.from('value1');
-    
-    const key2 = 'key2';
-    const value2 = Buffer.from('value2');
+    const keys = ['key0', 'key1'];
 
     const writeProperties = [
-      [key1, '0x'+value1.toString('hex')],
-      [key2, '0x'+value2.toString('hex')],
+      helper.ethProperty.property(keys[0], 'value0'),
+      helper.ethProperty.property(keys[1], 'value1'),
     ];
 
     await contract.methods.setCollectionProperties(writeProperties).send();
-    const readProperties = await contract.methods.collectionProperties([key1, key2]).call();
+    const readProperties = await contract.methods.collectionProperties([keys[0], keys[1]]).call();
     expect(readProperties).to.be.like(writeProperties);
+  }
+
+  itEth('Set/read properties ft', async ({helper}) => {
+    await testSetReadProperties(helper, 'ft');
+  });
+  itEth('Set/read properties rft', async ({helper}) => {
+    await testSetReadProperties(helper, 'rft');
+  });
+  itEth('Set/read properties nft', async ({helper}) => {
+    await testSetReadProperties(helper, 'nft');
+  });
+
+  async function testDeleteProperties(helper: EthUniqueHelper, mode: TCollectionMode) {
+    const collection = await helper[mode].mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+    await collection.addAdmin(alice, {Ethereum: sender});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, mode, sender);
+
+    const keys = ['key0', 'key1', 'key2', 'key3'];
+
+    {
+      const writeProperties = [
+        helper.ethProperty.property(keys[0], 'value0'),
+        helper.ethProperty.property(keys[1], 'value1'),
+        helper.ethProperty.property(keys[2], 'value2'),
+        helper.ethProperty.property(keys[3], 'value3'),
+      ];
+
+      await contract.methods.setCollectionProperties(writeProperties).send();
+      const readProperties = await contract.methods.collectionProperties([keys[0], keys[1], keys[2], keys[3]]).call();
+      expect(readProperties).to.be.like(writeProperties);
+    }
+
+    {
+      const expectProperties = [
+        helper.ethProperty.property(keys[0], 'value0'),
+        helper.ethProperty.property(keys[1], 'value1'),
+      ];
+
+      await contract.methods.deleteCollectionProperties([keys[2], keys[3]]).send();
+      const readProperties = await contract.methods.collectionProperties([]).call();
+      expect(readProperties).to.be.like(expectProperties);
+    }
+  }
+  
+  itEth('Delete properties ft', async ({helper}) => {
+    await testDeleteProperties(helper, 'ft');
+  });
+  itEth('Delete properties rft', async ({helper}) => {
+    await testDeleteProperties(helper, 'rft');
   });
+  itEth('Delete properties nft', async ({helper}) => {
+    await testDeleteProperties(helper, 'nft');
+  });
+    
 });
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
before · tests/src/eth/fungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "spender",15        "type": "address"16      },17      {18        "indexed": false,19        "internalType": "uint256",20        "name": "value",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "from",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "to",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "uint256",45        "name": "value",46        "type": "uint256"47      }48    ],49    "name": "Transfer",50    "type": "event"51  },52  {53    "inputs": [54      { "internalType": "address", "name": "newAdmin", "type": "address" }55    ],56    "name": "addCollectionAdmin",57    "outputs": [],58    "stateMutability": "nonpayable",59    "type": "function"60  },61  {62    "inputs": [63      {64        "components": [65          { "internalType": "address", "name": "field_0", "type": "address" },66          { "internalType": "uint256", "name": "field_1", "type": "uint256" }67        ],68        "internalType": "struct Tuple6",69        "name": "newAdmin",70        "type": "tuple"71      }72    ],73    "name": "addCollectionAdminCross",74    "outputs": [],75    "stateMutability": "nonpayable",76    "type": "function"77  },78  {79    "inputs": [80      { "internalType": "address", "name": "user", "type": "address" }81    ],82    "name": "addToCollectionAllowList",83    "outputs": [],84    "stateMutability": "nonpayable",85    "type": "function"86  },87  {88    "inputs": [89      {90        "components": [91          { "internalType": "address", "name": "field_0", "type": "address" },92          { "internalType": "uint256", "name": "field_1", "type": "uint256" }93        ],94        "internalType": "struct Tuple6",95        "name": "user",96        "type": "tuple"97      }98    ],99    "name": "addToCollectionAllowListCross",100    "outputs": [],101    "stateMutability": "nonpayable",102    "type": "function"103  },104  {105    "inputs": [106      { "internalType": "address", "name": "owner", "type": "address" },107      { "internalType": "address", "name": "spender", "type": "address" }108    ],109    "name": "allowance",110    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],111    "stateMutability": "view",112    "type": "function"113  },114  {115    "inputs": [116      { "internalType": "address", "name": "user", "type": "address" }117    ],118    "name": "allowed",119    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],120    "stateMutability": "view",121    "type": "function"122  },123  {124    "inputs": [125      { "internalType": "address", "name": "spender", "type": "address" },126      { "internalType": "uint256", "name": "amount", "type": "uint256" }127    ],128    "name": "approve",129    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],130    "stateMutability": "nonpayable",131    "type": "function"132  },133  {134    "inputs": [135      {136        "components": [137          { "internalType": "address", "name": "field_0", "type": "address" },138          { "internalType": "uint256", "name": "field_1", "type": "uint256" }139        ],140        "internalType": "struct Tuple6",141        "name": "spender",142        "type": "tuple"143      },144      { "internalType": "uint256", "name": "amount", "type": "uint256" }145    ],146    "name": "approveCross",147    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],148    "stateMutability": "nonpayable",149    "type": "function"150  },151  {152    "inputs": [153      { "internalType": "address", "name": "owner", "type": "address" }154    ],155    "name": "balanceOf",156    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],157    "stateMutability": "view",158    "type": "function"159  },160  {161    "inputs": [162      { "internalType": "address", "name": "from", "type": "address" },163      { "internalType": "uint256", "name": "amount", "type": "uint256" }164    ],165    "name": "burnFrom",166    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],167    "stateMutability": "nonpayable",168    "type": "function"169  },170  {171    "inputs": [172      {173        "components": [174          { "internalType": "address", "name": "field_0", "type": "address" },175          { "internalType": "uint256", "name": "field_1", "type": "uint256" }176        ],177        "internalType": "struct Tuple6",178        "name": "from",179        "type": "tuple"180      },181      { "internalType": "uint256", "name": "amount", "type": "uint256" }182    ],183    "name": "burnFromCross",184    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],185    "stateMutability": "nonpayable",186    "type": "function"187  },188  {189    "inputs": [190      { "internalType": "address", "name": "newOwner", "type": "address" }191    ],192    "name": "changeCollectionOwner",193    "outputs": [],194    "stateMutability": "nonpayable",195    "type": "function"196  },197  {198    "inputs": [],199    "name": "collectionAdmins",200    "outputs": [201      {202        "components": [203          { "internalType": "address", "name": "field_0", "type": "address" },204          { "internalType": "uint256", "name": "field_1", "type": "uint256" }205        ],206        "internalType": "struct Tuple6[]",207        "name": "",208        "type": "tuple[]"209      }210    ],211    "stateMutability": "view",212    "type": "function"213  },214  {215    "inputs": [],216    "name": "collectionOwner",217    "outputs": [218      {219        "components": [220          { "internalType": "address", "name": "field_0", "type": "address" },221          { "internalType": "uint256", "name": "field_1", "type": "uint256" }222        ],223        "internalType": "struct Tuple6",224        "name": "",225        "type": "tuple"226      }227    ],228    "stateMutability": "view",229    "type": "function"230  },231  {232    "inputs": [233      { "internalType": "string[]", "name": "keys", "type": "string[]" }234    ],235    "name": "collectionProperties",236    "outputs": [237      {238        "components": [239          { "internalType": "string", "name": "field_0", "type": "string" },240          { "internalType": "bytes", "name": "field_1", "type": "bytes" }241        ],242        "internalType": "struct Tuple10[]",243        "name": "",244        "type": "tuple[]"245      }246    ],247    "stateMutability": "view",248    "type": "function"249  },250  {251    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],252    "name": "collectionProperty",253    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],254    "stateMutability": "view",255    "type": "function"256  },257  {258    "inputs": [],259    "name": "collectionSponsor",260    "outputs": [261      {262        "components": [263          { "internalType": "address", "name": "field_0", "type": "address" },264          { "internalType": "uint256", "name": "field_1", "type": "uint256" }265        ],266        "internalType": "struct Tuple6",267        "name": "",268        "type": "tuple"269      }270    ],271    "stateMutability": "view",272    "type": "function"273  },274  {275    "inputs": [],276    "name": "confirmCollectionSponsorship",277    "outputs": [],278    "stateMutability": "nonpayable",279    "type": "function"280  },281  {282    "inputs": [],283    "name": "contractAddress",284    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],285    "stateMutability": "view",286    "type": "function"287  },288  {289    "inputs": [],290    "name": "decimals",291    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],292    "stateMutability": "view",293    "type": "function"294  },295  {296    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],297    "name": "deleteCollectionProperty",298    "outputs": [],299    "stateMutability": "nonpayable",300    "type": "function"301  },302  {303    "inputs": [],304    "name": "hasCollectionPendingSponsor",305    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],306    "stateMutability": "view",307    "type": "function"308  },309  {310    "inputs": [311      { "internalType": "address", "name": "user", "type": "address" }312    ],313    "name": "isOwnerOrAdmin",314    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],315    "stateMutability": "view",316    "type": "function"317  },318  {319    "inputs": [320      {321        "components": [322          { "internalType": "address", "name": "field_0", "type": "address" },323          { "internalType": "uint256", "name": "field_1", "type": "uint256" }324        ],325        "internalType": "struct Tuple6",326        "name": "user",327        "type": "tuple"328      }329    ],330    "name": "isOwnerOrAdminCross",331    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],332    "stateMutability": "view",333    "type": "function"334  },335  {336    "inputs": [337      { "internalType": "address", "name": "to", "type": "address" },338      { "internalType": "uint256", "name": "amount", "type": "uint256" }339    ],340    "name": "mint",341    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],342    "stateMutability": "nonpayable",343    "type": "function"344  },345  {346    "inputs": [347      {348        "components": [349          { "internalType": "address", "name": "field_0", "type": "address" },350          { "internalType": "uint256", "name": "field_1", "type": "uint256" }351        ],352        "internalType": "struct Tuple6[]",353        "name": "amounts",354        "type": "tuple[]"355      }356    ],357    "name": "mintBulk",358    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],359    "stateMutability": "nonpayable",360    "type": "function"361  },362  {363    "inputs": [],364    "name": "name",365    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],366    "stateMutability": "view",367    "type": "function"368  },369  {370    "inputs": [371      { "internalType": "address", "name": "admin", "type": "address" }372    ],373    "name": "removeCollectionAdmin",374    "outputs": [],375    "stateMutability": "nonpayable",376    "type": "function"377  },378  {379    "inputs": [380      {381        "components": [382          { "internalType": "address", "name": "field_0", "type": "address" },383          { "internalType": "uint256", "name": "field_1", "type": "uint256" }384        ],385        "internalType": "struct Tuple6",386        "name": "admin",387        "type": "tuple"388      }389    ],390    "name": "removeCollectionAdminCross",391    "outputs": [],392    "stateMutability": "nonpayable",393    "type": "function"394  },395  {396    "inputs": [],397    "name": "removeCollectionSponsor",398    "outputs": [],399    "stateMutability": "nonpayable",400    "type": "function"401  },402  {403    "inputs": [404      { "internalType": "address", "name": "user", "type": "address" }405    ],406    "name": "removeFromCollectionAllowList",407    "outputs": [],408    "stateMutability": "nonpayable",409    "type": "function"410  },411  {412    "inputs": [413      {414        "components": [415          { "internalType": "address", "name": "field_0", "type": "address" },416          { "internalType": "uint256", "name": "field_1", "type": "uint256" }417        ],418        "internalType": "struct Tuple6",419        "name": "user",420        "type": "tuple"421      }422    ],423    "name": "removeFromCollectionAllowListCross",424    "outputs": [],425    "stateMutability": "nonpayable",426    "type": "function"427  },428  {429    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],430    "name": "setCollectionAccess",431    "outputs": [],432    "stateMutability": "nonpayable",433    "type": "function"434  },435  {436    "inputs": [437      { "internalType": "string", "name": "limit", "type": "string" },438      { "internalType": "uint32", "name": "value", "type": "uint32" }439    ],440    "name": "setCollectionLimit",441    "outputs": [],442    "stateMutability": "nonpayable",443    "type": "function"444  },445  {446    "inputs": [447      { "internalType": "string", "name": "limit", "type": "string" },448      { "internalType": "bool", "name": "value", "type": "bool" }449    ],450    "name": "setCollectionLimit",451    "outputs": [],452    "stateMutability": "nonpayable",453    "type": "function"454  },455  {456    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],457    "name": "setCollectionMintMode",458    "outputs": [],459    "stateMutability": "nonpayable",460    "type": "function"461  },462  {463    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],464    "name": "setCollectionNesting",465    "outputs": [],466    "stateMutability": "nonpayable",467    "type": "function"468  },469  {470    "inputs": [471      { "internalType": "bool", "name": "enable", "type": "bool" },472      {473        "internalType": "address[]",474        "name": "collections",475        "type": "address[]"476      }477    ],478    "name": "setCollectionNesting",479    "outputs": [],480    "stateMutability": "nonpayable",481    "type": "function"482  },483  {484    "inputs": [485      {486        "components": [487          { "internalType": "string", "name": "field_0", "type": "string" },488          { "internalType": "bytes", "name": "field_1", "type": "bytes" }489        ],490        "internalType": "struct Tuple10[]",491        "name": "properties",492        "type": "tuple[]"493      }494    ],495    "name": "setCollectionProperties",496    "outputs": [],497    "stateMutability": "nonpayable",498    "type": "function"499  },500  {501    "inputs": [502      { "internalType": "string", "name": "key", "type": "string" },503      { "internalType": "bytes", "name": "value", "type": "bytes" }504    ],505    "name": "setCollectionProperty",506    "outputs": [],507    "stateMutability": "nonpayable",508    "type": "function"509  },510  {511    "inputs": [512      { "internalType": "address", "name": "sponsor", "type": "address" }513    ],514    "name": "setCollectionSponsor",515    "outputs": [],516    "stateMutability": "nonpayable",517    "type": "function"518  },519  {520    "inputs": [521      {522        "components": [523          { "internalType": "address", "name": "field_0", "type": "address" },524          { "internalType": "uint256", "name": "field_1", "type": "uint256" }525        ],526        "internalType": "struct Tuple6",527        "name": "sponsor",528        "type": "tuple"529      }530    ],531    "name": "setCollectionSponsorCross",532    "outputs": [],533    "stateMutability": "nonpayable",534    "type": "function"535  },536  {537    "inputs": [538      {539        "components": [540          { "internalType": "address", "name": "field_0", "type": "address" },541          { "internalType": "uint256", "name": "field_1", "type": "uint256" }542        ],543        "internalType": "struct Tuple6",544        "name": "newOwner",545        "type": "tuple"546      }547    ],548    "name": "setOwnerCross",549    "outputs": [],550    "stateMutability": "nonpayable",551    "type": "function"552  },553  {554    "inputs": [555      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }556    ],557    "name": "supportsInterface",558    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],559    "stateMutability": "view",560    "type": "function"561  },562  {563    "inputs": [],564    "name": "symbol",565    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],566    "stateMutability": "view",567    "type": "function"568  },569  {570    "inputs": [],571    "name": "totalSupply",572    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],573    "stateMutability": "view",574    "type": "function"575  },576  {577    "inputs": [578      { "internalType": "address", "name": "to", "type": "address" },579      { "internalType": "uint256", "name": "amount", "type": "uint256" }580    ],581    "name": "transfer",582    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],583    "stateMutability": "nonpayable",584    "type": "function"585  },586  {587    "inputs": [588      { "internalType": "address", "name": "from", "type": "address" },589      { "internalType": "address", "name": "to", "type": "address" },590      { "internalType": "uint256", "name": "amount", "type": "uint256" }591    ],592    "name": "transferFrom",593    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],594    "stateMutability": "nonpayable",595    "type": "function"596  },597  {598    "inputs": [599      {600        "components": [601          { "internalType": "address", "name": "field_0", "type": "address" },602          { "internalType": "uint256", "name": "field_1", "type": "uint256" }603        ],604        "internalType": "struct Tuple6",605        "name": "from",606        "type": "tuple"607      },608      {609        "components": [610          { "internalType": "address", "name": "field_0", "type": "address" },611          { "internalType": "uint256", "name": "field_1", "type": "uint256" }612        ],613        "internalType": "struct Tuple6",614        "name": "to",615        "type": "tuple"616      },617      { "internalType": "uint256", "name": "amount", "type": "uint256" }618    ],619    "name": "transferFromCross",620    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],621    "stateMutability": "nonpayable",622    "type": "function"623  },624  {625    "inputs": [],626    "name": "uniqueCollectionType",627    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],628    "stateMutability": "view",629    "type": "function"630  }631]
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -316,6 +316,15 @@
     "type": "function"
   },
   {
+    "inputs": [
+      { "internalType": "string[]", "name": "keys", "type": "string[]" }
+    ],
+    "name": "deleteCollectionProperties",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
     "name": "deleteCollectionProperty",
     "outputs": [],
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -298,6 +298,15 @@
     "type": "function"
   },
   {
+    "inputs": [
+      { "internalType": "string[]", "name": "keys", "type": "string[]" }
+    ],
+    "name": "deleteCollectionProperties",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
     "name": "deleteCollectionProperty",
     "outputs": [],
modifiedtests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -19,3 +19,6 @@
   readonly field_0: string,
   readonly field_1: string | Uint8Array,
 }
+
+export type EthProperty = string[];
+
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -18,7 +18,7 @@
 
 import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
 
-import {ContractImports, CompiledContract, TEthCrossAccount, NormalizedEvent} from './types';
+import {ContractImports, CompiledContract, TEthCrossAccount, NormalizedEvent, EthProperty} from './types';
 
 // Native contracts ABI
 import collectionHelpersAbi from '../../collectionHelpersAbi.json';
@@ -28,6 +28,7 @@
 import refungibleTokenAbi from '../../reFungibleTokenAbi.json';
 import contractHelpersAbi from './../contractHelpersAbi.json';
 import {ICrossAccountId, TEthereumAccount} from '../../../util/playgrounds/types';
+import {TCollectionMode} from '../../../util/playgrounds/types';
 
 class EthGroupBase {
   helper: EthUniqueHelper;
@@ -107,7 +108,7 @@
     return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
   }
 
-  collection(address: string, mode: 'nft' | 'rft' | 'ft', caller?: string): Contract {
+  collection(address: string, mode: TCollectionMode, caller?: string): Contract {
     const abi = {
       'nft': nonFungibleAbi,
       'rft': refungibleAbi,
@@ -336,8 +337,16 @@
   normalizeAddress(address: string): string {
     return '0x' + address.substring(address.length - 40);
   }
-}
+}  
 
+export class EthPropertyGroup extends EthGroupBase {
+  property(key: string, value: string): EthProperty {
+    return [
+      key, 
+      '0x'+Buffer.from(value).toString('hex'),
+    ];
+  }
+}
 export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper;
 
 export class EthCrossAccountGroup extends EthGroupBase {
@@ -369,6 +378,7 @@
   ethNativeContract: NativeContractGroup;
   ethContract: ContractGroup;
   ethCrossAccount: EthCrossAccountGroup;
+  ethProperty: EthPropertyGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? EthUniqueHelper;
@@ -379,6 +389,7 @@
     this.ethCrossAccount = new EthCrossAccountGroup(this);
     this.ethNativeContract = new NativeContractGroup(this);
     this.ethContract = new ContractGroup(this);
+    this.ethProperty = new EthPropertyGroup(this);
   }
 
   getWeb3(): Web3 {
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -224,3 +224,4 @@
 export type TRelayNetworks = 'rococo' | 'westend';
 export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks;
 export type TSigner = IKeyringPair; // | 'string'
+export type TCollectionMode = 'nft' | 'rft' | 'ft';