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

difftreelog

fix rft test

Trubnikov Sergey2022-12-15parent: #a37fff1.patch.diff
in: master

6 files changed

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,30 +18,45 @@
 }
 
 /// @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 0x91a97a68
+/// @dev the ERC-165 identifier for this interface is 0xde0695c2
 contract TokenProperties is Dummy, ERC165 {
+	// /// @notice Set permissions for token property.
+	// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
+	// /// @param key Property key.
+	// /// @param isMutable Permission to mutate property.
+	// /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.
+	// /// @param tokenOwner Permission to mutate property by token owner if property is mutable.
+	// /// @dev EVM selector for this function is: 0x222d97fa,
+	// ///  or in textual repr: setTokenPropertyPermission(string,bool,bool,bool)
+	// function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) public {
+	// 	require(false, stub_error);
+	// 	key;
+	// 	isMutable;
+	// 	collectionAdmin;
+	// 	tokenOwner;
+	// 	dummy = 0;
+	// }
+
 	/// @notice Set permissions for token property.
 	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
-	/// @param key Property key.
-	/// @param isMutable Permission to mutate property.
-	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.
-	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.
-	/// @dev EVM selector for this function is: 0x222d97fa,
-	///  or in textual repr: setTokenPropertyPermission(string,bool,bool,bool)
-	function setTokenPropertyPermission(
-		string memory key,
-		bool isMutable,
-		bool collectionAdmin,
-		bool tokenOwner
-	) public {
+	/// @param permissions Permissions for keys.
+	/// @dev EVM selector for this function is: 0xbd92983a,
+	///  or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[])
+	function setTokenPropertyPermissions(Tuple53[] memory permissions) public {
 		require(false, stub_error);
-		key;
-		isMutable;
-		collectionAdmin;
-		tokenOwner;
+		permissions;
 		dummy = 0;
 	}
 
+	/// @notice Get permissions for token properties.
+	/// @dev EVM selector for this function is: 0xf23d7790,
+	///  or in textual repr: tokenPropertyPermissions()
+	function tokenPropertyPermissions() public view returns (Tuple53[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple53[](0);
+	}
+
 	// /// @notice Set token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
 	// /// @param tokenId ID of the token.
@@ -118,6 +133,24 @@
 	bytes value;
 }
 
+enum EthTokenPermissions {
+	Mutable,
+	TokenOwner,
+	CollectionAdmin
+}
+
+/// @dev anonymous struct
+struct Tuple53 {
+	string field_0;
+	Tuple51[] field_1;
+}
+
+/// @dev anonymous struct
+struct Tuple51 {
+	EthTokenPermissions field_0;
+	bool field_1;
+}
+
 /// @title A contract that allows you to work with collections.
 /// @dev the ERC-165 identifier for this interface is 0xb5e1747f
 contract Collection is Dummy, ERC165 {
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -679,12 +679,29 @@
   },
   {
     "inputs": [
-      { "internalType": "string", "name": "key", "type": "string" },
-      { "internalType": "bool", "name": "isMutable", "type": "bool" },
-      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },
-      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }
+      {
+        "components": [
+          { "internalType": "string", "name": "field_0", "type": "string" },
+          {
+            "components": [
+              {
+                "internalType": "enum EthTokenPermissions",
+                "name": "field_0",
+                "type": "uint8"
+              },
+              { "internalType": "bool", "name": "field_1", "type": "bool" }
+            ],
+            "internalType": "struct Tuple51[]",
+            "name": "field_1",
+            "type": "tuple[]"
+          }
+        ],
+        "internalType": "struct Tuple53[]",
+        "name": "permissions",
+        "type": "tuple[]"
+      }
     ],
-    "name": "setTokenPropertyPermission",
+    "name": "setTokenPropertyPermissions",
     "outputs": [],
     "stateMutability": "nonpayable",
     "type": "function"
@@ -734,6 +751,35 @@
     "type": "function"
   },
   {
+    "inputs": [],
+    "name": "tokenPropertyPermissions",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "string", "name": "field_0", "type": "string" },
+          {
+            "components": [
+              {
+                "internalType": "enum EthTokenPermissions",
+                "name": "field_0",
+                "type": "uint8"
+              },
+              { "internalType": "bool", "name": "field_1", "type": "bool" }
+            ],
+            "internalType": "struct Tuple51[]",
+            "name": "field_1",
+            "type": "tuple[]"
+          }
+        ],
+        "internalType": "struct Tuple53[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
     ],
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -13,22 +13,29 @@
 }
 
 /// @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 0x91a97a68
+/// @dev the ERC-165 identifier for this interface is 0xde0695c2
 interface TokenProperties is Dummy, ERC165 {
+	// /// @notice Set permissions for token property.
+	// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
+	// /// @param key Property key.
+	// /// @param isMutable Permission to mutate property.
+	// /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.
+	// /// @param tokenOwner Permission to mutate property by token owner if property is mutable.
+	// /// @dev EVM selector for this function is: 0x222d97fa,
+	// ///  or in textual repr: setTokenPropertyPermission(string,bool,bool,bool)
+	// function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) external;
+
 	/// @notice Set permissions for token property.
 	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
-	/// @param key Property key.
-	/// @param isMutable Permission to mutate property.
-	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.
-	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.
-	/// @dev EVM selector for this function is: 0x222d97fa,
-	///  or in textual repr: setTokenPropertyPermission(string,bool,bool,bool)
-	function setTokenPropertyPermission(
-		string memory key,
-		bool isMutable,
-		bool collectionAdmin,
-		bool tokenOwner
-	) external;
+	/// @param permissions Permissions for keys.
+	/// @dev EVM selector for this function is: 0xbd92983a,
+	///  or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[])
+	function setTokenPropertyPermissions(Tuple47[] memory permissions) external;
+
+	/// @notice Get permissions for token properties.
+	/// @dev EVM selector for this function is: 0xf23d7790,
+	///  or in textual repr: tokenPropertyPermissions()
+	function tokenPropertyPermissions() external view returns (Tuple47[] memory);
 
 	// /// @notice Set token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
@@ -79,6 +86,24 @@
 	bytes value;
 }
 
+enum EthTokenPermissions {
+	Mutable,
+	TokenOwner,
+	CollectionAdmin
+}
+
+/// @dev anonymous struct
+struct Tuple47 {
+	string field_0;
+	Tuple45[] field_1;
+}
+
+/// @dev anonymous struct
+struct Tuple45 {
+	EthTokenPermissions field_0;
+	bool field_1;
+}
+
 /// @title A contract that allows you to work with collections.
 /// @dev the ERC-165 identifier for this interface is 0xb5e1747f
 interface Collection is Dummy, ERC165 {
modifiedtests/src/eth/events.test.tsdiffbeforeafterboth
--- a/tests/src/eth/events.test.ts
+++ b/tests/src/eth/events.test.ts
@@ -119,7 +119,7 @@
     ethEvents.push(event);
   });
   const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['PropertyPermissionSet']}]);
-  await collection.methods.setTokenPropertyPermission('testKey', true, true, true).send({from: owner});
+  await collection.methods.setTokenPropertyPermissions([['testKey', [[0, true], [1, true], [2, true]]]]).send({from: owner});
   await helper.wait.newBlocks(1);
   expect(ethEvents).to.be.like([
     {
@@ -374,7 +374,7 @@
   const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
   const result = await collection.methods.mint(owner).send({from: owner});
   const tokenId = result.events.Transfer.returnValues.tokenId;
-  await collection.methods.setTokenPropertyPermission('A', true, true, true).send({from: owner});
+  await collection.methods.setTokenPropertyPermissions([['A', [[0, true], [1, true], [2, true]]]]).send({from: owner});
 
 
   const ethEvents: any = [];
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 {IKeyringPair} from '@polkadot/types/types';18import {Contract} from 'web3-eth-contract';19import {itEth, usingEthPlaygrounds, expect} from './util';20import {ITokenPropertyPermission} from '../util/playgrounds/types';21import {Pallets} from '../util';22import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique';2324describe('EVM token properties', () => {25  let donor: IKeyringPair;26  let alice: IKeyringPair;2728  before(async function() {29    await usingEthPlaygrounds(async (helper, privateKey) => {30      donor = await privateKey({filename: __filename});31      [alice] = await helper.arrange.createAccounts([100n], donor);32    });33  });3435  itEth('Can be reconfigured', async({helper}) => {36    const owner = await helper.eth.createAccountWithBalance(donor);37    const caller = await helper.eth.createAccountWithBalance(donor);38    for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {39      const collection = await helper.nft.mintCollection(alice);40      await collection.addAdmin(alice, {Ethereum: caller});41      42      const address = helper.ethAddress.fromCollectionId(collection.collectionId);43      const contract = helper.ethNativeContract.collection(address, 'nft', caller);44  45      await contract.methods.setTokenPropertyPermissions([['testKey', [[0, mutable], [1, tokenOwner], [2, collectionAdmin]]]]).send({from: caller});46  47      expect(await collection.getPropertyPermissions()).to.be.deep.equal([{48        key: 'testKey',49        permission: {mutable, collectionAdmin, tokenOwner},50      }]);5152      expect(await contract.methods.tokenPropertyPermissions().call({from: caller})).to.be.like([53        ['testKey', [['0', mutable], ['1', tokenOwner], ['2', collectionAdmin]]]54      ]);55    }56  });5758  [59    {60      method: 'setProperties',61      methodParams: [[{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}]],62      expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}],63    },64    {65      method: 'setProperty' /*Soft-deprecated*/, 66      methodParams: ['testKey1', Buffer.from('testValue1')],67      expectedProps: [{key: 'testKey1', value: 'testValue1'}],68    },69  ].map(testCase => 70    itEth(`[${testCase.method}] Can be set`, async({helper}) => {71      const caller = await helper.eth.createAccountWithBalance(donor);72      const collection = await helper.nft.mintCollection(alice, {73        tokenPropertyPermissions: [{74          key: 'testKey1',75          permission: {76            collectionAdmin: true,77          },78        }, {79          key: 'testKey2',80          permission: {81            collectionAdmin: true,82          },83        }],84      });8586      await collection.addAdmin(alice, {Ethereum: caller});87      const token = await collection.mintToken(alice);88  89      const collectionEvm = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller, testCase.method === 'setProperty');90  91      await collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller});92  93      const properties = await token.getProperties();94      expect(properties).to.deep.equal(testCase.expectedProps);95    }));96  97  [98    {mode: 'nft' as const, requiredPallets: []},99    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},100  ].map(testCase => 101    itEth.ifWithPallets(`Can be multiple set/read for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {102      const caller = await helper.eth.createAccountWithBalance(donor);103      104      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });105      const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,106        collectionAdmin: true,107        mutable: true}}; });108      109      const collection = await helper[testCase.mode].mintCollection(alice, {110        tokenPrefix: 'ethp',111        tokenPropertyPermissions: permissions,112      }) as UniqueNFTCollection | UniqueRFTCollection;113      114      const token = await collection.mintToken(alice);115      116      const valuesBefore = await token.getProperties(properties.map(p => p.key));117      expect(valuesBefore).to.be.deep.equal([]);118      119      120      await collection.addAdmin(alice, {Ethereum: caller});121      122      const address = helper.ethAddress.fromCollectionId(collection.collectionId);123      const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);124      125      expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]);126  127      await contract.methods.setProperties(token.tokenId, properties).send({from: caller});128  129      const values = await token.getProperties(properties.map(p => p.key));130      expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; }));131      132      expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties133        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));134      135      expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call())136        .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]);137    }));138  139  [140    {mode: 'nft' as const, requiredPallets: []},141    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},142  ].map(testCase => 143    itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {144      const caller = await helper.eth.createAccountWithBalance(donor);145      const collection = await helper[testCase.mode].mintCollection(alice, {146        tokenPropertyPermissions: [{147          key: 'testKey',148          permission: {149            mutable: true,150            collectionAdmin: true,151          },152        },153        {154          key: 'testKey_1',155          permission: {156            mutable: true,157            collectionAdmin: true,158          },159        }],160      });161    162      const token = await collection.mintToken(alice);163      await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]);164      expect(await token.getProperties()).to.has.length(2);165166      await collection.addAdmin(alice, {Ethereum: caller});167168      const address = helper.ethAddress.fromCollectionId(collection.collectionId);169      const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);170171      await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller});172173      const result = await token.getProperties(['testKey', 'testKey_1']);174      expect(result.length).to.equal(0);175    }));176177  itEth('Can be read', async({helper}) => {178    const caller = helper.eth.createAccount();179    const collection = await helper.nft.mintCollection(alice, {180      tokenPropertyPermissions: [{181        key: 'testKey',182        permission: {183          collectionAdmin: true,184        },185      }],186    });187  188    const token = await collection.mintToken(alice);189    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);190191    const address = helper.ethAddress.fromCollectionId(collection.collectionId);192    const contract = helper.ethNativeContract.collection(address, 'nft', caller);193194    const value = await contract.methods.property(token.tokenId, 'testKey').call();195    expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));196  });197});198199describe('EVM token properties negative', () => {200  let donor: IKeyringPair;201  let alice: IKeyringPair;202  let caller: string;203  let aliceCollection: UniqueNFTCollection;204  let token: UniqueNFToken;205  const tokenProps = [{key: 'testKey_1', value: 'testValue_1'}, {key: 'testKey_2', value: 'testValue_2'}];206  let collectionEvm: Contract;207208  before(async function() {209    await usingEthPlaygrounds(async (helper, privateKey) => {210      donor = await privateKey({filename: __filename});211      [alice] = await helper.arrange.createAccounts([100n], donor);212    });213  });214215  beforeEach(async () => {216    // 1. create collection with props: testKey_1, testKey_2217    // 2. create token and set props testKey_1, testKey_2218    await usingEthPlaygrounds(async (helper) => {219      aliceCollection = await helper.nft.mintCollection(alice, {220        tokenPropertyPermissions: [{221          key: 'testKey_1',222          permission: {223            mutable: true,224            collectionAdmin: true,225          },226        },227        {228          key: 'testKey_2',229          permission: {230            mutable: true,231            collectionAdmin: true,232          },233        }],234      }); 235      token = await aliceCollection.mintToken(alice);236      await token.setProperties(alice, tokenProps);237      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);238    });239  });240241  [242    {method: 'setProperty', methodParams: [tokenProps[1].key, Buffer.from('newValue')]},243    {method: 'setProperties', methodParams: [[{key: tokenProps[1].key, value: Buffer.from('newValue')}]]},244  ].map(testCase =>245    itEth(`[${testCase.method}] Cannot set properties of non-owned collection`, async ({helper}) => {246      caller = await helper.eth.createAccountWithBalance(donor);247      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);248      // Caller not an owner and not an admin, so he cannot set properties:249      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');250      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;251252      // Props have not changed:253      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));254      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();255      expect(actualProps).to.deep.eq(expectedProps);256    }));257258  [259    {method: 'setProperty', methodParams: ['testKey_3', Buffer.from('testValue3')]},260    {method: 'setProperties', methodParams: [[{key: 'testKey_3', value: Buffer.from('testValue3')}]]},261  ].map(testCase =>262    itEth(`[${testCase.method}] Cannot set non-existing properties`, async ({helper}) => {263      caller = await helper.eth.createAccountWithBalance(donor);264      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);265      await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});266267      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');268      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;269270      // Props have not changed:271      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));272      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();273      expect(actualProps).to.deep.eq(expectedProps);274    }));275276  [277    {method: 'deleteProperty', methodParams: ['testKey_2']},278    {method: 'deleteProperties', methodParams: [['testKey_2']]},279  ].map(testCase =>  280    itEth(`[${testCase.method}] Cannot delete properties of non-owned collection`, async ({helper}) => {281      caller = await helper.eth.createAccountWithBalance(donor);282      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');283      // Caller not an owner and not an admin, so he cannot set properties:284      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');285      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;286287      // Props have not changed:288      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));289      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();290      expect(actualProps).to.deep.eq(expectedProps);291    }));292   293  [294    {method: 'deleteProperty', methodParams: ['testKey_3']},295    {method: 'deleteProperties', methodParams: [['testKey_3']]},296  ].map(testCase =>  297    itEth(`[${testCase.method}] Cannot delete non-existing properties`, async ({helper}) => {298      caller = await helper.eth.createAccountWithBalance(donor);299      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');300      await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});301      // Caller cannot delete non-existing properties:302      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');303      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;304      // Props have not changed:305      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));306      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();307      expect(actualProps).to.deep.eq(expectedProps);308    }));309});310311312type ElementOf<A> = A extends readonly (infer T)[] ? T : never;313function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {314  if(args.length === 0) {315    yield internalRest as any;316    return;317  }318  for(const value of args[0]) {319    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;320  }321}
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 {IKeyringPair} from '@polkadot/types/types';18import {Contract} from 'web3-eth-contract';19import {itEth, usingEthPlaygrounds, expect} from './util';20import {ITokenPropertyPermission, TCollectionMode} from '../util/playgrounds/types';21import {Pallets} from '../util';22import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique';2324describe('EVM token properties', () => {25  let donor: IKeyringPair;26  let alice: IKeyringPair;2728  before(async function() {29    await usingEthPlaygrounds(async (helper, privateKey) => {30      donor = await privateKey({filename: __filename});31      [alice] = await helper.arrange.createAccounts([100n], donor);32    });33  });3435  [36    {mode: 'nft'},37    {mode: 'rft'}38  ].map(testCase =>39  itEth(`[${testCase.mode}] Set and get token property permissions`, async({helper}) => {40    const owner = await helper.eth.createAccountWithBalance(donor);41    const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);42    const mode: any = testCase.mode;43    for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {44      const {collectionId, collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C');45      const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner);46      await collection.methods.addCollectionAdminCross(caller).send({from: owner});4748      await collection.methods.setTokenPropertyPermissions([['testKey', [[0, mutable], [1, tokenOwner], [2, collectionAdmin]]]]).send({from: caller.eth});49      50      expect(await helper.nft.getPropertyPermissions(collectionId)).to.be.deep.equal([{51        key: 'testKey',52        permission: {mutable, collectionAdmin, tokenOwner},53      }]);5455      expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([56        ['testKey', [['0', mutable], ['1', tokenOwner], ['2', collectionAdmin]]]57      ]);58    }59  })60  );6162  [63    {64      method: 'setProperties',65      methodParams: [[{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}]],66      expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}],67    },68    {69      method: 'setProperty' /*Soft-deprecated*/, 70      methodParams: ['testKey1', Buffer.from('testValue1')],71      expectedProps: [{key: 'testKey1', value: 'testValue1'}],72    },73  ].map(testCase => 74    itEth(`[${testCase.method}] Can be set`, async({helper}) => {75      const caller = await helper.eth.createAccountWithBalance(donor);76      const collection = await helper.nft.mintCollection(alice, {77        tokenPropertyPermissions: [{78          key: 'testKey1',79          permission: {80            collectionAdmin: true,81          },82        }, {83          key: 'testKey2',84          permission: {85            collectionAdmin: true,86          },87        }],88      });8990      await collection.addAdmin(alice, {Ethereum: caller});91      const token = await collection.mintToken(alice);92  93      const collectionEvm = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller, testCase.method === 'setProperty');94  95      await collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller});96  97      const properties = await token.getProperties();98      expect(properties).to.deep.equal(testCase.expectedProps);99    }));100  101  [102    {mode: 'nft' as const, requiredPallets: []},103    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},104  ].map(testCase => 105    itEth.ifWithPallets(`Can be multiple set/read for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {106      const caller = await helper.eth.createAccountWithBalance(donor);107      108      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });109      const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,110        collectionAdmin: true,111        mutable: true}}; });112      113      const collection = await helper[testCase.mode].mintCollection(alice, {114        tokenPrefix: 'ethp',115        tokenPropertyPermissions: permissions,116      }) as UniqueNFTCollection | UniqueRFTCollection;117      118      const token = await collection.mintToken(alice);119      120      const valuesBefore = await token.getProperties(properties.map(p => p.key));121      expect(valuesBefore).to.be.deep.equal([]);122      123      124      await collection.addAdmin(alice, {Ethereum: caller});125      126      const address = helper.ethAddress.fromCollectionId(collection.collectionId);127      const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);128      129      expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]);130  131      await contract.methods.setProperties(token.tokenId, properties).send({from: caller});132  133      const values = await token.getProperties(properties.map(p => p.key));134      expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; }));135      136      expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties137        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));138      139      expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call())140        .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]);141    }));142  143  [144    {mode: 'nft' as const, requiredPallets: []},145    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},146  ].map(testCase => 147    itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {148      const caller = await helper.eth.createAccountWithBalance(donor);149      const collection = await helper[testCase.mode].mintCollection(alice, {150        tokenPropertyPermissions: [{151          key: 'testKey',152          permission: {153            mutable: true,154            collectionAdmin: true,155          },156        },157        {158          key: 'testKey_1',159          permission: {160            mutable: true,161            collectionAdmin: true,162          },163        }],164      });165    166      const token = await collection.mintToken(alice);167      await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]);168      expect(await token.getProperties()).to.has.length(2);169170      await collection.addAdmin(alice, {Ethereum: caller});171172      const address = helper.ethAddress.fromCollectionId(collection.collectionId);173      const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);174175      await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller});176177      const result = await token.getProperties(['testKey', 'testKey_1']);178      expect(result.length).to.equal(0);179    }));180181  itEth('Can be read', async({helper}) => {182    const caller = helper.eth.createAccount();183    const collection = await helper.nft.mintCollection(alice, {184      tokenPropertyPermissions: [{185        key: 'testKey',186        permission: {187          collectionAdmin: true,188        },189      }],190    });191  192    const token = await collection.mintToken(alice);193    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);194195    const address = helper.ethAddress.fromCollectionId(collection.collectionId);196    const contract = helper.ethNativeContract.collection(address, 'nft', caller);197198    const value = await contract.methods.property(token.tokenId, 'testKey').call();199    expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));200  });201});202203describe('EVM token properties negative', () => {204  let donor: IKeyringPair;205  let alice: IKeyringPair;206  let caller: string;207  let aliceCollection: UniqueNFTCollection;208  let token: UniqueNFToken;209  const tokenProps = [{key: 'testKey_1', value: 'testValue_1'}, {key: 'testKey_2', value: 'testValue_2'}];210  let collectionEvm: Contract;211212  before(async function() {213    await usingEthPlaygrounds(async (helper, privateKey) => {214      donor = await privateKey({filename: __filename});215      [alice] = await helper.arrange.createAccounts([100n], donor);216    });217  });218219  beforeEach(async () => {220    // 1. create collection with props: testKey_1, testKey_2221    // 2. create token and set props testKey_1, testKey_2222    await usingEthPlaygrounds(async (helper) => {223      aliceCollection = await helper.nft.mintCollection(alice, {224        tokenPropertyPermissions: [{225          key: 'testKey_1',226          permission: {227            mutable: true,228            collectionAdmin: true,229          },230        },231        {232          key: 'testKey_2',233          permission: {234            mutable: true,235            collectionAdmin: true,236          },237        }],238      }); 239      token = await aliceCollection.mintToken(alice);240      await token.setProperties(alice, tokenProps);241      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);242    });243  });244245  [246    {method: 'setProperty', methodParams: [tokenProps[1].key, Buffer.from('newValue')]},247    {method: 'setProperties', methodParams: [[{key: tokenProps[1].key, value: Buffer.from('newValue')}]]},248  ].map(testCase =>249    itEth(`[${testCase.method}] Cannot set properties of non-owned collection`, async ({helper}) => {250      caller = await helper.eth.createAccountWithBalance(donor);251      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);252      // Caller not an owner and not an admin, so he cannot set properties:253      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');254      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;255256      // Props have not changed:257      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));258      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();259      expect(actualProps).to.deep.eq(expectedProps);260    }));261262  [263    {method: 'setProperty', methodParams: ['testKey_3', Buffer.from('testValue3')]},264    {method: 'setProperties', methodParams: [[{key: 'testKey_3', value: Buffer.from('testValue3')}]]},265  ].map(testCase =>266    itEth(`[${testCase.method}] Cannot set non-existing properties`, async ({helper}) => {267      caller = await helper.eth.createAccountWithBalance(donor);268      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);269      await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});270271      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');272      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;273274      // Props have not changed:275      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));276      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();277      expect(actualProps).to.deep.eq(expectedProps);278    }));279280  [281    {method: 'deleteProperty', methodParams: ['testKey_2']},282    {method: 'deleteProperties', methodParams: [['testKey_2']]},283  ].map(testCase =>  284    itEth(`[${testCase.method}] Cannot delete properties of non-owned collection`, async ({helper}) => {285      caller = await helper.eth.createAccountWithBalance(donor);286      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');287      // Caller not an owner and not an admin, so he cannot set properties:288      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');289      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;290291      // Props have not changed:292      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));293      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();294      expect(actualProps).to.deep.eq(expectedProps);295    }));296   297  [298    {method: 'deleteProperty', methodParams: ['testKey_3']},299    {method: 'deleteProperties', methodParams: [['testKey_3']]},300  ].map(testCase =>  301    itEth(`[${testCase.method}] Cannot delete non-existing properties`, async ({helper}) => {302      caller = await helper.eth.createAccountWithBalance(donor);303      collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');304      await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});305      // Caller cannot delete non-existing properties:306      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');307      await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;308      // Props have not changed:309      const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));310      const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();311      expect(actualProps).to.deep.eq(expectedProps);312    }));313});314315316type ElementOf<A> = A extends readonly (infer T)[] ? T : never;317function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {318  if(args.length === 0) {319    yield internalRest as any;320    return;321  }322  for(const value of args[0]) {323    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;324  }325}