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
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -659,6 +659,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/tokenProperties.test.tsdiffbeforeafterboth
before · tests/src/eth/tokenProperties.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('EVM token properties', () => {21  let donor: IKeyringPair;22  let alice: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      donor = await privateKey({filename: __filename});27      [alice] = await helper.arrange.createAccounts([100n], donor);28    });29  });3031  itEth('Can be reconfigured', async({helper}) => {32    const caller = await helper.eth.createAccountWithBalance(donor);33    for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {34      const collection = await helper.nft.mintCollection(alice);35      await collection.addAdmin(alice, {Ethereum: caller});36      37      const address = helper.ethAddress.fromCollectionId(collection.collectionId);38      const contract = helper.ethNativeContract.collection(address, 'nft', caller);39  40      await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});41  42      expect(await collection.getPropertyPermissions()).to.be.deep.equal([{43        key: 'testKey',44        permission: {mutable, collectionAdmin, tokenOwner},45      }]);46    }47  });4849  itEth('Can be set', async({helper}) => {50    const caller = await helper.eth.createAccountWithBalance(donor);51    const collection = await helper.nft.mintCollection(alice, {52      tokenPropertyPermissions: [{53        key: 'testKey',54        permission: {55          collectionAdmin: true,56        },57      }],58    });59    const token = await collection.mintToken(alice);6061    await collection.addAdmin(alice, {Ethereum: caller});6263    const address = helper.ethAddress.fromCollectionId(collection.collectionId);64    const contract = helper.ethNativeContract.collection(address, 'nft', caller);6566    await contract.methods.setProperty(token.tokenId, 'testKey', Buffer.from('testValue')).send({from: caller});6768    const [{value}] = await token.getProperties(['testKey']);69    expect(value).to.equal('testValue');70  });7172  itEth('Can be deleted', async({helper}) => {73    const caller = await helper.eth.createAccountWithBalance(donor);74    const collection = await helper.nft.mintCollection(alice, {75      tokenPropertyPermissions: [{76        key: 'testKey',77        permission: {78          mutable: true,79          collectionAdmin: true,80        },81      }],82    });83    84    const token = await collection.mintToken(alice);85    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);8687    await collection.addAdmin(alice, {Ethereum: caller});8889    const address = helper.ethAddress.fromCollectionId(collection.collectionId);90    const contract = helper.ethNativeContract.collection(address, 'nft', caller);9192    await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});9394    const result = await token.getProperties(['testKey']);95    expect(result.length).to.equal(0);96  });9798  itEth('Can be read', async({helper}) => {99    const caller = helper.eth.createAccount();100    const collection = await helper.nft.mintCollection(alice, {101      tokenPropertyPermissions: [{102        key: 'testKey',103        permission: {104          collectionAdmin: true,105        },106      }],107    });108  109    const token = await collection.mintToken(alice);110    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);111112    const address = helper.ethAddress.fromCollectionId(collection.collectionId);113    const contract = helper.ethNativeContract.collection(address, 'nft', caller);114115    const value = await contract.methods.property(token.tokenId, 'testKey').call();116    expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));117  });118});119120121type ElementOf<A> = A extends readonly (infer T)[] ? T : never;122function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {123  if(args.length === 0) {124    yield internalRest as any;125    return;126  }127  for(const value of args[0]) {128    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;129  }130}
after · tests/src/eth/tokenProperties.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {ITokenPropertyPermission} from '../util/playgrounds/types';2021describe('EVM token properties', () => {22  let donor: IKeyringPair;23  let alice: IKeyringPair;2425  before(async function() {26    await usingEthPlaygrounds(async (helper, privateKey) => {27      donor = await privateKey({filename: __filename});28      [alice] = await helper.arrange.createAccounts([100n], donor);29    });30  });3132  itEth('Can be reconfigured', async({helper}) => {33    const caller = await helper.eth.createAccountWithBalance(donor);34    for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {35      const collection = await helper.nft.mintCollection(alice);36      await collection.addAdmin(alice, {Ethereum: caller});37      38      const address = helper.ethAddress.fromCollectionId(collection.collectionId);39      const contract = helper.ethNativeContract.collection(address, 'nft', caller);40  41      await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});42  43      expect(await collection.getPropertyPermissions()).to.be.deep.equal([{44        key: 'testKey',45        permission: {mutable, collectionAdmin, tokenOwner},46      }]);47    }48  });4950  itEth('Can be set', async({helper}) => {51    const caller = await helper.eth.createAccountWithBalance(donor);52    const collection = await helper.nft.mintCollection(alice, {53      tokenPropertyPermissions: [{54        key: 'testKey',55        permission: {56          collectionAdmin: true,57        },58      }],59    });60    const token = await collection.mintToken(alice);6162    await collection.addAdmin(alice, {Ethereum: caller});6364    const address = helper.ethAddress.fromCollectionId(collection.collectionId);65    const contract = helper.ethNativeContract.collection(address, 'nft', caller);6667    await contract.methods.setProperty(token.tokenId, 'testKey', Buffer.from('testValue')).send({from: caller});6869    const [{value}] = await token.getProperties(['testKey']);70    expect(value).to.equal('testValue');71  });72  73  itEth('Can be multiple set for NFT ', async({helper}) => {74    const caller = await helper.eth.createAccountWithBalance(donor);75    76    const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });77    const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,78      collectionAdmin: true,79      mutable: true}}; });80    81    const collection = await helper.nft.mintCollection(alice, {82      tokenPrefix: 'ethp',83      tokenPropertyPermissions: permissions,84    });85    86    const token = await collection.mintToken(alice);87    88    const valuesBefore = await token.getProperties(properties.map(p => p.field_0));89    expect(valuesBefore).to.be.deep.equal([]);90    91    await collection.addAdmin(alice, {Ethereum: caller});9293    const address = helper.ethAddress.fromCollectionId(collection.collectionId);94    const contract = helper.ethNativeContract.collection(address, 'nft', caller);9596    await contract.methods.setProperties(token.tokenId, properties).send({from: caller});9798    const values = await token.getProperties(properties.map(p => p.field_0));99    expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));100  });101  102  itEth('Can be multiple set for RFT ', async({helper}) => {103    const caller = await helper.eth.createAccountWithBalance(donor);104    105    const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });106    const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,107      collectionAdmin: true,108      mutable: true}}; });109    110    const collection = await helper.rft.mintCollection(alice, {111      tokenPrefix: 'ethp',112      tokenPropertyPermissions: permissions,113    });114        115    const token = await collection.mintToken(alice);116    117    const valuesBefore = await token.getProperties(properties.map(p => p.field_0));118    expect(valuesBefore).to.be.deep.equal([]);119    120    await collection.addAdmin(alice, {Ethereum: caller});121122    const address = helper.ethAddress.fromCollectionId(collection.collectionId);123    const contract = helper.ethNativeContract.collection(address, 'rft', caller);124125    await contract.methods.setProperties(token.tokenId, properties).send({from: caller});126127    const values = await token.getProperties(properties.map(p => p.field_0));128    expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));129  });130131  itEth('Can be deleted', async({helper}) => {132    const caller = await helper.eth.createAccountWithBalance(donor);133    const collection = await helper.nft.mintCollection(alice, {134      tokenPropertyPermissions: [{135        key: 'testKey',136        permission: {137          mutable: true,138          collectionAdmin: true,139        },140      }],141    });142    143    const token = await collection.mintToken(alice);144    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);145146    await collection.addAdmin(alice, {Ethereum: caller});147148    const address = helper.ethAddress.fromCollectionId(collection.collectionId);149    const contract = helper.ethNativeContract.collection(address, 'nft', caller);150151    await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});152153    const result = await token.getProperties(['testKey']);154    expect(result.length).to.equal(0);155  });156157  itEth('Can be read', async({helper}) => {158    const caller = helper.eth.createAccount();159    const collection = await helper.nft.mintCollection(alice, {160      tokenPropertyPermissions: [{161        key: 'testKey',162        permission: {163          collectionAdmin: true,164        },165      }],166    });167  168    const token = await collection.mintToken(alice);169    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);170171    const address = helper.ethAddress.fromCollectionId(collection.collectionId);172    const contract = helper.ethNativeContract.collection(address, 'nft', caller);173174    const value = await contract.methods.property(token.tokenId, 'testKey').call();175    expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));176  });177});178179180type ElementOf<A> = A extends readonly (infer T)[] ? T : never;181function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {182  if(args.length === 0) {183    yield internalRest as any;184    return;185  }186  for(const value of args[0]) {187    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;188  }189}