git.delta.rocks / unique-network / refs/commits / 4da04995c680

difftreelog

source

js-packages/tests/sub/nesting/unnest.test.ts13.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import type {IKeyringPair} from '@polkadot/types/types';18import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';19import {CrossAccountId, UniqueFTCollection, UniqueNFToken, UniqueRFToken} from '@unique-nft/playgrounds/unique.js';2021describe('Integration Test: Unnesting', () => {22  let alice: IKeyringPair;23  let bob: IKeyringPair;24  let charlie: IKeyringPair;2526  before(async () => {27    await usingPlaygrounds(async (helper, privateKey) => {28      const donor = await privateKey({url: import.meta.url});29      [alice, bob, charlie] = await helper.arrange.createAccounts([200n, 50n, 50n], donor);30    });31  });3233  itSub('NFT: allows the owner to successfully unnest a token', async ({helper}) => {34    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});35    const targetToken = await collection.mintToken(alice);3637    // Create a nested token38    const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());3940    // Unnest41    await expect(nestedToken.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}), 'while unnesting').to.be.fulfilled;42    expect(await nestedToken.getOwner()).to.be.deep.equal({Substrate: alice.address});4344    // Nest and burn45    await nestedToken.nest(alice, targetToken);46    await expect(nestedToken.burnFrom(alice, targetToken.nestingAccount()), 'while burning').to.be.fulfilled;47    await expect(nestedToken.getOwner()).to.be.rejected;48  });4950  itSub('NativeFungible: allows the owner to successfully unnest a token', async ({helper}) => {51    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});52    const targetToken = await collection.mintToken(alice);5354    const collectionFT = helper.ft.getCollectionObject(0);5556    // Nest57    await collectionFT.transfer(alice, targetToken.nestingAccount(), 10n);58    // Unnest59    await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;6061    expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n);62  });6364  itSub('Fungible: allows the owner to successfully unnest a token', async ({helper}) => {65    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});66    const targetToken = await collection.mintToken(alice);6768    const collectionFT = await helper.ft.mintCollection(alice);6970    // Nest and unnest71    await collectionFT.mint(alice, 10n, targetToken.nestingAccount());72    await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;73    expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(9n);74    expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n);7576    // Nest and burn77    await collectionFT.transfer(alice, targetToken.nestingAccount(), 5n);78    await expect(collectionFT.burnTokensFrom(alice, targetToken.nestingAccount(), 6n), 'while burning').to.be.fulfilled;79    expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(4n);80    expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(0n);81    expect(await targetToken.getChildren()).to.be.length(0);82  });8384  itSub.ifWithPallets('ReFungible: allows the owner to successfully unnest a token', [Pallets.ReFungible], async ({helper}) => {85    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});86    const targetToken = await collection.mintToken(alice);8788    const collectionRFT = await helper.rft.mintCollection(alice);8990    // Nest and unnest91    const token = await collectionRFT.mintToken(alice, 10n, targetToken.nestingAccount());92    await expect(token.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;93    expect(await token.getBalance({Substrate: alice.address})).to.be.equal(9n);94    expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(1n);9596    // Nest and burn97    await token.transfer(alice, targetToken.nestingAccount(), 5n);98    await expect(token.burnFrom(alice, targetToken.nestingAccount(), 6n), 'while burning').to.be.fulfilled;99    expect(await token.getBalance({Substrate: alice.address})).to.be.equal(4n);100    expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(0n);101    expect(await targetToken.getChildren()).to.be.length(0);102  });103104  async function checkNestedAmountState({105    expectedBalance,106    childrenShouldPresent,107    nested,108    targetNft,109  }: {110    expectedBalance: bigint,111    childrenShouldPresent: boolean,112    nested: UniqueFTCollection | UniqueRFToken,113    targetNft: UniqueNFToken,114  }) {115    const balance = await nested.getBalance(targetNft.nestingAccount());116    expect(balance).to.be.equal(expectedBalance);117118    const children = await targetNft.getChildren();119120    if(childrenShouldPresent) {121      expect(children[0]).to.be.deep.equal({122        collectionId: nested.collectionId,123        tokenId: (nested instanceof UniqueFTCollection) ? 0 : nested.tokenId,124      });125    } else {126      expect(children.length).to.be.equal(0);127    }128  }129130  function ownerOrAdminUnnestCases(modes: ('ft' | 'nft' | 'rft')[]): {131    mode: 'ft' | 'nft' | 'rft',132    sender: string,133    op: 'transfer' | 'burn',134    requiredPallets: Pallets[],135  }[] {136    const senders = ['owner', 'admin'];137    const ops = ['transfer', 'burn'];138139    const cases = [];140    for(const mode of modes) {141      const requiredPallets = (mode === 'rft')142        ? [Pallets.ReFungible]143        : [];144145      for(const sender of senders) {146        for(const op of ops) {147          cases.push({148            mode: mode as 'ft' | 'nft' | 'rft',149            sender,150            op: op as 'transfer' | 'burn',151            requiredPallets,152          });153        }154      }155    }156157    return cases;158  }159160  ownerOrAdminUnnestCases(['ft', 'rft']).map(testCase =>161    itSub.ifWithPallets(`[${testCase.mode}]: allows a collection ${testCase.sender} to ${testCase.op} nested token`, testCase.requiredPallets, async({helper}) => {162      const owner = alice;163      const admin = bob;164165      const unnester = (testCase.sender === 'owner')166        ? owner167        : admin;168169      const collectionNFT = await helper.nft.mintCollection(owner);170      await collectionNFT.setPermissions(owner, {nesting: {tokenOwner: true}});171172      const collectionNested = await helper[testCase.mode as 'ft' | 'rft'].mintCollection(owner, {173        limits: {174          ownerCanTransfer: true,175        },176      });177      await collectionNested.addAdmin(owner, {Substrate: admin.address});178179      const targetNft = await collectionNFT.mintToken(owner, {Substrate: charlie.address});180181      let nested: UniqueFTCollection | UniqueRFToken;182      const totalAmount = 5n;183      const firstUnnestAmount = 2n;184      const restUnnestAmount = totalAmount - firstUnnestAmount;185186      if(collectionNested instanceof UniqueFTCollection) {187        await collectionNested.mint(owner, totalAmount, {Substrate: charlie.address});188        nested = collectionNested;189      } else {190        nested = await collectionNested.mintToken(owner, totalAmount, {Substrate: charlie.address});191      }192193      // transfer/burn `amount` of nested assets by `unnester`.194      const doOperationAndCheck = async ({195        amount,196        shouldBeNestedAfterOp,197      }: {198        amount: bigint,199        shouldBeNestedAfterOp: boolean,200      }) => {201        const nestedBalanceBeforeOp = await nested.getBalance(targetNft.nestingAccount());202203        if(testCase.op === 'transfer') {204          const bobBalanceBeforeOp = await nested.getBalance({Substrate: bob.address});205206          await nested.transferFrom(unnester, targetNft.nestingAccount(), {Substrate: bob.address}, amount);207          expect(await nested.getBalance({Substrate: bob.address})).to.be.equal(bobBalanceBeforeOp + amount);208        } else {209          if(nested instanceof UniqueFTCollection) {210            await nested.burnTokensFrom(unnester, targetNft.nestingAccount(), amount);211          } else {212            await nested.burnFrom(unnester, targetNft.nestingAccount(), amount);213          }214        }215216        await checkNestedAmountState({217          expectedBalance: nestedBalanceBeforeOp - amount,218          childrenShouldPresent: shouldBeNestedAfterOp,219          nested,220          targetNft,221        });222      };223224      // Initial setup: nest (fungibles/rft parts).225      // Check NFT's balance of nested assets and NFT's children.226      await nested.transfer(charlie, targetNft.nestingAccount(), totalAmount);227      await checkNestedAmountState({228        expectedBalance: totalAmount,229        childrenShouldPresent: true,230        nested,231        targetNft,232      });233234      // Transfer/burn only a part of nested assets.235      // Check that NFT's balance of the nested assets correctly decreased and NFT's children are not changed.236      await doOperationAndCheck({237        amount: firstUnnestAmount,238        shouldBeNestedAfterOp: true,239      });240241      // Transfer/burn all remaining nested assets.242      // Check that NFT's balance of the nested assets is 0 and NFT has no more children.243      await doOperationAndCheck({244        amount: restUnnestAmount,245        shouldBeNestedAfterOp: false,246      });247    }));248249  ownerOrAdminUnnestCases(['nft']).map(testCase =>250    itSub(`[nft]: allows a collection ${testCase.sender} to ${testCase.op} nested token`, async ({helper}) => {251      const owner = alice;252      const admin = bob;253254      const unnester = (testCase.sender === 'owner')255        ? owner256        : admin;257258      const collectionNFT = await helper.nft.mintCollection(owner);259      await collectionNFT.setPermissions(owner, {nesting: {tokenOwner: true}});260261      const collectionNested = await helper.nft.mintCollection(owner, {262        limits: {263          ownerCanTransfer: true,264        },265      });266      await collectionNested.addAdmin(owner, {Substrate: admin.address});267268      const targetNft = await collectionNFT.mintToken(owner, {Substrate: charlie.address});269      const nested = await collectionNested.mintToken(owner, {Substrate: charlie.address});270271      await nested.transfer(charlie, targetNft.nestingAccount());272      expect(await targetNft.getChildren()).to.be.deep.equal([{273        collectionId: nested.collectionId,274        tokenId: nested.tokenId,275      }]);276277      if(testCase.op === 'transfer') {278        await nested.transferFrom(unnester, targetNft.nestingAccount(), {Substrate: bob.address});279      } else {280        await nested.burnFrom(unnester, targetNft.nestingAccount());281      }282283      expect((await targetNft.getChildren()).length).to.be.equal(0);284    }));285});286287describe('Negative Test: Unnesting', () => {288  let alice: IKeyringPair;289  let bob: IKeyringPair;290291  before(async () => {292    await usingPlaygrounds(async (helper, privateKey) => {293      const donor = await privateKey({url: import.meta.url});294      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);295    });296  });297298  itSub('Disallows a non-owner to unnest/burn a token', async ({helper}) => {299    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});300    const targetToken = await collection.mintToken(alice);301302    // Create a nested token303    const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());304305    // Try to unnest306    await expect(nestedToken.unnest(bob, targetToken, {Substrate: alice.address})).to.be.rejectedWith(/common\.ApprovedValueTooLow/);307    expect(await nestedToken.getOwner()).to.be.deep.equal(CrossAccountId.toLowerCase(targetToken.nestingAccount()));308309    // Try to burn310    await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.ApprovedValueTooLow/);311    expect(await nestedToken.getOwner()).to.be.deep.equal(CrossAccountId.toLowerCase(targetToken.nestingAccount()));312  });313314  // todo another test for creating excessive depth matryoshka with Ethereum?315316  // Recursive nesting317  itSub('Prevents Ouroboros creation', async ({helper}) => {318    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});319    const targetToken = await collection.mintToken(alice);320321    // Fail to create a nested token ouroboros322    const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());323    await expect(targetToken.nest(alice, nestedToken)).to.be.rejectedWith(/^structure\.OuroborosDetected$/);324  });325});