difftreelog
test(nesting) extended test suite
in: master
4 files changed
tests/package.jsondiffbeforeafterboth1{2 "name": "unique-tests",3 "version": "1.0.0",4 "description": "Unique Chain Tests",5 "main": "",6 "devDependencies": {7 "@polkadot/dev": "0.66.17",8 "@polkadot/ts": "0.4.22",9 "@polkadot/typegen": "8.3.1",10 "@types/chai": "^4.3.1",11 "@types/chai-as-promised": "^7.1.5",12 "@types/mocha": "^9.1.1",13 "@types/node": "^17.0.31",14 "@typescript-eslint/eslint-plugin": "^5.22.0",15 "@typescript-eslint/parser": "^5.22.0",16 "chai": "^4.3.6",17 "eslint": "^8.14.0",18 "mocha": "^10.0.0",19 "ts-node": "^10.7.0",20 "typescript": "^4.6.4"21 },22 "mocha": {23 "timeout": 9999999,24 "require": "ts-node/register"25 },26 "scripts": {27 "lint": "eslint --ext .ts,.js src/",28 "fix": "eslint --ext .ts,.js src/ --fix",29 "test": "mocha --timeout 9999999 -r ts-node/register './src/**/*.test.ts'",30 "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",31 "testEthMarketplace": "mocha --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'",32 "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",33 "loadTransfer": "ts-node src/transfer.nload.ts",34 "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",35 "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",36 "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",37 "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",38 "testSetSchemaVersion": "mocha --timeout 9999999 -r ts-node/register ./**/setSchemaVersion.test.ts",39 "testSetVariableMetaData": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetaData.test.ts",40 "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",41 "testSetCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionSponsor.test.ts",42 "testConfirmSponsorship": "mocha --timeout 9999999 -r ts-node/register ./**/confirmSponsorship.test.ts",43 "testRemoveCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionAdmin.test.ts",44 "testRemoveCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionSponsor.test.ts",45 "testRemoveFromAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromAllowList.test.ts",46 "testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts",47 "testCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",48 "testContracts": "mocha --timeout 9999999 -r ts-node/register ./**/contracts.test.ts",49 "testCreateItem": "mocha --timeout 9999999 -r ts-node/register ./**/createItem.test.ts",50 "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts",51 "testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts",52 "testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts",53 "testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",54 "testToggleContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/toggleContractAllowList.test.ts",55 "testAddToContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/addToContractAllowList.test.ts",56 "testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts",57 "testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts",58 "testSetMintPermission": "mocha --timeout 9999999 -r ts-node/register ./**/setMintPermission.test.ts",59 "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts",60 "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts",61 "testRemoveFromContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromContractAllowList.test.ts",62 "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts",63 "testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts",64 "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",65 "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",66 "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",67 "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",68 "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",69 "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",70 "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",71 "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",72 "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",73 "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",74 "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",75 "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"76 },77 "author": "",78 "license": "SEE LICENSE IN ../LICENSE",79 "homepage": "",80 "dependencies": {81 "@polkadot/api": "8.3.1",82 "@polkadot/api-contract": "8.3.1",83 "@polkadot/util-crypto": "^9.1.1",84 "bignumber.js": "^9.0.2",85 "chai-as-promised": "^7.1.1",86 "solc": "^0.8.13",87 "web3": "^1.7.3"88 },89 "standard": {90 "globals": [91 "it",92 "assert",93 "beforeEach",94 "afterEach",95 "describe",96 "contract",97 "artifacts"98 ]99 },100 "resolutions": {101 "simple-get": "^4.0.1"102 }103}tests/src/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -1,37 +1,510 @@
import {expect} from 'chai';
import {tokenIdToAddress} from '../eth/util/helpers';
import privateKey from '../substrate/privateKey';
-import usingApi from '../substrate/substrate-api';
-import {createCollectionExpectSuccess, createItemExpectSuccess, getTokenOwner, getTopmostTokenOwner, setCollectionLimitsExpectSuccess, transferExpectSuccess, transferFromExpectSuccess} from '../util/helpers';
+import usingApi, {executeTransaction} from '../substrate/substrate-api';
+import {
+ addToAllowListExpectSuccess,
+ createCollectionExpectSuccess,
+ createItemExpectFailure,
+ createItemExpectSuccess,
+ enableAllowListExpectSuccess,
+ enablePublicMintingExpectSuccess,
+ getTokenOwner,
+ getTopmostTokenOwner,
+ setCollectionLimitsExpectSuccess,
+ transferExpectFailure,
+ transferExpectSuccess,
+ transferFromExpectSuccess,
+} from '../util/helpers';
+import {IKeyringPair} from '@polkadot/types/types';
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+
+describe('Integration Test: Nesting', () => {
+ before(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+
+ // ---------- Non-Fungible ----------
-describe('nesting', () => {
- it('allows to nest/unnest token', async () => {
+ // todo refactor names
+ // todo remove excessive bundling, leave it for a single test
+ it('NFT: allows to nest/unnest token if nesting rule is Owner', async () => {
await usingApi(async api => {
- const alice = privateKey('//Alice');
- const bob = privateKey('//Bob');
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
+ const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ // Create a nested token
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ // Nest
+ await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Move bundle to different user
+ await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});
+ expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Unnest
+ await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});
+ });
+ });
+ it('NFT: allows to nest/unnest token if Owner-Restricted', async () => {
+ await usingApi(async api => {
const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
- await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule:{OwnerRestricted:[collection]}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});
const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
- const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT');
+ // Create a nested token
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+
// Nest
- await transferExpectSuccess(collection, nestedToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
-
+ await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
// Move bundle to different user
await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});
-
expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});
expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Unnest
+ await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});
+ });
+ });
+
+ // ---------- Fungible ----------
+
+ it('Fungible: allows to nest/unnest token if Owner', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ // Create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.not.be.rejected;
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ // Nest
+ await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');
+ // Move bundle to different user
+ await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
+ // Unnest
+ await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');
+ });
+ });
+
+ it('Fungible: allows to nest/unnest token if Owner-Restricted', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionFT]}});
+
+ // Create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.not.be.rejected;
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ // Nest
+ await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');
+ // Move bundle to different user
+ await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
// Unnest
- await transferFromExpectSuccess(collection, nestedToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});
+ await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');
+ });
+ });
+
+ // ---------- Re-Fungible ----------
+
+ it('ReFungible: allows to nest/unnest token if Owner', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
- expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});
+ // Create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.not.be.rejected;
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ // Nest
+ await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');
+ // Move bundle to different user
+ await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
+ // Unnest
+ await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');
+ });
+ });
+
+ it('ReFungible: allows to nest/unnest token if Owner-Restricted', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionRFT]}});
+
+ // Create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.not.be.rejected;
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ // Nest
+ await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');
+ // Move bundle to different user
+ await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
+ // Unnest
+ await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');
+ });
+ });
+});
+
+describe('Negative Test: Nesting', async() => {
+ before(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+
+ // ---------- Non-Fungible ----------
+
+ it('NFT: disallows to nest token if nesting is disabled', async () => {
+ await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Disabled'});
+ const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ // Try to create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collection,
+ {Ethereum: tokenIdToAddress(collection, targetToken)},
+ {nft: {const_data: [], variable_data: []}} as any,
+ ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+ // Try to nest
+ await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); // todo to.be.rejected
+ expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
+ });
+ });
+
+ it('NFT: disallows to nest token if not Owner', async () => {
+ await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
+
+ await addToAllowListExpectSuccess(alice, collection, bob.address);
+ await enableAllowListExpectSuccess(alice, collection);
+ await enablePublicMintingExpectSuccess(alice, collection);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');
+
+ // Try to create a nested token in the wrong collection
+ await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+ await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
+ });
+ });
+
+ it('NFT: disallows to nest token if not Owner (Restricted nesting)', async () => {
+ await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});
+
+ await addToAllowListExpectSuccess(alice, collection, bob.address);
+ await enableAllowListExpectSuccess(alice, collection);
+ await enablePublicMintingExpectSuccess(alice, collection);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');
+
+ // Try to create a nested token in the wrong collection
+ await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+ await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
+ });
+ });
+
+ it('NFT: disallows to nest token to an unlisted collection', async () => {
+ await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[]}});
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ // Try to create a nested token in the wrong collection
+ await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
+ await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
+ });
+ });
+
+ // ---------- Fungible ----------
+
+ it('Fungible: disallows to nest token if nesting is disabled', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ // Try to create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ // Try to nest
+ await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);
+ });
+ });
+
+ it('Fungible: disallows to nest token if not Owner', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
+
+ await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
+ await enableAllowListExpectSuccess(alice, collectionNFT);
+ await enablePublicMintingExpectSuccess(alice, collectionNFT);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);
+ });
+ });
+
+ it('Fungible: disallows to nest token if not Owner (Restricted nesting)', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}}); // todo clear redundant restrictions
+
+ await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
+ await enableAllowListExpectSuccess(alice, collectionNFT);
+ await enablePublicMintingExpectSuccess(alice, collectionNFT);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);
+ });
+ });
+
+ it('Fungible: disallows to nest token to an unlisted collection', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionFT,
+ targetAddress,
+ {Fungible: {Value: 10}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
+ await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);
+ });
+ });
+
+ // ---------- Re-Fungible ----------
+
+ it('ReFungible: disallows to nest token if nesting is disabled', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Disabled'});
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+
+ // Create a nested token
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);
+
+ // Create a token to be nested
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ // Try to nest
+ await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);
+ });
+ });
+
+ it('ReFungible: disallows to nest token if not Owner', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
+
+ await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
+ await enableAllowListExpectSuccess(alice, collectionNFT);
+ await enablePublicMintingExpectSuccess(alice, collectionNFT);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);
+ });
+ });
+
+ it('ReFungible: disallows to nest token if not Owner (Restricted nesting)', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}});
+
+ await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
+ await enableAllowListExpectSuccess(alice, collectionNFT);
+ await enablePublicMintingExpectSuccess(alice, collectionNFT);
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);
+ });
+ });
+
+ it('ReFungible: disallows to nest token to an unlisted collection', async () => {
+ await usingApi(async api => {
+ const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});
+
+ // Create a token to attempt to be nested into
+ const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
+
+ const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+
+ // Try to create a nested token in the wrong collection
+ await expect(executeTransaction(api, alice, api.tx.unique.createItem(
+ collectionRFT,
+ targetAddress,
+ {ReFungible: {const_data: [], variable_data: [], pieces: 100}},
+ ))).to.be.rejected;
+
+ // Try to create and nest a token in the wrong collection
+ const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
+ await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);
});
});
});
tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/nesting/unnest.test.ts
@@ -0,0 +1,130 @@
+import {expect} from 'chai';
+import {tokenIdToAddress} from '../eth/util/helpers';
+import privateKey from '../substrate/privateKey';
+import usingApi, {executeTransaction} from '../substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ createItemExpectFailure,
+ createItemExpectSuccess,
+ getTokenOwner,
+ getTopmostTokenOwner,
+ normalizeAccountId,
+ setCollectionLimitsExpectSuccess,
+ transferExpectFailure,
+ transferExpectSuccess,
+} from '../util/helpers';
+import {IKeyringPair} from '@polkadot/types/types';
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+
+describe('Integration Test: Unnesting', () => {
+ before(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+
+ it('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)};
+
+ // Create a nested token
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);
+
+ // Unnest
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),
+ )).to.not.be.rejected;
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
+
+ // Nest and burn
+ await transferExpectSuccess(collection, nestedToken, alice, targetAddress);
+ 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'
+ });
+ });
+
+ // todo refungible-fungible test just in case
+});
+
+describe('Negative Test: Unnesting', () => {
+ before(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+
+ it('Disallows a non-owner to unnest/burn 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)};
+
+ // Create a nested token
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);
+
+ // Try to unnest
+ await expect(executeTransaction(
+ 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});
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Try to burn
+ await expect(executeTransaction(
+ api,
+ bob,
+ api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),
+ )).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)});
+
+ 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-nested operations', 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 ouroboros
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+ await transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)});
+
+ expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+
+ // Make sure the ouroboros is detected
+ await expect(getTopmostTokenOwner(api, collection, nestedToken)).to.be.rejected; // With(/^common\.DepthLimit$/);
+ // todo transferFrom, must exit with Ouroboros error
+ });
+ });
+});
\ No newline at end of file
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -943,11 +943,11 @@
collectionId: number,
tokenId: number,
sender: IKeyringPair,
- recipient: IKeyringPair,
+ recipient: IKeyringPair | CrossAccountId,
value: number | bigint = 1,
) {
await usingApi(async (api: ApiPromise) => {
- const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
+ const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value);
const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected;
const result = getGenericResult(events);
// if (events && Array.isArray(events)) {
@@ -1090,7 +1090,7 @@
return newItemId;
}
-export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: string = sender.address) {
+export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) {
await usingApi(async (api) => {
const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode);