git.delta.rocks / unique-network / refs/commits / 57a093883bee

difftreelog

Added `set_properties` method for `TokenProperties` interface.

PraetorP2022-10-24parent: #7125f4f.patch.diff
in: master

19 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6358,7 +6358,7 @@
 
 [[package]]
 name = "pallet-nonfungible"
-version = "0.1.5"
+version = "0.1.6"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6480,7 +6480,7 @@
 
 [[package]]
 name = "pallet-refungible"
-version = "0.2.4"
+version = "0.2.5"
 dependencies = [
  "derivative",
  "ethereum",
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
@@ -385,7 +385,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedpallets/nonfungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -2,12 +2,20 @@
 
 All notable changes to this project will be documented in this file.
 
+<!-- bureaucrate goes here -->
+
+## [v0.1.6] - 2022-20-10
+
+### Change
+
+- Added `set_properties` method for `TokenProperties` interface.
+
 ## [v0.1.5] - 2022-08-24
 
 ### Change
- - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
 
-<!-- bureaucrate goes here -->
+- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
+
 ## [v0.1.4] 2022-08-16
 
 ### Other changes
@@ -28,7 +36,9 @@
 - build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
 
 ## [0.1.2] - 2022-07-25
+
 ### Changed
+
 - New `token_uri` retrieval logic:
 
       If the collection has a `url` property and it is not empty, it is returned.
@@ -39,8 +49,9 @@
       otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
 
 ## [0.1.1] - 2022-07-14
+
 ### Added
 
 - Implementation of RPC method `token_owners`.
-   For reasons of compatibility with this pallet, returns only one owner if token exists.
-   This was an internal request to improve the web interface and support fractionalization event.
+  For reasons of compatibility with this pallet, returns only one owner if token exists.
+  This was an internal request to improve the web interface and support fractionalization event.
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-nonfungible"
-version = "0.1.5"
+version = "0.1.6"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -114,6 +114,47 @@
 		.map_err(dispatch_to_evm::<T>)
 	}
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	fn set_properties(
+		&mut self,
+		caller: caller,
+		token_id: uint256,
+		properties: Vec<(string, bytes)>,
+	) -> Result<()> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+
+		let nesting_budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let properties = properties
+			.into_iter()
+			.map(|(key, value)| {
+				let key = <Vec<u8>>::from(key)
+					.try_into()
+					.map_err(|_| "key too large")?;
+
+				let value = value.0.try_into().map_err(|_| "value too large")?;
+
+				Ok(Property { key, value })
+			})
+			.collect::<Result<Vec<_>>>()?;
+
+		<Pallet<T>>::set_token_properties(
+			self,
+			&caller,
+			TokenId(token_id),
+			properties.into_iter(),
+			<Pallet<T>>::token_exists(&self, TokenId(token_id)),
+			&nesting_budget,
+		)
+		.map_err(dispatch_to_evm::<T>)
+	}
+
 	/// @notice Delete token property value.
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the token.
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
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x41369377
+/// @dev the ERC-165 identifier for this interface is 0x55dba919
 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.
@@ -61,6 +61,19 @@
 		dummy = 0;
 	}
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	/// @dev EVM selector for this function is: 0x14ed3a6e,
+	///  or in textual repr: setProperties(uint256,(string,bytes)[])
+	function setProperties(uint256 tokenId, Tuple19[] memory properties) public {
+		require(false, stub_error);
+		tokenId;
+		properties;
+		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.
@@ -458,7 +471,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -2,12 +2,20 @@
 
 All notable changes to this project will be documented in this file.
 
+## [v0.2.5] - 2022-20-10
+
+### Change
+
+- Added `set_properties` method for `TokenProperties` interface.
+
 ## [v0.2.4] - 2022-08-24
 
 ### Change
- - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
 
+- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
+
 <!-- bureaucrate goes here -->
+
 ## [v0.2.3] 2022-08-16
 
 ### Other changes
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-refungible"
-version = "0.2.4"
+version = "0.2.5"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -117,6 +117,47 @@
 		.map_err(dispatch_to_evm::<T>)
 	}
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	fn set_properties(
+		&mut self,
+		caller: caller,
+		token_id: uint256,
+		properties: Vec<(string, bytes)>,
+	) -> Result<()> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+
+		let nesting_budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let properties = properties
+			.into_iter()
+			.map(|(key, value)| {
+				let key = <Vec<u8>>::from(key)
+					.try_into()
+					.map_err(|_| "key too large")?;
+
+				let value = value.0.try_into().map_err(|_| "value too large")?;
+
+				Ok(Property { key, value })
+			})
+			.collect::<Result<Vec<_>>>()?;
+
+		<Pallet<T>>::set_token_properties(
+			self,
+			&caller,
+			TokenId(token_id),
+			properties.into_iter(),
+			<Pallet<T>>::token_exists(&self, TokenId(token_id)),
+			&nesting_budget,
+		)
+		.map_err(dispatch_to_evm::<T>)
+	}
+
 	/// @notice Delete token property value.
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the 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
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x41369377
+/// @dev the ERC-165 identifier for this interface is 0x55dba919
 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.
@@ -61,6 +61,19 @@
 		dummy = 0;
 	}
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	/// @dev EVM selector for this function is: 0x14ed3a6e,
+	///  or in textual repr: setProperties(uint256,(string,bytes)[])
+	function setProperties(uint256 tokenId, Tuple19[] memory properties) public {
+		require(false, stub_error);
+		tokenId;
+		properties;
+		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.
@@ -458,7 +471,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -249,7 +249,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x41369377
+/// @dev the ERC-165 identifier for this interface is 0x55dba919
 interface TokenProperties is Dummy, ERC165 {
 	/// @notice Set permissions for token property.
 	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -43,6 +43,14 @@
 		bytes memory value
 	) external;
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	/// @dev EVM selector for this function is: 0x14ed3a6e,
+	///  or in textual repr: setProperties(uint256,(string,bytes)[])
+	function setProperties(uint256 tokenId, Tuple19[] memory properties) external;
+
 	/// @notice Delete token property value.
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the token.
@@ -298,7 +306,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows to set and delete token properties and change token property permissions.
-/// @dev the ERC-165 identifier for this interface is 0x41369377
+/// @dev the ERC-165 identifier for this interface is 0x55dba919
 interface TokenProperties is Dummy, ERC165 {
 	/// @notice Set permissions for token property.
 	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
@@ -43,6 +43,14 @@
 		bytes memory value
 	) external;
 
+	/// @notice Set token properties value.
+	/// @dev Throws error if `msg.sender` has no permission to edit the property.
+	/// @param tokenId ID of the token.
+	/// @param properties settable properties
+	/// @dev EVM selector for this function is: 0x14ed3a6e,
+	///  or in textual repr: setProperties(uint256,(string,bytes)[])
+	function setProperties(uint256 tokenId, Tuple19[] memory properties) external;
+
 	/// @notice Delete token property value.
 	/// @dev Throws error if `msg.sender` has no permission to edit the property.
 	/// @param tokenId ID of the token.
@@ -298,7 +306,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -677,6 +677,24 @@
   {
     "inputs": [
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      {
+        "components": [
+          { "internalType": "string", "name": "field_0", "type": "string" },
+          { "internalType": "bytes", "name": "field_1", "type": "bytes" }
+        ],
+        "internalType": "struct Tuple19[]",
+        "name": "properties",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "setProperties",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
       { "internalType": "string", "name": "key", "type": "string" },
       { "internalType": "bytes", "name": "value", "type": "bytes" }
     ],
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
before · tests/src/eth/reFungibleAbi.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": "approved",15        "type": "address"16      },17      {18        "indexed": true,19        "internalType": "uint256",20        "name": "tokenId",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": "owner",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "operator",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "bool",45        "name": "approved",46        "type": "bool"47      }48    ],49    "name": "ApprovalForAll",50    "type": "event"51  },52  {53    "anonymous": false,54    "inputs": [],55    "name": "MintingFinished",56    "type": "event"57  },58  {59    "anonymous": false,60    "inputs": [61      {62        "indexed": true,63        "internalType": "address",64        "name": "from",65        "type": "address"66      },67      {68        "indexed": true,69        "internalType": "address",70        "name": "to",71        "type": "address"72      },73      {74        "indexed": true,75        "internalType": "uint256",76        "name": "tokenId",77        "type": "uint256"78      }79    ],80    "name": "Transfer",81    "type": "event"82  },83  {84    "inputs": [85      { "internalType": "address", "name": "newAdmin", "type": "address" }86    ],87    "name": "addCollectionAdmin",88    "outputs": [],89    "stateMutability": "nonpayable",90    "type": "function"91  },92  {93    "inputs": [94      {95        "components": [96          { "internalType": "address", "name": "field_0", "type": "address" },97          { "internalType": "uint256", "name": "field_1", "type": "uint256" }98        ],99        "internalType": "struct Tuple6",100        "name": "newAdmin",101        "type": "tuple"102      }103    ],104    "name": "addCollectionAdminCross",105    "outputs": [],106    "stateMutability": "nonpayable",107    "type": "function"108  },109  {110    "inputs": [111      { "internalType": "address", "name": "user", "type": "address" }112    ],113    "name": "addToCollectionAllowList",114    "outputs": [],115    "stateMutability": "nonpayable",116    "type": "function"117  },118  {119    "inputs": [120      {121        "components": [122          { "internalType": "address", "name": "field_0", "type": "address" },123          { "internalType": "uint256", "name": "field_1", "type": "uint256" }124        ],125        "internalType": "struct Tuple6",126        "name": "user",127        "type": "tuple"128      }129    ],130    "name": "addToCollectionAllowListCross",131    "outputs": [],132    "stateMutability": "nonpayable",133    "type": "function"134  },135  {136    "inputs": [137      { "internalType": "address", "name": "user", "type": "address" }138    ],139    "name": "allowed",140    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],141    "stateMutability": "view",142    "type": "function"143  },144  {145    "inputs": [146      { "internalType": "address", "name": "approved", "type": "address" },147      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }148    ],149    "name": "approve",150    "outputs": [],151    "stateMutability": "nonpayable",152    "type": "function"153  },154  {155    "inputs": [156      { "internalType": "address", "name": "owner", "type": "address" }157    ],158    "name": "balanceOf",159    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],160    "stateMutability": "view",161    "type": "function"162  },163  {164    "inputs": [165      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }166    ],167    "name": "burn",168    "outputs": [],169    "stateMutability": "nonpayable",170    "type": "function"171  },172  {173    "inputs": [174      { "internalType": "address", "name": "from", "type": "address" },175      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }176    ],177    "name": "burnFrom",178    "outputs": [],179    "stateMutability": "nonpayable",180    "type": "function"181  },182  {183    "inputs": [184      {185        "components": [186          { "internalType": "address", "name": "field_0", "type": "address" },187          { "internalType": "uint256", "name": "field_1", "type": "uint256" }188        ],189        "internalType": "struct Tuple6",190        "name": "from",191        "type": "tuple"192      },193      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }194    ],195    "name": "burnFromCross",196    "outputs": [],197    "stateMutability": "nonpayable",198    "type": "function"199  },200  {201    "inputs": [202      { "internalType": "address", "name": "newOwner", "type": "address" }203    ],204    "name": "changeCollectionOwner",205    "outputs": [],206    "stateMutability": "nonpayable",207    "type": "function"208  },209  {210    "inputs": [],211    "name": "collectionAdmins",212    "outputs": [213      {214        "components": [215          { "internalType": "address", "name": "field_0", "type": "address" },216          { "internalType": "uint256", "name": "field_1", "type": "uint256" }217        ],218        "internalType": "struct Tuple6[]",219        "name": "",220        "type": "tuple[]"221      }222    ],223    "stateMutability": "view",224    "type": "function"225  },226  {227    "inputs": [],228    "name": "collectionOwner",229    "outputs": [230      {231        "components": [232          { "internalType": "address", "name": "field_0", "type": "address" },233          { "internalType": "uint256", "name": "field_1", "type": "uint256" }234        ],235        "internalType": "struct Tuple6",236        "name": "",237        "type": "tuple"238      }239    ],240    "stateMutability": "view",241    "type": "function"242  },243  {244    "inputs": [245      { "internalType": "string[]", "name": "keys", "type": "string[]" }246    ],247    "name": "collectionProperties",248    "outputs": [249      {250        "components": [251          { "internalType": "string", "name": "field_0", "type": "string" },252          { "internalType": "bytes", "name": "field_1", "type": "bytes" }253        ],254        "internalType": "struct Tuple19[]",255        "name": "",256        "type": "tuple[]"257      }258    ],259    "stateMutability": "view",260    "type": "function"261  },262  {263    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],264    "name": "collectionProperty",265    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],266    "stateMutability": "view",267    "type": "function"268  },269  {270    "inputs": [],271    "name": "collectionSponsor",272    "outputs": [273      {274        "components": [275          { "internalType": "address", "name": "field_0", "type": "address" },276          { "internalType": "uint256", "name": "field_1", "type": "uint256" }277        ],278        "internalType": "struct Tuple6",279        "name": "",280        "type": "tuple"281      }282    ],283    "stateMutability": "view",284    "type": "function"285  },286  {287    "inputs": [],288    "name": "confirmCollectionSponsorship",289    "outputs": [],290    "stateMutability": "nonpayable",291    "type": "function"292  },293  {294    "inputs": [],295    "name": "contractAddress",296    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],297    "stateMutability": "view",298    "type": "function"299  },300  {301    "inputs": [302      { "internalType": "string[]", "name": "keys", "type": "string[]" }303    ],304    "name": "deleteCollectionProperties",305    "outputs": [],306    "stateMutability": "nonpayable",307    "type": "function"308  },309  {310    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],311    "name": "deleteCollectionProperty",312    "outputs": [],313    "stateMutability": "nonpayable",314    "type": "function"315  },316  {317    "inputs": [318      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },319      { "internalType": "string", "name": "key", "type": "string" }320    ],321    "name": "deleteProperty",322    "outputs": [],323    "stateMutability": "nonpayable",324    "type": "function"325  },326  {327    "inputs": [],328    "name": "finishMinting",329    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],330    "stateMutability": "nonpayable",331    "type": "function"332  },333  {334    "inputs": [335      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }336    ],337    "name": "getApproved",338    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],339    "stateMutability": "view",340    "type": "function"341  },342  {343    "inputs": [],344    "name": "hasCollectionPendingSponsor",345    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],346    "stateMutability": "view",347    "type": "function"348  },349  {350    "inputs": [351      { "internalType": "address", "name": "owner", "type": "address" },352      { "internalType": "address", "name": "operator", "type": "address" }353    ],354    "name": "isApprovedForAll",355    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],356    "stateMutability": "view",357    "type": "function"358  },359  {360    "inputs": [361      { "internalType": "address", "name": "user", "type": "address" }362    ],363    "name": "isOwnerOrAdmin",364    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],365    "stateMutability": "view",366    "type": "function"367  },368  {369    "inputs": [370      {371        "components": [372          { "internalType": "address", "name": "field_0", "type": "address" },373          { "internalType": "uint256", "name": "field_1", "type": "uint256" }374        ],375        "internalType": "struct Tuple6",376        "name": "user",377        "type": "tuple"378      }379    ],380    "name": "isOwnerOrAdminCross",381    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],382    "stateMutability": "view",383    "type": "function"384  },385  {386    "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],387    "name": "mint",388    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],389    "stateMutability": "nonpayable",390    "type": "function"391  },392  {393    "inputs": [394      { "internalType": "address", "name": "to", "type": "address" },395      { "internalType": "string", "name": "tokenUri", "type": "string" }396    ],397    "name": "mintWithTokenURI",398    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],399    "stateMutability": "nonpayable",400    "type": "function"401  },402  {403    "inputs": [],404    "name": "mintingFinished",405    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],406    "stateMutability": "view",407    "type": "function"408  },409  {410    "inputs": [],411    "name": "name",412    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],413    "stateMutability": "view",414    "type": "function"415  },416  {417    "inputs": [],418    "name": "nextTokenId",419    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],420    "stateMutability": "view",421    "type": "function"422  },423  {424    "inputs": [425      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }426    ],427    "name": "ownerOf",428    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],429    "stateMutability": "view",430    "type": "function"431  },432  {433    "inputs": [434      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },435      { "internalType": "string", "name": "key", "type": "string" }436    ],437    "name": "property",438    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],439    "stateMutability": "view",440    "type": "function"441  },442  {443    "inputs": [444      { "internalType": "address", "name": "admin", "type": "address" }445    ],446    "name": "removeCollectionAdmin",447    "outputs": [],448    "stateMutability": "nonpayable",449    "type": "function"450  },451  {452    "inputs": [453      {454        "components": [455          { "internalType": "address", "name": "field_0", "type": "address" },456          { "internalType": "uint256", "name": "field_1", "type": "uint256" }457        ],458        "internalType": "struct Tuple6",459        "name": "admin",460        "type": "tuple"461      }462    ],463    "name": "removeCollectionAdminCross",464    "outputs": [],465    "stateMutability": "nonpayable",466    "type": "function"467  },468  {469    "inputs": [],470    "name": "removeCollectionSponsor",471    "outputs": [],472    "stateMutability": "nonpayable",473    "type": "function"474  },475  {476    "inputs": [477      { "internalType": "address", "name": "user", "type": "address" }478    ],479    "name": "removeFromCollectionAllowList",480    "outputs": [],481    "stateMutability": "nonpayable",482    "type": "function"483  },484  {485    "inputs": [486      {487        "components": [488          { "internalType": "address", "name": "field_0", "type": "address" },489          { "internalType": "uint256", "name": "field_1", "type": "uint256" }490        ],491        "internalType": "struct Tuple6",492        "name": "user",493        "type": "tuple"494      }495    ],496    "name": "removeFromCollectionAllowListCross",497    "outputs": [],498    "stateMutability": "nonpayable",499    "type": "function"500  },501  {502    "inputs": [503      { "internalType": "address", "name": "from", "type": "address" },504      { "internalType": "address", "name": "to", "type": "address" },505      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }506    ],507    "name": "safeTransferFrom",508    "outputs": [],509    "stateMutability": "nonpayable",510    "type": "function"511  },512  {513    "inputs": [514      { "internalType": "address", "name": "from", "type": "address" },515      { "internalType": "address", "name": "to", "type": "address" },516      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },517      { "internalType": "bytes", "name": "data", "type": "bytes" }518    ],519    "name": "safeTransferFromWithData",520    "outputs": [],521    "stateMutability": "nonpayable",522    "type": "function"523  },524  {525    "inputs": [526      { "internalType": "address", "name": "operator", "type": "address" },527      { "internalType": "bool", "name": "approved", "type": "bool" }528    ],529    "name": "setApprovalForAll",530    "outputs": [],531    "stateMutability": "nonpayable",532    "type": "function"533  },534  {535    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],536    "name": "setCollectionAccess",537    "outputs": [],538    "stateMutability": "nonpayable",539    "type": "function"540  },541  {542    "inputs": [543      { "internalType": "string", "name": "limit", "type": "string" },544      { "internalType": "uint32", "name": "value", "type": "uint32" }545    ],546    "name": "setCollectionLimit",547    "outputs": [],548    "stateMutability": "nonpayable",549    "type": "function"550  },551  {552    "inputs": [553      { "internalType": "string", "name": "limit", "type": "string" },554      { "internalType": "bool", "name": "value", "type": "bool" }555    ],556    "name": "setCollectionLimit",557    "outputs": [],558    "stateMutability": "nonpayable",559    "type": "function"560  },561  {562    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],563    "name": "setCollectionMintMode",564    "outputs": [],565    "stateMutability": "nonpayable",566    "type": "function"567  },568  {569    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],570    "name": "setCollectionNesting",571    "outputs": [],572    "stateMutability": "nonpayable",573    "type": "function"574  },575  {576    "inputs": [577      { "internalType": "bool", "name": "enable", "type": "bool" },578      {579        "internalType": "address[]",580        "name": "collections",581        "type": "address[]"582      }583    ],584    "name": "setCollectionNesting",585    "outputs": [],586    "stateMutability": "nonpayable",587    "type": "function"588  },589  {590    "inputs": [591      {592        "components": [593          { "internalType": "string", "name": "field_0", "type": "string" },594          { "internalType": "bytes", "name": "field_1", "type": "bytes" }595        ],596        "internalType": "struct Tuple19[]",597        "name": "properties",598        "type": "tuple[]"599      }600    ],601    "name": "setCollectionProperties",602    "outputs": [],603    "stateMutability": "nonpayable",604    "type": "function"605  },606  {607    "inputs": [608      { "internalType": "string", "name": "key", "type": "string" },609      { "internalType": "bytes", "name": "value", "type": "bytes" }610    ],611    "name": "setCollectionProperty",612    "outputs": [],613    "stateMutability": "nonpayable",614    "type": "function"615  },616  {617    "inputs": [618      { "internalType": "address", "name": "sponsor", "type": "address" }619    ],620    "name": "setCollectionSponsor",621    "outputs": [],622    "stateMutability": "nonpayable",623    "type": "function"624  },625  {626    "inputs": [627      {628        "components": [629          { "internalType": "address", "name": "field_0", "type": "address" },630          { "internalType": "uint256", "name": "field_1", "type": "uint256" }631        ],632        "internalType": "struct Tuple6",633        "name": "sponsor",634        "type": "tuple"635      }636    ],637    "name": "setCollectionSponsorCross",638    "outputs": [],639    "stateMutability": "nonpayable",640    "type": "function"641  },642  {643    "inputs": [644      {645        "components": [646          { "internalType": "address", "name": "field_0", "type": "address" },647          { "internalType": "uint256", "name": "field_1", "type": "uint256" }648        ],649        "internalType": "struct Tuple6",650        "name": "newOwner",651        "type": "tuple"652      }653    ],654    "name": "setOwnerCross",655    "outputs": [],656    "stateMutability": "nonpayable",657    "type": "function"658  },659  {660    "inputs": [661      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },662      { "internalType": "string", "name": "key", "type": "string" },663      { "internalType": "bytes", "name": "value", "type": "bytes" }664    ],665    "name": "setProperty",666    "outputs": [],667    "stateMutability": "nonpayable",668    "type": "function"669  },670  {671    "inputs": [672      { "internalType": "string", "name": "key", "type": "string" },673      { "internalType": "bool", "name": "isMutable", "type": "bool" },674      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },675      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }676    ],677    "name": "setTokenPropertyPermission",678    "outputs": [],679    "stateMutability": "nonpayable",680    "type": "function"681  },682  {683    "inputs": [684      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }685    ],686    "name": "supportsInterface",687    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],688    "stateMutability": "view",689    "type": "function"690  },691  {692    "inputs": [],693    "name": "symbol",694    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],695    "stateMutability": "view",696    "type": "function"697  },698  {699    "inputs": [700      { "internalType": "uint256", "name": "index", "type": "uint256" }701    ],702    "name": "tokenByIndex",703    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],704    "stateMutability": "view",705    "type": "function"706  },707  {708    "inputs": [709      { "internalType": "uint256", "name": "token", "type": "uint256" }710    ],711    "name": "tokenContractAddress",712    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],713    "stateMutability": "view",714    "type": "function"715  },716  {717    "inputs": [718      { "internalType": "address", "name": "owner", "type": "address" },719      { "internalType": "uint256", "name": "index", "type": "uint256" }720    ],721    "name": "tokenOfOwnerByIndex",722    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],723    "stateMutability": "view",724    "type": "function"725  },726  {727    "inputs": [728      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }729    ],730    "name": "tokenURI",731    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],732    "stateMutability": "view",733    "type": "function"734  },735  {736    "inputs": [],737    "name": "totalSupply",738    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],739    "stateMutability": "view",740    "type": "function"741  },742  {743    "inputs": [744      { "internalType": "address", "name": "to", "type": "address" },745      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }746    ],747    "name": "transfer",748    "outputs": [],749    "stateMutability": "nonpayable",750    "type": "function"751  },752  {753    "inputs": [754      { "internalType": "address", "name": "from", "type": "address" },755      { "internalType": "address", "name": "to", "type": "address" },756      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }757    ],758    "name": "transferFrom",759    "outputs": [],760    "stateMutability": "nonpayable",761    "type": "function"762  },763  {764    "inputs": [765      {766        "components": [767          { "internalType": "address", "name": "field_0", "type": "address" },768          { "internalType": "uint256", "name": "field_1", "type": "uint256" }769        ],770        "internalType": "struct Tuple6",771        "name": "from",772        "type": "tuple"773      },774      {775        "components": [776          { "internalType": "address", "name": "field_0", "type": "address" },777          { "internalType": "uint256", "name": "field_1", "type": "uint256" }778        ],779        "internalType": "struct Tuple6",780        "name": "to",781        "type": "tuple"782      },783      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }784    ],785    "name": "transferFromCross",786    "outputs": [],787    "stateMutability": "nonpayable",788    "type": "function"789  },790  {791    "inputs": [],792    "name": "uniqueCollectionType",793    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],794    "stateMutability": "view",795    "type": "function"796  }797]
modifiedtests/src/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -16,6 +16,7 @@
 
 import {itEth, usingEthPlaygrounds, expect} from './util';
 import {IKeyringPair} from '@polkadot/types/types';
+import {ITokenPropertyPermission} from '../util/playgrounds/types';
 
 describe('EVM token properties', () => {
   let donor: IKeyringPair;
@@ -68,6 +69,64 @@
     const [{value}] = await token.getProperties(['testKey']);
     expect(value).to.equal('testValue');
   });
+  
+  itEth('Can be multiple set for NFT ', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    
+    const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });
+    const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,
+      collectionAdmin: true,
+      mutable: true}}; });
+    
+    const collection = await helper.nft.mintCollection(alice, {
+      tokenPrefix: 'ethp',
+      tokenPropertyPermissions: permissions,
+    });
+    
+    const token = await collection.mintToken(alice);
+    
+    const valuesBefore = await token.getProperties(properties.map(p => p.field_0));
+    expect(valuesBefore).to.be.deep.equal([]);
+    
+    await collection.addAdmin(alice, {Ethereum: caller});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft', caller);
+
+    await contract.methods.setProperties(token.tokenId, properties).send({from: caller});
+
+    const values = await token.getProperties(properties.map(p => p.field_0));
+    expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));
+  });
+  
+  itEth('Can be multiple set for RFT ', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    
+    const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });
+    const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,
+      collectionAdmin: true,
+      mutable: true}}; });
+    
+    const collection = await helper.rft.mintCollection(alice, {
+      tokenPrefix: 'ethp',
+      tokenPropertyPermissions: permissions,
+    });
+        
+    const token = await collection.mintToken(alice);
+    
+    const valuesBefore = await token.getProperties(properties.map(p => p.field_0));
+    expect(valuesBefore).to.be.deep.equal([]);
+    
+    await collection.addAdmin(alice, {Ethereum: caller});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft', caller);
+
+    await contract.methods.setProperties(token.tokenId, properties).send({from: caller});
+
+    const values = await token.getProperties(properties.map(p => p.field_0));
+    expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));
+  });
 
   itEth('Can be deleted', async({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);