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

difftreelog

test(structure) refactor properties and nesting

Fahrrader2022-05-13parent: #1c4a2b3.patch.diff
in: master

7 files changed

modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -33,7 +33,9 @@
     "loadTransfer": "ts-node src/transfer.nload.ts",
     "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
     "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",
-    "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",
+    "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",
+    "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",
+    "testStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",
     "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts",
     "testMigrationStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",
     "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
modifiedtests/src/nesting/graphs.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/graphs.test.ts
+++ b/tests/src/nesting/graphs.test.ts
@@ -14,7 +14,7 @@
  * ```
  */
 async function buildComplexObjectGraph(api: ApiPromise, sender: IKeyringPair): Promise<number> {
-  const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+  const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT', limits: {nestingRule: 'Owner'}}));
   const {collectionId} = getCreateCollectionResult(events);
 
   await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})}));
@@ -32,20 +32,26 @@
   return collectionId;
 }
 
-describe('graphs', () => {
-  it('ouroboros can\'t be created in graph', async () => {
+describe('Graphs', () => {
+  it('Ouroboros can\'t be created in a complex graph', async () => {
     await usingApi(async api => {
       const alice = privateKey('//Alice');
       const collection = await buildComplexObjectGraph(api, alice);
 
       // to self
-      await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)))
-        .to.be.rejectedWith(/structure\.OuroborosDetected/);
+      await expect(
+        executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)),
+        'first transaction',  
+      ).to.be.rejectedWith(/structure\.OuroborosDetected/);
       // to nested part of graph
-      await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)))
-        .to.be.rejectedWith(/structure\.OuroborosDetected/);
-      await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 8), collection, 2, 1)))
-        .to.be.rejectedWith(/structure\.OuroborosDetected/);
+      await expect(
+        executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)),
+        'second transaction',
+      ).to.be.rejectedWith(/structure\.OuroborosDetected/);
+      await expect(
+        executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 8), collection, 2, 1)),
+        'third transaction',
+      ).to.be.rejectedWith(/structure\.OuroborosDetected/);
     });
   });
 });
modifiedtests/src/nesting/migration-check.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/migration-check.test.ts
+++ b/tests/src/nesting/migration-check.test.ts
@@ -18,7 +18,7 @@
     });
   });
 
-  it('Preserves collection settings', async () => {
+  it('Preserves collection settings after migration', async () => {
     let oldVersion: number;
     let collectionId: number;
     let collectionOld: any;
@@ -71,6 +71,7 @@
         });
       } catch (_) {
         connectionFailCounter++;
+        console.log(`Still waiting for the parachain upgrade from ${oldVersion!}...`);
         await new Promise(resolve => setTimeout(resolve, 12000));
       }
     }
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
before · tests/src/nesting/nest.test.ts
1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import privateKey from '../substrate/privateKey';4import usingApi, {executeTransaction} from '../substrate/substrate-api';5import {6  addToAllowListExpectSuccess,7  createCollectionExpectSuccess,8  createItemExpectFailure, 9  createItemExpectSuccess,10  enableAllowListExpectSuccess,11  enablePublicMintingExpectSuccess,12  getTokenOwner, 13  getTopmostTokenOwner, 14  setCollectionLimitsExpectSuccess, 15  transferExpectFailure, 16  transferExpectSuccess, 17  transferFromExpectSuccess,18} from '../util/helpers';19import {IKeyringPair} from '@polkadot/types/types';2021let alice: IKeyringPair;22let bob: IKeyringPair;2324describe('Integration Test: Nesting', () => {25  before(async () => {26    await usingApi(async api => {27      alice = privateKey('//Alice');28      bob = privateKey('//Bob');29    });30  });3132  // ---------- Non-Fungible ----------3334  // todo refactor names35  // todo remove excessive bundling, leave it for a single test36  it('NFT: allows to nest/unnest token if nesting rule is Owner', async () => {37    await usingApi(async api => {38      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});39      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});40      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');4142      // Create a nested token43      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});44      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});45      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4647      // Create a token to be nested48      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');49      50      // Nest51      await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});52      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});53      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5455      // Move bundle to different user56      await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});57      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});58      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5960      // Unnest61      await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});62      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});63    });64  });6566  it('NFT: allows to nest/unnest token if Owner-Restricted', async () => {67    await usingApi(async api => {68      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});69      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});70      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');7172      // Create a nested token73      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});74      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});75      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});7677      // Create a token to be nested78      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');79      80      // Nest81      await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});82      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});83      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});8485      // Move bundle to different user86      await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});87      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});88      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});89      90      // Unnest91      await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});92      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});93    });94  });9596  // ---------- Fungible ----------9798  it('Fungible: allows to nest/unnest token if Owner', async () => {99    await usingApi(async api => {100      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});101      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});102      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});103      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};104105      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});106107      // Create a nested token108      await expect(executeTransaction(api, alice, api.tx.unique.createItem(109        collectionFT, 110        targetAddress, 111        {Fungible: {Value: 10}},112      ))).to.not.be.rejected;113114      // Create a token to be nested115      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');116      // Nest117      await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');118      // Move bundle to different user119      await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});120      // Unnest121      await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');122    });123  });124125  it('Fungible: allows to nest/unnest token if Owner-Restricted', async () => {126    await usingApi(async api => {127      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});128      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});129      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};130131      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});132133      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionFT]}});134135      // Create a nested token136      await expect(executeTransaction(api, alice, api.tx.unique.createItem(137        collectionFT, 138        targetAddress, 139        {Fungible: {Value: 10}},140      ))).to.not.be.rejected;141142      // Create a token to be nested143      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');144      // Nest145      await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');146      // Move bundle to different user147      await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});148      // Unnest149      await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');150    });151  });152153  // ---------- Re-Fungible ----------154155  it('ReFungible: allows to nest/unnest token if Owner', async () => {156    await usingApi(async api => {157      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});158      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});159      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});160      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};161162      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});163164      // Create a nested token165      await expect(executeTransaction(api, alice, api.tx.unique.createItem(166        collectionRFT, 167        targetAddress, 168        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},169      ))).to.not.be.rejected;170171      // Create a token to be nested172      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');173      // Nest174      await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');175      // Move bundle to different user176      await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});177      // Unnest178      await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');179    });180  });181182  it('ReFungible: allows to nest/unnest token if Owner-Restricted', async () => {183    await usingApi(async api => {184      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});185      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});186      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};187188      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});189190      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionRFT]}});191192      // Create a nested token193      await expect(executeTransaction(api, alice, api.tx.unique.createItem(194        collectionRFT, 195        targetAddress,196        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},197      ))).to.not.be.rejected;198199      // Create a token to be nested200      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');201      // Nest202      await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');203      // Move bundle to different user204      await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});205      // Unnest206      await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');207    });208  });209});210211describe('Negative Test: Nesting', async() => {212  before(async () => {213    await usingApi(async api => {214      alice = privateKey('//Alice');215      bob = privateKey('//Bob');216    });217  });218219  // ---------- Non-Fungible ----------220221  it('NFT: disallows to nest token if nesting is disabled', async () => {222    await usingApi(async api => {223      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});224      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Disabled'});225      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');226227      // Try to create a nested token228      await expect(executeTransaction(api, alice, api.tx.unique.createItem(229        collection, 230        {Ethereum: tokenIdToAddress(collection, targetToken)}, 231        {nft: {const_data: [], variable_data: []}} as any,232      ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);233234      // Create a token to be nested235      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');236      // Try to nest237      await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); // todo to.be.rejected238      expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});239      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});240    });241  });242243  it('NFT: disallows to nest token if not Owner', async () => {244    await usingApi(async api => {245      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});246      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});247248      await addToAllowListExpectSuccess(alice, collection, bob.address);249      await enableAllowListExpectSuccess(alice, collection);250      await enablePublicMintingExpectSuccess(alice, collection);251252      // Create a token to attempt to be nested into253      const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');254255      // Try to create a nested token in the wrong collection256      await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});257258      // Try to create and nest a token in the wrong collection259      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');260      await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});261      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});262    });263  });264265  it('NFT: disallows to nest token if not Owner (Restricted nesting)', async () => {266    await usingApi(async api => {267      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});268      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});269270      await addToAllowListExpectSuccess(alice, collection, bob.address);271      await enableAllowListExpectSuccess(alice, collection);272      await enablePublicMintingExpectSuccess(alice, collection);273274      // Create a token to attempt to be nested into275      const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');276277      // Try to create a nested token in the wrong collection278      await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});279280      // Try to create and nest a token in the wrong collection281      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');282      await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});283      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});284    });285  });286287  it('NFT: disallows to nest token to an unlisted collection', async () => {288    await usingApi(async api => {289      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});290      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[]}});291292      // Create a token to attempt to be nested into293      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');294295      // Try to create a nested token in the wrong collection296      await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});297298      // Try to create and nest a token in the wrong collection299      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');300      await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});301      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});302    });303  });304305  // ---------- Fungible ----------306307  it('Fungible: disallows to nest token if nesting is disabled', async () => {308    await usingApi(async api => {309      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});310      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});311      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');312      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};313314      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});315316      // Try to create a nested token317      await expect(executeTransaction(api, alice, api.tx.unique.createItem(318        collectionFT, 319        targetAddress, 320        {Fungible: {Value: 10}},321      ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);322323      // Create a token to be nested324      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');325      // Try to nest326      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);327    });328  });329330  it('Fungible: disallows to nest token if not Owner', async () => {331    await usingApi(async api => {332      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});333      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});334335      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);336      await enableAllowListExpectSuccess(alice, collectionNFT);337      await enablePublicMintingExpectSuccess(alice, collectionNFT);338339      // Create a token to attempt to be nested into340      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');341      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};342343      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});344345      // Try to create a nested token in the wrong collection346      await expect(executeTransaction(api, alice, api.tx.unique.createItem(347        collectionFT, 348        targetAddress, 349        {Fungible: {Value: 10}},350      ))).to.be.rejected;351352      // Try to create and nest a token in the wrong collection353      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');354      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);355    });356  });357358  it('Fungible: disallows to nest token if not Owner (Restricted nesting)', async () => {359    await usingApi(async api => {360      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});361      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}}); // todo clear redundant restrictions362363      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);364      await enableAllowListExpectSuccess(alice, collectionNFT);365      await enablePublicMintingExpectSuccess(alice, collectionNFT);366367      // Create a token to attempt to be nested into368      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');369      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};370371      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});372373      // Try to create a nested token in the wrong collection374      await expect(executeTransaction(api, alice, api.tx.unique.createItem(375        collectionFT, 376        targetAddress, 377        {Fungible: {Value: 10}},378      ))).to.be.rejected;379380      // Try to create and nest a token in the wrong collection381      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');382      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);383    });384  });385386  it('Fungible: disallows to nest token to an unlisted collection', async () => {387    await usingApi(async api => {388      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});389      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});390391      // Create a token to attempt to be nested into392      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');393      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};394395      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});396397      // Try to create a nested token in the wrong collection398      await expect(executeTransaction(api, alice, api.tx.unique.createItem(399        collectionFT, 400        targetAddress, 401        {Fungible: {Value: 10}},402      ))).to.be.rejected;403404      // Try to create and nest a token in the wrong collection405      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');406      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);407    });408  });409410  // ---------- Re-Fungible ----------411412  it('ReFungible: disallows to nest token if nesting is disabled', async () => {413    await usingApi(async api => {414      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});415      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});416      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');417      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};418419      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});420421      // Create a nested token422      await expect(executeTransaction(api, alice, api.tx.unique.createItem(423        collectionRFT, 424        targetAddress, 425        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},426      ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);427428      // Create a token to be nested429      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');430      // Try to nest431      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);432    });433  });434435  it('ReFungible: disallows to nest token if not Owner', async () => {436    await usingApi(async api => {437      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});438      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});439440      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);441      await enableAllowListExpectSuccess(alice, collectionNFT);442      await enablePublicMintingExpectSuccess(alice, collectionNFT);443444      // Create a token to attempt to be nested into445      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');446      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};447448      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});449450      // Try to create a nested token in the wrong collection451      await expect(executeTransaction(api, alice, api.tx.unique.createItem(452        collectionRFT, 453        targetAddress, 454        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},455      ))).to.be.rejected;456457      // Try to create and nest a token in the wrong collection458      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');459      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);460    });461  });462463  it('ReFungible: disallows to nest token if not Owner (Restricted nesting)', async () => {464    await usingApi(async api => {465      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});466      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}});467468      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);469      await enableAllowListExpectSuccess(alice, collectionNFT);470      await enablePublicMintingExpectSuccess(alice, collectionNFT);471472      // Create a token to attempt to be nested into473      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');474      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};475476      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});477478      // Try to create a nested token in the wrong collection479      await expect(executeTransaction(api, alice, api.tx.unique.createItem(480        collectionRFT, 481        targetAddress, 482        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},483      ))).to.be.rejected;484485      // Try to create and nest a token in the wrong collection486      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');487      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);488    });489  });490491  it('ReFungible: disallows to nest token to an unlisted collection', async () => {492    await usingApi(async api => {493      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});494      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});495496      // Create a token to attempt to be nested into497      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');498      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};499500      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});501502      // Try to create a nested token in the wrong collection503      await expect(executeTransaction(api, alice, api.tx.unique.createItem(504        collectionRFT, 505        targetAddress, 506        {ReFungible: {const_data: [], variable_data: [], pieces: 100}},507      ))).to.be.rejected;508509      // Try to create and nest a token in the wrong collection510      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');511      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);512    });513  });514});
after · tests/src/nesting/nest.test.ts
1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import privateKey from '../substrate/privateKey';4import usingApi, {executeTransaction} from '../substrate/substrate-api';5import {6  addToAllowListExpectSuccess,7  createCollectionExpectSuccess,8  createItemExpectSuccess,9  enableAllowListExpectSuccess,10  enablePublicMintingExpectSuccess,11  getTokenOwner, 12  getTopmostTokenOwner, 13  normalizeAccountId, 14  setCollectionLimitsExpectSuccess, 15  transferExpectFailure, 16  transferExpectSuccess, 17  transferFromExpectSuccess,18} from '../util/helpers';19import {IKeyringPair} from '@polkadot/types/types';2021let alice: IKeyringPair;22let bob: IKeyringPair;2324describe('Integration Test: Nesting', () => {25  before(async () => {26    await usingApi(async api => {27      alice = privateKey('//Alice');28      bob = privateKey('//Bob');29    });30  });3132  it('Performs the full suite: bundles a token, transfers, and allows to unnest', async () => {33    await usingApi(async api => {34      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});35      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});36      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');3738      // Create a nested token39      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});40      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});41      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4243      // Create a token to be nested44      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');45      46      // Nest47      await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});48      expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});49      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5051      // Move bundle to different user52      await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});53      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});54      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5556      // Unnest57      await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});58      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});59    });60  });6162  // ---------- Non-Fungible ----------6364  it('NFT: allows an Owner to nest/unnest their token', async () => {65    await usingApi(async api => {66      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});67      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});68      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');6970      // Create a nested token71      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});72      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});73      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});7475      // Create a token to be nested and nest76      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');77      await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});78      expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});79      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});80    });81  });8283  it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {84    await usingApi(async api => {85      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});86      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});87      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');8889      // Create a nested token90      const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});91      expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});92      expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});9394      // Create a token to be nested and nest95      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');96      await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});97      expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});98      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});99    });100  });101102  // ---------- Fungible ----------103104  it('Fungible: allows an Owner to nest/unnest their token', async () => {105    await usingApi(async api => {106      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});107      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});108      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});109      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};110111      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});112113      // Create a nested token114      await expect(executeTransaction(api, alice, api.tx.unique.createItem(115        collectionFT, 116        targetAddress, 117        {Fungible: {Value: 10}},118      ))).to.not.be.rejected;119120      // Nest a new token121      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');122      await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');123    });124  });125126  it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {127    await usingApi(async api => {128      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});129      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});130      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};131132      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});133134      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionFT]}});135136      // Create a nested token137      await expect(executeTransaction(api, alice, api.tx.unique.createItem(138        collectionFT, 139        targetAddress, 140        {Fungible: {Value: 10}},141      ))).to.not.be.rejected;142143      // Nest a new token144      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');145      await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');146    });147  });148149  // ---------- Re-Fungible ----------150151  it('ReFungible: allows an Owner to nest/unnest their token', async () => {152    await usingApi(async api => {153      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});154      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});155      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});156      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};157158      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});159160      // Create a nested token161      await expect(executeTransaction(api, alice, api.tx.unique.createItem(162        collectionRFT, 163        targetAddress, 164        {ReFungible: {const_data: [], pieces: 100}},165      ))).to.not.be.rejected;166167      // Nest a new token168      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');169      await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');170    });171  });172173  it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {174    await usingApi(async api => {175      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});176      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});177      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};178179      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});180181      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionRFT]}});182183      // Create a nested token184      await expect(executeTransaction(api, alice, api.tx.unique.createItem(185        collectionRFT, 186        targetAddress,187        {ReFungible: {const_data: [], pieces: 100}},188      ))).to.not.be.rejected;189190      // Nest a new token191      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');192      await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');193    });194  });195});196197describe('Negative Test: Nesting', async() => {198  before(async () => {199    await usingApi(async api => {200      alice = privateKey('//Alice');201      bob = privateKey('//Bob');202    });203  });204205  it('Disallows excessive token nesting', async () => {206    await usingApi(async api => {207      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});209      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');210211      // Create a nested-token matryoshka212      const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});213      const nestedToken2 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken1)});214      // The nesting depth is limited by 2215      await expect(executeTransaction(216        api, alice, 217        api.tx.unique.createItem(218          collection, 219          {Ethereum: tokenIdToAddress(collection, nestedToken2)}, 220          {nft: {const_data: [], variable_data: []}} as any,221        ),222      ), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/); // OuroborosDetected?223224      expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});225    });226  });227228  // ---------- Non-Fungible ----------229230  it('NFT: disallows to nest token if nesting is disabled', async () => {231    await usingApi(async api => {232      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});233      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Disabled'});234      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');235236      // Try to create a nested token237      await expect(executeTransaction(238        api, alice, 239        api.tx.unique.createItem(240          collection, 241          {Ethereum: tokenIdToAddress(collection, targetToken)}, 242          {nft: {const_data: [], variable_data: []}} as any,243        ),244      ), 'while creating nested token').to.be.rejectedWith(/^common\.NestingIsDisabled$/);245246      // Create a token to be nested247      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');248      // Try to nest249      await expect(executeTransaction(250        api, alice, 251        api.tx.unique.transfer(252          normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,253        ),254      ), 'while nesting new token').to.be.rejectedWith(/common\.NestingIsDisabled/); // todo to.be.rejected for all255      expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});256      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});257    });258  });259260  it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {261    await usingApi(async api => {262      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});263      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});264265      await addToAllowListExpectSuccess(alice, collection, bob.address);266      await enableAllowListExpectSuccess(alice, collection);267      await enablePublicMintingExpectSuccess(alice, collection);268269      // Create a token to attempt to be nested into270      const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');271272      // Try to create a nested token in the wrong collection273      await expect(executeTransaction(274        api, alice, 275        api.tx.unique.createItem(276          collection, 277          {Ethereum: tokenIdToAddress(collection, targetToken)}, 278          {nft: {const_data: [], variable_data: []}} as any,279        ),280      ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/); // todo NestingIsDisabled?281282      // Try to create and nest a token in the wrong collection283      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');284      await expect(executeTransaction(285        api, alice, 286        api.tx.unique.transfer(287          normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,288        ),289      ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);290      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});291    });292  });293294  it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {295    await usingApi(async api => {296      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});297      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});298299      await addToAllowListExpectSuccess(alice, collection, bob.address);300      await enableAllowListExpectSuccess(alice, collection);301      await enablePublicMintingExpectSuccess(alice, collection);302303      // Create a token to attempt to be nested into304      const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');305306      // Try to create a nested token in the wrong collection307      await expect(executeTransaction(308        api, alice, 309        api.tx.unique.createItem(310          collection, 311          {Ethereum: tokenIdToAddress(collection, targetToken)}, 312          {nft: {const_data: [], variable_data: []}} as any,313        ),314      ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/);315316      // Try to create and nest a token in the wrong collection317      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');318      await expect(executeTransaction(319        api, alice, 320        api.tx.unique.transfer(321          normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,322        ),323      ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);324      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});325    });326  });327328  it('NFT: disallows to nest token in an unlisted collection', async () => {329    await usingApi(async api => {330      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});331      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[]}});332333      // Create a token to attempt to be nested into334      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');335336      // Try to create a nested token in the wrong collection337      await expect(executeTransaction(338        api, alice, 339        api.tx.unique.createItem(340          collection, 341          {Ethereum: tokenIdToAddress(collection, targetToken)}, 342          {nft: {const_data: [], variable_data: []}} as any,343        ),344      ), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);345346      // Try to create and nest a token in the wrong collection347      const newToken = await createItemExpectSuccess(alice, collection, 'NFT');348      await expect(executeTransaction(349        api, alice, 350        api.tx.unique.transfer(351          normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,352        ),353      ), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);354      expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});355    });356  });357358  // ---------- Fungible ----------359360  it('Fungible: disallows to nest token if nesting is disabled', async () => {361    await usingApi(async api => {362      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});363      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});364      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');365      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};366367      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});368369      // Try to create a nested token370      await expect(executeTransaction(371        api, alice, 372        api.tx.unique.createItem(373          collectionFT, 374          targetAddress, 375          {Fungible: {Value: 10}},376        )377      ), 'while creating nested token').to.be.rejectedWith(/^common\.NestingIsDisabled$/);378379      // Create a token to be nested380      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');381      // Try to nest382      await expect(executeTransaction(383        api, alice, 384        api.tx.unique.transfer(385          normalizeAccountId({Ethereum: tokenIdToAddress(collectionFT, targetToken)}), collectionFT, newToken, 1,386        ),387      ), 'while nesting new token').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);388    });389  });390391  it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {392    await usingApi(async api => {393      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});394      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});395396      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);397      await enableAllowListExpectSuccess(alice, collectionNFT);398      await enablePublicMintingExpectSuccess(alice, collectionNFT);399400      // Create a token to attempt to be nested into401      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');402      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};403404      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});405406      // Try to create a nested token in the wrong collection407      await expect(executeTransaction(408        api, alice, 409        api.tx.unique.createItem(410          collectionFT, 411          targetAddress, 412          {Fungible: {Value: 10}},413        )414      ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/);415416      // Try to create and nest a token in the wrong collection417      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');418      await expect(executeTransaction(419        api, alice, 420        api.tx.unique.transfer(421          normalizeAccountId({Ethereum: tokenIdToAddress(collectionFT, targetToken)}), collectionFT, newToken, 1,422        ),423      ), 'while nesting new token').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);424    });425  });426427  it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {428    await usingApi(async api => {429      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});430      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}}); // todo clear redundant restrictions?431432      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);433      await enableAllowListExpectSuccess(alice, collectionNFT);434      await enablePublicMintingExpectSuccess(alice, collectionNFT);435436      // Create a token to attempt to be nested into437      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');438      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};439440      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});441442      // Try to create a nested token in the wrong collection443      await expect(executeTransaction(api, alice, api.tx.unique.createItem(444        collectionFT, 445        targetAddress, 446        {Fungible: {Value: 10}},447      ))).to.be.rejected;448449      // Try to create and nest a token in the wrong collection450      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');451      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);452    });453  });454455  it('Fungible: disallows to nest token in an unlisted collection', async () => {456    await usingApi(async api => {457      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});458      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});459460      // Create a token to attempt to be nested into461      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');462      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};463464      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});465466      // Try to create a nested token in the wrong collection467      await expect(executeTransaction(api, alice, api.tx.unique.createItem(468        collectionFT, 469        targetAddress, 470        {Fungible: {Value: 10}},471      ))).to.be.rejected;472473      // Try to create and nest a token in the wrong collection474      const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');475      await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);476    });477  });478479  // ---------- Re-Fungible ----------480481  it('ReFungible: disallows to nest token if nesting is disabled', async () => {482    await usingApi(async api => {483      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});484      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});485      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');486      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};487488      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});489490      // Create a nested token491      await expect(executeTransaction(api, alice, api.tx.unique.createItem(492        collectionRFT, 493        targetAddress, 494        {ReFungible: {const_data: [], pieces: 100}},495      ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);496497      // Create a token to be nested498      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');499      // Try to nest500      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);501    });502  });503504  it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {505    await usingApi(async api => {506      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});507      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});508509      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);510      await enableAllowListExpectSuccess(alice, collectionNFT);511      await enablePublicMintingExpectSuccess(alice, collectionNFT);512513      // Create a token to attempt to be nested into514      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');515      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};516517      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});518519      // Try to create a nested token in the wrong collection520      await expect(executeTransaction(api, alice, api.tx.unique.createItem(521        collectionRFT, 522        targetAddress, 523        {ReFungible: {const_data: [], pieces: 100}},524      ))).to.be.rejected;525526      // Try to create and nest a token in the wrong collection527      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');528      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);529    });530  });531532  it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {533    await usingApi(async api => {534      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});535      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}});536537      await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);538      await enableAllowListExpectSuccess(alice, collectionNFT);539      await enablePublicMintingExpectSuccess(alice, collectionNFT);540541      // Create a token to attempt to be nested into542      const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');543      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};544545      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});546547      // Try to create a nested token in the wrong collection548      await expect(executeTransaction(api, alice, api.tx.unique.createItem(549        collectionRFT, 550        targetAddress, 551        {ReFungible: {const_data: [], pieces: 100}},552      ))).to.be.rejected;553554      // Try to create and nest a token in the wrong collection555      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');556      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);557    });558  });559560  it('ReFungible: disallows to nest token to an unlisted collection', async () => {561    await usingApi(async api => {562      const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});563      await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});564565      // Create a token to attempt to be nested into566      const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');567      const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};568569      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});570571      // Try to create a nested token in the wrong collection572      await expect(executeTransaction(api, alice, api.tx.unique.createItem(573        collectionRFT, 574        targetAddress, 575        {ReFungible: {const_data: [], pieces: 100}},576      ))).to.be.rejected;577578      // Try to create and nest a token in the wrong collection579      const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');580      await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);581    });582  });583});
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -51,13 +51,13 @@
       await expect(executeTransaction(
         api, 
         alice, 
-        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black hole'}]), 
+        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 
       )).to.not.be.rejected;
 
-      const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black hole'])).toJSON();
+      const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();
       expect(properties).to.be.deep.equal([
-        {key: `0x${Buffer.from('electron').toString('hex')}`, value: `0x${Buffer.from('come bond').toString('hex')}`},
-        {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('').toString('hex')}`},
+        {key: 'electron', value: 'come bond'},
+        {key: 'black_hole', value: ''},
       ]);
     });
   });
@@ -69,20 +69,20 @@
       await expect(executeTransaction(
         api, 
         alice, 
-        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole'}]), 
+        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 
       )).to.not.be.rejected;
 
       // Mutate the properties
       await expect(executeTransaction(
         api, 
         alice, 
-        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black hole', value: 'LIGO'}]), 
+        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 
       )).to.not.be.rejected;
 
-      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();
+      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
       expect(properties).to.be.deep.equal([
-        {key: `0x${Buffer.from('electron').toString('hex')}`, value: `0x${Buffer.from('bonded').toString('hex')}`},
-        {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('LIGO').toString('hex')}`},
+        {key: 'electron', value: 'bonded'},
+        {key: 'black_hole', value: 'LIGO'},
       ]);
     });
   });
@@ -94,7 +94,7 @@
       await expect(executeTransaction(
         api, 
         alice, 
-        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole', value: 'LIGO'}]), 
+        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 
       )).to.not.be.rejected;
 
       await expect(executeTransaction(
@@ -103,9 +103,9 @@
         api.tx.unique.deleteCollectionProperties(collection, ['electron']), 
       )).to.not.be.rejected;
 
-      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();
+      const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
       expect(properties).to.be.deep.equal([
-        {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('LIGO').toString('hex')}`},
+        {key: 'black_hole', value: 'LIGO'},
       ]);
     });
   });
@@ -126,7 +126,7 @@
       await expect(executeTransaction(
         api, 
         bob, 
-        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole', value: 'LIGO'}]), 
+        api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 
       )).to.be.rejectedWith(/common\.NoPermission/);
   
       const properties = (await api.query.common.collectionProperties(collection)).toJSON();
@@ -158,7 +158,7 @@
         alice, 
         api.tx.unique.setCollectionProperties(collection, [
           {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 
-          {key: 'black hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 
+          {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 
         ]), 
       )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
 
@@ -174,7 +174,7 @@
       const propertiesToBeSet = [];
       for (let i = 0; i < 65; i++) {
         propertiesToBeSet.push({
-          key: 'electron ' + i,
+          key: 'electron_' + i,
           value: Math.random() > 0.5 ? 'high' : 'low',
         });
       }
@@ -190,6 +190,55 @@
       expect(properties.consumedSpace).to.equal(0);
     });
   });
+
+  it('Fails to set properties with invalid names', async () => {
+    await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess();
+
+      const invalidProperties = [
+        [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],
+        [{key: 'Mr.Sandman', value: 'Bring me a gene'}],
+        [{key: 'déjà vu', value: 'hmm...'}],
+      ];
+
+      for (let i = 0; i < invalidProperties.length; i++) {
+        await expect(executeTransaction(
+          api, 
+          alice, 
+          api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 
+        ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
+      }
+
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 
+      ), `on rejecting an unnamed property`).to.be.rejectedWith(/common\.EmptyPropertyKey/);
+
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setCollectionProperties(collection, [
+          {key: 'CRISPR-Cas9', value: 'rewriting nature!'}
+        ]), 
+      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
+
+      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');
+
+      const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();
+      expect(properties).to.be.deep.equal([
+        {key: 'CRISPR-Cas9', value: 'rewriting nature!'},
+      ]);
+
+      for (let i = 0; i < invalidProperties.length; i++) {
+        await expect(executeTransaction(
+          api, 
+          alice, 
+          api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 
+        ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
+      }
+    });
+  });
 });
 
 // ---------- ACCESS RIGHTS
@@ -228,10 +277,10 @@
         api.tx.unique.setPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 
       )).to.not.be.rejected;
 
-      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toJSON();
+      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();
       expect(propertyRights).to.be.deep.equal([
-        {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},
-        {key: `0x${Buffer.from('mindgame').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},
+        {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},
+        {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},
       ]);
     });
   });
@@ -252,9 +301,9 @@
         api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 
       )).to.not.be.rejected;
 
-      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();
+      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
       expect(propertyRights).to.be.deep.equal([
-        {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
+        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
       ]);
     });
   });
@@ -290,7 +339,7 @@
       const constitution = [];
       for (let i = 0; i < 65; i++) {
         constitution.push({
-          key: 'property ' + i,
+          key: 'property_' + i,
           permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},
         });
       }
@@ -322,9 +371,51 @@
         api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 
       )).to.be.rejectedWith(/common\.NoPermission/);
 
-      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();
+      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
       expect(propertyRights).to.deep.equal([
-        {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
+        {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
+      ]);
+    });
+  });
+
+  it('Prevents adding properties with invalid names', async () => {
+    await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess();
+
+      const invalidProperties = [
+        [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],
+        [{key: 'G#4', permission: {tokenOwner: true}}],
+        [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],
+      ];
+
+      for (let i = 0; i < invalidProperties.length; i++) {
+        await expect(executeTransaction(
+          api, 
+          alice, 
+          api.tx.unique.setPropertyPermissions(collection, invalidProperties[i]), 
+        ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
+      }
+
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setPropertyPermissions(collection, [{key: '', permission: {}}]), 
+      ), `on rejecting an unnamed property`).to.be.rejectedWith(/common\.EmptyPropertyKey/);
+
+      const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string
+      await expect(executeTransaction(
+        api, 
+        alice, 
+        api.tx.unique.setPropertyPermissions(collection, [
+          {key: correctKey, permission: {collectionAdmin: true}},
+        ]), 
+      ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
+
+      const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');
+
+      const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();
+      expect(propertyRights).to.be.deep.equal([
+        {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},
       ]);
     });
   });
@@ -361,7 +452,7 @@
     await transferExpectSuccess(collection, token, alice, charlie);
   });
   
-  it('Reads properties of a token', async () => {
+  it('Reads yet empty properties of a token', async () => {
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess();
       const token = await createItemExpectSuccess(alice, collection, 'NFT');
@@ -381,7 +472,7 @@
       let i = 0;
       for (const permission of permissions) {
         for (const signer of permission.signers) {
-          const key = i + ' ' + signer.address;
+          const key = i + '_' + signer.address;
           propertyKeys.push(key);
 
           await expect(executeTransaction(
@@ -400,11 +491,11 @@
         i++;
       }
 
-      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];
-      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];
+      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
+      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
       for (let i = 0; i < properties.length; i++) {
-        expect(properties[i].value).to.be.equal(`0x${Buffer.from('Serotonin increase').toString('hex')}`);
-        expect(tokensData[i].value).to.be.equal(`0x${Buffer.from('Serotonin increase').toString('hex')}`);
+        expect(properties[i].value).to.be.equal('Serotonin increase');
+        expect(tokensData[i].value).to.be.equal('Serotonin increase');
       }
     });
   });
@@ -417,7 +508,7 @@
         if (!permission.permission.mutable) continue;
         
         for (const signer of permission.signers) {
-          const key = i + ' ' + signer.address;
+          const key = i + '_' + signer.address;
           propertyKeys.push(key);
 
           await expect(executeTransaction(
@@ -442,11 +533,11 @@
         i++;
       }
 
-      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];
-      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];
+      const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
+      const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
       for (let i = 0; i < properties.length; i++) {
-        expect(properties[i].value).to.be.equal(`0x${Buffer.from('Serotonin stable').toString('hex')}`);
-        expect(tokensData[i].value).to.be.equal(`0x${Buffer.from('Serotonin stable').toString('hex')}`);
+        expect(properties[i].value).to.be.equal('Serotonin stable');
+        expect(tokensData[i].value).to.be.equal('Serotonin stable');
       }
     });
   });
@@ -460,7 +551,7 @@
         if (!permission.permission.mutable) continue;
         
         for (const signer of permission.signers) {
-          const key = i + ' ' + signer.address;
+          const key = i + '_' + signer.address;
           propertyKeys.push(key);
 
           await expect(executeTransaction(
@@ -532,13 +623,13 @@
         await expect(executeTransaction(
           api, 
           alice, 
-          api.tx.unique.setPropertyPermissions(collection, [{key: i, permission: passage.permission}]), 
+          api.tx.unique.setPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 
         ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
 
         await expect(executeTransaction(
           api, 
           signer, 
-          api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin increase'}]), 
+          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 
         ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
 
         i++;
@@ -558,17 +649,16 @@
         await expect(executeTransaction(
           api, 
           forbiddance.sinner, 
-          api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin down'}]), 
+          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 
         ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
 
         await expect(executeTransaction(
           api, 
           forbiddance.sinner, 
-          api.tx.unique.deleteTokenProperties(collection, token, [forbiddance.permission]), 
+          api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 
         ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
       }
 
-      // todo RPC?
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.consumedSpace).to.be.equal(originalSpace);
     });
@@ -584,7 +674,7 @@
         await expect(executeTransaction(
           api, 
           permission.signers[0], 
-          api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin down'}]), 
+          api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 
         ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
 
         await expect(executeTransaction(
@@ -594,7 +684,6 @@
         ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
       }
 
-      // todo RPC?
       const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(properties.consumedSpace).to.be.equal(originalSpace);
     });
@@ -632,8 +721,8 @@
         api, 
         alice, 
         api.tx.unique.setPropertyPermissions(collection, [
-          {key: 'a holy book', permission: {collectionAdmin: true, tokenOwner: true}}, 
-          {key: 'young years', permission: {collectionAdmin: true, tokenOwner: true}},
+          {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 
+          {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},
         ]), 
       ), 'on setting a new non-permitted property').to.not.be.rejected;
 
@@ -643,7 +732,7 @@
         await expect(executeTransaction(
           api, 
           alice, 
-          api.tx.unique.setCollectionProperties(collection, [{key: 'a holy book', value: 'word '.repeat(6554)}]), 
+          api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 
         )).to.be.rejected;
       }
 
@@ -651,12 +740,12 @@
         api, 
         alice, 
         api.tx.unique.setTokenProperties(collection, token, [
-          {key: 'a holy book', value: 'word '.repeat(3277)}, 
-          {key: 'young years', value: 'neverending'.repeat(1490)},
+          {key: 'a_holy_book', value: 'word '.repeat(3277)}, 
+          {key: 'young_years', value: 'neverending'.repeat(1490)},
         ]), 
       )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
 
-      expect((await api.rpc.unique.tokenProperties(collection, token, ['a holy book', 'young years'])).toJSON()).to.be.empty;
+      expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young years'])).toJSON()).to.be.empty;
       const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
       expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);
     });
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -6,11 +6,13 @@
   createCollectionExpectSuccess,
   createItemExpectFailure,
   createItemExpectSuccess,
+  getBalance,
   getTokenOwner,
   getTopmostTokenOwner,
   normalizeAccountId,
   setCollectionLimitsExpectSuccess,
   transferExpectSuccess,
+  transferFromExpectSuccess,
 } from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -25,7 +27,7 @@
     });
   });
 
-  it('Allows the owner to successfully unnest a token', async () => {
+  it('NFT: allows the owner to successfully unnest a token', async () => {
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
@@ -40,7 +42,7 @@
         api,
         alice,
         api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),
-      )).to.not.be.rejected;
+      ), 'while unnesting').to.not.be.rejected;
       expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
 
       // Nest and burn
@@ -48,13 +50,64 @@
       await expect(executeTransaction(
         api,
         alice,
-        api.tx.unique.burnFrom(collection, normalizeAccountId(alice.address), nestedToken, 1),
-      )).to.not.be.rejected;
-      await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected; // 'owner == null'
+        api.tx.unique.burnFrom(collection, normalizeAccountId(targetAddress), nestedToken, 1),
+      ), 'while burning').to.not.be.rejected;
+      await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected;
     });
   });
 
-  // todo refungible-fungible test just in case
+  it('Fungible: allows the owner to successfully unnest a token', async () => {
+    await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
+      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
+      const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};
+
+      const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+      const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+
+      // Nest and unnest
+      await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');
+      await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible');
+
+      // Nest and burn
+      await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');
+      const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);
+      await expect(executeTransaction(
+        api,
+        alice,
+        api.tx.unique.burnFrom(collectionFT, normalizeAccountId(targetAddress), nestedToken, 1),
+      ), 'while burning').to.not.be.rejected;
+      const balanceAfter = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);
+      expect(balanceAfter + BigInt(1)).to.be.equal(balanceBefore);
+    });
+  });
+
+  it('ReFungible: allows the owner to successfully unnest a token', async () => {
+    await usingApi(async api => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
+      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
+      const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};
+
+      const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+      const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+
+      // Nest and unnest
+      await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');
+      await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible');
+
+      // Nest and burn
+      await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');
+      await expect(executeTransaction(
+        api,
+        alice,
+        api.tx.unique.burnFrom(collectionRFT, normalizeAccountId(targetAddress), nestedToken, 1),
+      ), 'while burning').to.not.be.rejected;
+      const balance = await getBalance(api, collectionRFT, normalizeAccountId(targetAddress), nestedToken);
+      expect(balance).to.be.equal(0n);
+    });
+  });
 });
 
 describe('Negative Test: Unnesting', () => {
@@ -80,8 +133,7 @@
         api,
         bob,
         api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),
-      )).to.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
-      //await transferFromExpectSuccess(collection, nestedToken, bob, targetAddress, {Substrate: bob.address});
+      ), 'while unnesting').to.be.rejectedWith(/^structure\.DepthLimit$/); // todo ApprovedValueTooLow?
       expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
 
       // Try to burn
@@ -89,31 +141,15 @@
         api,
         bob,
         api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),
-      )).to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
+      ), 'while burning').to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
       expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
     });
   });
 
-  it('Disallows excessive token nesting', async () => {
-    await usingApi(async api => {
-      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-      await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
-      const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
-
-      // Create a nested token matryoshka
-      const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
-      const nestedToken2 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken1)});
-      // The nesting depth is limited by 2
-      await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken2)});
+  // todo another test for creating excessive depth matryoshka with Ethereum?
 
-      expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});
-    });
-  });
-
-  // todo another test for creating excessive depth matryoshka with Ethereum, move this one to nest ^
-
   // Recursive nesting
-  it('Prevents ouroboros creation', async () => {
+  it('Prevents Ouroboros creation', async () => {
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
     const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -903,7 +903,7 @@
     const from = normalizeAccountId(accountFrom);
     const to = normalizeAccountId(accountTo);
     let balanceBefore = 0n;
-    if (type === 'Fungible') {
+    if (type === 'Fungible' || type === 'ReFungible') {
       balanceBefore = await getBalance(api, collectionId, to, tokenId);
     }
     const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom), to, collectionId, tokenId, value);
@@ -923,7 +923,7 @@
       }
     }
     if (type === 'ReFungible') {
-      expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(BigInt(value));
+      expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value));
     }
   });
 }