1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import privateKey from '../substrate/privateKey';4import usingApi, {executeTransaction} from '../substrate/substrate-api';5import {6 createCollectionExpectSuccess,7 createItemExpectFailure,8 createItemExpectSuccess,9 getBalance,10 getTokenOwner,11 getTopmostTokenOwner,12 normalizeAccountId,13 setCollectionLimitsExpectSuccess,14 transferExpectSuccess,15 transferFromExpectSuccess,16} from '../util/helpers';17import {IKeyringPair} from '@polkadot/types/types';1819let alice: IKeyringPair;20let bob: IKeyringPair;2122describe('Integration Test: Unnesting', () => {23 before(async () => {24 await usingApi(async api => {25 alice = privateKey('//Alice');26 bob = privateKey('//Bob');27 });28 });2930 it('NFT: allows the owner to successfully unnest a token', async () => {31 await usingApi(async api => {32 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});33 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});34 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');35 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};3637 38 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);3940 41 await expect(executeTransaction(42 api,43 alice,44 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),45 ), 'while unnesting').to.not.be.rejected;46 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});4748 49 await transferExpectSuccess(collection, nestedToken, alice, targetAddress);50 await expect(executeTransaction(51 api,52 alice,53 api.tx.unique.burnFrom(collection, normalizeAccountId(targetAddress), nestedToken, 1),54 ), 'while burning').to.not.be.rejected;55 await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected;56 });57 });5859 it('Fungible: allows the owner to successfully unnest a token', async () => {60 await usingApi(async api => {61 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});62 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});63 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');64 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};6566 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});67 const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');6869 70 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');71 await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible');7273 74 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');75 const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);76 await expect(executeTransaction(77 api,78 alice,79 api.tx.unique.burnFrom(collectionFT, normalizeAccountId(targetAddress), nestedToken, 1),80 ), 'while burning').to.not.be.rejected;81 const balanceAfter = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);82 expect(balanceAfter + BigInt(1)).to.be.equal(balanceBefore);83 });84 });8586 it('ReFungible: allows the owner to successfully unnest a token', async () => {87 await usingApi(async api => {88 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});89 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});90 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');91 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};9293 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});94 const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');9596 97 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');98 await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible');99100 101 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');102 await expect(executeTransaction(103 api,104 alice,105 api.tx.unique.burnFrom(collectionRFT, normalizeAccountId(targetAddress), nestedToken, 1),106 ), 'while burning').to.not.be.rejected;107 const balance = await getBalance(api, collectionRFT, normalizeAccountId(targetAddress), nestedToken);108 expect(balance).to.be.equal(0n);109 });110 });111});112113describe('Negative Test: Unnesting', () => {114 before(async () => {115 await usingApi(async api => {116 alice = privateKey('//Alice');117 bob = privateKey('//Bob');118 });119 });120121 it('Disallows a non-owner to unnest/burn a token', async () => {122 await usingApi(async api => {123 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});124 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});125 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');126 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};127128 129 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);130131 132 await expect(executeTransaction(133 api,134 bob,135 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),136 ), 'while unnesting').to.be.rejectedWith(/^structure\.DepthLimit$/); 137 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});138139 140 await expect(executeTransaction(141 api,142 bob,143 api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),144 ), 'while burning').to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);145 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});146 });147 });148149 150151 152 it('Prevents Ouroboros creation', async () => {153 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});154 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});155 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');156157 158 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});159 expect(transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)})).to.be.rejectedWith(/^structure\.OuroborosDetected$/);160 });161});