--- /dev/null +++ b/tests/src/.outdated/rmrk/acceptNft.seqtest.ts @@ -0,0 +1,107 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import { + createCollection, + mintNft, + sendNft, + acceptNft, +} from './util/tx'; +import {NftIdTuple} from './util/fetch'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; + +describe('integration test: accept NFT', () => { + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + const alice = '//Alice'; + const bob = '//Bob'; + + const createTestCollection = async (issuerUri: string) => { + return await createCollection( + api, + issuerUri, + 'accept-metadata', + null, + 'acpt', + ); + }; + + it('accept NFT', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); + await acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); + + const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.true; + }); + + it('[negative] unable to accept NFT by a not-an-owner', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); + const tx = acceptNft(api, bob, bobCollectionId, childNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative] unable to accept non-existing NFT', async () => { + const collectionId = 0; + const maxNftId = 0xFFFFFFFF; + + const owner = alice; + const aliceCollectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, owner, aliceCollectionId, 'parent-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + const tx = acceptNft(api, alice, collectionId, maxNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] unable to accept NFT which is not sent', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + const tx = acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + + const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.false; + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/addResource.seqtest.ts @@ -0,0 +1,435 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {NftIdTuple} from './util/fetch'; +import {expectTxFailure, getResourceById, requirePallets, Pallets} from './util/helpers'; +import { + addNftBasicResource, + acceptNftResource, + createCollection, + mintNft, + sendNft, + addNftSlotResource, + addNftComposableResource, +} from './util/tx'; +import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup'; + +describe('integration test: add NFT resource', () => { + const alice = '//Alice'; + const bob = '//Bob'; + const src = 'test-res-src'; + const metadata = 'test-res-metadata'; + const license = 'test-res-license'; + const thumb = 'test-res-thumb'; + + const nonexistentId = 99999; + + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + it('add resource', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + }); + + it('add a resource to the nested NFT', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; + + await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); + + await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + childNftId, + src, + metadata, + license, + thumb, + ); + }); + + it('add multiple resources', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const baseId = 42; + const slotId = 10; + const parts = [0, 5, 2]; + + const resourcesInfo = []; + const resourceNum = 4; + + const checkResource = (resource: ResourceInfo, resType: string, expectedId: number, expected: { + src: string, + metadata: string, + license: string, + thumb: string + }) => { + + // FIXME A workaround. It seems it is a PolkadotJS bug. + // All of the following are `false`. + // + // console.log('>>> basic:', resource.resource.isBasic); + // console.log('>>> composable:', resource.resource.isComposable); + // console.log('>>> slot:', resource.resource.isSlot); + const resourceJson = (resource.resource.toHuman() as any)[resType]; + + expect(resource.id.toNumber(), 'Error: Invalid resource Id') + .to.be.eq(expectedId); + + expect(resourceJson.src, 'Error: Invalid resource src') + .to.be.eq(expected.src); + expect(resourceJson.metadata, 'Error: Invalid resource metadata') + .to.be.eq(expected.metadata); + expect(resourceJson.license, 'Error: Invalid resource license') + .to.be.eq(expected.license); + expect(resourceJson.thumb, 'Error: Invalid resource thumb') + .to.be.eq(expected.thumb); + }; + + for (let i = 0; i < resourceNum; i++) { + resourcesInfo.push({ + src: src + 'r-' + i, + metadata: metadata + 'r-' + i, + license: license + 'r-' + i, + thumb: thumb + 'r-' + i, + }); + } + + const firstBasicResourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + resourcesInfo[0].src, + resourcesInfo[0].metadata, + resourcesInfo[0].license, + resourcesInfo[0].thumb, + ); + + const secondBasicResourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + resourcesInfo[1].src, + resourcesInfo[1].metadata, + resourcesInfo[1].license, + resourcesInfo[1].thumb, + ); + + const composableResourceId = await addNftComposableResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + parts, + baseId, + resourcesInfo[2].src, + resourcesInfo[2].metadata, + resourcesInfo[2].license, + resourcesInfo[2].thumb, + ); + + const slotResourceId = await addNftSlotResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + baseId, + slotId, + resourcesInfo[3].src, + resourcesInfo[3].metadata, + resourcesInfo[3].license, + resourcesInfo[3].thumb, + ); + + const firstResource = await getResourceById(api, collectionIdAlice, nftAlice, firstBasicResourceId); + await checkResource(firstResource, 'Basic', firstBasicResourceId, resourcesInfo[0]); + + const secondResource = await getResourceById(api, collectionIdAlice, nftAlice, secondBasicResourceId); + await checkResource(secondResource, 'Basic', secondBasicResourceId, resourcesInfo[1]); + + const composableResource = await getResourceById(api, collectionIdAlice, nftAlice, composableResourceId); + await checkResource(composableResource, 'Composable', composableResourceId, resourcesInfo[2]); + + const slotResource = await getResourceById(api, collectionIdAlice, nftAlice, slotResourceId); + await checkResource(slotResource, 'Slot', slotResourceId, resourcesInfo[3]); + }); + + it('[negative]: unable to add a resource to the non-existing NFT', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const tx = addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nonexistentId, + src, + metadata, + license, + thumb, + ); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative]: unable to add a resource by a not-an-owner user', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const tx = addNftBasicResource( + api, + bob, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative]: unable to add a resource to the nested NFT if it isnt root owned by the caller', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; + + await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); + + const tx = addNftBasicResource( + api, + bob, + 'added', + collectionIdAlice, + childNftId, + src, + metadata, + license, + thumb, + ); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('accept resource', async () => { + const collectionIdBob = await createCollection( + api, + bob, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + bob, + alice, + collectionIdBob, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + bob, + 'pending', + collectionIdBob, + nftAlice, + src, + metadata, + license, + thumb, + ); + + await acceptNftResource(api, alice, collectionIdBob, nftAlice, resourceId); + }); + + it('[negative]: unable to accept a non-existing resource', async () => { + const collectionIdBob = await createCollection( + api, + bob, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + bob, + alice, + collectionIdBob, + 'nft-metadata', + ); + + const tx = acceptNftResource(api, alice, collectionIdBob, nftAlice, nonexistentId); + await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); + }); + + it('[negative]: unable to accept a resource by a not-an-NFT-owner user', async () => { + const collectionIdBob = await createCollection( + api, + bob, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + bob, + alice, + collectionIdBob, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + bob, + 'pending', + collectionIdBob, + nftAlice, + src, + metadata, + license, + thumb, + ); + + const tx = acceptNftResource(api, bob, collectionIdBob, nftAlice, resourceId); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative]: unable to accept a resource to a non-target NFT', async () => { + const collectionIdBob = await createCollection( + api, + bob, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + bob, + alice, + collectionIdBob, + 'nft-metadata', + ); + + const wrongNft = await mintNft( + api, + bob, + alice, + collectionIdBob, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + bob, + 'pending', + collectionIdBob, + nftAlice, + src, + metadata, + license, + thumb, + ); + + const tx = acceptNftResource(api, bob, collectionIdBob, wrongNft, resourceId); + + await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); + }); + + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/addTheme.seqtest.ts @@ -0,0 +1,129 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {createBase, addTheme} from './util/tx'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {getThemeNames} from './util/fetch'; + +describe('integration test: add Theme to Base', () => { + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkEquip]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + + it('add default theme', async () => { + const baseId = await createBase(api, alice, 'default-themed-base', 'DTBase', []); + await addTheme(api, alice, baseId, { + name: 'default', + properties: [ + { + key: 'some-key', + value: 'some-key-value', + }, + { + key: 'another-key', + value: 'another-key-value', + }, + ], + }); + }); + + it('add default theme and a custom one', async () => { + const baseId = await createBase(api, alice, '2-themed-base', '2TBase', []); + await addTheme(api, alice, baseId, { + name: 'default', + properties: [ + { + key: 'default-key', + value: 'default-key-value', + }, + ], + }); + await addTheme(api, alice, baseId, { + name: 'custom-theme', + properties: [ + { + key: 'custom-key-0', + value: 'custom-key-value-0', + }, + { + key: 'custom-key-1', + value: 'custom-key-value-1', + }, + ], + }); + }); + + it('fetch filtered theme keys', async () => { + const baseId = await createBase(api, alice, '2-themed-base', '2TBase', []); + await addTheme(api, alice, baseId, { + name: 'default', + properties: [ + { + key: 'first-key', + value: 'first-key-value', + }, + { + key: 'second-key', + value: 'second-key-value', + }, + ], + }, ['second-key']); + }); + + it('fetch theme names', async() => { + const baseId = await createBase(api, alice, '3-themed-base', '3TBase', []); + const names = [ + 'default', + 'first-theme', + 'second-theme', + ]; + + for (let i = 0; i < names.length; i++) { + await addTheme(api, alice, baseId, {name: names[i], properties: [{key: 'dummy', value: 'dummy'}]}); + } + + const fetchedNames = await getThemeNames(api, baseId); + + for (let i = 0; i < names.length; i++) { + const isFound = fetchedNames.find((name) => name === names[i]) !== undefined; + + expect(isFound, 'Error: invalid theme names').to.be.true; + } + }); + + it('[negative] unable to add theme to non-existing base', async () => { + const maxBaseId = 0xFFFFFFFF; + const tx = addTheme(api, alice, maxBaseId, { + name: 'default', + properties: [], + }); + + await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); + }); + + it('[negative] unable to add custom theme if no default theme', async () => { + const baseId = await createBase(api, alice, 'no-default-themed-base', 'NDTBase', []); + const tx = addTheme(api, alice, baseId, { + name: 'custom-theme', + properties: [], + }); + + await expectTxFailure(/rmrkEquip\.NeedsDefaultThemeFirst/, tx); + }); + + it('[negative] unable to add theme by a not-an-owner', async () => { + const baseId = await createBase(api, alice, 'no-default-themed-base', 'NDTBase', []); + const tx = addTheme(api, bob, baseId, { + name: 'default', + properties: [], + }); + + await expectTxFailure(/rmrkEquip\.PermissionError/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/burnNft.seqtest.ts @@ -0,0 +1,170 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {NftIdTuple, getChildren} from './util/fetch'; +import {burnNft, createCollection, sendNft, mintNft} from './util/tx'; + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('integration test: burn nft', () => { + const alice = '//Alice'; + const bob = '//Bob'; + + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + it('burn nft', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const nftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + await burnNft(api, alice, collectionId, nftId); + }); + }); + + it('burn nft with children', async () => { + const collectionId = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const childNftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + const childrenBefore = await getChildren(api, collectionId, parentNftId); + expect(childrenBefore.length === 1, 'Error: parent NFT should have children') + .to.be.true; + + const child = childrenBefore[0]; + expect(child.collectionId.eq(collectionId), 'Error: invalid child collection Id') + .to.be.true; + + expect(child.nftId.eq(childNftId), 'Error: invalid child NFT Id') + .to.be.true; + + await burnNft(api, alice, collectionId, parentNftId); + + const childrenAfter = await getChildren(api, collectionId, parentNftId); + + expect(childrenAfter.length === 0, 'Error: children should be burned').to.be.true; + }); + + it('burn child nft', async () => { + const collectionId = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const childNftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + const childrenBefore = await getChildren(api, collectionId, parentNftId); + expect(childrenBefore.length === 1, 'Error: parent NFT should have children') + .to.be.true; + + const child = childrenBefore[0]; + expect(child.collectionId.eq(collectionId), 'Error: invalid child collection Id') + .to.be.true; + + expect(child.nftId.eq(childNftId), 'Error: invalid child NFT Id') + .to.be.true; + + await burnNft(api, alice, collectionId, childNftId); + + const childrenAfter = await getChildren(api, collectionId, parentNftId); + + expect(childrenAfter.length === 0, 'Error: children should be burned').to.be.true; + }); + + it('[negative] burn non-existing NFT', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const tx = burnNft(api, alice, collectionId, 99999); + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + }); + + it('[negative] burn not an owner NFT user', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const nftId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + const tx = burnNft(api, bob, collectionId, nftId); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/changeCollectionIssuer.seqtest.ts @@ -0,0 +1,53 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import { + changeIssuer, + createCollection, +} from './util/tx'; + +describe('integration test: collection issuer', () => { + const alice = '//Alice'; + const bob = '//Bob'; + + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + + it('change collection issuer', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + await changeIssuer(api, alice, collectionId, bob); + }); + }); + + it('[negative] change not an owner NFT collection issuer', async () => { + await createCollection(api, bob, 'test-metadata', null, 'test-symbol').then(async (collectionId) => { + const tx = changeIssuer(api, alice, collectionId, bob); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + }); + + it('[negative] change non-existigit NFT collection issuer', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async () => { + const tx = changeIssuer(api, alice, 99999, bob); + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/createBase.seqtest.ts @@ -0,0 +1,88 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from './util/helpers'; +import {createCollection, createBase} from './util/tx'; + +describe('integration test: create new Base', () => { + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); + }); + + const alice = '//Alice'; + + it('create empty Base', async () => { + await createBase(api, alice, 'empty-base-type', 'EBase', []); + }); + + it('create Base with fixed part', async () => { + await createBase(api, alice, 'fixedpart-base-type', 'FPBase', [ + { + 'FixedPart': { + id: 42, + z: 0, + src: 'some-fixed-url', + }, + }, + ]); + }); + + it('create Base with slot part (no collection)', async () => { + await createBase(api, alice, 'slotpart-base-type', 'SPBase', [ + { + 'SlotPart': { + id: 112, + equippable: 'Empty', + z: 0, + src: 'some-fallback-slot-url', + }, + }, + ]); + }); + + it('create Base with slot part (any collection)', async () => { + await createBase(api, alice, 'slotpartany-base-type', 'SPABase', [ + { + 'SlotPart': { + id: 222, + equippable: 'All', + z: 1, + src: 'some-fallback-slot-url', + }, + }, + ]); + }); + + it('create Base with slot part (custom collections)', async () => { + const firstCollectionId = await createCollection( + api, + alice, + 'first-collection-meta', + null, + 'first-collection', + ); + + const secondCollectionId = await createCollection( + api, + alice, + 'first-collection-meta', + null, + 'first-collection', + ); + + await createBase(api, alice, 'slotpartcustom-base-type', 'SPCBase', [ + { + 'SlotPart': { + id: 1024, + equippable: { + 'Custom': [firstCollectionId, secondCollectionId], + }, + z: 2, + src: 'some-fallback-slot-url', + }, + }, + ]); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/createCollection.seqtest.ts @@ -0,0 +1,25 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from './util/helpers'; +import {createCollection} from './util/tx'; + +describe('Integration test: create new collection', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + + const alice = '//Alice'; + + it('create NFT collection', async () => { + await createCollection(api, alice, 'test-metadata', 42, 'test-symbol'); + }); + + it('create NFT collection without token limit', async () => { + await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol'); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/deleteCollection.seqtest.ts @@ -0,0 +1,46 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, deleteCollection} from './util/tx'; + +describe('integration test: delete collection', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + + it('delete NFT collection', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + await deleteCollection(api, alice, collectionId.toString()); + }); + }); + + it('[negative] delete non-existing NFT collection', async () => { + const tx = deleteCollection(api, alice, '99999'); + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); + }); + + it('[negative] delete not an owner NFT collection', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const tx = deleteCollection(api, bob, collectionId.toString()); + await expectTxFailure(/rmrkCore.NoPermission/, tx); + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/equipNft.seqtest.ts @@ -0,0 +1,340 @@ +import {ApiPromise} from '@polkadot/api'; +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {getNft, NftIdTuple} from './util/fetch'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import { + addNftComposableResource, + addNftSlotResource, + createBase, + createCollection, + equipNft, + mintNft, + sendNft, + unequipNft, +} from './util/tx'; + +const alice = '//Alice'; +const bob = '//Bob'; + +const composableParts: number[] = [5, 2, 7]; +const composableSrc = 'test-cmp-src'; +const composableMetadata = 'test-cmp-metadata'; +const composableLicense = 'test-comp-license'; +const composableThumb = 'test-comp-thumb'; + +const slotSrc = 'test-slot-src'; +const slotLicense = 'test-slot-license'; +const slotMetadata = 'test-slot-metadata'; +const slotThumb = 'test-slot-thumb'; + +const slotId = 1; + +function createTestCollection(api: ApiPromise): Promise { + return createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); +} + +async function mintTestNft(api: ApiPromise, collectionId: number): Promise { + return await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); +} + +async function mintChildNft(api: ApiPromise, collectionId: number, parentNftId: number): Promise { + const nftChildId = await mintTestNft(api, collectionId); + + const parentNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, nftChildId, parentNFT); + + return nftChildId; +} + +function createTestBase(api: ApiPromise): Promise { + return createBase(api, alice, 'test-base', 'DTBase', [ + { + SlotPart: { + id: slotId, + equippable: 'All', + z: 1, + src: slotSrc, + }, + }, + ]); +} + +async function addTestComposable(api: ApiPromise, collectionId: number, nftId: number, baseId: number) { + await addNftComposableResource( + api, + alice, + 'added', + collectionId, + nftId, + composableParts, + baseId, + composableSrc, + composableMetadata, + composableLicense, + composableThumb, + ); +} + +async function addTestSlot(api: ApiPromise, collectionId: number, nftId: number, baseId: number, slotId: number): Promise { + return await addNftSlotResource( + api, + alice, + 'added', + collectionId, + nftId, + baseId, + slotId, + slotSrc, + slotMetadata, + slotLicense, + slotThumb, + ); +} + +async function checkEquipStatus( + api: ApiPromise, + expectedStatus: 'equipped' | 'unequipped', + collectionId: number, + nftId: number, +) { + const itemNftDataOpt = await getNft(api, collectionId, nftId); + expect(itemNftDataOpt.isSome, 'Error: unable to fetch item NFT data'); + + const itemNftData = itemNftDataOpt.unwrap(); + expect(itemNftData.equipped.isTrue, `Error: item NFT should be ${expectedStatus}`) + .to.be.equal(expectedStatus === 'equipped'); +} + +describe.skip('integration test: Equip NFT', () => { + + let api: any; + + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); + }); + + it('equip nft', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + await equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + + await checkEquipStatus(api, 'equipped', collectionId, nftChildId); + }); + + it('unequip nft', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + await equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + + await checkEquipStatus(api, 'equipped', collectionId, nftChildId); + + await unequipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + await checkEquipStatus(api, 'unequipped', collectionId, nftChildId); + }); + + it('[negative] equip NFT onto non-existing NFT', async () => { + const collectionId = await createTestCollection(api); + + const nftChildId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + const invalidEquipperNFT: NftIdTuple = [collectionId, 9999999]; + + const baseId = 0; + const resourceId = 0; + + const tx = equipNft(api, alice, itemNFT, invalidEquipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] equip non-existing NFT', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintNft( + api, + alice, + alice, + collectionId, + 'nft-metadata', + ); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const invalidItemNFT: NftIdTuple = [collectionId, 99999999]; + + const resourceId = 0; + + const tx = equipNft(api, alice, invalidItemNFT, equipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] equip NFT by a not-an-owner user', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const tx = equipNft(api, bob, itemNFT, equipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkEquip\.PermissionError/, tx); + }); + + it('[negative] unable to equip NFT onto indirect parent NFT', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + const nftGrandchildId = await mintChildNft(api, collectionId, nftChildId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftGrandchildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftGrandchildId]; + + const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkEquip\.MustBeDirectParent/, tx); + }); + + it('[negative] unable to equip NFT onto parent NFT with another base', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + const invalidBaseId = 99999; + + const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, invalidBaseId, slotId); + await expectTxFailure(/rmrkEquip\.NoResourceForThisBaseFoundOnNft/, tx); + }); + + it('[negative] unable to equip NFT into slot with another id', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createTestBase(api); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + const incorrectSlotId = slotId + 1; + const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, incorrectSlotId); + await expectTxFailure(/rmrkEquip\.ItemHasNoResourceToEquipThere/, tx); + }); + + it('[negative] unable to equip NFT with incorrect slot (fixed part)', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createBase(api, alice, 'test-base', 'DTBase', [ + { + FixedPart: { + id: slotId, + equippable: 'All', + z: 1, + src: slotSrc, + }, + }, + ]); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkEquip\.CantEquipFixedPart/, tx); + }); + + it('[negative] unable to equip NFT from a collection that is not allowed by the slot', async () => { + const collectionId = await createTestCollection(api); + const nftParentId = await mintTestNft(api, collectionId); + const nftChildId = await mintChildNft(api, collectionId, nftParentId); + + const baseId = await createBase(api, alice, 'test-base', 'DTBase', [ + { + SlotPart: { + id: 1, + z: 1, + equippable: 'Empty', + src: slotSrc, + }, + }, + ]); + + await addTestComposable(api, collectionId, nftParentId, baseId); + const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); + + const equipperNFT: NftIdTuple = [collectionId, nftParentId]; + const itemNFT: NftIdTuple = [collectionId, nftChildId]; + + const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); + await expectTxFailure(/rmrkEquip\.CollectionNotEquippable/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/getOwnedNfts.seqtest.ts @@ -0,0 +1,80 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from './util/helpers'; +import {getOwnedNfts} from './util/fetch'; +import {mintNft, createCollection} from './util/tx'; + +describe('integration test: get owned NFTs', () => { + let api: any; + + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + const alice = '//Alice'; + + it('fetch all NFTs owned by a user', async () => { + const owner = alice; + const collectionMetadata = 'aliceCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'AliceSym'; + const recipientUri = null; + const royalty = null; + const nftMetadata = 'alice-NFT-metadata'; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + const nftIds = [ + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata + '-0', + recipientUri, + royalty, + ), + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata + '-1', + recipientUri, + royalty, + ), + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata + '-2', + recipientUri, + royalty, + ), + ]; + + const ownedNfts = await getOwnedNfts(api, alice, collectionId); + + const isFound = (nftId: number) => { + return ownedNfts.find((ownedNftId) => { + return ownedNftId === nftId; + }) !== undefined; + }; + + nftIds.forEach((nftId) => { + expect(isFound(nftId), `NFT ${nftId} should be owned by ${alice}`) + .to.be.true; + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/lockCollection.seqtest.ts @@ -0,0 +1,116 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, lockCollection, mintNft} from './util/tx'; + +describe('integration test: lock collection', () => { + const alice = '//Alice'; + const bob = '//Bob'; + const max = 5; + + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + it('lock collection', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + await lockCollection(api, alice, collectionId); + }); + }); + + it('[negative] lock non-existing NFT collection', async () => { + const tx = lockCollection(api, alice, 99999); + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); + }); + + it('[negative] lock not an owner NFT collection issuer', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const tx = lockCollection(api, bob, collectionId); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + }); + + it('lock collection with minting', async () => { + await createCollection( + api, + alice, + 'test-metadata', + max, + 'test-symbol', + ).then(async (collectionId) => { + for (let i = 0; i < 5; i++) { + await mintNft( + api, + alice, + alice, + collectionId, + 'test-metadata', + null, + null, + ); + } + await lockCollection(api, alice, collectionId, max); + }); + }); + + it('[negative] unable to mint NFT inside a locked collection', async () => { + await createCollection( + api, + alice, + 'test-metadata', + max, + 'test-symbol', + ).then(async (collectionId) => { + await lockCollection(api, alice, collectionId); + const tx = mintNft( + api, + alice, + alice, + collectionId, + 'test-metadata', + null, + null, + ); + await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); + }); + }); + + it('[negative] unable to mint NFT inside a full collection', async () => { + await createCollection(api, alice, 'test-metadata', 1, 'test-symbol').then(async (collectionId) => { + await mintNft( + api, + alice, + alice, + collectionId, + 'test-metadata', + null, + null, + ); + const tx = mintNft( + api, + alice, + alice, + collectionId, + 'test-metadata', + null, + null, + ); + await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/mintNft.seqtest.ts @@ -0,0 +1,211 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {getNft} from './util/fetch'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, mintNft} from './util/tx'; + +describe('integration test: mint new NFT', () => { + let api: any; + + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + const alice = '//Alice'; + const bob = '//Bob'; + const maxCollectionId = 0xFFFFFFFF; + const maxNftId = 0xFFFFFFFF; + + it('mint NFT', async () => { + const owner = null; + const collectionMetadata = 'mintingCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'MCS'; + const recipientUri = null; + const royalty = null; + const nftMetadata = 'NFT-test-metadata'; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata, + recipientUri, + royalty, + ); + }); + + it('mint NFT and set another owner', async () => { + const owner = bob; + const collectionMetadata = 'setOwnerCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'SOCS'; + const recipientUri = null; + const royalty = null; + const nftMetadata = 'setOwner-NFT-metadata'; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata, + recipientUri, + royalty, + ); + }); + + it('mint NFT with recipient and roalty', async () => { + const owner = alice; + const collectionMetadata = 'mintingCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'MCS'; + const recipientUri = bob; + const royalty = 70000; + const nftMetadata = 'recipient-royalty-NFT-test-metadata'; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata, + recipientUri, + royalty, + ); + }); + + it('mint NFT with resources', async () => { + const owner = alice; + const collectionMetadata = 'mintingCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'MCS'; + const nftMetadata = 'NFT-with-resources-test-metadata'; + const resources = [ + { + basic: { + metadata: 'basic-resource-nft-minting', + }, + }, { + slot: { + metadata: 'slot-resource-nft-minting', + slot: 9, + }, + }, { + composable: { + metadata: 'composable-resource-nft-minting', + parts: [0, 5, 2], + }, + }, { + slot: { + metadata: 'slot-resource-nft-minting-2', + thumb: 'srnm2', + base: 5, + }, + }, + ]; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + await mintNft( + api, + alice, + owner, + collectionId, + nftMetadata, + null, + null, + true, + resources, + ); + }); + + it('[negative] unable to mint NFT within non-existing collection', async () => { + const owner = alice; + const recipientUri = null; + const royalty = null; + const nftMetadata = 'NFT-test-metadata'; + + const tx = mintNft( + api, + alice, + owner, + maxCollectionId, + nftMetadata, + recipientUri, + royalty, + ); + + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); + }); + + it("[negative] unable to mint NFT by a user that isn't the owner of the collection", async () => { + const owner = alice; + const collectionMetadata = 'mintingCollectionMetadata'; + const collectionMax = null; + const collectionSymbol = 'MCS'; + const recipientUri = null; + const royalty = null; + const nftMetadata = 'NFT-test-metadata'; + + const collectionId = await createCollection( + api, + alice, + collectionMetadata, + collectionMax, + collectionSymbol, + ); + + const tx = mintNft( + api, + bob, + owner, + collectionId, + nftMetadata, + recipientUri, + royalty, + ); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative] unable to fetch non-existing NFT', async () => { + const nft = await getNft(api, maxCollectionId, maxNftId); + expect(nft.isSome).to.be.false; + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/rejectNft.seqtest.ts @@ -0,0 +1,94 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import { + createCollection, + mintNft, + sendNft, + rejectNft, +} from './util/tx'; +import {NftIdTuple} from './util/fetch'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; + +describe('integration test: reject NFT', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + + + const alice = '//Alice'; + const bob = '//Bob'; + + const createTestCollection = async (issuerUri: string) => { + return await createCollection( + api, + issuerUri, + 'reject-metadata', + null, + 'rjct', + ); + }; + + it('reject NFT', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); + await rejectNft(api, alice, bobCollectionId, childNftId); + + const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); + expect(isChild, 'Error: rejected NFT is still a child of the target NFT').to.be.false; + }); + + it('[negative] unable to reject NFT by a not-an-owner', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); + const tx = rejectNft(api, bob, bobCollectionId, childNftId); + + await expectTxFailure(/rmrkCore\.CannotRejectNonOwnedNft/, tx); + }); + + it('[negative] unable to reject non-existing NFT', async () => { + const maxNftId = 0xFFFFFFFF; + + const collectionId = await createTestCollection(alice); + + const tx = rejectNft(api, alice, collectionId, maxNftId); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] unable to reject NFT which is not sent', async () => { + const ownerAlice = alice; + + const collectionId = await createTestCollection(alice); + + const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'parent-nft-metadata'); + + const tx = rejectNft(api, alice, collectionId, nftId); + + await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/removeResource.seqtest.ts @@ -0,0 +1,340 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {NftIdTuple} from './util/fetch'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import { + acceptResourceRemoval, addNftBasicResource, createCollection, mintNft, removeNftResource, sendNft, +} from './util/tx'; + + + + +describe('Integration test: remove nft resource', () => { + let api: any; + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + const src = 'test-basic-src'; + const metadata = 'test-basic-metadata'; + const license = 'test-basic-license'; + const thumb = 'test-basic-thumb'; + + it('deleting a resource directly by the NFT owner', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + + await removeNftResource(api, 'removed', alice, collectionIdAlice, nftAlice, resourceId); + }); + + it('deleting resources indirectly by the NFT owner', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); + + const resourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + childNftId, + src, + metadata, + license, + thumb, + ); + + const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; + + await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); + + await removeNftResource(api, 'removed', alice, collectionIdAlice, childNftId, resourceId); + }); + + it('deleting a resource by the collection owner', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftBob = await mintNft( + api, + alice, + bob, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'pending', + collectionIdAlice, + nftBob, + src, + metadata, + license, + thumb, + ); + + await removeNftResource(api, 'pending', alice, collectionIdAlice, nftBob, resourceId); + await acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, resourceId); + }); + + it('deleting a resource in a nested NFT by the collection owner', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const parentNftId = await mintNft( + api, + alice, + bob, + collectionIdAlice, + 'parent-nft-metadata', + ); + const childNftId = await mintNft( + api, + alice, + bob, + collectionIdAlice, + 'child-nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'pending', + collectionIdAlice, + childNftId, + src, + metadata, + license, + thumb, + ); + + const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; + + await sendNft(api, 'sent', bob, collectionIdAlice, childNftId, newOwnerNFT); + + await removeNftResource(api, 'pending', alice, collectionIdAlice, childNftId, resourceId); + await acceptResourceRemoval(api, bob, collectionIdAlice, childNftId, resourceId); + }); + + it('[negative]: can\'t delete a resource in a non-existing collection', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + + const tx = removeNftResource(api, 'removed', alice, 0xFFFFFFFF, nftAlice, resourceId); + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); // FIXME: inappropriate error message (NoAvailableNftId) + }); + + it('[negative]: only collection owner can delete a resource', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + + const tx = removeNftResource(api, 'removed', bob, collectionIdAlice, nftAlice, resourceId); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative]: cannot delete a resource that does not exist', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const tx = removeNftResource(api, 'removed', alice, collectionIdAlice, nftAlice, 127); + await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); + }); + + it('[negative]: Cannot accept deleting resource without owner attempt do delete it', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftBob = await mintNft( + api, + alice, + bob, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'pending', + collectionIdAlice, + nftBob, + src, + metadata, + license, + thumb, + ); + + const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, resourceId); + await expectTxFailure(/rmrkCore\.ResourceNotPending/, tx); + }); + + it('[negative]: cannot confirm the deletion of a non-existing resource', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftBob = await mintNft( + api, + alice, + bob, + collectionIdAlice, + 'nft-metadata', + ); + + const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, 127); + await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); + }); + + it('[negative]: Non-owner user cannot confirm the deletion of resource', async () => { + const collectionIdAlice = await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ); + + const nftAlice = await mintNft( + api, + alice, + alice, + collectionIdAlice, + 'nft-metadata', + ); + + const resourceId = await addNftBasicResource( + api, + alice, + 'added', + collectionIdAlice, + nftAlice, + src, + metadata, + license, + thumb, + ); + + const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftAlice, resourceId); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/rmrkIsolation.seqtest.ts @@ -0,0 +1,225 @@ +import {executeTransaction} from '../substrate/substrate-api'; +import {IKeyringPair} from '@polkadot/types/types'; +import {itSub, expect, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../../util'; +import {UniqueHelper} from '../../util/playgrounds/unique'; + +let alice: IKeyringPair; +let bob: IKeyringPair; + +async function createRmrkCollection(helper: UniqueHelper, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> { + const result = await helper.executeExtrinsic(sender, 'api.tx.rmrkCore.createCollection', ['metadata', null, 'symbol'], true); + + const uniqueId = helper.util.extractCollectionIdFromCreationResult(result); + + let rmrkId = null; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'rmrkCore') && (method === 'CollectionCreated')) { + rmrkId = parseInt(data[1].toString(), 10); + } + }); + + if (rmrkId === null) { + throw Error('No rmrkCore.CollectionCreated event was found!'); + } + + return { + uniqueId, + rmrkId, + }; +} + +async function createRmrkNft(helper: UniqueHelper, sender: IKeyringPair, collectionId: number): Promise { + const result = await helper.executeExtrinsic( + sender, + 'api.tx.rmrkCore.mintNft', + [ + sender.address, + collectionId, + sender.address, + null, + 'nft-metadata', + true, + null, + ], + true, + ); + + let rmrkNftId = null; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'rmrkCore') && (method === 'NftMinted')) { + rmrkNftId = parseInt(data[2].toString(), 10); + } + }); + + if (rmrkNftId === null) { + throw Error('No rmrkCore.NftMinted event was found!'); + } + + return rmrkNftId; +} + +describe('RMRK External Integration Test', () => { + before(async function() { + await usingPlaygrounds(async (_helper, privateKey) => { + alice = await privateKey('//Alice'); + }); + }); + + itSub.ifWithPallets('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', [Pallets.RmrkCore], async ({helper}) => { + // throw away collection to bump last Unique collection ID to test ID mapping + await helper.nft.mintCollection(alice, {tokenPrefix: 'unqt'}); + + const collectionIds = await createRmrkCollection(helper, alice); + + expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping'); + + const collection = (await helper.nft.getCollectionObject(collectionIds.uniqueId).getData())!; // (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!; + expect(collection.raw.readOnly, 'tagged external').to.be.true; + }); +}); + +describe('Negative Integration Test: External Collections, Internal Ops', () => { + let uniqueCollectionId: number; + let rmrkCollectionId: number; + let rmrkNftId: number; + let normalizedAlice: {Substrate: string}; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); + normalizedAlice = {Substrate: helper.address.normalizeSubstrateToChainFormat(alice.address)}; + + requirePalletsOrSkip(this, helper, [Pallets.RmrkCore]); + + const collectionIds = await createRmrkCollection(helper, alice); + uniqueCollectionId = collectionIds.uniqueId; + rmrkCollectionId = collectionIds.rmrkId; + + rmrkNftId = await createRmrkNft(helper, alice, rmrkCollectionId); + }); + }); + + itSub.ifWithPallets('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', [Pallets.RmrkCore], async ({helper}) => { + // Collection item creation + + await expect(helper.nft.mintToken(alice, {collectionId: uniqueCollectionId, owner: {Substrate: alice.address}}), 'creating item') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + const txCreateMultipleItems = helper.getApi().tx.unique.createMultipleItems(uniqueCollectionId, normalizedAlice, [{NFT: {}}, {NFT: {}}]); + await expect(executeTransaction(helper.getApi(), alice, txCreateMultipleItems), 'creating multiple') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.mintMultipleTokens(alice, uniqueCollectionId, [{owner: {Substrate: alice.address}}]), 'creating multiple ex') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // Collection properties + + await expect(helper.nft.setProperties(alice, uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]), 'setting collection properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.deleteProperties(alice, uniqueCollectionId, ['a']), 'deleting collection properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.setTokenPropertyPermissions(alice, uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]), 'setting property permissions') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // NFT + + await expect(helper.nft.burnToken(alice, uniqueCollectionId, rmrkNftId, 1n), 'burning') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.burnTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, 1n), 'burning-from') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.transferToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'transferring') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.approveToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'approving') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.transferTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, {Substrate: bob.address}), 'transferring-from') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // NFT properties + + await expect(helper.nft.setTokenProperties(alice, uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]), 'setting token properties') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.deleteTokenProperties(alice, uniqueCollectionId, rmrkNftId, ['a'])) + .to.be.rejectedWith(/common\.CollectionIsExternal/); + }); + + itSub.ifWithPallets('[Negative] Forbids Unique collection operations with an external collection', [Pallets.RmrkCore], async ({helper}) => { + await expect(helper.nft.burn(alice, uniqueCollectionId), 'destroying collection') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // Allow list + + await expect(helper.nft.addToAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding to allow list') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.removeFromAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing from allowlist') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // Owner / Admin / Sponsor + + await expect(helper.nft.changeOwner(alice, uniqueCollectionId, bob.address), 'changing owner') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.addAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding admin') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.removeAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing admin') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.setSponsor(alice, uniqueCollectionId, bob.address), 'setting sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.confirmSponsorship(alice, uniqueCollectionId), 'confirming sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.removeSponsor(alice, uniqueCollectionId), 'removing sponsor') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + // Limits / permissions / transfers + + const txSetTransfers = helper.getApi().tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true); + await expect(executeTransaction(helper.getApi(), alice, txSetTransfers), 'setting transfers enabled flag') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.setLimits(alice, uniqueCollectionId, {transfersEnabled: false}), 'setting collection limits') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + + await expect(helper.nft.setPermissions(alice, uniqueCollectionId, {access: 'AllowList'}), 'setting collection permissions') + .to.be.rejectedWith(/common\.CollectionIsExternal/); + }); +}); + +describe('Negative Integration Test: Internal Collections, External Ops', () => { + let collectionId: number; + let nftId: number; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); + + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'iceo'}); + collectionId = collection.collectionId; + nftId = (await collection.mintToken(alice)).tokenId; + }); + }); + + itSub.ifWithPallets('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', [Pallets.RmrkCore], async ({helper}) => { + const api = helper.getApi(); + + const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); + await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer') + .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); + + const maxBurns = 10; + const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); + await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); + }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/sendNft.seqtest.ts @@ -0,0 +1,255 @@ +import {expect} from 'chai'; +import {getApiConnection} from '../substrate/substrate-api'; +import {createCollection, mintNft, sendNft} from './util/tx'; +import {NftIdTuple} from './util/fetch'; +import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; + +describe('integration test: send NFT', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const maxNftId = 0xFFFFFFFF; + + const alice = '//Alice'; + const bob = '//Bob'; + + const createTestCollection = async (issuerUri: string) => { + return await createCollection( + api, + issuerUri, + 'nft-collection-metadata', + null, + 'nft-collection', + ); + }; + + + it('send NFT to another user', async () => { + const originalOwnerUri = alice; + const newOwnerUri = bob; + + const collectionId = await createTestCollection(alice); + + const nftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'nft-metadata'); + + await sendNft(api, 'sent', originalOwnerUri, collectionId, nftId, newOwnerUri); + }); + + it('[negative] unable to send non-existing NFT', async () => { + const originalOwnerUri = alice; + const newOwnerUri = bob; + + const collectionId = 0; + const tx = sendNft(api, 'sent', originalOwnerUri, collectionId, maxNftId, newOwnerUri); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] unable to send NFT by a not-an-owner', async () => { + const originalOwnerUri = alice; + const newOwnerUri = bob; + + const collectionId = await createTestCollection(alice); + + const nftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'nft-metadata'); + + const tx = sendNft(api, 'sent', newOwnerUri, collectionId, nftId, newOwnerUri); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('send NFT to another NFT (same owner)', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.true; + }); + + it('[negative] send non-existing NFT to another NFT', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); + const childNftId = maxNftId; + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + const tx = sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.false; + }); + + it('send NFT to another NFT (by not-an-owner)', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const author = alice; + const attacker = bob; + + const parentNftId = await mintNft(api, author, originalOwnerUri, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, author, originalOwnerUri, collectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + const tx = sendNft(api, 'sent', attacker, collectionId, childNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.false; + }); + + it('[negative] send NFT to non-existing NFT', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const parentNftId = maxNftId; + const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + const tx = sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.false; + }); + + it('send NFT to another NFT owned by another user', async () => { + const ownerAlice = alice; + const ownerBob = bob; + + const aliceCollectionId = await createTestCollection(alice); + const bobCollectionId = await createTestCollection(bob); + + const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; + + await sendNft(api, 'pending', bob, bobCollectionId, childNftId, newOwnerNFT); + }); + + it('[negative] unable to send NFT to itself', async () => { + const nftOwner = alice; + const collectionId = await createTestCollection(alice); + + const nftId = await mintNft(api, alice, nftOwner, collectionId, 'ouroboros-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionId, nftId]; + + const tx = sendNft(api, 'sent', alice, collectionId, nftId, newOwnerNFT); + + await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); + + const isChild = await isNftChildOfAnother(api, collectionId, nftId, newOwnerNFT); + expect(isChild).to.be.false; + }); + + it('[negative] unable to send NFT to child NFT', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); + + const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); + expect(isChild).to.be.true; + + const descendentOwner: NftIdTuple = [collectionId, childNftId]; + const tx = sendNft(api, 'sent', alice, collectionId, parentNftId, descendentOwner); + + await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); + const isOuroboros = await isNftChildOfAnother(api, collectionId, parentNftId, descendentOwner); + expect(isOuroboros).to.be.false; + }); + + it('[negative] unable to send NFT to descendent NFT', async () => { + const originalOwnerUri = alice; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); + const grandsonNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'grandson-nft-metadata'); + + const ownerParentNFT: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', alice, collectionId, childNftId, ownerParentNFT); + + const isChild = await isNftChildOfAnother(api, collectionId, childNftId, ownerParentNFT); + expect(isChild).to.be.true; + + const ownerChildNFT: NftIdTuple = [collectionId, childNftId]; + await sendNft(api, 'sent', alice, collectionId, grandsonNftId, ownerChildNFT); + + const isGrandson = await isNftChildOfAnother(api, collectionId, grandsonNftId, ownerChildNFT); + expect(isGrandson).to.be.true; + + const ownerGrandsonNFT: NftIdTuple = [collectionId, grandsonNftId]; + const tx = sendNft(api, 'sent', alice, collectionId, parentNftId, ownerGrandsonNFT); + + await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); + const isOuroboros = await isNftChildOfAnother(api, collectionId, parentNftId, ownerGrandsonNFT); + expect(isOuroboros).to.be.false; + }); + + it('send nested NFT to another user', async () => { + const originalOwner = alice; + const newOwner = bob; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwner, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, originalOwner, collectionId, 'child-nft-metadata'); + + const parentNftTuple: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', originalOwner, collectionId, childNftId, parentNftTuple); + + await sendNft(api, 'sent', originalOwner, collectionId, childNftId, newOwner); + }); + + it('[negative] send nested NFT to another user (by a not-root-owner)', async () => { + const originalOwner = alice; + const newOwner = bob; + + const collectionId = await createTestCollection(alice); + + const parentNftId = await mintNft(api, alice, originalOwner, collectionId, 'parent-nft-metadata'); + const childNftId = await mintNft(api, alice, originalOwner, collectionId, 'child-nft-metadata'); + + const parentNftTuple: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', originalOwner, collectionId, childNftId, parentNftTuple); + + const tx = sendNft(api, 'sent', newOwner, collectionId, childNftId, newOwner); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/setCollectionProperty.seqtest.ts @@ -0,0 +1,66 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, setPropertyCollection} from './util/tx'; + +describe('integration test: set collection property', () => { + const alice = '//Alice'; + const bob = '//Bob'; + + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + it('set collection property', async () => { + await createCollection( + api, + alice, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + await setPropertyCollection(api, alice, collectionId, 'test_key', '42'); + await setPropertyCollection(api, alice, collectionId, 'test_key', '10'); + await setPropertyCollection( + api, + alice, + collectionId, + 'second_test_key', + '111', + ); + }); + }); + + it('[negative] set non-existing collection property', async () => { + const tx = setPropertyCollection( + api, + alice, + 9999, + 'test_key', + '42', + ); + await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); + }); + + it('[negative] set property not an owner NFT collection issuer', async () => { + await createCollection( + api, + bob, + 'test-metadata', + null, + 'test-symbol', + ).then(async (collectionId) => { + const tx = setPropertyCollection( + api, + alice, + collectionId, + 'test_key', + '42', + ); + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/setEquippableList.seqtest.ts @@ -0,0 +1,114 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, createBase, setEquippableList} from './util/tx'; + +describe("integration test: set slot's Equippable List", () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + + it("set Base's slot Equippable List", async () => { + const collectionIds = [ + await createCollection( + api, + alice, + 'equiplist-collection-metadata', + null, + 'equiplist-0', + ), + await createCollection( + api, + alice, + 'equiplist-collection-metadata', + null, + 'equiplist-1', + ), + ]; + + const slotId = 202; + + const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ + { + 'SlotPart': { + id: slotId, + equippable: 'All', + z: 1, + src: 'some-fallback-slot-url', + }, + }, + ]); + + await setEquippableList(api, alice, baseId, slotId, 'All'); + await setEquippableList(api, alice, baseId, slotId, 'Empty'); + await setEquippableList(api, alice, baseId, slotId, {'Custom': collectionIds}); + }); + + it('[negative] unable to set equippable list of a slot of non-existing base', async () => { + const maxBaseId = 0xFFFFFFFF; + const slotId = 0; + + const tx = setEquippableList(api, alice, maxBaseId, slotId, 'All'); + await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); + }); + + it('[negative] unable to set equippable list by a not-an-owner', async () => { + const slotId = 42; + + const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ + { + 'SlotPart': { + id: slotId, + equippable: 'All', + z: 1, + src: 'some-fallback-slot-url', + }, + }, + ]); + + const tx = setEquippableList(api, bob, baseId, slotId, 'All'); + await expectTxFailure(/rmrkEquip\.PermissionError/, tx); + }); + + it('[negative] unable to set equippable list to a fixed part', async () => { + const fixedPartId = 42; + + const baseId = await createBase(api, alice, 'fixedpart-base-type', 'fixedpart', [ + { + 'FixedPart': { + id: fixedPartId, + z: 0, + src: 'fixed-part-url', + }, + }, + ]); + + const tx = setEquippableList(api, alice, baseId, fixedPartId, 'All'); + await expectTxFailure(/rmrkEquip\.NoEquippableOnFixedPart/, tx); + }); + + it('[negative] unable to set equippable list to non-existing slot', async () => { + const slotId = 777; + const maxSlotId = 0xFFFFFFFF; + + const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ + { + 'SlotPart': { + id: slotId, + equippable: 'All', + z: 1, + src: 'some-fallback-slot-url', + }, + }, + ]); + + const tx = setEquippableList(api, alice, baseId, maxSlotId, 'All'); + await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/setNftProperty.seqtest.ts @@ -0,0 +1,88 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {NftIdTuple} from './util/fetch'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx'; + +describe('integration test: set NFT property', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + + const createTestCollection = async (issuerUri: string) => { + return await createCollection( + api, + issuerUri, + 'setprop-nft-collection-metadata', + null, + 'setprop', + ); + }; + + it('set NFT property', async () => { + const ownerAlice = alice; + + const collectionId = await createTestCollection(alice); + const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-nft'); + + await setNftProperty(api, alice, collectionId, nftId, 'test-key', 'test-key-value'); + await setNftProperty(api, alice, collectionId, nftId, 'test-key', 'updated-key-value'); + await setNftProperty(api, alice, collectionId, nftId, 'second-test-key', 'second-test-key-value'); + }); + + it('[negative] unable to set a property of non-existing NFT', async () => { + const collectionId = 0; + const maxNftId = 0xFFFFFFFF; + + const tx = setNftProperty(api, alice, collectionId, maxNftId, 'test-key', 'test-value'); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + it('[negative] unable to set a property by not-an-owner', async () => { + const ownerAlice = alice; + + const collectionId = await createTestCollection(alice); + const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-nft'); + + const tx = setNftProperty(api, bob, collectionId, nftId, 'test-key', 'test-key-value'); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('set a property to nested NFT', async () => { + const ownerAlice = alice; + + const collectionId = await createTestCollection(alice); + const parentNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-parent-nft'); + const childNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-child-nft'); + + const ownerNft: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', ownerAlice, collectionId, childNftId, ownerNft); + + await setNftProperty(api, alice, collectionId, childNftId, 'test-key', 'test-key-value'); + }); + + it('[negative] set a property to nested NFT (by not-root-owner)', async () => { + const ownerAlice = alice; + + const collectionId = await createTestCollection(alice); + const parentNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-parent-nft'); + const childNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-child-nft'); + + const ownerNft: NftIdTuple = [collectionId, parentNftId]; + + await sendNft(api, 'sent', ownerAlice, collectionId, childNftId, ownerNft); + + const tx = setNftProperty(api, bob, collectionId, childNftId, 'test-key', 'test-key-value'); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/setResourcePriorities.seqtest.ts @@ -0,0 +1,56 @@ +import {getApiConnection} from '../substrate/substrate-api'; +import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; +import {mintNft, createCollection, setResourcePriorities} from './util/tx'; + +describe('integration test: set NFT resource priorities', () => { + let api: any; + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; + const bob = '//Bob'; + + const createTestCollection = (issuerUri: string) => { + return createCollection( + api, + issuerUri, + 'resprio-collection-metadata', + null, + 'resprio', + ); + }; + + it('set NFT resource priorities', async () => { + const owner = alice; + + const collectionId = await createTestCollection(alice); + const nftId = await mintNft(api, alice, owner, collectionId, 'resprio-nft-metadata'); + + await setResourcePriorities(api, alice, collectionId, nftId, [10, 42]); + }); + + it('[negative] set NFT resource priorities by a not-an-owner', async () => { + const owner = alice; + const attacker = bob; + + const collectionId = await createTestCollection(alice); + const nftId = await mintNft(api, alice, owner, collectionId, 'resprio-nft-metadata'); + + const tx = setResourcePriorities(api, attacker, collectionId, nftId, [10, 42]); + + await expectTxFailure(/rmrkCore\.NoPermission/, tx); + }); + + it('[negative] set NFT resource priorities to non-existing NFT', async () => { + const collectionId = 0; + const maxNftId = 0xFFFFFFFF; + + const tx = setResourcePriorities(api, alice, collectionId, maxNftId, [10, 42]); + + await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); + }); + + after(async() => { await api.disconnect(); }); +}); --- /dev/null +++ b/tests/src/.outdated/rmrk/util/fetch.ts @@ -0,0 +1,130 @@ +import {ApiPromise} from '@polkadot/api'; +import type {Option} from '@polkadot/types-codec'; +import type { + RmrkTraitsCollectionCollectionInfo as Collection, + RmrkTraitsNftNftInfo as Nft, + RmrkTraitsResourceResourceInfo as Resource, + RmrkTraitsBaseBaseInfo as Base, + RmrkTraitsPartPartType as PartType, + RmrkTraitsNftNftChild as NftChild, + RmrkTraitsTheme as Theme, + RmrkTraitsPropertyPropertyInfo as Property, +} from '../../../interfaces/default/types'; // '@polkadot/types/lookup'; +import '../../../interfaces/augment-api'; +import '../../../interfaces/augment-api-query'; +import privateKey from '../../substrate/privateKey'; + +export type NftIdTuple = [number, number]; + +export async function getCollectionsCount(api: ApiPromise): Promise { + return (await api.rpc.rmrk.lastCollectionIdx()).toNumber(); +} + +export function getCollection(api: ApiPromise, id: number): Promise> { + return api.rpc.rmrk.collectionById(id); +} + +export async function getOwnedNfts( + api: ApiPromise, + ownerUri: string, + collectionId: number, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const owner = privateKey(ownerUri, Number(ss58Format)); + + return (await api.rpc.rmrk.accountTokens(owner.address, collectionId)) + .map((value) => value.toNumber()); +} + +export function getNft(api: ApiPromise, collectionId: number, nftId: number): Promise> { + return api.rpc.rmrk.nftById(collectionId, nftId); +} + +export async function getCollectionProperties(api: ApiPromise, collectionId: number): Promise { + return (await api.rpc.rmrk.collectionProperties(collectionId)).toArray(); +} + +export async function getNftProperties(api: ApiPromise, collectionId: number, nftId: number): Promise { + return (await api.rpc.rmrk.nftProperties(collectionId, nftId)).toArray(); +} + +export async function getChildren( + api: ApiPromise, + collectionId: number, + nftId: number, +): Promise { + return (await api.rpc.rmrk.nftChildren(collectionId, nftId)).toArray(); +} + +export function getBase(api: ApiPromise, baseId: number): Promise> { + return api.rpc.rmrk.base(baseId); +} + +export async function getParts(api: ApiPromise, baseId: number): Promise { + return (await api.rpc.rmrk.baseParts(baseId)).toArray(); +} + +export async function getEquippableList( + api: ApiPromise, + baseId: number, + slotId: number, +): Promise<'All' | 'Empty' | { 'Custom': number[] } | null> { + const parts = await getParts(api, baseId); + + const part = parts.find((part) => { + if (part.isSlotPart) { + return part.asSlotPart.id.toNumber() === slotId; + } else { + return false; + } + }); + + if (part) { + const slot = part.asSlotPart; + if (slot.equippable.isCustom) { + return { + 'Custom': slot.equippable.asCustom + .toArray() + .map((collectionId) => collectionId.toNumber()), + }; + } else if (slot.equippable.isAll) { + return 'All'; + } else { + return 'Empty'; + } + } else { + return null; + } +} + +export async function getResourcePriority( + api: ApiPromise, + collectionId: number, + nftId: number, + resourceId: number, +): Promise { + return (await api.rpc.rmrk.nftResourcePriority(collectionId, nftId, resourceId)) + .unwrap().toNumber(); +} + +export async function getThemeNames(api: ApiPromise, baseId: number): Promise { + return (await api.rpc.rmrk.themeNames(baseId)) + .map((name) => name.toUtf8()); +} + +export function getTheme( + api: ApiPromise, + baseId: number, + themeName: string, + keys: string[] | null = null, +): Promise> { + return api.rpc.rmrk.themes(baseId, themeName, keys); +} + +export async function getResources( + api: ApiPromise, + collectionId: number, + nftId: number, +): Promise { + return (await api.rpc.rmrk.nftResources(collectionId, nftId)).toArray(); +} --- /dev/null +++ b/tests/src/.outdated/rmrk/util/helpers.ts @@ -0,0 +1,249 @@ +import {ApiPromise} from '@polkadot/api'; +import { + RmrkTraitsNftAccountIdOrCollectionNftTuple as NftOwner, + RmrkTraitsPropertyPropertyInfo as Property, + RmrkTraitsResourceResourceInfo as ResourceInfo, +} from '@polkadot/types/lookup'; +import type {EventRecord} from '@polkadot/types/interfaces'; +import type {GenericEventData} from '@polkadot/types'; +import privateKey from '../../substrate/privateKey'; +import {NftIdTuple, getChildren, getOwnedNfts, getCollectionProperties, getNftProperties, getResources} from './fetch'; +import chaiAsPromised from 'chai-as-promised'; +import chai from 'chai'; +import {getApiConnection} from '../../substrate/substrate-api'; +import {Context} from 'mocha'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +interface TxResult { + success: boolean; + successData: T | null; +} + +export enum Pallets { + Inflation = 'inflation', + RmrkCore = 'rmrkcore', + RmrkEquip = 'rmrkequip', + ReFungible = 'refungible', + Fungible = 'fungible', + NFT = 'nonfungible', + Scheduler = 'scheduler', + AppPromotion = 'apppromotion', +} + +let modulesNames: any; +export function getModuleNames(api: ApiPromise): string[] { + if (typeof modulesNames === 'undefined') + modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); + return modulesNames; +} + +export async function missingRequiredPallets(requiredPallets: string[]): Promise { + const api = await getApiConnection(); + const pallets = getModuleNames(api); + await api.disconnect(); + + return requiredPallets.filter(p => !pallets.includes(p)); +} + +export async function requirePallets(mocha: Context, requiredPallets: string[]) { + const missingPallets = await missingRequiredPallets(requiredPallets); + + if (missingPallets.length > 0) { + const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; + const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; + const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; + + console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); + + mocha.skip(); + } +} + +export function makeNftOwner(api: ApiPromise, owner: string | NftIdTuple): NftOwner { + const isNftSending = (typeof owner !== 'string'); + + if (isNftSending) { + return api.createType('RmrkTraitsNftAccountIdOrCollectionNftTuple', { + 'CollectionAndNftTuple': owner, + }); + } else { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + return api.createType('RmrkTraitsNftAccountIdOrCollectionNftTuple', { + AccountId: privateKey(owner, Number(ss58Format)).address, + }); + } +} + +export async function isNftOwnedBy( + api: ApiPromise, + owner: string | NftIdTuple, + collectionId: number, + nftId: number, +): Promise { + if (typeof owner === 'string') { + return (await getOwnedNfts(api, owner, collectionId)) + .find(ownedNftId => { + return ownedNftId === nftId; + }) !== undefined; + } else { + return (await getChildren(api, owner[0], owner[1])) + .find(child => { + return collectionId === child.collectionId.toNumber() + && nftId === child.nftId.toNumber(); + }) !== undefined; + } +} + +export function isPropertyExists( + key: string, + value: string, + props: Property[], +): boolean { + let isPropFound = false; + for (let i = 0; i < props.length && !isPropFound; i++) { + const fetchedProp = props[i]; + + isPropFound = fetchedProp.key.eq(key) + && fetchedProp.value.eq(value); + } + + return isPropFound; +} + +export async function isCollectionPropertyExists( + api: ApiPromise, + collectionId: number, + key: string, + value: string, +): Promise { + const fetchedProps = await getCollectionProperties(api, collectionId); + return isPropertyExists(key, value, fetchedProps); +} + +export async function isNftPropertyExists( + api: ApiPromise, + collectionId: number, + nftId: number, + key: string, + value: string, +): Promise { + const fetchedProps = await getNftProperties(api, collectionId, nftId); + return isPropertyExists(key, value, fetchedProps); +} + +export async function isNftChildOfAnother( + api: ApiPromise, + collectionId: number, + nftId: number, + parentNft: NftIdTuple, +): Promise { + return (await getChildren(api, parentNft[0], parentNft[1])) + .find((childNft) => { + return childNft.collectionId.toNumber() === collectionId + && childNft.nftId.toNumber() === nftId; + }) !== undefined; +} + +export function isTxResultSuccess(events: EventRecord[]): boolean { + let success = false; + + events.forEach(({event: {method}}) => { + if (method == 'ExtrinsicSuccess') { + success = true; + } + }); + + return success; +} + +export async function expectTxFailure(expectedError: RegExp, promise: Promise) { + await expect(promise).to.be.rejectedWith(expectedError); +} + +export function extractTxResult( + events: EventRecord[], + expectSection: string, + expectMethod: string, + extractAction: (data: any) => T, +): TxResult { + let success = false; + let successData = null; + events.forEach(({event: {data, method, section}}) => { + if (method == 'ExtrinsicSuccess') { + success = true; + } else if ((expectSection == section) && (expectMethod == method)) { + successData = extractAction(data); + } + }); + const result: TxResult = { + success, + successData, + }; + return result; +} + +export function extractRmrkCoreTxResult( + events: EventRecord[], + expectMethod: string, + extractAction: (data: GenericEventData) => T, +): TxResult { + return extractTxResult(events, 'rmrkCore', expectMethod, extractAction); +} + +export function extractRmrkEquipTxResult( + events: EventRecord[], + expectMethod: string, + extractAction: (data: GenericEventData) => T, +): TxResult { + return extractTxResult(events, 'rmrkEquip', expectMethod, extractAction); +} + +export async function findResourceById( + api: ApiPromise, + collectionId: number, + nftId: number, + resourceId: number, +): Promise { + const resources = await getResources(api, collectionId, nftId); + + let resource = null; + + for (let i = 0; i < resources.length; i++) { + const res = resources[i]; + + if (res.id.eq(resourceId)) { + resource = res; + break; + } + } + + return resource!; +} + +export async function getResourceById( + api: ApiPromise, + collectionId: number, + nftId: number, + resourceId: number, +): Promise { + const resource = await findResourceById( + api, + collectionId, + nftId, + resourceId, + ); + + expect(resource !== null, 'Error: resource was not found').to.be.true; + + return resource!; +} + +export function checkResourceStatus( + resource: ResourceInfo, + expectedStatus: 'pending' | 'added', +) { + expect(resource.pending.isTrue, `Error: added resource should be ${expectedStatus}`) + .to.be.equal(expectedStatus === 'pending'); +} --- /dev/null +++ b/tests/src/.outdated/rmrk/util/tx.ts @@ -0,0 +1,1132 @@ +import {ApiPromise} from '@polkadot/api'; +import {Bytes, Option, u32, Vec} from '@polkadot/types-codec'; +import { + RmrkTraitsNftAccountIdOrCollectionNftTuple as NftOwner, RmrkTraitsPartEquippableList as EquippableList, + RmrkTraitsPartPartType as PartType, RmrkTraitsResourceBasicResource as BasicResource, + RmrkTraitsResourceComposableResource as ComposableResource, RmrkTraitsResourceResourceInfo as ResourceInfo, RmrkTraitsResourceSlotResource as SlotResource, RmrkTraitsTheme as Theme, +} from '@polkadot/types/lookup'; +import {IKeyringPair} from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import '../../../interfaces/augment-api'; +import privateKey from '../../substrate/privateKey'; +import {executeTransaction} from '../../substrate/substrate-api'; +import { + getBase, + getCollection, + getCollectionsCount, + getEquippableList, + getNft, + getParts, + getResources, + getResourcePriority, + getTheme, + NftIdTuple, +} from './fetch'; +import { + extractRmrkCoreTxResult, + extractRmrkEquipTxResult, isCollectionPropertyExists, isNftOwnedBy, isNftPropertyExists, isTxResultSuccess, makeNftOwner, + findResourceById, getResourceById, checkResourceStatus, +} from './helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +export async function createCollection( + api: ApiPromise, + issuerUri: string, + metadata: string, + max: number | null, + symbol: string, +): Promise { + let collectionId = 0; + + const oldCollectionCount = await getCollectionsCount(api); + const maxOptional = max ? max.toString() : null; + const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkCore.createCollection(metadata, maxOptional, symbol); + const events = await executeTransaction(api, issuer, tx); + + const collectionResult = extractRmrkCoreTxResult(events, 'CollectionCreated', (data) => { + return parseInt(data[1].toString(), 10); + }); + expect(collectionResult.success, 'Error: unable to create a collection').to.be.true; + + collectionId = collectionResult.successData!; + + const newCollectionCount = await getCollectionsCount(api); + const collectionOption = await getCollection(api, collectionId); + + expect(newCollectionCount).to.be.equal(oldCollectionCount + 1, 'Error: NFT collection count should increase'); + expect(collectionOption.isSome, 'Error: unable to fetch created NFT collection').to.be.true; + + const collection = collectionOption.unwrap(); + + expect(collection.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT collection metadata'); + expect(collection.max.isSome).to.be.equal(max !== null, 'Error: Invalid NFT collection max'); + + if (collection.max.isSome) { + expect(collection.max.unwrap().toNumber()).to.be.equal(max, 'Error: Invalid NFT collection max'); + } + expect(collection.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid NFT collection's symbol"); + expect(collection.nftsCount.toNumber()).to.be.equal(0, "Error: NFT collection shoudn't have any tokens"); + expect(collection.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid NFT collection issuer'); + + return collectionId; +} + +export async function changeIssuer( + api: ApiPromise, + issuerUri: string, + collectionId: number, + newIssuer: string, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const alice = privateKey(issuerUri, Number(ss58Format)); + const bob = privateKey(newIssuer, Number(ss58Format)); + + // This is only needed when RMRK uses `uniques` pallet by Parity + // let tx = api.tx.uniques.setAcceptOwnership( + // api.createType('Option', collectionId) + // ); + // let events = await executeTransaction(api, bob, tx); + // expect(isTxResultSuccess(events), 'Error: Unable to accept ownership').to.be.true; + + const tx = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); + const events = await executeTransaction(api, alice, tx); + const changeIssuerResult = extractRmrkCoreTxResult(events, 'IssuerChanged', (data) => { + return parseInt(data[2].toString(), 10); + }); + expect(changeIssuerResult.success, 'Error: Unable to change NFT collection issuer').to.be.true; + expect(changeIssuerResult.successData!, 'Error: Invalid collection id after changing the issuer') + .to.be.eq(collectionId); + + await getCollection(api, collectionId).then((collectionOption) => { + const collection = collectionOption.unwrap(); + expect(collection.issuer.toString()) + .to.be.deep.eq(bob.address, 'Error: Invalid NFT collection issuer'); + }); +} + +export async function deleteCollection( + api: ApiPromise, + issuerUri: string, + collectionId: string, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkCore.destroyCollection(collectionId); + const events = await executeTransaction(api, issuer, tx); + + const collectionTxResult = extractRmrkCoreTxResult( + events, + 'CollectionDestroy', + (data) => { + return parseInt(data[1].toString(), 10); + }, + ); + expect(collectionTxResult.success, 'Error: Unable to delete NFT collection').to.be.true; + + const collection = await getCollection( + api, + parseInt(collectionId, 10), + ); + expect(collection.isEmpty, 'Error: NFT collection should be deleted').to.be.true; + + return 0; +} + +export async function negativeDeleteCollection( + api: ApiPromise, + issuerUri: string, + collectionId: string, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkCore.destroyCollection(collectionId); + await expect(executeTransaction(api, issuer, tx)).to.be.rejected; + + return 0; +} + +export async function setNftProperty( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, + key: string, + value: string, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const nftIdOpt = api.createType('Option', nftId); + const tx = api.tx.rmrkCore.setProperty( + collectionId, + nftIdOpt, + key, + value, + ); + const events = await executeTransaction(api, issuer, tx); + + const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => { + return { + collectionId: parseInt(data[0].toString(), 10), + nftId: data[1] as Option, + key: data[2] as Bytes, + value: data[3] as Bytes, + }; + }); + + expect(propResult.success, 'Error: Unable to set NFT property').to.be.true; + const eventData = propResult.successData!; + const eventDescription = 'from set NFT property event'; + + expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription) + .to.be.equal(collectionId); + + expect(eventData.nftId.eq(nftIdOpt), 'Error: Invalid NFT ID ' + eventDescription) + .to.be.true; + + expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription) + .to.be.true; + + expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription) + .to.be.true; + + expect( + await isNftPropertyExists(api, collectionId, nftId, key, value), + 'Error: NFT property is not found', + ).to.be.true; +} + +export async function mintNft( + api: ApiPromise, + issuerUri: string, + ownerUri: string | null, + collectionId: number, + metadata: string, + recipientUri: string | null = null, + royalty: number | null = null, + transferable = true, + resources: {basic?: any, composable?: any, slot?: any}[] | null = null, +): Promise { + let nftId = 0; + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const owner = ownerUri ? privateKey(ownerUri, Number(ss58Format)).address : null; + const recipient = recipientUri ? privateKey(recipientUri, Number(ss58Format)).address : null; + const royaltyOptional = royalty ? royalty.toString() : null; + + const actualOwnerUri = ownerUri ? ownerUri : issuerUri; + const actualOwnerAddress = ownerUri ? owner : issuer.address; + + const collectionOpt = await getCollection(api, collectionId); + + const tx = api.tx.rmrkCore.mintNft( + owner, + collectionId, + recipient, + royaltyOptional, + metadata, + transferable, + resources as any, + ); + + const events = await executeTransaction(api, issuer, tx); + const nftResult = extractRmrkCoreTxResult(events, 'NftMinted', (data) => { + return parseInt(data[2].toString(), 10); + }); + + expect(nftResult.success, 'Error: Unable to mint NFT').to.be.true; + + const newCollectionNftsCount = (await getCollection(api, collectionId)) + .unwrap() + .nftsCount + .toNumber(); + + const oldCollectionNftsCount = collectionOpt + .unwrap() + .nftsCount.toNumber(); + + expect(newCollectionNftsCount, 'Error: NFTs count should increase') + .to.be.equal(oldCollectionNftsCount + 1); + + nftId = nftResult.successData!; + + const nftOption = await getNft(api, collectionId, nftId); + + expect(nftOption.isSome, 'Error: Unable to fetch created NFT').to.be.true; + + const nft = nftOption.unwrap(); + + expect(nft.owner.isAccountId, 'Error: NFT owner should be some user').to.be.true; + expect(nft.owner.asAccountId.toString()).to.be.equal(actualOwnerAddress, 'Error: Invalid NFT owner'); + + const isOwnedInUniques = await isNftOwnedBy(api, actualOwnerUri, collectionId, nftId); + expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${ownerUri}`) + .to.be.true; + + if (recipient === null && royalty === null) { + expect(nft.royalty.isNone, 'Error: Invalid NFT recipient') + .to.be.true; + } else { + expect(nft.royalty.isSome, 'Error: NFT royalty not found') + .to.be.true; + + const nftRoyalty = nft.royalty.unwrap(); + expect(nftRoyalty.recipient.eq(recipient), 'Error: Invalid NFT recipient') + .to.be.true; + + expect(nftRoyalty.amount.eq(royalty), 'Error: Invalid NFT royalty') + .to.be.true; + } + + expect(nft.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT metadata'); + + const nftResources = await getResources(api, collectionId, nftId); + if (resources == null) { + expect(nftResources, 'Error: Invalid NFT resources').to.be.empty; + } else { + expect(nftResources.length).to.be.equal(resources.length); + + for (let i = 0; i < resources.length; i++) { + let successFindingResource = false; + const resource = resources[i]; + // try to find the matching resource from the query + for (let j = 0; j < nftResources.length && !successFindingResource; j++) { + const nftResourceData = nftResources[j].toHuman(); + expect( + Object.prototype.hasOwnProperty.call(nftResourceData, 'resource'), + `Error: Corrupted resource data on resource #${i}`, + ).to.be.true; + const nftResource = nftResourceData.resource!; + type NftResourceKey = keyof typeof nftResource; + + let typedResource = null; + let typedNftResource = null; + + if (resource.basic && Object.prototype.hasOwnProperty.call(nftResource, 'Basic')) { + typedResource = resource.basic!; + typedNftResource = nftResource['Basic' as NftResourceKey]!; + } else if (resource.composable && Object.prototype.hasOwnProperty.call(nftResource, 'Composable')) { + typedResource = resource.composable!; + typedNftResource = nftResource['Composable' as NftResourceKey]! as any; + if (typedResource.parts != undefined && typedResource.parts.toString() != typedNftResource.parts.toString() + || typedResource.base != typedNftResource.base && typedResource.base != undefined) { + continue; + } + } else if (resource.slot && Object.prototype.hasOwnProperty.call(nftResource, 'Slot')) { + typedResource = resource.slot!; + typedNftResource = nftResource['Slot' as NftResourceKey]! as any; + if (typedResource.slot != typedNftResource.slot && typedResource.slot != undefined + || typedResource.base != typedNftResource.base && typedResource.base != undefined) { + continue; + } + } else { + continue; + } + + if (typedResource.src != typedNftResource.src + || typedResource.metadata != typedNftResource.metadata + || typedResource.thumb != typedNftResource.thumb + || typedResource.license != typedNftResource.license + ) { + continue; + } + + // do final checks since this is now established to be the resource we seek + expect(nftResourceData.pending, `Error: Resource #${i} is pending`).to.be.false; + expect(nftResourceData.pendingRemoval, `Error: Resource #${i} is pending removal`).to.be.false; + + // remove the matching resource from the resources we check + nftResources.splice(j, 1); + successFindingResource = true; + } + + expect(successFindingResource, `Error: Couldn't find resource #${i}'s counterpart among the returned`).to.be.true; + } + } + + return nftId; +} + +export async function sendNft( + api: ApiPromise, + expectedStatus: 'pending' | 'sent', + originalOwnerUri: string, + collectionId: number, + nftId: number, + newOwner: string | NftIdTuple, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const originalOwner = privateKey(originalOwnerUri, Number(ss58Format)); + const newOwnerObj = makeNftOwner(api, newOwner); + + const nftBeforeSendingOpt = await getNft(api, collectionId, nftId); + + const tx = api.tx.rmrkCore.send(collectionId, nftId, newOwnerObj); + const events = await executeTransaction(api, originalOwner, tx); + + const sendResult = extractRmrkCoreTxResult(events, 'NFTSent', (data) => { + return { + dstOwner: data[1] as NftOwner, + collectionId: parseInt(data[2].toString(), 10), + nftId: parseInt(data[3].toString(), 10), + }; + }); + + expect(sendResult.success, 'Error: Unable to send NFT').to.be.true; + const sendData = sendResult.successData!; + + expect(sendData.dstOwner.eq(newOwnerObj), 'Error: Invalid target user (from event data)') + .to.be.true; + + expect(sendData.collectionId) + .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); + + expect(sendData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); + + expect(nftBeforeSendingOpt.isSome, 'Error: Unable to fetch NFT before sending').to.be.true; + + const nftBeforeSending = nftBeforeSendingOpt.unwrap(); + + const nftAfterSendingOpt = await getNft(api, collectionId, nftId); + + expect(nftAfterSendingOpt.isSome, 'Error: Unable to fetch NFT after sending').to.be.true; + + const nftAfterSending = nftAfterSendingOpt.unwrap(); + + const isOwnedByNewOwner = await isNftOwnedBy(api, newOwner, collectionId, nftId); + const isPending = expectedStatus === 'pending'; + + expect( + isOwnedByNewOwner, + `Error: The NFT should be owned by ${newOwner.toString()}`, + ).to.be.true; + + expect(nftAfterSending.royalty.eq(nftBeforeSending.royalty), 'Error: Invalid NFT royalty after sending') + .to.be.true; + + expect(nftAfterSending.metadata.eq(nftBeforeSending.metadata), 'Error: Invalid NFT metadata after sending') + .to.be.true; + + expect(nftAfterSending.equipped.eq(nftBeforeSending.equipped), 'Error: Invalid NFT equipped status after sending') + .to.be.true; + + expect(nftAfterSending.pending.eq(isPending), 'Error: Invalid NFT pending state') + .to.be.true; +} + +export async function acceptNft( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, + newOwner: string | [number, number], +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const newOwnerObj = makeNftOwner(api, newOwner); + + const nftBeforeOpt = await getNft(api, collectionId, nftId); + + const tx = api.tx.rmrkCore.acceptNft(collectionId, nftId, newOwnerObj); + const events = await executeTransaction(api, issuer, tx); + + const acceptResult = extractRmrkCoreTxResult(events, 'NFTAccepted', (data) => { + return { + recipient: data[1] as NftOwner, + collectionId: parseInt(data[2].toString(), 10), + nftId: parseInt(data[3].toString(), 10), + }; + }); + + expect(acceptResult.success, 'Error: Unable to accept NFT').to.be.true; + const acceptData = acceptResult.successData!; + + expect(acceptData.recipient.eq(newOwnerObj), 'Error: Invalid NFT recipient (from event data)') + .to.be.true; + + expect(acceptData.collectionId) + .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); + + expect(acceptData.nftId) + .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); + + const nftBefore = nftBeforeOpt.unwrap(); + + const isPendingBeforeAccept = nftBefore.pending.isTrue; + + const nftAfter = (await getNft(api, collectionId, nftId)).unwrap(); + const isPendingAfterAccept = nftAfter.pending.isTrue; + + expect(isPendingBeforeAccept, 'Error: NFT should be pending to be accepted') + .to.be.true; + + expect(isPendingAfterAccept, 'Error: NFT should NOT be pending after accept') + .to.be.false; + + const isOwnedInUniques = await isNftOwnedBy(api, newOwner, collectionId, nftId); + expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${newOwner.toString()}`) + .to.be.true; +} + +export async function rejectNft( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const nftBeforeOpt = await getNft(api, collectionId, nftId); + + const tx = api.tx.rmrkCore.rejectNft(collectionId, nftId); + const events = await executeTransaction(api, issuer, tx); + const rejectResult = extractRmrkCoreTxResult(events, 'NFTRejected', (data) => { + return { + collectionId: parseInt(data[1].toString(), 10), + nftId: parseInt(data[2].toString(), 10), + }; + }); + + const rejectData = rejectResult.successData!; + + expect(rejectData.collectionId) + .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); + + expect(rejectData.nftId) + .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); + + const nftBefore = nftBeforeOpt.unwrap(); + + const isPendingBeforeReject = nftBefore.pending.isTrue; + + const nftAfter = await getNft(api, collectionId, nftId); + + expect(isPendingBeforeReject, 'Error: NFT should be pending to be rejected') + .to.be.true; + + expect(nftAfter.isNone, 'Error: NFT should be burned after reject') + .to.be.true; +} + +export async function createBase( + api: ApiPromise, + issuerUri: string, + baseType: string, + symbol: string, + parts: object[], +): Promise { + let baseId = 0; + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const partTypes = api.createType('Vec', parts) as Vec; + + const tx = api.tx.rmrkEquip.createBase(baseType, symbol, partTypes); + const events = await executeTransaction(api, issuer, tx); + + const baseResult = extractRmrkEquipTxResult(events, 'BaseCreated', (data) => { + return parseInt(data[1].toString(), 10); + }); + + expect(baseResult.success, 'Error: Unable to create Base') + .to.be.true; + + baseId = baseResult.successData!; + const baseOptional = await getBase(api, baseId); + + expect(baseOptional.isSome, 'Error: Unable to fetch created Base') + .to.be.true; + + const base = baseOptional.unwrap(); + const baseParts = await getParts(api, baseId); + + expect(base.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid Base issuer'); + expect(base.baseType.toUtf8()).to.be.equal(baseType, 'Error: Invalid Base type'); + expect(base.symbol.toUtf8()).to.be.equal(symbol, 'Error: Invalid Base symbol'); + expect(partTypes.eq(baseParts), 'Error: Received invalid base parts').to.be.true; + + return baseId; +} + +export async function setResourcePriorities( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, + priorities: number[], +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const prioritiesVec = api.createType('Vec', priorities); + const tx = api.tx.rmrkCore.setPriority(collectionId, nftId, prioritiesVec); + const events = await executeTransaction(api, issuer, tx); + + const prioResult = extractRmrkCoreTxResult(events, 'PrioritySet', (data) => { + return { + collectionId: parseInt(data[0].toString(), 10), + nftId: parseInt(data[1].toString(), 10), + }; + }); + + expect(prioResult.success, 'Error: Unable to set resource priorities').to.be.true; + const eventData = prioResult.successData!; + + expect(eventData.collectionId) + .to.be.equal(collectionId, 'Error: Invalid collection ID (set priorities event data)'); + + expect(eventData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (set priorities event data'); + + for (let i = 0; i < priorities.length; i++) { + const resourceId = priorities[i]; + + const fetchedPrio = await getResourcePriority(api, collectionId, nftId, resourceId); + expect(fetchedPrio).to.be.equal(i, 'Error: Invalid priorities are set'); + } + +} + +export async function setEquippableList( + api: ApiPromise, + issuerUri: string, + baseId: number, + slotId: number, + equippableList: 'All' | 'Empty' | { 'Custom': number[] }, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const equippable = api.createType('RmrkTraitsPartEquippableList', equippableList) as EquippableList; + + const tx = api.tx.rmrkEquip.equippable(baseId, slotId, equippable); + const events = await executeTransaction(api, issuer, tx); + + const equipListResult = extractRmrkEquipTxResult(events, 'EquippablesUpdated', (data) => { + return { + baseId: parseInt(data[0].toString(), 10), + slotId: parseInt(data[1].toString(), 10), + }; + }); + + expect(equipListResult.success, 'Error: unable to update equippable list').to.be.true; + const updateEvent = equipListResult.successData!; + + expect(updateEvent.baseId) + .to.be.equal(baseId, 'Error: invalid base ID from update equippable event'); + + expect(updateEvent.slotId) + .to.be.equal(slotId, 'Error: invalid base ID from update equippable event'); + + const fetchedEquippableList = await getEquippableList(api, baseId, slotId); + + expect(fetchedEquippableList, 'Error: unable to fetch equippable list').to.be.not.null; + if (fetchedEquippableList) { + expect(fetchedEquippableList) + .to.be.deep.equal(equippableList, 'Error: invalid equippable list was set'); + } +} + +export async function addTheme( + api: ApiPromise, + issuerUri: string, + baseId: number, + themeObj: object, + filterKeys: string[] | null = null, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const theme = api.createType('RmrkTraitsTheme', themeObj) as Theme; + + const tx = api.tx.rmrkEquip.themeAdd(baseId, theme); + const events = await executeTransaction(api, issuer, tx); + + expect(isTxResultSuccess(events), 'Error: Unable to add Theme').to.be.true; + + const fetchedThemeOpt = await getTheme(api, baseId, theme.name.toUtf8(), null); + + expect(fetchedThemeOpt.isSome, 'Error: Unable to fetch theme').to.be.true; + + const fetchedTheme = fetchedThemeOpt.unwrap(); + + expect(theme.name.eq(fetchedTheme.name), 'Error: Invalid theme name').to.be.true; + + for (let i = 0; i < theme.properties.length; i++) { + const property = theme.properties[i]; + const propertyKey = property.key.toUtf8(); + + const propertyFoundCount = fetchedTheme.properties.filter((fetchedProp) => property.key.eq(fetchedProp.key)).length; + + expect(propertyFoundCount > 1, `Error: Too many properties with key ${propertyKey} found`) + .to.be.false; + + if (filterKeys) { + const isFiltered = fetchedTheme.properties.find((fetchedProp) => fetchedProp.key.eq(property.key)) === undefined; + + if (isFiltered) { + expect(propertyFoundCount === 0, `Error: Unexpected filtered key ${propertyKey}`) + .to.be.true; + continue; + } + } + + expect(propertyFoundCount === 1, `Error: The property with key ${propertyKey} is not found`) + .to.be.true; + } +} + +export async function lockCollection( + api: ApiPromise, + issuerUri: string, + collectionId: number, + max = 0, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkCore.lockCollection(collectionId); + const events = await executeTransaction(api, issuer, tx); + const lockResult = extractRmrkCoreTxResult(events, 'CollectionLocked', (data) => { + return parseInt(data[1].toString(), 10); + }); + expect(lockResult.success, 'Error: Unable to lock a collection').to.be.true; + expect(lockResult.successData!, 'Error: Invalid collection was locked') + .to.be.eq(collectionId); + + await getCollection(api, collectionId).then((collectionOption) => { + const collection = collectionOption.unwrap(); + expect(collection.max.unwrap().toNumber()).to.be.equal(max); + }); +} + +export async function setPropertyCollection( + api: ApiPromise, + issuerUri: string, + collectionId: number, + key: string, + value: string, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const alice = privateKey(issuerUri, Number(ss58Format)); + + const tx = api.tx.rmrkCore.setProperty(collectionId, null, key, value); + const events = await executeTransaction(api, alice, tx); + const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => { + return { + collectionId: parseInt(data[0].toString(), 10), + nftId: data[1] as Option, + key: data[2] as Bytes, + value: data[3] as Bytes, + }; + }); + + expect(propResult.success, 'Error: Unable to set collection property').to.be.true; + const eventData = propResult.successData!; + const eventDescription = 'from set collection property event'; + + expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription) + .to.be.equal(collectionId); + + expect(eventData.nftId.eq(null), 'Error: Unexpected NFT ID ' + eventDescription) + .to.be.true; + + expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription) + .to.be.true; + + expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription) + .to.be.true; + + expect(await isCollectionPropertyExists(api, collectionId, key, value)) + .to.be.true; +} + +export async function burnNft( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const maxBurns = 10; + const tx = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); + const events = await executeTransaction(api, issuer, tx); + const burnResult = extractRmrkCoreTxResult(events, 'NFTBurned', (data) => { + return parseInt(data[1].toString(), 10); + }); + + expect(burnResult.success, 'Error: Unable to burn an NFT').to.be.true; + expect(burnResult.successData!, 'Error: Invalid NFT was burned') + .to.be.eq(nftId); + + const nftBurned = await getNft(api, collectionId, nftId); + expect(nftBurned.isSome).to.be.false; +} + +export async function acceptNftResource( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, + resourceId: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const tx = api.tx.rmrkCore.acceptResource( + collectionId, + nftId, + resourceId, + ); + + const events = await executeTransaction(api, issuer, tx); + const acceptResult = extractRmrkCoreTxResult(events, 'ResourceAccepted', (data) => { + return { + nftId: parseInt(data[0].toString(), 10), + resourceId: parseInt(data[1].toString(), 10), + }; + }); + + expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true; + expect(acceptResult.successData!.nftId, 'Error: Invalid NFT ID while accepting a resource') + .to.be.eq(nftId); + expect(acceptResult.successData!.resourceId, 'Error: Invalid resource ID while accepting a resource') + .to.be.eq(resourceId); + + const resource = await getResourceById(api, collectionId, nftId, resourceId); + checkResourceStatus(resource, 'added'); +} + +async function executeResourceCreation( + api: ApiPromise, + issuer: IKeyringPair, + tx: any, + collectionId: number, + nftId: number, + expectedStatus: 'pending' | 'added', +): Promise { + const events = await executeTransaction(api, issuer, tx); + + const resourceResult = extractRmrkCoreTxResult(events, 'ResourceAdded', (data) => { + return parseInt(data[1].toString(), 10); + }); + expect(resourceResult.success, 'Error: Unable to add resource').to.be.true; + const resourceId = resourceResult.successData!; + + const resource = await getResourceById(api, collectionId, nftId, resourceId); + checkResourceStatus(resource, expectedStatus); + + return resource; +} + +export async function addNftBasicResource( + api: ApiPromise, + issuerUri: string, + expectedStatus: 'pending' | 'added', + collectionId: number, + nftId: number, + src: string | null, + metadata: string | null, + license: string | null, + thumb: string | null, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const basicResource = api.createType('RmrkTraitsResourceBasicResource', { + src: src, + metadata: metadata, + license: license, + thumb: thumb, + }) as BasicResource; + + const tx = api.tx.rmrkCore.addBasicResource( + collectionId, + nftId, + basicResource, + ); + + const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); + + // FIXME A workaround. It seems it is a PolkadotJS bug. + // All of the following are `false`. + // + // console.log('>>> basic:', resource.resource.isBasic); + // console.log('>>> composable:', resource.resource.isComposable); + // console.log('>>> slot:', resource.resource.isSlot); + const resourceJson = resource.resource.toHuman() as any; + + expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Basic'), 'Error: Expected basic resource type') + .to.be.true; + + const recvBasicRes = resourceJson['Basic']; + + expect(recvBasicRes.src, 'Error: Invalid basic resource src') + .to.be.eq(src); + expect(recvBasicRes.metadata, 'Error: basic first resource metadata') + .to.be.eq(metadata); + expect(recvBasicRes.license, 'Error: basic first resource license') + .to.be.eq(license); + expect(recvBasicRes.thumb, 'Error: basic first resource thumb') + .to.be.eq(thumb); + + return resource.id.toNumber(); +} + +export async function addNftComposableResource( + api: ApiPromise, + issuerUri: string, + expectedStatus: 'pending' | 'added', + collectionId: number, + nftId: number, + parts: number[], + baseId: number, + src: string | null, + metadata: string | null, + license: string | null, + thumb: string | null, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const composableResource = api.createType('RmrkTraitsResourceComposableResource', { + parts: parts, // api.createType('Vec', parts), + base: baseId, + src: src, + metadata: metadata, + license: license, + thumb: thumb, + }) as ComposableResource; + + const tx = api.tx.rmrkCore.addComposableResource( + collectionId, + nftId, + composableResource, + ); + + const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); + + // FIXME A workaround. It seems it is a PolkadotJS bug. + // All of the following are `false`. + // + // console.log('>>> basic:', resource.resource.isBasic); + // console.log('>>> composable:', resource.resource.isComposable); + // console.log('>>> slot:', resource.resource.isSlot); + const resourceJson = resource.resource.toHuman() as any; + + expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Composable'), 'Error: Expected composable resource type') + .to.be.true; + + const recvComposableRes = resourceJson['Composable']; + + expect(recvComposableRes.parts.toString(), 'Error: Invalid composable resource parts') + .to.be.eq(parts.toString()); + expect(recvComposableRes.base, 'Error: Invalid composable resource base id') + .to.be.eq(baseId.toString()); + expect(recvComposableRes.src, 'Error: Invalid composable resource src') + .to.be.eq(src); + expect(recvComposableRes.metadata, 'Error: Invalid composable resource metadata') + .to.be.eq(metadata); + expect(recvComposableRes.license, 'Error: Invalid composable resource license') + .to.be.eq(license); + expect(recvComposableRes.thumb, 'Error: Invalid composable resource thumb') + .to.be.eq(thumb); + + return resource.id.toNumber(); +} + +export async function addNftSlotResource( + api: ApiPromise, + issuerUri: string, + expectedStatus: 'pending' | 'added', + collectionId: number, + nftId: number, + baseId: number, + slotId: number, + src: string | null, + metadata: string | null, + license: string | null, + thumb: string | null, +): Promise { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const slotResource = api.createType('RmrkTraitsResourceSlotResource', { + base: baseId, + src: src, + metadata, + slot: slotId, + license: license, + thumb: thumb, + }) as SlotResource; + + const tx = api.tx.rmrkCore.addSlotResource( + collectionId, + nftId, + slotResource, + ); + + const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); + + // FIXME A workaround. It seems it is a PolkadotJS bug. + // All of the following are `false`. + // + // console.log('>>> basic:', resource.resource.isBasic); + // console.log('>>> composable:', resource.resource.isComposable); + // console.log('>>> slot:', resource.resource.isSlot); + const resourceJson = resource.resource.toHuman() as any; + + expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Slot'), 'Error: Expected slot resource type') + .to.be.true; + + const recvSlotRes = resourceJson['Slot']; + + expect(recvSlotRes.base, 'Error: Invalid slot resource base id') + .to.be.eq(baseId.toString()); + expect(recvSlotRes.slot, 'Error: Invalid slot resource slot id') + .to.be.eq(slotId.toString()); + expect(recvSlotRes.src, 'Error: Invalid slot resource src') + .to.be.eq(src); + expect(recvSlotRes.metadata, 'Error: Invalid slot resource metadata') + .to.be.eq(metadata); + expect(recvSlotRes.license, 'Error: Invalid slot resource license') + .to.be.eq(license); + expect(recvSlotRes.thumb, 'Error: Invalid slot resource thumb') + .to.be.eq(thumb); + + return resource.id.toNumber(); +} + +export async function equipNft( + api: ApiPromise, + issuerUri: string, + item: NftIdTuple, + equipper: NftIdTuple, + resource: number, + base: number, + slot: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot); + const events = await executeTransaction(api, issuer, tx); + const equipResult = extractRmrkEquipTxResult(events, 'SlotEquipped', (data) => { + return { + item_collection: parseInt(data[0].toString(), 10), + item_nft: parseInt(data[1].toString(), 10), + base_id: parseInt(data[2].toString(), 10), + slot_id: parseInt(data[3].toString(), 10), + }; + }); + expect(equipResult.success, 'Error: Unable to equip an item').to.be.true; + expect(equipResult.successData!.item_collection, 'Error: Invalid item collection id') + .to.be.eq(item[0]); + expect(equipResult.successData!.item_nft, 'Error: Invalid item NFT id') + .to.be.eq(item[1]); + expect(equipResult.successData!.base_id, 'Error: Invalid base id') + .to.be.eq(base); + expect(equipResult.successData!.slot_id, 'Error: Invalid slot id') + .to.be.eq(slot); +} + +export async function unequipNft( + api: ApiPromise, + issuerUri: string, + item: any, + equipper: any, + resource: number, + base: number, + slot: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot); + const events = await executeTransaction(api, issuer, tx); + + const unEquipResult = extractRmrkEquipTxResult( + events, + 'SlotUnequipped', + (data) => { + return { + item_collection: parseInt(data[0].toString(), 10), + item_nft: parseInt(data[1].toString(), 10), + base_id: parseInt(data[2].toString(), 10), + slot_id: parseInt(data[3].toString(), 10), + }; + }, + ); + + expect(unEquipResult.success, 'Error: Unable to unequip an item').to.be.true; + expect(unEquipResult.successData!.item_collection, 'Error: Invalid item collection id') + .to.be.eq(item[0]); + expect(unEquipResult.successData!.item_nft, 'Error: Invalid item NFT id') + .to.be.eq(item[1]); + expect(unEquipResult.successData!.base_id, 'Error: Invalid base id') + .to.be.eq(base); + expect(unEquipResult.successData!.slot_id, 'Error: Invalid slot id') + .to.be.eq(slot); +} + +export async function removeNftResource( + api: ApiPromise, + expectedStatus: 'pending' | 'removed', + issuerUri: string, + collectionId: number, + nftId: number, + resourceId: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const tx = api.tx.rmrkCore.removeResource(collectionId, nftId, resourceId); + const events = await executeTransaction(api, issuer, tx); + const removeResult = extractRmrkCoreTxResult(events, 'ResourceRemoval', (data) => { + return { + nftId: parseInt(data[0].toString(), 10), + resourceId: parseInt(data[1].toString(), 10), + }; + }); + expect(removeResult.success, 'Error: Unable to remove a resource').to.be.true; + expect(removeResult.successData!.nftId, 'Error: Invalid NFT Id while removing a resource') + .to.be.eq(nftId); + expect(removeResult.successData!.resourceId, 'Error: Invalid resource Id while removing a resource') + .to.be.eq(resourceId); + + const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId); + + if (expectedStatus === 'pending') { + expect(afterDeleting).not.to.be.null; + expect(afterDeleting?.pendingRemoval.isTrue).to.be.equal(true); + } else { + expect(afterDeleting).to.be.null; + } +} + +export async function acceptResourceRemoval( + api: ApiPromise, + issuerUri: string, + collectionId: number, + nftId: number, + resourceId: number, +) { + const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + const issuer = privateKey(issuerUri, Number(ss58Format)); + + const tx = api.tx.rmrkCore.acceptResourceRemoval(collectionId, nftId, resourceId); + const events = await executeTransaction(api, issuer, tx); + const acceptResult = extractRmrkCoreTxResult(events, 'ResourceRemovalAccepted', (data) => { + return { + nftId: parseInt(data[0].toString(), 10), + resourceId: parseInt(data[1].toString(), 10), + }; + }); + expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true; + expect(acceptResult.successData!.nftId, 'Error: Invalid NFT Id while accepting a resource') + .to.be.eq(nftId); + expect(acceptResult.successData!.resourceId, 'Error: Invalid resource Id while accepting a resource') + .to.be.eq(resourceId); + + const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId); + expect(afterDeleting, 'Error: resource deleting failed').to.be.null; +} --- /dev/null +++ b/tests/src/.outdated/substrate/get-balance.ts @@ -0,0 +1,40 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {ApiPromise} from '@polkadot/api'; +import {AccountInfo} from '@polkadot/types/interfaces/system'; +import promisifySubstrate from './promisify-substrate'; +import {IKeyringPair} from '@polkadot/types/types'; +import {submitTransactionAsync} from './substrate-api'; +import {getGenericResult} from '../deprecated-helpers/helpers'; +import {expect} from 'chai'; + +export default async function getBalance(api: ApiPromise, accounts: string[]): Promise> { + const balance = promisifySubstrate(api, (acc: string[]) => api.query.system.account.multi(acc)); + const responce = await balance(accounts) as unknown as AccountInfo[]; + return responce.map((r) => r.data.free.toBigInt().valueOf()); +} + +export async function getBalanceSingle(api: ApiPromise, account: string): Promise { + return (await getBalance(api, [account]))[0]; +} + +export async function transferBalanceExpectSuccess(api: ApiPromise, from: IKeyringPair, to: string, amount: bigint | string) { + const tx = api.tx.balances.transfer(to, amount); + const events = await submitTransactionAsync(from, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; +} --- /dev/null +++ b/tests/src/.outdated/substrate/privateKey.ts @@ -0,0 +1,26 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {Keyring} from '@polkadot/api'; +import {IKeyringPair} from '@polkadot/types/types'; + +// WARNING: the WASM interface must be initialized before this function is called. +// Use either `usingApi`, or `cryptoWaitReady` for consistency. +export default function privateKey(account: string, ss58Format?: number): IKeyringPair { + const keyring = new Keyring({ss58Format, type: 'sr25519'}); + + return keyring.addFromUri(account); +} --- /dev/null +++ b/tests/src/.outdated/substrate/promisify-substrate.ts @@ -0,0 +1,50 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {ApiPromise} from '@polkadot/api'; + +type PromiseType = T extends PromiseLike ? TInner : T; + +export default function promisifySubstrate any>(api: ApiPromise, action: T): (...args: Parameters) => Promise>> { + return (...args: Parameters) => { + const promise = new Promise>>((resolve: ((result: PromiseType>) => void) | undefined, reject: ((error: any) => void) | undefined) => { + const cleanup = () => { + api.off('disconnected', fail); + api.off('error', fail); + resolve = undefined; + reject = undefined; + }; + + const success = (r: any) => { + resolve && resolve(r); + cleanup(); + }; + const fail = (error: any) => { + reject && reject(error); + cleanup(); + }; + + api.on('disconnected', fail); + api.on('error', fail); + + const result = action(...args); + Promise.resolve(result) + .then(success, fail); + + }); + return promise as any; + }; +} --- /dev/null +++ b/tests/src/.outdated/substrate/substrate-api.ts @@ -0,0 +1,226 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {ApiPromise, WsProvider} from '@polkadot/api'; +import {ApiOptions, ApiTypes, SubmittableExtrinsic} from '@polkadot/api/types'; +import {ExtrinsicStatus} from '@polkadot/types/interfaces/author/types'; +import {EventRecord} from '@polkadot/types/interfaces/system/types'; +import {IKeyringPair} from '@polkadot/types/types'; +import config from '../../config'; +import '../../interfaces/augment-api-events'; +import * as defs from '../../interfaces/definitions'; +import privateKey from './privateKey'; +import promisifySubstrate from './promisify-substrate'; + +import {SilentConsole} from '../../util/playgrounds/unique.dev'; + + + +function defaultApiOptions(): ApiOptions { + const wsProvider = new WsProvider(config.substrateUrl); + return { + provider: wsProvider, signedExtensions: { + ContractHelpers: { + extrinsic: {}, + payload: {}, + }, + CheckMaintenance: { + extrinsic: {}, + payload: {}, + }, + FakeTransactionFinalizer: { + extrinsic: {}, + payload: {}, + }, + }, + rpc: { + unique: defs.unique.rpc, + appPromotion: defs.appPromotion.rpc, + rmrk: defs.rmrk.rpc, + eth: { + feeHistory: { + description: 'Dummy', + params: [], + type: 'u8', + }, + maxPriorityFeePerGas: { + description: 'Dummy', + params: [], + type: 'u8', + }, + }, + }, + }; +} + +export async function getApiConnection(settings: ApiOptions | undefined = undefined): Promise { + settings = settings || defaultApiOptions(); + const api = new ApiPromise(settings); + + if (api) { + await api.isReadyOrError; + } + + return api; +} + +export default async function usingApi(action: (api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair) => Promise, settings: ApiOptions | undefined = undefined): Promise { + settings = settings || defaultApiOptions(); + const api: ApiPromise = new ApiPromise(settings); + let result: T = null as unknown as T; + + const silentConsole = new SilentConsole(); + silentConsole.enable(); + + try { + await promisifySubstrate(api, async () => { + if (api) { + await api.isReadyOrError; + const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; + const privateKeyWrapper = (account: string) => privateKey(account, Number(ss58Format)); + result = await action(api, privateKeyWrapper); + } + })(); + } finally { + await api.disconnect(); + silentConsole.disable(); + } + return result as T; +} + +enum TransactionStatus { + Success, + Fail, + NotReady +} + +function getTransactionStatus(events: EventRecord[], status: ExtrinsicStatus): TransactionStatus { + if (status.isReady) { + return TransactionStatus.NotReady; + } + if (status.isBroadcast) { + return TransactionStatus.NotReady; + } + if (status.isRetracted) { + return TransactionStatus.NotReady; + } + if (status.isInBlock || status.isFinalized) { + if(events.filter(e => e.event.data.method === 'ExtrinsicFailed').length > 0) { + return TransactionStatus.Fail; + } + if(events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) { + return TransactionStatus.Success; + } + } + + return TransactionStatus.Fail; +} + +export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise { + return new Promise(async (res, rej) => { + try { + await transaction.signAndSend(sender, ({events, status}) => { + if (!status.isInBlock && !status.isFinalized) return; + for (const {event} of events) { + if (api.events.system.ExtrinsicSuccess.is(event)) { + res(events); + } else if (api.events.system.ExtrinsicFailed.is(event)) { + const {data: [error]} = event; + if (error.isModule) { + const decoded = api.registry.findMetaError(error.asModule); + const {method, section} = decoded; + rej(new Error(`${section}.${method}`)); + } else { + rej(new Error(error.toString())); + } + } + } + }); + } catch (e) { + rej(e); + } + }); +} + +/** + * @deprecated use `executeTransaction` instead + */ +export function +submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic): Promise { + /* eslint no-async-promise-executor: "off" */ + return new Promise(async (resolve, reject) => { + try { + await transaction.signAndSend(sender, ({events = [], status, dispatchError}) => { + const transactionStatus = getTransactionStatus(events, status); + + if (transactionStatus === TransactionStatus.Success) { + resolve(events); + } else if (transactionStatus === TransactionStatus.Fail) { + let moduleError = null; + + if (dispatchError) { + if (dispatchError.isModule) { + const modErr = dispatchError.asModule; + const errorMeta = dispatchError.registry.findMetaError(modErr); + + moduleError = JSON.stringify(errorMeta, null, 4); + } + } + + console.log(`Something went wrong with transaction. Status: ${status}\nModule error: ${moduleError}`); + reject(events); + } + }); + } catch (e) { + console.log('Error: ', e); + reject(e); + } + }); +} + +export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic): Promise { + console.error = () => {}; + console.log = () => {}; + + /* eslint no-async-promise-executor: "off" */ + return new Promise(async function(res, rej) { + const resolve = (rec: EventRecord[]) => { + setTimeout(() => { + res(rec); + }); + }; + const reject = (errror: any) => { + setTimeout(() => { + rej(errror); + }); + }; + try { + await transaction.signAndSend(sender, ({events = [], status}) => { + const transactionStatus = getTransactionStatus(events, status); + + // console.log('transactionStatus', transactionStatus, 'events', events); + + if (transactionStatus === TransactionStatus.Success) { + resolve(events); + } else if (transactionStatus === TransactionStatus.Fail) { + reject(events); + } + }); + } catch (e) { + reject(e); + } + }); +} --- /dev/null +++ b/tests/src/.outdated/substrate/wait-new-blocks.ts @@ -0,0 +1,34 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {ApiPromise} from '@polkadot/api'; + +/* eslint no-async-promise-executor: "off" */ +export default function waitNewBlocks(api: ApiPromise, blocksCount = 1): Promise { + const promise = new Promise(async (resolve) => { + + const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { + if (blocksCount > 0) { + blocksCount--; + } else { + unsubscribe(); + resolve(); + } + }); + }); + + return promise; +} --- a/tests/src/calibrateApply.ts +++ b/tests/src/calibrateApply.ts @@ -1,6 +1,6 @@ import {readFile, writeFile} from 'fs/promises'; import path from 'path'; -import usingApi from './substrate/substrate-api'; +import usingApi from './.outdated/substrate/substrate-api'; const formatNumber = (num: string): string => num.split('').reverse().join('').replace(/([0-9]{3})/g, '$1_').split('').reverse().join('').replace(/^_/, ''); --- a/tests/src/rmrk/acceptNft.seqtest.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import { - createCollection, - mintNft, - sendNft, - acceptNft, -} from './util/tx'; -import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; - -describe('integration test: accept NFT', () => { - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - const alice = '//Alice'; - const bob = '//Bob'; - - const createTestCollection = async (issuerUri: string) => { - return await createCollection( - api, - issuerUri, - 'accept-metadata', - null, - 'acpt', - ); - }; - - it('accept NFT', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); - await acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); - - const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.true; - }); - - it('[negative] unable to accept NFT by a not-an-owner', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); - const tx = acceptNft(api, bob, bobCollectionId, childNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative] unable to accept non-existing NFT', async () => { - const collectionId = 0; - const maxNftId = 0xFFFFFFFF; - - const owner = alice; - const aliceCollectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, owner, aliceCollectionId, 'parent-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - const tx = acceptNft(api, alice, collectionId, maxNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] unable to accept NFT which is not sent', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - const tx = acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - - const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.false; - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/addResource.seqtest.ts +++ /dev/null @@ -1,435 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {NftIdTuple} from './util/fetch'; -import {expectTxFailure, getResourceById, requirePallets, Pallets} from './util/helpers'; -import { - addNftBasicResource, - acceptNftResource, - createCollection, - mintNft, - sendNft, - addNftSlotResource, - addNftComposableResource, -} from './util/tx'; -import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup'; - -describe('integration test: add NFT resource', () => { - const alice = '//Alice'; - const bob = '//Bob'; - const src = 'test-res-src'; - const metadata = 'test-res-metadata'; - const license = 'test-res-license'; - const thumb = 'test-res-thumb'; - - const nonexistentId = 99999; - - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - it('add resource', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - }); - - it('add a resource to the nested NFT', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; - - await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); - - await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - childNftId, - src, - metadata, - license, - thumb, - ); - }); - - it('add multiple resources', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const baseId = 42; - const slotId = 10; - const parts = [0, 5, 2]; - - const resourcesInfo = []; - const resourceNum = 4; - - const checkResource = (resource: ResourceInfo, resType: string, expectedId: number, expected: { - src: string, - metadata: string, - license: string, - thumb: string - }) => { - - // FIXME A workaround. It seems it is a PolkadotJS bug. - // All of the following are `false`. - // - // console.log('>>> basic:', resource.resource.isBasic); - // console.log('>>> composable:', resource.resource.isComposable); - // console.log('>>> slot:', resource.resource.isSlot); - const resourceJson = (resource.resource.toHuman() as any)[resType]; - - expect(resource.id.toNumber(), 'Error: Invalid resource Id') - .to.be.eq(expectedId); - - expect(resourceJson.src, 'Error: Invalid resource src') - .to.be.eq(expected.src); - expect(resourceJson.metadata, 'Error: Invalid resource metadata') - .to.be.eq(expected.metadata); - expect(resourceJson.license, 'Error: Invalid resource license') - .to.be.eq(expected.license); - expect(resourceJson.thumb, 'Error: Invalid resource thumb') - .to.be.eq(expected.thumb); - }; - - for (let i = 0; i < resourceNum; i++) { - resourcesInfo.push({ - src: src + 'r-' + i, - metadata: metadata + 'r-' + i, - license: license + 'r-' + i, - thumb: thumb + 'r-' + i, - }); - } - - const firstBasicResourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - resourcesInfo[0].src, - resourcesInfo[0].metadata, - resourcesInfo[0].license, - resourcesInfo[0].thumb, - ); - - const secondBasicResourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - resourcesInfo[1].src, - resourcesInfo[1].metadata, - resourcesInfo[1].license, - resourcesInfo[1].thumb, - ); - - const composableResourceId = await addNftComposableResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - parts, - baseId, - resourcesInfo[2].src, - resourcesInfo[2].metadata, - resourcesInfo[2].license, - resourcesInfo[2].thumb, - ); - - const slotResourceId = await addNftSlotResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - baseId, - slotId, - resourcesInfo[3].src, - resourcesInfo[3].metadata, - resourcesInfo[3].license, - resourcesInfo[3].thumb, - ); - - const firstResource = await getResourceById(api, collectionIdAlice, nftAlice, firstBasicResourceId); - await checkResource(firstResource, 'Basic', firstBasicResourceId, resourcesInfo[0]); - - const secondResource = await getResourceById(api, collectionIdAlice, nftAlice, secondBasicResourceId); - await checkResource(secondResource, 'Basic', secondBasicResourceId, resourcesInfo[1]); - - const composableResource = await getResourceById(api, collectionIdAlice, nftAlice, composableResourceId); - await checkResource(composableResource, 'Composable', composableResourceId, resourcesInfo[2]); - - const slotResource = await getResourceById(api, collectionIdAlice, nftAlice, slotResourceId); - await checkResource(slotResource, 'Slot', slotResourceId, resourcesInfo[3]); - }); - - it('[negative]: unable to add a resource to the non-existing NFT', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const tx = addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nonexistentId, - src, - metadata, - license, - thumb, - ); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative]: unable to add a resource by a not-an-owner user', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const tx = addNftBasicResource( - api, - bob, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative]: unable to add a resource to the nested NFT if it isnt root owned by the caller', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; - - await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); - - const tx = addNftBasicResource( - api, - bob, - 'added', - collectionIdAlice, - childNftId, - src, - metadata, - license, - thumb, - ); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('accept resource', async () => { - const collectionIdBob = await createCollection( - api, - bob, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - bob, - alice, - collectionIdBob, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - bob, - 'pending', - collectionIdBob, - nftAlice, - src, - metadata, - license, - thumb, - ); - - await acceptNftResource(api, alice, collectionIdBob, nftAlice, resourceId); - }); - - it('[negative]: unable to accept a non-existing resource', async () => { - const collectionIdBob = await createCollection( - api, - bob, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - bob, - alice, - collectionIdBob, - 'nft-metadata', - ); - - const tx = acceptNftResource(api, alice, collectionIdBob, nftAlice, nonexistentId); - await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); - }); - - it('[negative]: unable to accept a resource by a not-an-NFT-owner user', async () => { - const collectionIdBob = await createCollection( - api, - bob, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - bob, - alice, - collectionIdBob, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - bob, - 'pending', - collectionIdBob, - nftAlice, - src, - metadata, - license, - thumb, - ); - - const tx = acceptNftResource(api, bob, collectionIdBob, nftAlice, resourceId); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative]: unable to accept a resource to a non-target NFT', async () => { - const collectionIdBob = await createCollection( - api, - bob, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - bob, - alice, - collectionIdBob, - 'nft-metadata', - ); - - const wrongNft = await mintNft( - api, - bob, - alice, - collectionIdBob, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - bob, - 'pending', - collectionIdBob, - nftAlice, - src, - metadata, - license, - thumb, - ); - - const tx = acceptNftResource(api, bob, collectionIdBob, wrongNft, resourceId); - - await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); - }); - - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/addTheme.seqtest.ts +++ /dev/null @@ -1,129 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {createBase, addTheme} from './util/tx'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {getThemeNames} from './util/fetch'; - -describe('integration test: add Theme to Base', () => { - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkEquip]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - - it('add default theme', async () => { - const baseId = await createBase(api, alice, 'default-themed-base', 'DTBase', []); - await addTheme(api, alice, baseId, { - name: 'default', - properties: [ - { - key: 'some-key', - value: 'some-key-value', - }, - { - key: 'another-key', - value: 'another-key-value', - }, - ], - }); - }); - - it('add default theme and a custom one', async () => { - const baseId = await createBase(api, alice, '2-themed-base', '2TBase', []); - await addTheme(api, alice, baseId, { - name: 'default', - properties: [ - { - key: 'default-key', - value: 'default-key-value', - }, - ], - }); - await addTheme(api, alice, baseId, { - name: 'custom-theme', - properties: [ - { - key: 'custom-key-0', - value: 'custom-key-value-0', - }, - { - key: 'custom-key-1', - value: 'custom-key-value-1', - }, - ], - }); - }); - - it('fetch filtered theme keys', async () => { - const baseId = await createBase(api, alice, '2-themed-base', '2TBase', []); - await addTheme(api, alice, baseId, { - name: 'default', - properties: [ - { - key: 'first-key', - value: 'first-key-value', - }, - { - key: 'second-key', - value: 'second-key-value', - }, - ], - }, ['second-key']); - }); - - it('fetch theme names', async() => { - const baseId = await createBase(api, alice, '3-themed-base', '3TBase', []); - const names = [ - 'default', - 'first-theme', - 'second-theme', - ]; - - for (let i = 0; i < names.length; i++) { - await addTheme(api, alice, baseId, {name: names[i], properties: [{key: 'dummy', value: 'dummy'}]}); - } - - const fetchedNames = await getThemeNames(api, baseId); - - for (let i = 0; i < names.length; i++) { - const isFound = fetchedNames.find((name) => name === names[i]) !== undefined; - - expect(isFound, 'Error: invalid theme names').to.be.true; - } - }); - - it('[negative] unable to add theme to non-existing base', async () => { - const maxBaseId = 0xFFFFFFFF; - const tx = addTheme(api, alice, maxBaseId, { - name: 'default', - properties: [], - }); - - await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); - }); - - it('[negative] unable to add custom theme if no default theme', async () => { - const baseId = await createBase(api, alice, 'no-default-themed-base', 'NDTBase', []); - const tx = addTheme(api, alice, baseId, { - name: 'custom-theme', - properties: [], - }); - - await expectTxFailure(/rmrkEquip\.NeedsDefaultThemeFirst/, tx); - }); - - it('[negative] unable to add theme by a not-an-owner', async () => { - const baseId = await createBase(api, alice, 'no-default-themed-base', 'NDTBase', []); - const tx = addTheme(api, bob, baseId, { - name: 'default', - properties: [], - }); - - await expectTxFailure(/rmrkEquip\.PermissionError/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/burnNft.seqtest.ts +++ /dev/null @@ -1,170 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {NftIdTuple, getChildren} from './util/fetch'; -import {burnNft, createCollection, sendNft, mintNft} from './util/tx'; - -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -describe('integration test: burn nft', () => { - const alice = '//Alice'; - const bob = '//Bob'; - - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - it('burn nft', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const nftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - await burnNft(api, alice, collectionId, nftId); - }); - }); - - it('burn nft with children', async () => { - const collectionId = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const childNftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - const childrenBefore = await getChildren(api, collectionId, parentNftId); - expect(childrenBefore.length === 1, 'Error: parent NFT should have children') - .to.be.true; - - const child = childrenBefore[0]; - expect(child.collectionId.eq(collectionId), 'Error: invalid child collection Id') - .to.be.true; - - expect(child.nftId.eq(childNftId), 'Error: invalid child NFT Id') - .to.be.true; - - await burnNft(api, alice, collectionId, parentNftId); - - const childrenAfter = await getChildren(api, collectionId, parentNftId); - - expect(childrenAfter.length === 0, 'Error: children should be burned').to.be.true; - }); - - it('burn child nft', async () => { - const collectionId = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const childNftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - const childrenBefore = await getChildren(api, collectionId, parentNftId); - expect(childrenBefore.length === 1, 'Error: parent NFT should have children') - .to.be.true; - - const child = childrenBefore[0]; - expect(child.collectionId.eq(collectionId), 'Error: invalid child collection Id') - .to.be.true; - - expect(child.nftId.eq(childNftId), 'Error: invalid child NFT Id') - .to.be.true; - - await burnNft(api, alice, collectionId, childNftId); - - const childrenAfter = await getChildren(api, collectionId, parentNftId); - - expect(childrenAfter.length === 0, 'Error: children should be burned').to.be.true; - }); - - it('[negative] burn non-existing NFT', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const tx = burnNft(api, alice, collectionId, 99999); - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - }); - - it('[negative] burn not an owner NFT user', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const nftId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - const tx = burnNft(api, bob, collectionId, nftId); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/changeCollectionIssuer.seqtest.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import { - changeIssuer, - createCollection, -} from './util/tx'; - -describe('integration test: collection issuer', () => { - const alice = '//Alice'; - const bob = '//Bob'; - - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - - it('change collection issuer', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - await changeIssuer(api, alice, collectionId, bob); - }); - }); - - it('[negative] change not an owner NFT collection issuer', async () => { - await createCollection(api, bob, 'test-metadata', null, 'test-symbol').then(async (collectionId) => { - const tx = changeIssuer(api, alice, collectionId, bob); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - }); - - it('[negative] change non-existigit NFT collection issuer', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async () => { - const tx = changeIssuer(api, alice, 99999, bob); - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/createBase.seqtest.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from './util/helpers'; -import {createCollection, createBase} from './util/tx'; - -describe('integration test: create new Base', () => { - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); - }); - - const alice = '//Alice'; - - it('create empty Base', async () => { - await createBase(api, alice, 'empty-base-type', 'EBase', []); - }); - - it('create Base with fixed part', async () => { - await createBase(api, alice, 'fixedpart-base-type', 'FPBase', [ - { - 'FixedPart': { - id: 42, - z: 0, - src: 'some-fixed-url', - }, - }, - ]); - }); - - it('create Base with slot part (no collection)', async () => { - await createBase(api, alice, 'slotpart-base-type', 'SPBase', [ - { - 'SlotPart': { - id: 112, - equippable: 'Empty', - z: 0, - src: 'some-fallback-slot-url', - }, - }, - ]); - }); - - it('create Base with slot part (any collection)', async () => { - await createBase(api, alice, 'slotpartany-base-type', 'SPABase', [ - { - 'SlotPart': { - id: 222, - equippable: 'All', - z: 1, - src: 'some-fallback-slot-url', - }, - }, - ]); - }); - - it('create Base with slot part (custom collections)', async () => { - const firstCollectionId = await createCollection( - api, - alice, - 'first-collection-meta', - null, - 'first-collection', - ); - - const secondCollectionId = await createCollection( - api, - alice, - 'first-collection-meta', - null, - 'first-collection', - ); - - await createBase(api, alice, 'slotpartcustom-base-type', 'SPCBase', [ - { - 'SlotPart': { - id: 1024, - equippable: { - 'Custom': [firstCollectionId, secondCollectionId], - }, - z: 2, - src: 'some-fallback-slot-url', - }, - }, - ]); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/createCollection.seqtest.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from './util/helpers'; -import {createCollection} from './util/tx'; - -describe('Integration test: create new collection', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - - const alice = '//Alice'; - - it('create NFT collection', async () => { - await createCollection(api, alice, 'test-metadata', 42, 'test-symbol'); - }); - - it('create NFT collection without token limit', async () => { - await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol'); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/deleteCollection.seqtest.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, deleteCollection} from './util/tx'; - -describe('integration test: delete collection', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - - it('delete NFT collection', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - await deleteCollection(api, alice, collectionId.toString()); - }); - }); - - it('[negative] delete non-existing NFT collection', async () => { - const tx = deleteCollection(api, alice, '99999'); - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); - }); - - it('[negative] delete not an owner NFT collection', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const tx = deleteCollection(api, bob, collectionId.toString()); - await expectTxFailure(/rmrkCore.NoPermission/, tx); - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/equipNft.seqtest.ts +++ /dev/null @@ -1,340 +0,0 @@ -import {ApiPromise} from '@polkadot/api'; -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {getNft, NftIdTuple} from './util/fetch'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import { - addNftComposableResource, - addNftSlotResource, - createBase, - createCollection, - equipNft, - mintNft, - sendNft, - unequipNft, -} from './util/tx'; - -const alice = '//Alice'; -const bob = '//Bob'; - -const composableParts: number[] = [5, 2, 7]; -const composableSrc = 'test-cmp-src'; -const composableMetadata = 'test-cmp-metadata'; -const composableLicense = 'test-comp-license'; -const composableThumb = 'test-comp-thumb'; - -const slotSrc = 'test-slot-src'; -const slotLicense = 'test-slot-license'; -const slotMetadata = 'test-slot-metadata'; -const slotThumb = 'test-slot-thumb'; - -const slotId = 1; - -function createTestCollection(api: ApiPromise): Promise { - return createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); -} - -async function mintTestNft(api: ApiPromise, collectionId: number): Promise { - return await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); -} - -async function mintChildNft(api: ApiPromise, collectionId: number, parentNftId: number): Promise { - const nftChildId = await mintTestNft(api, collectionId); - - const parentNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, nftChildId, parentNFT); - - return nftChildId; -} - -function createTestBase(api: ApiPromise): Promise { - return createBase(api, alice, 'test-base', 'DTBase', [ - { - SlotPart: { - id: slotId, - equippable: 'All', - z: 1, - src: slotSrc, - }, - }, - ]); -} - -async function addTestComposable(api: ApiPromise, collectionId: number, nftId: number, baseId: number) { - await addNftComposableResource( - api, - alice, - 'added', - collectionId, - nftId, - composableParts, - baseId, - composableSrc, - composableMetadata, - composableLicense, - composableThumb, - ); -} - -async function addTestSlot(api: ApiPromise, collectionId: number, nftId: number, baseId: number, slotId: number): Promise { - return await addNftSlotResource( - api, - alice, - 'added', - collectionId, - nftId, - baseId, - slotId, - slotSrc, - slotMetadata, - slotLicense, - slotThumb, - ); -} - -async function checkEquipStatus( - api: ApiPromise, - expectedStatus: 'equipped' | 'unequipped', - collectionId: number, - nftId: number, -) { - const itemNftDataOpt = await getNft(api, collectionId, nftId); - expect(itemNftDataOpt.isSome, 'Error: unable to fetch item NFT data'); - - const itemNftData = itemNftDataOpt.unwrap(); - expect(itemNftData.equipped.isTrue, `Error: item NFT should be ${expectedStatus}`) - .to.be.equal(expectedStatus === 'equipped'); -} - -describe.skip('integration test: Equip NFT', () => { - - let api: any; - - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); - }); - - it('equip nft', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - await equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - - await checkEquipStatus(api, 'equipped', collectionId, nftChildId); - }); - - it('unequip nft', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - await equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - - await checkEquipStatus(api, 'equipped', collectionId, nftChildId); - - await unequipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - await checkEquipStatus(api, 'unequipped', collectionId, nftChildId); - }); - - it('[negative] equip NFT onto non-existing NFT', async () => { - const collectionId = await createTestCollection(api); - - const nftChildId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - const invalidEquipperNFT: NftIdTuple = [collectionId, 9999999]; - - const baseId = 0; - const resourceId = 0; - - const tx = equipNft(api, alice, itemNFT, invalidEquipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] equip non-existing NFT', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintNft( - api, - alice, - alice, - collectionId, - 'nft-metadata', - ); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const invalidItemNFT: NftIdTuple = [collectionId, 99999999]; - - const resourceId = 0; - - const tx = equipNft(api, alice, invalidItemNFT, equipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] equip NFT by a not-an-owner user', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const tx = equipNft(api, bob, itemNFT, equipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkEquip\.PermissionError/, tx); - }); - - it('[negative] unable to equip NFT onto indirect parent NFT', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - const nftGrandchildId = await mintChildNft(api, collectionId, nftChildId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftGrandchildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftGrandchildId]; - - const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkEquip\.MustBeDirectParent/, tx); - }); - - it('[negative] unable to equip NFT onto parent NFT with another base', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - const invalidBaseId = 99999; - - const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, invalidBaseId, slotId); - await expectTxFailure(/rmrkEquip\.NoResourceForThisBaseFoundOnNft/, tx); - }); - - it('[negative] unable to equip NFT into slot with another id', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createTestBase(api); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - const incorrectSlotId = slotId + 1; - const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, incorrectSlotId); - await expectTxFailure(/rmrkEquip\.ItemHasNoResourceToEquipThere/, tx); - }); - - it('[negative] unable to equip NFT with incorrect slot (fixed part)', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createBase(api, alice, 'test-base', 'DTBase', [ - { - FixedPart: { - id: slotId, - equippable: 'All', - z: 1, - src: slotSrc, - }, - }, - ]); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkEquip\.CantEquipFixedPart/, tx); - }); - - it('[negative] unable to equip NFT from a collection that is not allowed by the slot', async () => { - const collectionId = await createTestCollection(api); - const nftParentId = await mintTestNft(api, collectionId); - const nftChildId = await mintChildNft(api, collectionId, nftParentId); - - const baseId = await createBase(api, alice, 'test-base', 'DTBase', [ - { - SlotPart: { - id: 1, - z: 1, - equippable: 'Empty', - src: slotSrc, - }, - }, - ]); - - await addTestComposable(api, collectionId, nftParentId, baseId); - const resourceId = await addTestSlot(api, collectionId, nftChildId, baseId, slotId); - - const equipperNFT: NftIdTuple = [collectionId, nftParentId]; - const itemNFT: NftIdTuple = [collectionId, nftChildId]; - - const tx = equipNft(api, alice, itemNFT, equipperNFT, resourceId, baseId, slotId); - await expectTxFailure(/rmrkEquip\.CollectionNotEquippable/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/getOwnedNfts.seqtest.ts +++ /dev/null @@ -1,80 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from './util/helpers'; -import {getOwnedNfts} from './util/fetch'; -import {mintNft, createCollection} from './util/tx'; - -describe('integration test: get owned NFTs', () => { - let api: any; - - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - const alice = '//Alice'; - - it('fetch all NFTs owned by a user', async () => { - const owner = alice; - const collectionMetadata = 'aliceCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'AliceSym'; - const recipientUri = null; - const royalty = null; - const nftMetadata = 'alice-NFT-metadata'; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - const nftIds = [ - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata + '-0', - recipientUri, - royalty, - ), - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata + '-1', - recipientUri, - royalty, - ), - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata + '-2', - recipientUri, - royalty, - ), - ]; - - const ownedNfts = await getOwnedNfts(api, alice, collectionId); - - const isFound = (nftId: number) => { - return ownedNfts.find((ownedNftId) => { - return ownedNftId === nftId; - }) !== undefined; - }; - - nftIds.forEach((nftId) => { - expect(isFound(nftId), `NFT ${nftId} should be owned by ${alice}`) - .to.be.true; - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/lockCollection.seqtest.ts +++ /dev/null @@ -1,116 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, lockCollection, mintNft} from './util/tx'; - -describe('integration test: lock collection', () => { - const alice = '//Alice'; - const bob = '//Bob'; - const max = 5; - - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - it('lock collection', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - await lockCollection(api, alice, collectionId); - }); - }); - - it('[negative] lock non-existing NFT collection', async () => { - const tx = lockCollection(api, alice, 99999); - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); - }); - - it('[negative] lock not an owner NFT collection issuer', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const tx = lockCollection(api, bob, collectionId); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - }); - - it('lock collection with minting', async () => { - await createCollection( - api, - alice, - 'test-metadata', - max, - 'test-symbol', - ).then(async (collectionId) => { - for (let i = 0; i < 5; i++) { - await mintNft( - api, - alice, - alice, - collectionId, - 'test-metadata', - null, - null, - ); - } - await lockCollection(api, alice, collectionId, max); - }); - }); - - it('[negative] unable to mint NFT inside a locked collection', async () => { - await createCollection( - api, - alice, - 'test-metadata', - max, - 'test-symbol', - ).then(async (collectionId) => { - await lockCollection(api, alice, collectionId); - const tx = mintNft( - api, - alice, - alice, - collectionId, - 'test-metadata', - null, - null, - ); - await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); - }); - }); - - it('[negative] unable to mint NFT inside a full collection', async () => { - await createCollection(api, alice, 'test-metadata', 1, 'test-symbol').then(async (collectionId) => { - await mintNft( - api, - alice, - alice, - collectionId, - 'test-metadata', - null, - null, - ); - const tx = mintNft( - api, - alice, - alice, - collectionId, - 'test-metadata', - null, - null, - ); - await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/mintNft.seqtest.ts +++ /dev/null @@ -1,211 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {getNft} from './util/fetch'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, mintNft} from './util/tx'; - -describe('integration test: mint new NFT', () => { - let api: any; - - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - const alice = '//Alice'; - const bob = '//Bob'; - const maxCollectionId = 0xFFFFFFFF; - const maxNftId = 0xFFFFFFFF; - - it('mint NFT', async () => { - const owner = null; - const collectionMetadata = 'mintingCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'MCS'; - const recipientUri = null; - const royalty = null; - const nftMetadata = 'NFT-test-metadata'; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata, - recipientUri, - royalty, - ); - }); - - it('mint NFT and set another owner', async () => { - const owner = bob; - const collectionMetadata = 'setOwnerCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'SOCS'; - const recipientUri = null; - const royalty = null; - const nftMetadata = 'setOwner-NFT-metadata'; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata, - recipientUri, - royalty, - ); - }); - - it('mint NFT with recipient and roalty', async () => { - const owner = alice; - const collectionMetadata = 'mintingCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'MCS'; - const recipientUri = bob; - const royalty = 70000; - const nftMetadata = 'recipient-royalty-NFT-test-metadata'; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata, - recipientUri, - royalty, - ); - }); - - it('mint NFT with resources', async () => { - const owner = alice; - const collectionMetadata = 'mintingCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'MCS'; - const nftMetadata = 'NFT-with-resources-test-metadata'; - const resources = [ - { - basic: { - metadata: 'basic-resource-nft-minting', - }, - }, { - slot: { - metadata: 'slot-resource-nft-minting', - slot: 9, - }, - }, { - composable: { - metadata: 'composable-resource-nft-minting', - parts: [0, 5, 2], - }, - }, { - slot: { - metadata: 'slot-resource-nft-minting-2', - thumb: 'srnm2', - base: 5, - }, - }, - ]; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - await mintNft( - api, - alice, - owner, - collectionId, - nftMetadata, - null, - null, - true, - resources, - ); - }); - - it('[negative] unable to mint NFT within non-existing collection', async () => { - const owner = alice; - const recipientUri = null; - const royalty = null; - const nftMetadata = 'NFT-test-metadata'; - - const tx = mintNft( - api, - alice, - owner, - maxCollectionId, - nftMetadata, - recipientUri, - royalty, - ); - - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); - }); - - it("[negative] unable to mint NFT by a user that isn't the owner of the collection", async () => { - const owner = alice; - const collectionMetadata = 'mintingCollectionMetadata'; - const collectionMax = null; - const collectionSymbol = 'MCS'; - const recipientUri = null; - const royalty = null; - const nftMetadata = 'NFT-test-metadata'; - - const collectionId = await createCollection( - api, - alice, - collectionMetadata, - collectionMax, - collectionSymbol, - ); - - const tx = mintNft( - api, - bob, - owner, - collectionId, - nftMetadata, - recipientUri, - royalty, - ); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative] unable to fetch non-existing NFT', async () => { - const nft = await getNft(api, maxCollectionId, maxNftId); - expect(nft.isSome).to.be.false; - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/rejectNft.seqtest.ts +++ /dev/null @@ -1,94 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import { - createCollection, - mintNft, - sendNft, - rejectNft, -} from './util/tx'; -import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; - -describe('integration test: reject NFT', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - - - const alice = '//Alice'; - const bob = '//Bob'; - - const createTestCollection = async (issuerUri: string) => { - return await createCollection( - api, - issuerUri, - 'reject-metadata', - null, - 'rjct', - ); - }; - - it('reject NFT', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); - await rejectNft(api, alice, bobCollectionId, childNftId); - - const isChild = await isNftChildOfAnother(api, bobCollectionId, childNftId, newOwnerNFT); - expect(isChild, 'Error: rejected NFT is still a child of the target NFT').to.be.false; - }); - - it('[negative] unable to reject NFT by a not-an-owner', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - await sendNft(api, 'pending', ownerBob, bobCollectionId, childNftId, newOwnerNFT); - const tx = rejectNft(api, bob, bobCollectionId, childNftId); - - await expectTxFailure(/rmrkCore\.CannotRejectNonOwnedNft/, tx); - }); - - it('[negative] unable to reject non-existing NFT', async () => { - const maxNftId = 0xFFFFFFFF; - - const collectionId = await createTestCollection(alice); - - const tx = rejectNft(api, alice, collectionId, maxNftId); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] unable to reject NFT which is not sent', async () => { - const ownerAlice = alice; - - const collectionId = await createTestCollection(alice); - - const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'parent-nft-metadata'); - - const tx = rejectNft(api, alice, collectionId, nftId); - - await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/removeResource.seqtest.ts +++ /dev/null @@ -1,340 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {NftIdTuple} from './util/fetch'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import { - acceptResourceRemoval, addNftBasicResource, createCollection, mintNft, removeNftResource, sendNft, -} from './util/tx'; - - - - -describe('Integration test: remove nft resource', () => { - let api: any; - before(async function() { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - const src = 'test-basic-src'; - const metadata = 'test-basic-metadata'; - const license = 'test-basic-license'; - const thumb = 'test-basic-thumb'; - - it('deleting a resource directly by the NFT owner', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - - await removeNftResource(api, 'removed', alice, collectionIdAlice, nftAlice, resourceId); - }); - - it('deleting resources indirectly by the NFT owner', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft(api, alice, alice, collectionIdAlice, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, alice, collectionIdAlice, 'child-nft-metadata'); - - const resourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - childNftId, - src, - metadata, - license, - thumb, - ); - - const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; - - await sendNft(api, 'sent', alice, collectionIdAlice, childNftId, newOwnerNFT); - - await removeNftResource(api, 'removed', alice, collectionIdAlice, childNftId, resourceId); - }); - - it('deleting a resource by the collection owner', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftBob = await mintNft( - api, - alice, - bob, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'pending', - collectionIdAlice, - nftBob, - src, - metadata, - license, - thumb, - ); - - await removeNftResource(api, 'pending', alice, collectionIdAlice, nftBob, resourceId); - await acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, resourceId); - }); - - it('deleting a resource in a nested NFT by the collection owner', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const parentNftId = await mintNft( - api, - alice, - bob, - collectionIdAlice, - 'parent-nft-metadata', - ); - const childNftId = await mintNft( - api, - alice, - bob, - collectionIdAlice, - 'child-nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'pending', - collectionIdAlice, - childNftId, - src, - metadata, - license, - thumb, - ); - - const newOwnerNFT: NftIdTuple = [collectionIdAlice, parentNftId]; - - await sendNft(api, 'sent', bob, collectionIdAlice, childNftId, newOwnerNFT); - - await removeNftResource(api, 'pending', alice, collectionIdAlice, childNftId, resourceId); - await acceptResourceRemoval(api, bob, collectionIdAlice, childNftId, resourceId); - }); - - it('[negative]: can\'t delete a resource in a non-existing collection', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - - const tx = removeNftResource(api, 'removed', alice, 0xFFFFFFFF, nftAlice, resourceId); - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); // FIXME: inappropriate error message (NoAvailableNftId) - }); - - it('[negative]: only collection owner can delete a resource', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - - const tx = removeNftResource(api, 'removed', bob, collectionIdAlice, nftAlice, resourceId); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative]: cannot delete a resource that does not exist', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const tx = removeNftResource(api, 'removed', alice, collectionIdAlice, nftAlice, 127); - await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); - }); - - it('[negative]: Cannot accept deleting resource without owner attempt do delete it', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftBob = await mintNft( - api, - alice, - bob, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'pending', - collectionIdAlice, - nftBob, - src, - metadata, - license, - thumb, - ); - - const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, resourceId); - await expectTxFailure(/rmrkCore\.ResourceNotPending/, tx); - }); - - it('[negative]: cannot confirm the deletion of a non-existing resource', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftBob = await mintNft( - api, - alice, - bob, - collectionIdAlice, - 'nft-metadata', - ); - - const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftBob, 127); - await expectTxFailure(/rmrkCore\.ResourceDoesntExist/, tx); - }); - - it('[negative]: Non-owner user cannot confirm the deletion of resource', async () => { - const collectionIdAlice = await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ); - - const nftAlice = await mintNft( - api, - alice, - alice, - collectionIdAlice, - 'nft-metadata', - ); - - const resourceId = await addNftBasicResource( - api, - alice, - 'added', - collectionIdAlice, - nftAlice, - src, - metadata, - license, - thumb, - ); - - const tx = acceptResourceRemoval(api, bob, collectionIdAlice, nftAlice, resourceId); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/rmrkIsolation.seqtest.ts +++ /dev/null @@ -1,225 +0,0 @@ -import {executeTransaction} from '../substrate/substrate-api'; -import {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../util'; -import {UniqueHelper} from '../util/playgrounds/unique'; - -let alice: IKeyringPair; -let bob: IKeyringPair; - -async function createRmrkCollection(helper: UniqueHelper, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> { - const result = await helper.executeExtrinsic(sender, 'api.tx.rmrkCore.createCollection', ['metadata', null, 'symbol'], true); - - const uniqueId = helper.util.extractCollectionIdFromCreationResult(result); - - let rmrkId = null; - result.result.events.forEach(({event: {data, method, section}}) => { - if ((section === 'rmrkCore') && (method === 'CollectionCreated')) { - rmrkId = parseInt(data[1].toString(), 10); - } - }); - - if (rmrkId === null) { - throw Error('No rmrkCore.CollectionCreated event was found!'); - } - - return { - uniqueId, - rmrkId, - }; -} - -async function createRmrkNft(helper: UniqueHelper, sender: IKeyringPair, collectionId: number): Promise { - const result = await helper.executeExtrinsic( - sender, - 'api.tx.rmrkCore.mintNft', - [ - sender.address, - collectionId, - sender.address, - null, - 'nft-metadata', - true, - null, - ], - true, - ); - - let rmrkNftId = null; - result.result.events.forEach(({event: {data, method, section}}) => { - if ((section === 'rmrkCore') && (method === 'NftMinted')) { - rmrkNftId = parseInt(data[2].toString(), 10); - } - }); - - if (rmrkNftId === null) { - throw Error('No rmrkCore.NftMinted event was found!'); - } - - return rmrkNftId; -} - -describe('RMRK External Integration Test', () => { - before(async function() { - await usingPlaygrounds(async (_helper, privateKey) => { - alice = await privateKey('//Alice'); - }); - }); - - itSub.ifWithPallets('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', [Pallets.RmrkCore], async ({helper}) => { - // throw away collection to bump last Unique collection ID to test ID mapping - await helper.nft.mintCollection(alice, {tokenPrefix: 'unqt'}); - - const collectionIds = await createRmrkCollection(helper, alice); - - expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping'); - - const collection = (await helper.nft.getCollectionObject(collectionIds.uniqueId).getData())!; // (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!; - expect(collection.raw.readOnly, 'tagged external').to.be.true; - }); -}); - -describe('Negative Integration Test: External Collections, Internal Ops', () => { - let uniqueCollectionId: number; - let rmrkCollectionId: number; - let rmrkNftId: number; - let normalizedAlice: {Substrate: string}; - - before(async function() { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - bob = await privateKey('//Bob'); - normalizedAlice = {Substrate: helper.address.normalizeSubstrateToChainFormat(alice.address)}; - - requirePalletsOrSkip(this, helper, [Pallets.RmrkCore]); - - const collectionIds = await createRmrkCollection(helper, alice); - uniqueCollectionId = collectionIds.uniqueId; - rmrkCollectionId = collectionIds.rmrkId; - - rmrkNftId = await createRmrkNft(helper, alice, rmrkCollectionId); - }); - }); - - itSub.ifWithPallets('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', [Pallets.RmrkCore], async ({helper}) => { - // Collection item creation - - await expect(helper.nft.mintToken(alice, {collectionId: uniqueCollectionId, owner: {Substrate: alice.address}}), 'creating item') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - const txCreateMultipleItems = helper.getApi().tx.unique.createMultipleItems(uniqueCollectionId, normalizedAlice, [{NFT: {}}, {NFT: {}}]); - await expect(executeTransaction(helper.getApi(), alice, txCreateMultipleItems), 'creating multiple') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.mintMultipleTokens(alice, uniqueCollectionId, [{owner: {Substrate: alice.address}}]), 'creating multiple ex') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // Collection properties - - await expect(helper.nft.setProperties(alice, uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]), 'setting collection properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.deleteProperties(alice, uniqueCollectionId, ['a']), 'deleting collection properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.setTokenPropertyPermissions(alice, uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]), 'setting property permissions') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // NFT - - await expect(helper.nft.burnToken(alice, uniqueCollectionId, rmrkNftId, 1n), 'burning') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.burnTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, 1n), 'burning-from') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.transferToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'transferring') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.approveToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'approving') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.transferTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, {Substrate: bob.address}), 'transferring-from') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // NFT properties - - await expect(helper.nft.setTokenProperties(alice, uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]), 'setting token properties') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.deleteTokenProperties(alice, uniqueCollectionId, rmrkNftId, ['a'])) - .to.be.rejectedWith(/common\.CollectionIsExternal/); - }); - - itSub.ifWithPallets('[Negative] Forbids Unique collection operations with an external collection', [Pallets.RmrkCore], async ({helper}) => { - await expect(helper.nft.burn(alice, uniqueCollectionId), 'destroying collection') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // Allow list - - await expect(helper.nft.addToAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding to allow list') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.removeFromAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing from allowlist') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // Owner / Admin / Sponsor - - await expect(helper.nft.changeOwner(alice, uniqueCollectionId, bob.address), 'changing owner') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.addAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding admin') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.removeAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing admin') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.setSponsor(alice, uniqueCollectionId, bob.address), 'setting sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.confirmSponsorship(alice, uniqueCollectionId), 'confirming sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.removeSponsor(alice, uniqueCollectionId), 'removing sponsor') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - // Limits / permissions / transfers - - const txSetTransfers = helper.getApi().tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true); - await expect(executeTransaction(helper.getApi(), alice, txSetTransfers), 'setting transfers enabled flag') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.setLimits(alice, uniqueCollectionId, {transfersEnabled: false}), 'setting collection limits') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - - await expect(helper.nft.setPermissions(alice, uniqueCollectionId, {access: 'AllowList'}), 'setting collection permissions') - .to.be.rejectedWith(/common\.CollectionIsExternal/); - }); -}); - -describe('Negative Integration Test: Internal Collections, External Ops', () => { - let collectionId: number; - let nftId: number; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - alice = await privateKey('//Alice'); - bob = await privateKey('//Bob'); - - const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'iceo'}); - collectionId = collection.collectionId; - nftId = (await collection.mintToken(alice)).tokenId; - }); - }); - - itSub.ifWithPallets('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', [Pallets.RmrkCore], async ({helper}) => { - const api = helper.getApi(); - - const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); - await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer') - .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); - - const maxBurns = 10; - const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); - await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/); - }); -}); --- a/tests/src/rmrk/sendNft.seqtest.ts +++ /dev/null @@ -1,255 +0,0 @@ -import {expect} from 'chai'; -import {getApiConnection} from '../substrate/substrate-api'; -import {createCollection, mintNft, sendNft} from './util/tx'; -import {NftIdTuple} from './util/fetch'; -import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers'; - -describe('integration test: send NFT', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const maxNftId = 0xFFFFFFFF; - - const alice = '//Alice'; - const bob = '//Bob'; - - const createTestCollection = async (issuerUri: string) => { - return await createCollection( - api, - issuerUri, - 'nft-collection-metadata', - null, - 'nft-collection', - ); - }; - - - it('send NFT to another user', async () => { - const originalOwnerUri = alice; - const newOwnerUri = bob; - - const collectionId = await createTestCollection(alice); - - const nftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'nft-metadata'); - - await sendNft(api, 'sent', originalOwnerUri, collectionId, nftId, newOwnerUri); - }); - - it('[negative] unable to send non-existing NFT', async () => { - const originalOwnerUri = alice; - const newOwnerUri = bob; - - const collectionId = 0; - const tx = sendNft(api, 'sent', originalOwnerUri, collectionId, maxNftId, newOwnerUri); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] unable to send NFT by a not-an-owner', async () => { - const originalOwnerUri = alice; - const newOwnerUri = bob; - - const collectionId = await createTestCollection(alice); - - const nftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'nft-metadata'); - - const tx = sendNft(api, 'sent', newOwnerUri, collectionId, nftId, newOwnerUri); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('send NFT to another NFT (same owner)', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.true; - }); - - it('[negative] send non-existing NFT to another NFT', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); - const childNftId = maxNftId; - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - const tx = sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.false; - }); - - it('send NFT to another NFT (by not-an-owner)', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const author = alice; - const attacker = bob; - - const parentNftId = await mintNft(api, author, originalOwnerUri, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, author, originalOwnerUri, collectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - const tx = sendNft(api, 'sent', attacker, collectionId, childNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.false; - }); - - it('[negative] send NFT to non-existing NFT', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const parentNftId = maxNftId; - const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - const tx = sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.false; - }); - - it('send NFT to another NFT owned by another user', async () => { - const ownerAlice = alice; - const ownerBob = bob; - - const aliceCollectionId = await createTestCollection(alice); - const bobCollectionId = await createTestCollection(bob); - - const parentNftId = await mintNft(api, alice, ownerAlice, aliceCollectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, bob, ownerBob, bobCollectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; - - await sendNft(api, 'pending', bob, bobCollectionId, childNftId, newOwnerNFT); - }); - - it('[negative] unable to send NFT to itself', async () => { - const nftOwner = alice; - const collectionId = await createTestCollection(alice); - - const nftId = await mintNft(api, alice, nftOwner, collectionId, 'ouroboros-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionId, nftId]; - - const tx = sendNft(api, 'sent', alice, collectionId, nftId, newOwnerNFT); - - await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); - - const isChild = await isNftChildOfAnother(api, collectionId, nftId, newOwnerNFT); - expect(isChild).to.be.false; - }); - - it('[negative] unable to send NFT to child NFT', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); - - const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, childNftId, newOwnerNFT); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, newOwnerNFT); - expect(isChild).to.be.true; - - const descendentOwner: NftIdTuple = [collectionId, childNftId]; - const tx = sendNft(api, 'sent', alice, collectionId, parentNftId, descendentOwner); - - await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); - const isOuroboros = await isNftChildOfAnother(api, collectionId, parentNftId, descendentOwner); - expect(isOuroboros).to.be.false; - }); - - it('[negative] unable to send NFT to descendent NFT', async () => { - const originalOwnerUri = alice; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'child-nft-metadata'); - const grandsonNftId = await mintNft(api, alice, originalOwnerUri, collectionId, 'grandson-nft-metadata'); - - const ownerParentNFT: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', alice, collectionId, childNftId, ownerParentNFT); - - const isChild = await isNftChildOfAnother(api, collectionId, childNftId, ownerParentNFT); - expect(isChild).to.be.true; - - const ownerChildNFT: NftIdTuple = [collectionId, childNftId]; - await sendNft(api, 'sent', alice, collectionId, grandsonNftId, ownerChildNFT); - - const isGrandson = await isNftChildOfAnother(api, collectionId, grandsonNftId, ownerChildNFT); - expect(isGrandson).to.be.true; - - const ownerGrandsonNFT: NftIdTuple = [collectionId, grandsonNftId]; - const tx = sendNft(api, 'sent', alice, collectionId, parentNftId, ownerGrandsonNFT); - - await expectTxFailure(/rmrkCore\.CannotSendToDescendentOrSelf/, tx); - const isOuroboros = await isNftChildOfAnother(api, collectionId, parentNftId, ownerGrandsonNFT); - expect(isOuroboros).to.be.false; - }); - - it('send nested NFT to another user', async () => { - const originalOwner = alice; - const newOwner = bob; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwner, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, originalOwner, collectionId, 'child-nft-metadata'); - - const parentNftTuple: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', originalOwner, collectionId, childNftId, parentNftTuple); - - await sendNft(api, 'sent', originalOwner, collectionId, childNftId, newOwner); - }); - - it('[negative] send nested NFT to another user (by a not-root-owner)', async () => { - const originalOwner = alice; - const newOwner = bob; - - const collectionId = await createTestCollection(alice); - - const parentNftId = await mintNft(api, alice, originalOwner, collectionId, 'parent-nft-metadata'); - const childNftId = await mintNft(api, alice, originalOwner, collectionId, 'child-nft-metadata'); - - const parentNftTuple: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', originalOwner, collectionId, childNftId, parentNftTuple); - - const tx = sendNft(api, 'sent', newOwner, collectionId, childNftId, newOwner); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/setCollectionProperty.seqtest.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, setPropertyCollection} from './util/tx'; - -describe('integration test: set collection property', () => { - const alice = '//Alice'; - const bob = '//Bob'; - - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - it('set collection property', async () => { - await createCollection( - api, - alice, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - await setPropertyCollection(api, alice, collectionId, 'test_key', '42'); - await setPropertyCollection(api, alice, collectionId, 'test_key', '10'); - await setPropertyCollection( - api, - alice, - collectionId, - 'second_test_key', - '111', - ); - }); - }); - - it('[negative] set non-existing collection property', async () => { - const tx = setPropertyCollection( - api, - alice, - 9999, - 'test_key', - '42', - ); - await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); - }); - - it('[negative] set property not an owner NFT collection issuer', async () => { - await createCollection( - api, - bob, - 'test-metadata', - null, - 'test-symbol', - ).then(async (collectionId) => { - const tx = setPropertyCollection( - api, - alice, - collectionId, - 'test_key', - '42', - ); - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/setEquippableList.seqtest.ts +++ /dev/null @@ -1,114 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, createBase, setEquippableList} from './util/tx'; - -describe("integration test: set slot's Equippable List", () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - - it("set Base's slot Equippable List", async () => { - const collectionIds = [ - await createCollection( - api, - alice, - 'equiplist-collection-metadata', - null, - 'equiplist-0', - ), - await createCollection( - api, - alice, - 'equiplist-collection-metadata', - null, - 'equiplist-1', - ), - ]; - - const slotId = 202; - - const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ - { - 'SlotPart': { - id: slotId, - equippable: 'All', - z: 1, - src: 'some-fallback-slot-url', - }, - }, - ]); - - await setEquippableList(api, alice, baseId, slotId, 'All'); - await setEquippableList(api, alice, baseId, slotId, 'Empty'); - await setEquippableList(api, alice, baseId, slotId, {'Custom': collectionIds}); - }); - - it('[negative] unable to set equippable list of a slot of non-existing base', async () => { - const maxBaseId = 0xFFFFFFFF; - const slotId = 0; - - const tx = setEquippableList(api, alice, maxBaseId, slotId, 'All'); - await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); - }); - - it('[negative] unable to set equippable list by a not-an-owner', async () => { - const slotId = 42; - - const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ - { - 'SlotPart': { - id: slotId, - equippable: 'All', - z: 1, - src: 'some-fallback-slot-url', - }, - }, - ]); - - const tx = setEquippableList(api, bob, baseId, slotId, 'All'); - await expectTxFailure(/rmrkEquip\.PermissionError/, tx); - }); - - it('[negative] unable to set equippable list to a fixed part', async () => { - const fixedPartId = 42; - - const baseId = await createBase(api, alice, 'fixedpart-base-type', 'fixedpart', [ - { - 'FixedPart': { - id: fixedPartId, - z: 0, - src: 'fixed-part-url', - }, - }, - ]); - - const tx = setEquippableList(api, alice, baseId, fixedPartId, 'All'); - await expectTxFailure(/rmrkEquip\.NoEquippableOnFixedPart/, tx); - }); - - it('[negative] unable to set equippable list to non-existing slot', async () => { - const slotId = 777; - const maxSlotId = 0xFFFFFFFF; - - const baseId = await createBase(api, alice, 'slotpartany-base-type', 'slotpartany', [ - { - 'SlotPart': { - id: slotId, - equippable: 'All', - z: 1, - src: 'some-fallback-slot-url', - }, - }, - ]); - - const tx = setEquippableList(api, alice, baseId, maxSlotId, 'All'); - await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/setNftProperty.seqtest.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {NftIdTuple} from './util/fetch'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx'; - -describe('integration test: set NFT property', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - - const createTestCollection = async (issuerUri: string) => { - return await createCollection( - api, - issuerUri, - 'setprop-nft-collection-metadata', - null, - 'setprop', - ); - }; - - it('set NFT property', async () => { - const ownerAlice = alice; - - const collectionId = await createTestCollection(alice); - const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-nft'); - - await setNftProperty(api, alice, collectionId, nftId, 'test-key', 'test-key-value'); - await setNftProperty(api, alice, collectionId, nftId, 'test-key', 'updated-key-value'); - await setNftProperty(api, alice, collectionId, nftId, 'second-test-key', 'second-test-key-value'); - }); - - it('[negative] unable to set a property of non-existing NFT', async () => { - const collectionId = 0; - const maxNftId = 0xFFFFFFFF; - - const tx = setNftProperty(api, alice, collectionId, maxNftId, 'test-key', 'test-value'); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - it('[negative] unable to set a property by not-an-owner', async () => { - const ownerAlice = alice; - - const collectionId = await createTestCollection(alice); - const nftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-nft'); - - const tx = setNftProperty(api, bob, collectionId, nftId, 'test-key', 'test-key-value'); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('set a property to nested NFT', async () => { - const ownerAlice = alice; - - const collectionId = await createTestCollection(alice); - const parentNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-parent-nft'); - const childNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-child-nft'); - - const ownerNft: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', ownerAlice, collectionId, childNftId, ownerNft); - - await setNftProperty(api, alice, collectionId, childNftId, 'test-key', 'test-key-value'); - }); - - it('[negative] set a property to nested NFT (by not-root-owner)', async () => { - const ownerAlice = alice; - - const collectionId = await createTestCollection(alice); - const parentNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-parent-nft'); - const childNftId = await mintNft(api, alice, ownerAlice, collectionId, 'prop-child-nft'); - - const ownerNft: NftIdTuple = [collectionId, parentNftId]; - - await sendNft(api, 'sent', ownerAlice, collectionId, childNftId, ownerNft); - - const tx = setNftProperty(api, bob, collectionId, childNftId, 'test-key', 'test-key-value'); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/setResourcePriorities.seqtest.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {getApiConnection} from '../substrate/substrate-api'; -import {expectTxFailure, requirePallets, Pallets} from './util/helpers'; -import {mintNft, createCollection, setResourcePriorities} from './util/tx'; - -describe('integration test: set NFT resource priorities', () => { - let api: any; - before(async function () { - api = await getApiConnection(); - await requirePallets(this, [Pallets.RmrkCore]); - }); - - const alice = '//Alice'; - const bob = '//Bob'; - - const createTestCollection = (issuerUri: string) => { - return createCollection( - api, - issuerUri, - 'resprio-collection-metadata', - null, - 'resprio', - ); - }; - - it('set NFT resource priorities', async () => { - const owner = alice; - - const collectionId = await createTestCollection(alice); - const nftId = await mintNft(api, alice, owner, collectionId, 'resprio-nft-metadata'); - - await setResourcePriorities(api, alice, collectionId, nftId, [10, 42]); - }); - - it('[negative] set NFT resource priorities by a not-an-owner', async () => { - const owner = alice; - const attacker = bob; - - const collectionId = await createTestCollection(alice); - const nftId = await mintNft(api, alice, owner, collectionId, 'resprio-nft-metadata'); - - const tx = setResourcePriorities(api, attacker, collectionId, nftId, [10, 42]); - - await expectTxFailure(/rmrkCore\.NoPermission/, tx); - }); - - it('[negative] set NFT resource priorities to non-existing NFT', async () => { - const collectionId = 0; - const maxNftId = 0xFFFFFFFF; - - const tx = setResourcePriorities(api, alice, collectionId, maxNftId, [10, 42]); - - await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); - }); - - after(async() => { await api.disconnect(); }); -}); --- a/tests/src/rmrk/util/fetch.ts +++ /dev/null @@ -1,130 +0,0 @@ -import {ApiPromise} from '@polkadot/api'; -import type {Option} from '@polkadot/types-codec'; -import type { - RmrkTraitsCollectionCollectionInfo as Collection, - RmrkTraitsNftNftInfo as Nft, - RmrkTraitsResourceResourceInfo as Resource, - RmrkTraitsBaseBaseInfo as Base, - RmrkTraitsPartPartType as PartType, - RmrkTraitsNftNftChild as NftChild, - RmrkTraitsTheme as Theme, - RmrkTraitsPropertyPropertyInfo as Property, -} from '../../interfaces/default/types'; // '@polkadot/types/lookup'; -import '../../interfaces/augment-api'; -import '../../interfaces/augment-api-query'; -import privateKey from '../../substrate/privateKey'; - -export type NftIdTuple = [number, number]; - -export async function getCollectionsCount(api: ApiPromise): Promise { - return (await api.rpc.rmrk.lastCollectionIdx()).toNumber(); -} - -export function getCollection(api: ApiPromise, id: number): Promise> { - return api.rpc.rmrk.collectionById(id); -} - -export async function getOwnedNfts( - api: ApiPromise, - ownerUri: string, - collectionId: number, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const owner = privateKey(ownerUri, Number(ss58Format)); - - return (await api.rpc.rmrk.accountTokens(owner.address, collectionId)) - .map((value) => value.toNumber()); -} - -export function getNft(api: ApiPromise, collectionId: number, nftId: number): Promise> { - return api.rpc.rmrk.nftById(collectionId, nftId); -} - -export async function getCollectionProperties(api: ApiPromise, collectionId: number): Promise { - return (await api.rpc.rmrk.collectionProperties(collectionId)).toArray(); -} - -export async function getNftProperties(api: ApiPromise, collectionId: number, nftId: number): Promise { - return (await api.rpc.rmrk.nftProperties(collectionId, nftId)).toArray(); -} - -export async function getChildren( - api: ApiPromise, - collectionId: number, - nftId: number, -): Promise { - return (await api.rpc.rmrk.nftChildren(collectionId, nftId)).toArray(); -} - -export function getBase(api: ApiPromise, baseId: number): Promise> { - return api.rpc.rmrk.base(baseId); -} - -export async function getParts(api: ApiPromise, baseId: number): Promise { - return (await api.rpc.rmrk.baseParts(baseId)).toArray(); -} - -export async function getEquippableList( - api: ApiPromise, - baseId: number, - slotId: number, -): Promise<'All' | 'Empty' | { 'Custom': number[] } | null> { - const parts = await getParts(api, baseId); - - const part = parts.find((part) => { - if (part.isSlotPart) { - return part.asSlotPart.id.toNumber() === slotId; - } else { - return false; - } - }); - - if (part) { - const slot = part.asSlotPart; - if (slot.equippable.isCustom) { - return { - 'Custom': slot.equippable.asCustom - .toArray() - .map((collectionId) => collectionId.toNumber()), - }; - } else if (slot.equippable.isAll) { - return 'All'; - } else { - return 'Empty'; - } - } else { - return null; - } -} - -export async function getResourcePriority( - api: ApiPromise, - collectionId: number, - nftId: number, - resourceId: number, -): Promise { - return (await api.rpc.rmrk.nftResourcePriority(collectionId, nftId, resourceId)) - .unwrap().toNumber(); -} - -export async function getThemeNames(api: ApiPromise, baseId: number): Promise { - return (await api.rpc.rmrk.themeNames(baseId)) - .map((name) => name.toUtf8()); -} - -export function getTheme( - api: ApiPromise, - baseId: number, - themeName: string, - keys: string[] | null = null, -): Promise> { - return api.rpc.rmrk.themes(baseId, themeName, keys); -} - -export async function getResources( - api: ApiPromise, - collectionId: number, - nftId: number, -): Promise { - return (await api.rpc.rmrk.nftResources(collectionId, nftId)).toArray(); -} --- a/tests/src/rmrk/util/helpers.ts +++ /dev/null @@ -1,249 +0,0 @@ -import {ApiPromise} from '@polkadot/api'; -import { - RmrkTraitsNftAccountIdOrCollectionNftTuple as NftOwner, - RmrkTraitsPropertyPropertyInfo as Property, - RmrkTraitsResourceResourceInfo as ResourceInfo, -} from '@polkadot/types/lookup'; -import type {EventRecord} from '@polkadot/types/interfaces'; -import type {GenericEventData} from '@polkadot/types'; -import privateKey from '../../substrate/privateKey'; -import {NftIdTuple, getChildren, getOwnedNfts, getCollectionProperties, getNftProperties, getResources} from './fetch'; -import chaiAsPromised from 'chai-as-promised'; -import chai from 'chai'; -import {getApiConnection} from '../../substrate/substrate-api'; -import {Context} from 'mocha'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -interface TxResult { - success: boolean; - successData: T | null; -} - -export enum Pallets { - Inflation = 'inflation', - RmrkCore = 'rmrkcore', - RmrkEquip = 'rmrkequip', - ReFungible = 'refungible', - Fungible = 'fungible', - NFT = 'nonfungible', - Scheduler = 'scheduler', - AppPromotion = 'apppromotion', -} - -let modulesNames: any; -export function getModuleNames(api: ApiPromise): string[] { - if (typeof modulesNames === 'undefined') - modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); - return modulesNames; -} - -export async function missingRequiredPallets(requiredPallets: string[]): Promise { - const api = await getApiConnection(); - const pallets = getModuleNames(api); - await api.disconnect(); - - return requiredPallets.filter(p => !pallets.includes(p)); -} - -export async function requirePallets(mocha: Context, requiredPallets: string[]) { - const missingPallets = await missingRequiredPallets(requiredPallets); - - if (missingPallets.length > 0) { - const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; - const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; - const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; - - console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); - - mocha.skip(); - } -} - -export function makeNftOwner(api: ApiPromise, owner: string | NftIdTuple): NftOwner { - const isNftSending = (typeof owner !== 'string'); - - if (isNftSending) { - return api.createType('RmrkTraitsNftAccountIdOrCollectionNftTuple', { - 'CollectionAndNftTuple': owner, - }); - } else { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - return api.createType('RmrkTraitsNftAccountIdOrCollectionNftTuple', { - AccountId: privateKey(owner, Number(ss58Format)).address, - }); - } -} - -export async function isNftOwnedBy( - api: ApiPromise, - owner: string | NftIdTuple, - collectionId: number, - nftId: number, -): Promise { - if (typeof owner === 'string') { - return (await getOwnedNfts(api, owner, collectionId)) - .find(ownedNftId => { - return ownedNftId === nftId; - }) !== undefined; - } else { - return (await getChildren(api, owner[0], owner[1])) - .find(child => { - return collectionId === child.collectionId.toNumber() - && nftId === child.nftId.toNumber(); - }) !== undefined; - } -} - -export function isPropertyExists( - key: string, - value: string, - props: Property[], -): boolean { - let isPropFound = false; - for (let i = 0; i < props.length && !isPropFound; i++) { - const fetchedProp = props[i]; - - isPropFound = fetchedProp.key.eq(key) - && fetchedProp.value.eq(value); - } - - return isPropFound; -} - -export async function isCollectionPropertyExists( - api: ApiPromise, - collectionId: number, - key: string, - value: string, -): Promise { - const fetchedProps = await getCollectionProperties(api, collectionId); - return isPropertyExists(key, value, fetchedProps); -} - -export async function isNftPropertyExists( - api: ApiPromise, - collectionId: number, - nftId: number, - key: string, - value: string, -): Promise { - const fetchedProps = await getNftProperties(api, collectionId, nftId); - return isPropertyExists(key, value, fetchedProps); -} - -export async function isNftChildOfAnother( - api: ApiPromise, - collectionId: number, - nftId: number, - parentNft: NftIdTuple, -): Promise { - return (await getChildren(api, parentNft[0], parentNft[1])) - .find((childNft) => { - return childNft.collectionId.toNumber() === collectionId - && childNft.nftId.toNumber() === nftId; - }) !== undefined; -} - -export function isTxResultSuccess(events: EventRecord[]): boolean { - let success = false; - - events.forEach(({event: {method}}) => { - if (method == 'ExtrinsicSuccess') { - success = true; - } - }); - - return success; -} - -export async function expectTxFailure(expectedError: RegExp, promise: Promise) { - await expect(promise).to.be.rejectedWith(expectedError); -} - -export function extractTxResult( - events: EventRecord[], - expectSection: string, - expectMethod: string, - extractAction: (data: any) => T, -): TxResult { - let success = false; - let successData = null; - events.forEach(({event: {data, method, section}}) => { - if (method == 'ExtrinsicSuccess') { - success = true; - } else if ((expectSection == section) && (expectMethod == method)) { - successData = extractAction(data); - } - }); - const result: TxResult = { - success, - successData, - }; - return result; -} - -export function extractRmrkCoreTxResult( - events: EventRecord[], - expectMethod: string, - extractAction: (data: GenericEventData) => T, -): TxResult { - return extractTxResult(events, 'rmrkCore', expectMethod, extractAction); -} - -export function extractRmrkEquipTxResult( - events: EventRecord[], - expectMethod: string, - extractAction: (data: GenericEventData) => T, -): TxResult { - return extractTxResult(events, 'rmrkEquip', expectMethod, extractAction); -} - -export async function findResourceById( - api: ApiPromise, - collectionId: number, - nftId: number, - resourceId: number, -): Promise { - const resources = await getResources(api, collectionId, nftId); - - let resource = null; - - for (let i = 0; i < resources.length; i++) { - const res = resources[i]; - - if (res.id.eq(resourceId)) { - resource = res; - break; - } - } - - return resource!; -} - -export async function getResourceById( - api: ApiPromise, - collectionId: number, - nftId: number, - resourceId: number, -): Promise { - const resource = await findResourceById( - api, - collectionId, - nftId, - resourceId, - ); - - expect(resource !== null, 'Error: resource was not found').to.be.true; - - return resource!; -} - -export function checkResourceStatus( - resource: ResourceInfo, - expectedStatus: 'pending' | 'added', -) { - expect(resource.pending.isTrue, `Error: added resource should be ${expectedStatus}`) - .to.be.equal(expectedStatus === 'pending'); -} --- a/tests/src/rmrk/util/tx.ts +++ /dev/null @@ -1,1132 +0,0 @@ -import {ApiPromise} from '@polkadot/api'; -import {Bytes, Option, u32, Vec} from '@polkadot/types-codec'; -import { - RmrkTraitsNftAccountIdOrCollectionNftTuple as NftOwner, RmrkTraitsPartEquippableList as EquippableList, - RmrkTraitsPartPartType as PartType, RmrkTraitsResourceBasicResource as BasicResource, - RmrkTraitsResourceComposableResource as ComposableResource, RmrkTraitsResourceResourceInfo as ResourceInfo, RmrkTraitsResourceSlotResource as SlotResource, RmrkTraitsTheme as Theme, -} from '@polkadot/types/lookup'; -import {IKeyringPair} from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import '../../interfaces/augment-api'; -import privateKey from '../../substrate/privateKey'; -import {executeTransaction} from '../../substrate/substrate-api'; -import { - getBase, - getCollection, - getCollectionsCount, - getEquippableList, - getNft, - getParts, - getResources, - getResourcePriority, - getTheme, - NftIdTuple, -} from './fetch'; -import { - extractRmrkCoreTxResult, - extractRmrkEquipTxResult, isCollectionPropertyExists, isNftOwnedBy, isNftPropertyExists, isTxResultSuccess, makeNftOwner, - findResourceById, getResourceById, checkResourceStatus, -} from './helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -export async function createCollection( - api: ApiPromise, - issuerUri: string, - metadata: string, - max: number | null, - symbol: string, -): Promise { - let collectionId = 0; - - const oldCollectionCount = await getCollectionsCount(api); - const maxOptional = max ? max.toString() : null; - const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkCore.createCollection(metadata, maxOptional, symbol); - const events = await executeTransaction(api, issuer, tx); - - const collectionResult = extractRmrkCoreTxResult(events, 'CollectionCreated', (data) => { - return parseInt(data[1].toString(), 10); - }); - expect(collectionResult.success, 'Error: unable to create a collection').to.be.true; - - collectionId = collectionResult.successData!; - - const newCollectionCount = await getCollectionsCount(api); - const collectionOption = await getCollection(api, collectionId); - - expect(newCollectionCount).to.be.equal(oldCollectionCount + 1, 'Error: NFT collection count should increase'); - expect(collectionOption.isSome, 'Error: unable to fetch created NFT collection').to.be.true; - - const collection = collectionOption.unwrap(); - - expect(collection.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT collection metadata'); - expect(collection.max.isSome).to.be.equal(max !== null, 'Error: Invalid NFT collection max'); - - if (collection.max.isSome) { - expect(collection.max.unwrap().toNumber()).to.be.equal(max, 'Error: Invalid NFT collection max'); - } - expect(collection.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid NFT collection's symbol"); - expect(collection.nftsCount.toNumber()).to.be.equal(0, "Error: NFT collection shoudn't have any tokens"); - expect(collection.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid NFT collection issuer'); - - return collectionId; -} - -export async function changeIssuer( - api: ApiPromise, - issuerUri: string, - collectionId: number, - newIssuer: string, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const alice = privateKey(issuerUri, Number(ss58Format)); - const bob = privateKey(newIssuer, Number(ss58Format)); - - // This is only needed when RMRK uses `uniques` pallet by Parity - // let tx = api.tx.uniques.setAcceptOwnership( - // api.createType('Option', collectionId) - // ); - // let events = await executeTransaction(api, bob, tx); - // expect(isTxResultSuccess(events), 'Error: Unable to accept ownership').to.be.true; - - const tx = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address); - const events = await executeTransaction(api, alice, tx); - const changeIssuerResult = extractRmrkCoreTxResult(events, 'IssuerChanged', (data) => { - return parseInt(data[2].toString(), 10); - }); - expect(changeIssuerResult.success, 'Error: Unable to change NFT collection issuer').to.be.true; - expect(changeIssuerResult.successData!, 'Error: Invalid collection id after changing the issuer') - .to.be.eq(collectionId); - - await getCollection(api, collectionId).then((collectionOption) => { - const collection = collectionOption.unwrap(); - expect(collection.issuer.toString()) - .to.be.deep.eq(bob.address, 'Error: Invalid NFT collection issuer'); - }); -} - -export async function deleteCollection( - api: ApiPromise, - issuerUri: string, - collectionId: string, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkCore.destroyCollection(collectionId); - const events = await executeTransaction(api, issuer, tx); - - const collectionTxResult = extractRmrkCoreTxResult( - events, - 'CollectionDestroy', - (data) => { - return parseInt(data[1].toString(), 10); - }, - ); - expect(collectionTxResult.success, 'Error: Unable to delete NFT collection').to.be.true; - - const collection = await getCollection( - api, - parseInt(collectionId, 10), - ); - expect(collection.isEmpty, 'Error: NFT collection should be deleted').to.be.true; - - return 0; -} - -export async function negativeDeleteCollection( - api: ApiPromise, - issuerUri: string, - collectionId: string, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkCore.destroyCollection(collectionId); - await expect(executeTransaction(api, issuer, tx)).to.be.rejected; - - return 0; -} - -export async function setNftProperty( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, - key: string, - value: string, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const nftIdOpt = api.createType('Option', nftId); - const tx = api.tx.rmrkCore.setProperty( - collectionId, - nftIdOpt, - key, - value, - ); - const events = await executeTransaction(api, issuer, tx); - - const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => { - return { - collectionId: parseInt(data[0].toString(), 10), - nftId: data[1] as Option, - key: data[2] as Bytes, - value: data[3] as Bytes, - }; - }); - - expect(propResult.success, 'Error: Unable to set NFT property').to.be.true; - const eventData = propResult.successData!; - const eventDescription = 'from set NFT property event'; - - expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription) - .to.be.equal(collectionId); - - expect(eventData.nftId.eq(nftIdOpt), 'Error: Invalid NFT ID ' + eventDescription) - .to.be.true; - - expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription) - .to.be.true; - - expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription) - .to.be.true; - - expect( - await isNftPropertyExists(api, collectionId, nftId, key, value), - 'Error: NFT property is not found', - ).to.be.true; -} - -export async function mintNft( - api: ApiPromise, - issuerUri: string, - ownerUri: string | null, - collectionId: number, - metadata: string, - recipientUri: string | null = null, - royalty: number | null = null, - transferable = true, - resources: {basic?: any, composable?: any, slot?: any}[] | null = null, -): Promise { - let nftId = 0; - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const owner = ownerUri ? privateKey(ownerUri, Number(ss58Format)).address : null; - const recipient = recipientUri ? privateKey(recipientUri, Number(ss58Format)).address : null; - const royaltyOptional = royalty ? royalty.toString() : null; - - const actualOwnerUri = ownerUri ? ownerUri : issuerUri; - const actualOwnerAddress = ownerUri ? owner : issuer.address; - - const collectionOpt = await getCollection(api, collectionId); - - const tx = api.tx.rmrkCore.mintNft( - owner, - collectionId, - recipient, - royaltyOptional, - metadata, - transferable, - resources as any, - ); - - const events = await executeTransaction(api, issuer, tx); - const nftResult = extractRmrkCoreTxResult(events, 'NftMinted', (data) => { - return parseInt(data[2].toString(), 10); - }); - - expect(nftResult.success, 'Error: Unable to mint NFT').to.be.true; - - const newCollectionNftsCount = (await getCollection(api, collectionId)) - .unwrap() - .nftsCount - .toNumber(); - - const oldCollectionNftsCount = collectionOpt - .unwrap() - .nftsCount.toNumber(); - - expect(newCollectionNftsCount, 'Error: NFTs count should increase') - .to.be.equal(oldCollectionNftsCount + 1); - - nftId = nftResult.successData!; - - const nftOption = await getNft(api, collectionId, nftId); - - expect(nftOption.isSome, 'Error: Unable to fetch created NFT').to.be.true; - - const nft = nftOption.unwrap(); - - expect(nft.owner.isAccountId, 'Error: NFT owner should be some user').to.be.true; - expect(nft.owner.asAccountId.toString()).to.be.equal(actualOwnerAddress, 'Error: Invalid NFT owner'); - - const isOwnedInUniques = await isNftOwnedBy(api, actualOwnerUri, collectionId, nftId); - expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${ownerUri}`) - .to.be.true; - - if (recipient === null && royalty === null) { - expect(nft.royalty.isNone, 'Error: Invalid NFT recipient') - .to.be.true; - } else { - expect(nft.royalty.isSome, 'Error: NFT royalty not found') - .to.be.true; - - const nftRoyalty = nft.royalty.unwrap(); - expect(nftRoyalty.recipient.eq(recipient), 'Error: Invalid NFT recipient') - .to.be.true; - - expect(nftRoyalty.amount.eq(royalty), 'Error: Invalid NFT royalty') - .to.be.true; - } - - expect(nft.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT metadata'); - - const nftResources = await getResources(api, collectionId, nftId); - if (resources == null) { - expect(nftResources, 'Error: Invalid NFT resources').to.be.empty; - } else { - expect(nftResources.length).to.be.equal(resources.length); - - for (let i = 0; i < resources.length; i++) { - let successFindingResource = false; - const resource = resources[i]; - // try to find the matching resource from the query - for (let j = 0; j < nftResources.length && !successFindingResource; j++) { - const nftResourceData = nftResources[j].toHuman(); - expect( - Object.prototype.hasOwnProperty.call(nftResourceData, 'resource'), - `Error: Corrupted resource data on resource #${i}`, - ).to.be.true; - const nftResource = nftResourceData.resource!; - type NftResourceKey = keyof typeof nftResource; - - let typedResource = null; - let typedNftResource = null; - - if (resource.basic && Object.prototype.hasOwnProperty.call(nftResource, 'Basic')) { - typedResource = resource.basic!; - typedNftResource = nftResource['Basic' as NftResourceKey]!; - } else if (resource.composable && Object.prototype.hasOwnProperty.call(nftResource, 'Composable')) { - typedResource = resource.composable!; - typedNftResource = nftResource['Composable' as NftResourceKey]! as any; - if (typedResource.parts != undefined && typedResource.parts.toString() != typedNftResource.parts.toString() - || typedResource.base != typedNftResource.base && typedResource.base != undefined) { - continue; - } - } else if (resource.slot && Object.prototype.hasOwnProperty.call(nftResource, 'Slot')) { - typedResource = resource.slot!; - typedNftResource = nftResource['Slot' as NftResourceKey]! as any; - if (typedResource.slot != typedNftResource.slot && typedResource.slot != undefined - || typedResource.base != typedNftResource.base && typedResource.base != undefined) { - continue; - } - } else { - continue; - } - - if (typedResource.src != typedNftResource.src - || typedResource.metadata != typedNftResource.metadata - || typedResource.thumb != typedNftResource.thumb - || typedResource.license != typedNftResource.license - ) { - continue; - } - - // do final checks since this is now established to be the resource we seek - expect(nftResourceData.pending, `Error: Resource #${i} is pending`).to.be.false; - expect(nftResourceData.pendingRemoval, `Error: Resource #${i} is pending removal`).to.be.false; - - // remove the matching resource from the resources we check - nftResources.splice(j, 1); - successFindingResource = true; - } - - expect(successFindingResource, `Error: Couldn't find resource #${i}'s counterpart among the returned`).to.be.true; - } - } - - return nftId; -} - -export async function sendNft( - api: ApiPromise, - expectedStatus: 'pending' | 'sent', - originalOwnerUri: string, - collectionId: number, - nftId: number, - newOwner: string | NftIdTuple, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const originalOwner = privateKey(originalOwnerUri, Number(ss58Format)); - const newOwnerObj = makeNftOwner(api, newOwner); - - const nftBeforeSendingOpt = await getNft(api, collectionId, nftId); - - const tx = api.tx.rmrkCore.send(collectionId, nftId, newOwnerObj); - const events = await executeTransaction(api, originalOwner, tx); - - const sendResult = extractRmrkCoreTxResult(events, 'NFTSent', (data) => { - return { - dstOwner: data[1] as NftOwner, - collectionId: parseInt(data[2].toString(), 10), - nftId: parseInt(data[3].toString(), 10), - }; - }); - - expect(sendResult.success, 'Error: Unable to send NFT').to.be.true; - const sendData = sendResult.successData!; - - expect(sendData.dstOwner.eq(newOwnerObj), 'Error: Invalid target user (from event data)') - .to.be.true; - - expect(sendData.collectionId) - .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); - - expect(sendData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); - - expect(nftBeforeSendingOpt.isSome, 'Error: Unable to fetch NFT before sending').to.be.true; - - const nftBeforeSending = nftBeforeSendingOpt.unwrap(); - - const nftAfterSendingOpt = await getNft(api, collectionId, nftId); - - expect(nftAfterSendingOpt.isSome, 'Error: Unable to fetch NFT after sending').to.be.true; - - const nftAfterSending = nftAfterSendingOpt.unwrap(); - - const isOwnedByNewOwner = await isNftOwnedBy(api, newOwner, collectionId, nftId); - const isPending = expectedStatus === 'pending'; - - expect( - isOwnedByNewOwner, - `Error: The NFT should be owned by ${newOwner.toString()}`, - ).to.be.true; - - expect(nftAfterSending.royalty.eq(nftBeforeSending.royalty), 'Error: Invalid NFT royalty after sending') - .to.be.true; - - expect(nftAfterSending.metadata.eq(nftBeforeSending.metadata), 'Error: Invalid NFT metadata after sending') - .to.be.true; - - expect(nftAfterSending.equipped.eq(nftBeforeSending.equipped), 'Error: Invalid NFT equipped status after sending') - .to.be.true; - - expect(nftAfterSending.pending.eq(isPending), 'Error: Invalid NFT pending state') - .to.be.true; -} - -export async function acceptNft( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, - newOwner: string | [number, number], -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const newOwnerObj = makeNftOwner(api, newOwner); - - const nftBeforeOpt = await getNft(api, collectionId, nftId); - - const tx = api.tx.rmrkCore.acceptNft(collectionId, nftId, newOwnerObj); - const events = await executeTransaction(api, issuer, tx); - - const acceptResult = extractRmrkCoreTxResult(events, 'NFTAccepted', (data) => { - return { - recipient: data[1] as NftOwner, - collectionId: parseInt(data[2].toString(), 10), - nftId: parseInt(data[3].toString(), 10), - }; - }); - - expect(acceptResult.success, 'Error: Unable to accept NFT').to.be.true; - const acceptData = acceptResult.successData!; - - expect(acceptData.recipient.eq(newOwnerObj), 'Error: Invalid NFT recipient (from event data)') - .to.be.true; - - expect(acceptData.collectionId) - .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); - - expect(acceptData.nftId) - .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); - - const nftBefore = nftBeforeOpt.unwrap(); - - const isPendingBeforeAccept = nftBefore.pending.isTrue; - - const nftAfter = (await getNft(api, collectionId, nftId)).unwrap(); - const isPendingAfterAccept = nftAfter.pending.isTrue; - - expect(isPendingBeforeAccept, 'Error: NFT should be pending to be accepted') - .to.be.true; - - expect(isPendingAfterAccept, 'Error: NFT should NOT be pending after accept') - .to.be.false; - - const isOwnedInUniques = await isNftOwnedBy(api, newOwner, collectionId, nftId); - expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${newOwner.toString()}`) - .to.be.true; -} - -export async function rejectNft( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const nftBeforeOpt = await getNft(api, collectionId, nftId); - - const tx = api.tx.rmrkCore.rejectNft(collectionId, nftId); - const events = await executeTransaction(api, issuer, tx); - const rejectResult = extractRmrkCoreTxResult(events, 'NFTRejected', (data) => { - return { - collectionId: parseInt(data[1].toString(), 10), - nftId: parseInt(data[2].toString(), 10), - }; - }); - - const rejectData = rejectResult.successData!; - - expect(rejectData.collectionId) - .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)'); - - expect(rejectData.nftId) - .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)'); - - const nftBefore = nftBeforeOpt.unwrap(); - - const isPendingBeforeReject = nftBefore.pending.isTrue; - - const nftAfter = await getNft(api, collectionId, nftId); - - expect(isPendingBeforeReject, 'Error: NFT should be pending to be rejected') - .to.be.true; - - expect(nftAfter.isNone, 'Error: NFT should be burned after reject') - .to.be.true; -} - -export async function createBase( - api: ApiPromise, - issuerUri: string, - baseType: string, - symbol: string, - parts: object[], -): Promise { - let baseId = 0; - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const partTypes = api.createType('Vec', parts) as Vec; - - const tx = api.tx.rmrkEquip.createBase(baseType, symbol, partTypes); - const events = await executeTransaction(api, issuer, tx); - - const baseResult = extractRmrkEquipTxResult(events, 'BaseCreated', (data) => { - return parseInt(data[1].toString(), 10); - }); - - expect(baseResult.success, 'Error: Unable to create Base') - .to.be.true; - - baseId = baseResult.successData!; - const baseOptional = await getBase(api, baseId); - - expect(baseOptional.isSome, 'Error: Unable to fetch created Base') - .to.be.true; - - const base = baseOptional.unwrap(); - const baseParts = await getParts(api, baseId); - - expect(base.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid Base issuer'); - expect(base.baseType.toUtf8()).to.be.equal(baseType, 'Error: Invalid Base type'); - expect(base.symbol.toUtf8()).to.be.equal(symbol, 'Error: Invalid Base symbol'); - expect(partTypes.eq(baseParts), 'Error: Received invalid base parts').to.be.true; - - return baseId; -} - -export async function setResourcePriorities( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, - priorities: number[], -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const prioritiesVec = api.createType('Vec', priorities); - const tx = api.tx.rmrkCore.setPriority(collectionId, nftId, prioritiesVec); - const events = await executeTransaction(api, issuer, tx); - - const prioResult = extractRmrkCoreTxResult(events, 'PrioritySet', (data) => { - return { - collectionId: parseInt(data[0].toString(), 10), - nftId: parseInt(data[1].toString(), 10), - }; - }); - - expect(prioResult.success, 'Error: Unable to set resource priorities').to.be.true; - const eventData = prioResult.successData!; - - expect(eventData.collectionId) - .to.be.equal(collectionId, 'Error: Invalid collection ID (set priorities event data)'); - - expect(eventData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (set priorities event data'); - - for (let i = 0; i < priorities.length; i++) { - const resourceId = priorities[i]; - - const fetchedPrio = await getResourcePriority(api, collectionId, nftId, resourceId); - expect(fetchedPrio).to.be.equal(i, 'Error: Invalid priorities are set'); - } - -} - -export async function setEquippableList( - api: ApiPromise, - issuerUri: string, - baseId: number, - slotId: number, - equippableList: 'All' | 'Empty' | { 'Custom': number[] }, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const equippable = api.createType('RmrkTraitsPartEquippableList', equippableList) as EquippableList; - - const tx = api.tx.rmrkEquip.equippable(baseId, slotId, equippable); - const events = await executeTransaction(api, issuer, tx); - - const equipListResult = extractRmrkEquipTxResult(events, 'EquippablesUpdated', (data) => { - return { - baseId: parseInt(data[0].toString(), 10), - slotId: parseInt(data[1].toString(), 10), - }; - }); - - expect(equipListResult.success, 'Error: unable to update equippable list').to.be.true; - const updateEvent = equipListResult.successData!; - - expect(updateEvent.baseId) - .to.be.equal(baseId, 'Error: invalid base ID from update equippable event'); - - expect(updateEvent.slotId) - .to.be.equal(slotId, 'Error: invalid base ID from update equippable event'); - - const fetchedEquippableList = await getEquippableList(api, baseId, slotId); - - expect(fetchedEquippableList, 'Error: unable to fetch equippable list').to.be.not.null; - if (fetchedEquippableList) { - expect(fetchedEquippableList) - .to.be.deep.equal(equippableList, 'Error: invalid equippable list was set'); - } -} - -export async function addTheme( - api: ApiPromise, - issuerUri: string, - baseId: number, - themeObj: object, - filterKeys: string[] | null = null, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const theme = api.createType('RmrkTraitsTheme', themeObj) as Theme; - - const tx = api.tx.rmrkEquip.themeAdd(baseId, theme); - const events = await executeTransaction(api, issuer, tx); - - expect(isTxResultSuccess(events), 'Error: Unable to add Theme').to.be.true; - - const fetchedThemeOpt = await getTheme(api, baseId, theme.name.toUtf8(), null); - - expect(fetchedThemeOpt.isSome, 'Error: Unable to fetch theme').to.be.true; - - const fetchedTheme = fetchedThemeOpt.unwrap(); - - expect(theme.name.eq(fetchedTheme.name), 'Error: Invalid theme name').to.be.true; - - for (let i = 0; i < theme.properties.length; i++) { - const property = theme.properties[i]; - const propertyKey = property.key.toUtf8(); - - const propertyFoundCount = fetchedTheme.properties.filter((fetchedProp) => property.key.eq(fetchedProp.key)).length; - - expect(propertyFoundCount > 1, `Error: Too many properties with key ${propertyKey} found`) - .to.be.false; - - if (filterKeys) { - const isFiltered = fetchedTheme.properties.find((fetchedProp) => fetchedProp.key.eq(property.key)) === undefined; - - if (isFiltered) { - expect(propertyFoundCount === 0, `Error: Unexpected filtered key ${propertyKey}`) - .to.be.true; - continue; - } - } - - expect(propertyFoundCount === 1, `Error: The property with key ${propertyKey} is not found`) - .to.be.true; - } -} - -export async function lockCollection( - api: ApiPromise, - issuerUri: string, - collectionId: number, - max = 0, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkCore.lockCollection(collectionId); - const events = await executeTransaction(api, issuer, tx); - const lockResult = extractRmrkCoreTxResult(events, 'CollectionLocked', (data) => { - return parseInt(data[1].toString(), 10); - }); - expect(lockResult.success, 'Error: Unable to lock a collection').to.be.true; - expect(lockResult.successData!, 'Error: Invalid collection was locked') - .to.be.eq(collectionId); - - await getCollection(api, collectionId).then((collectionOption) => { - const collection = collectionOption.unwrap(); - expect(collection.max.unwrap().toNumber()).to.be.equal(max); - }); -} - -export async function setPropertyCollection( - api: ApiPromise, - issuerUri: string, - collectionId: number, - key: string, - value: string, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const alice = privateKey(issuerUri, Number(ss58Format)); - - const tx = api.tx.rmrkCore.setProperty(collectionId, null, key, value); - const events = await executeTransaction(api, alice, tx); - const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => { - return { - collectionId: parseInt(data[0].toString(), 10), - nftId: data[1] as Option, - key: data[2] as Bytes, - value: data[3] as Bytes, - }; - }); - - expect(propResult.success, 'Error: Unable to set collection property').to.be.true; - const eventData = propResult.successData!; - const eventDescription = 'from set collection property event'; - - expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription) - .to.be.equal(collectionId); - - expect(eventData.nftId.eq(null), 'Error: Unexpected NFT ID ' + eventDescription) - .to.be.true; - - expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription) - .to.be.true; - - expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription) - .to.be.true; - - expect(await isCollectionPropertyExists(api, collectionId, key, value)) - .to.be.true; -} - -export async function burnNft( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const maxBurns = 10; - const tx = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns); - const events = await executeTransaction(api, issuer, tx); - const burnResult = extractRmrkCoreTxResult(events, 'NFTBurned', (data) => { - return parseInt(data[1].toString(), 10); - }); - - expect(burnResult.success, 'Error: Unable to burn an NFT').to.be.true; - expect(burnResult.successData!, 'Error: Invalid NFT was burned') - .to.be.eq(nftId); - - const nftBurned = await getNft(api, collectionId, nftId); - expect(nftBurned.isSome).to.be.false; -} - -export async function acceptNftResource( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, - resourceId: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const tx = api.tx.rmrkCore.acceptResource( - collectionId, - nftId, - resourceId, - ); - - const events = await executeTransaction(api, issuer, tx); - const acceptResult = extractRmrkCoreTxResult(events, 'ResourceAccepted', (data) => { - return { - nftId: parseInt(data[0].toString(), 10), - resourceId: parseInt(data[1].toString(), 10), - }; - }); - - expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true; - expect(acceptResult.successData!.nftId, 'Error: Invalid NFT ID while accepting a resource') - .to.be.eq(nftId); - expect(acceptResult.successData!.resourceId, 'Error: Invalid resource ID while accepting a resource') - .to.be.eq(resourceId); - - const resource = await getResourceById(api, collectionId, nftId, resourceId); - checkResourceStatus(resource, 'added'); -} - -async function executeResourceCreation( - api: ApiPromise, - issuer: IKeyringPair, - tx: any, - collectionId: number, - nftId: number, - expectedStatus: 'pending' | 'added', -): Promise { - const events = await executeTransaction(api, issuer, tx); - - const resourceResult = extractRmrkCoreTxResult(events, 'ResourceAdded', (data) => { - return parseInt(data[1].toString(), 10); - }); - expect(resourceResult.success, 'Error: Unable to add resource').to.be.true; - const resourceId = resourceResult.successData!; - - const resource = await getResourceById(api, collectionId, nftId, resourceId); - checkResourceStatus(resource, expectedStatus); - - return resource; -} - -export async function addNftBasicResource( - api: ApiPromise, - issuerUri: string, - expectedStatus: 'pending' | 'added', - collectionId: number, - nftId: number, - src: string | null, - metadata: string | null, - license: string | null, - thumb: string | null, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const basicResource = api.createType('RmrkTraitsResourceBasicResource', { - src: src, - metadata: metadata, - license: license, - thumb: thumb, - }) as BasicResource; - - const tx = api.tx.rmrkCore.addBasicResource( - collectionId, - nftId, - basicResource, - ); - - const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); - - // FIXME A workaround. It seems it is a PolkadotJS bug. - // All of the following are `false`. - // - // console.log('>>> basic:', resource.resource.isBasic); - // console.log('>>> composable:', resource.resource.isComposable); - // console.log('>>> slot:', resource.resource.isSlot); - const resourceJson = resource.resource.toHuman() as any; - - expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Basic'), 'Error: Expected basic resource type') - .to.be.true; - - const recvBasicRes = resourceJson['Basic']; - - expect(recvBasicRes.src, 'Error: Invalid basic resource src') - .to.be.eq(src); - expect(recvBasicRes.metadata, 'Error: basic first resource metadata') - .to.be.eq(metadata); - expect(recvBasicRes.license, 'Error: basic first resource license') - .to.be.eq(license); - expect(recvBasicRes.thumb, 'Error: basic first resource thumb') - .to.be.eq(thumb); - - return resource.id.toNumber(); -} - -export async function addNftComposableResource( - api: ApiPromise, - issuerUri: string, - expectedStatus: 'pending' | 'added', - collectionId: number, - nftId: number, - parts: number[], - baseId: number, - src: string | null, - metadata: string | null, - license: string | null, - thumb: string | null, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const composableResource = api.createType('RmrkTraitsResourceComposableResource', { - parts: parts, // api.createType('Vec', parts), - base: baseId, - src: src, - metadata: metadata, - license: license, - thumb: thumb, - }) as ComposableResource; - - const tx = api.tx.rmrkCore.addComposableResource( - collectionId, - nftId, - composableResource, - ); - - const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); - - // FIXME A workaround. It seems it is a PolkadotJS bug. - // All of the following are `false`. - // - // console.log('>>> basic:', resource.resource.isBasic); - // console.log('>>> composable:', resource.resource.isComposable); - // console.log('>>> slot:', resource.resource.isSlot); - const resourceJson = resource.resource.toHuman() as any; - - expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Composable'), 'Error: Expected composable resource type') - .to.be.true; - - const recvComposableRes = resourceJson['Composable']; - - expect(recvComposableRes.parts.toString(), 'Error: Invalid composable resource parts') - .to.be.eq(parts.toString()); - expect(recvComposableRes.base, 'Error: Invalid composable resource base id') - .to.be.eq(baseId.toString()); - expect(recvComposableRes.src, 'Error: Invalid composable resource src') - .to.be.eq(src); - expect(recvComposableRes.metadata, 'Error: Invalid composable resource metadata') - .to.be.eq(metadata); - expect(recvComposableRes.license, 'Error: Invalid composable resource license') - .to.be.eq(license); - expect(recvComposableRes.thumb, 'Error: Invalid composable resource thumb') - .to.be.eq(thumb); - - return resource.id.toNumber(); -} - -export async function addNftSlotResource( - api: ApiPromise, - issuerUri: string, - expectedStatus: 'pending' | 'added', - collectionId: number, - nftId: number, - baseId: number, - slotId: number, - src: string | null, - metadata: string | null, - license: string | null, - thumb: string | null, -): Promise { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const slotResource = api.createType('RmrkTraitsResourceSlotResource', { - base: baseId, - src: src, - metadata, - slot: slotId, - license: license, - thumb: thumb, - }) as SlotResource; - - const tx = api.tx.rmrkCore.addSlotResource( - collectionId, - nftId, - slotResource, - ); - - const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus); - - // FIXME A workaround. It seems it is a PolkadotJS bug. - // All of the following are `false`. - // - // console.log('>>> basic:', resource.resource.isBasic); - // console.log('>>> composable:', resource.resource.isComposable); - // console.log('>>> slot:', resource.resource.isSlot); - const resourceJson = resource.resource.toHuman() as any; - - expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Slot'), 'Error: Expected slot resource type') - .to.be.true; - - const recvSlotRes = resourceJson['Slot']; - - expect(recvSlotRes.base, 'Error: Invalid slot resource base id') - .to.be.eq(baseId.toString()); - expect(recvSlotRes.slot, 'Error: Invalid slot resource slot id') - .to.be.eq(slotId.toString()); - expect(recvSlotRes.src, 'Error: Invalid slot resource src') - .to.be.eq(src); - expect(recvSlotRes.metadata, 'Error: Invalid slot resource metadata') - .to.be.eq(metadata); - expect(recvSlotRes.license, 'Error: Invalid slot resource license') - .to.be.eq(license); - expect(recvSlotRes.thumb, 'Error: Invalid slot resource thumb') - .to.be.eq(thumb); - - return resource.id.toNumber(); -} - -export async function equipNft( - api: ApiPromise, - issuerUri: string, - item: NftIdTuple, - equipper: NftIdTuple, - resource: number, - base: number, - slot: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot); - const events = await executeTransaction(api, issuer, tx); - const equipResult = extractRmrkEquipTxResult(events, 'SlotEquipped', (data) => { - return { - item_collection: parseInt(data[0].toString(), 10), - item_nft: parseInt(data[1].toString(), 10), - base_id: parseInt(data[2].toString(), 10), - slot_id: parseInt(data[3].toString(), 10), - }; - }); - expect(equipResult.success, 'Error: Unable to equip an item').to.be.true; - expect(equipResult.successData!.item_collection, 'Error: Invalid item collection id') - .to.be.eq(item[0]); - expect(equipResult.successData!.item_nft, 'Error: Invalid item NFT id') - .to.be.eq(item[1]); - expect(equipResult.successData!.base_id, 'Error: Invalid base id') - .to.be.eq(base); - expect(equipResult.successData!.slot_id, 'Error: Invalid slot id') - .to.be.eq(slot); -} - -export async function unequipNft( - api: ApiPromise, - issuerUri: string, - item: any, - equipper: any, - resource: number, - base: number, - slot: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot); - const events = await executeTransaction(api, issuer, tx); - - const unEquipResult = extractRmrkEquipTxResult( - events, - 'SlotUnequipped', - (data) => { - return { - item_collection: parseInt(data[0].toString(), 10), - item_nft: parseInt(data[1].toString(), 10), - base_id: parseInt(data[2].toString(), 10), - slot_id: parseInt(data[3].toString(), 10), - }; - }, - ); - - expect(unEquipResult.success, 'Error: Unable to unequip an item').to.be.true; - expect(unEquipResult.successData!.item_collection, 'Error: Invalid item collection id') - .to.be.eq(item[0]); - expect(unEquipResult.successData!.item_nft, 'Error: Invalid item NFT id') - .to.be.eq(item[1]); - expect(unEquipResult.successData!.base_id, 'Error: Invalid base id') - .to.be.eq(base); - expect(unEquipResult.successData!.slot_id, 'Error: Invalid slot id') - .to.be.eq(slot); -} - -export async function removeNftResource( - api: ApiPromise, - expectedStatus: 'pending' | 'removed', - issuerUri: string, - collectionId: number, - nftId: number, - resourceId: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const tx = api.tx.rmrkCore.removeResource(collectionId, nftId, resourceId); - const events = await executeTransaction(api, issuer, tx); - const removeResult = extractRmrkCoreTxResult(events, 'ResourceRemoval', (data) => { - return { - nftId: parseInt(data[0].toString(), 10), - resourceId: parseInt(data[1].toString(), 10), - }; - }); - expect(removeResult.success, 'Error: Unable to remove a resource').to.be.true; - expect(removeResult.successData!.nftId, 'Error: Invalid NFT Id while removing a resource') - .to.be.eq(nftId); - expect(removeResult.successData!.resourceId, 'Error: Invalid resource Id while removing a resource') - .to.be.eq(resourceId); - - const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId); - - if (expectedStatus === 'pending') { - expect(afterDeleting).not.to.be.null; - expect(afterDeleting?.pendingRemoval.isTrue).to.be.equal(true); - } else { - expect(afterDeleting).to.be.null; - } -} - -export async function acceptResourceRemoval( - api: ApiPromise, - issuerUri: string, - collectionId: number, - nftId: number, - resourceId: number, -) { - const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; - const issuer = privateKey(issuerUri, Number(ss58Format)); - - const tx = api.tx.rmrkCore.acceptResourceRemoval(collectionId, nftId, resourceId); - const events = await executeTransaction(api, issuer, tx); - const acceptResult = extractRmrkCoreTxResult(events, 'ResourceRemovalAccepted', (data) => { - return { - nftId: parseInt(data[0].toString(), 10), - resourceId: parseInt(data[1].toString(), 10), - }; - }); - expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true; - expect(acceptResult.successData!.nftId, 'Error: Invalid NFT Id while accepting a resource') - .to.be.eq(nftId); - expect(acceptResult.successData!.resourceId, 'Error: Invalid resource Id while accepting a resource') - .to.be.eq(resourceId); - - const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId); - expect(afterDeleting, 'Error: resource deleting failed').to.be.null; -} --- a/tests/src/substrate/get-balance.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {ApiPromise} from '@polkadot/api'; -import {AccountInfo} from '@polkadot/types/interfaces/system'; -import promisifySubstrate from './promisify-substrate'; -import {IKeyringPair} from '@polkadot/types/types'; -import {submitTransactionAsync} from './substrate-api'; -import {getGenericResult} from '../deprecated-helpers/helpers'; -import {expect} from 'chai'; - -export default async function getBalance(api: ApiPromise, accounts: string[]): Promise> { - const balance = promisifySubstrate(api, (acc: string[]) => api.query.system.account.multi(acc)); - const responce = await balance(accounts) as unknown as AccountInfo[]; - return responce.map((r) => r.data.free.toBigInt().valueOf()); -} - -export async function getBalanceSingle(api: ApiPromise, account: string): Promise { - return (await getBalance(api, [account]))[0]; -} - -export async function transferBalanceExpectSuccess(api: ApiPromise, from: IKeyringPair, to: string, amount: bigint | string) { - const tx = api.tx.balances.transfer(to, amount); - const events = await submitTransactionAsync(from, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; -} --- a/tests/src/substrate/privateKey.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {Keyring} from '@polkadot/api'; -import {IKeyringPair} from '@polkadot/types/types'; - -// WARNING: the WASM interface must be initialized before this function is called. -// Use either `usingApi`, or `cryptoWaitReady` for consistency. -export default function privateKey(account: string, ss58Format?: number): IKeyringPair { - const keyring = new Keyring({ss58Format, type: 'sr25519'}); - - return keyring.addFromUri(account); -} --- a/tests/src/substrate/promisify-substrate.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {ApiPromise} from '@polkadot/api'; - -type PromiseType = T extends PromiseLike ? TInner : T; - -export default function promisifySubstrate any>(api: ApiPromise, action: T): (...args: Parameters) => Promise>> { - return (...args: Parameters) => { - const promise = new Promise>>((resolve: ((result: PromiseType>) => void) | undefined, reject: ((error: any) => void) | undefined) => { - const cleanup = () => { - api.off('disconnected', fail); - api.off('error', fail); - resolve = undefined; - reject = undefined; - }; - - const success = (r: any) => { - resolve && resolve(r); - cleanup(); - }; - const fail = (error: any) => { - reject && reject(error); - cleanup(); - }; - - api.on('disconnected', fail); - api.on('error', fail); - - const result = action(...args); - Promise.resolve(result) - .then(success, fail); - - }); - return promise as any; - }; -} --- a/tests/src/substrate/substrate-api.ts +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {ApiPromise, WsProvider} from '@polkadot/api'; -import {ApiOptions, ApiTypes, SubmittableExtrinsic} from '@polkadot/api/types'; -import {ExtrinsicStatus} from '@polkadot/types/interfaces/author/types'; -import {EventRecord} from '@polkadot/types/interfaces/system/types'; -import {IKeyringPair} from '@polkadot/types/types'; -import config from '../config'; -import '../interfaces/augment-api-events'; -import * as defs from '../interfaces/definitions'; -import privateKey from './privateKey'; -import promisifySubstrate from './promisify-substrate'; - -import {SilentConsole} from '../util/playgrounds/unique.dev'; - - - -function defaultApiOptions(): ApiOptions { - const wsProvider = new WsProvider(config.substrateUrl); - return { - provider: wsProvider, signedExtensions: { - ContractHelpers: { - extrinsic: {}, - payload: {}, - }, - CheckMaintenance: { - extrinsic: {}, - payload: {}, - }, - FakeTransactionFinalizer: { - extrinsic: {}, - payload: {}, - }, - }, - rpc: { - unique: defs.unique.rpc, - appPromotion: defs.appPromotion.rpc, - rmrk: defs.rmrk.rpc, - eth: { - feeHistory: { - description: 'Dummy', - params: [], - type: 'u8', - }, - maxPriorityFeePerGas: { - description: 'Dummy', - params: [], - type: 'u8', - }, - }, - }, - }; -} - -export async function getApiConnection(settings: ApiOptions | undefined = undefined): Promise { - settings = settings || defaultApiOptions(); - const api = new ApiPromise(settings); - - if (api) { - await api.isReadyOrError; - } - - return api; -} - -export default async function usingApi(action: (api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair) => Promise, settings: ApiOptions | undefined = undefined): Promise { - settings = settings || defaultApiOptions(); - const api: ApiPromise = new ApiPromise(settings); - let result: T = null as unknown as T; - - const silentConsole = new SilentConsole(); - silentConsole.enable(); - - try { - await promisifySubstrate(api, async () => { - if (api) { - await api.isReadyOrError; - const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; - const privateKeyWrapper = (account: string) => privateKey(account, Number(ss58Format)); - result = await action(api, privateKeyWrapper); - } - })(); - } finally { - await api.disconnect(); - silentConsole.disable(); - } - return result as T; -} - -enum TransactionStatus { - Success, - Fail, - NotReady -} - -function getTransactionStatus(events: EventRecord[], status: ExtrinsicStatus): TransactionStatus { - if (status.isReady) { - return TransactionStatus.NotReady; - } - if (status.isBroadcast) { - return TransactionStatus.NotReady; - } - if (status.isRetracted) { - return TransactionStatus.NotReady; - } - if (status.isInBlock || status.isFinalized) { - if(events.filter(e => e.event.data.method === 'ExtrinsicFailed').length > 0) { - return TransactionStatus.Fail; - } - if(events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) { - return TransactionStatus.Success; - } - } - - return TransactionStatus.Fail; -} - -export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise { - return new Promise(async (res, rej) => { - try { - await transaction.signAndSend(sender, ({events, status}) => { - if (!status.isInBlock && !status.isFinalized) return; - for (const {event} of events) { - if (api.events.system.ExtrinsicSuccess.is(event)) { - res(events); - } else if (api.events.system.ExtrinsicFailed.is(event)) { - const {data: [error]} = event; - if (error.isModule) { - const decoded = api.registry.findMetaError(error.asModule); - const {method, section} = decoded; - rej(new Error(`${section}.${method}`)); - } else { - rej(new Error(error.toString())); - } - } - } - }); - } catch (e) { - rej(e); - } - }); -} - -/** - * @deprecated use `executeTransaction` instead - */ -export function -submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic): Promise { - /* eslint no-async-promise-executor: "off" */ - return new Promise(async (resolve, reject) => { - try { - await transaction.signAndSend(sender, ({events = [], status, dispatchError}) => { - const transactionStatus = getTransactionStatus(events, status); - - if (transactionStatus === TransactionStatus.Success) { - resolve(events); - } else if (transactionStatus === TransactionStatus.Fail) { - let moduleError = null; - - if (dispatchError) { - if (dispatchError.isModule) { - const modErr = dispatchError.asModule; - const errorMeta = dispatchError.registry.findMetaError(modErr); - - moduleError = JSON.stringify(errorMeta, null, 4); - } - } - - console.log(`Something went wrong with transaction. Status: ${status}\nModule error: ${moduleError}`); - reject(events); - } - }); - } catch (e) { - console.log('Error: ', e); - reject(e); - } - }); -} - -export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic): Promise { - console.error = () => {}; - console.log = () => {}; - - /* eslint no-async-promise-executor: "off" */ - return new Promise(async function(res, rej) { - const resolve = (rec: EventRecord[]) => { - setTimeout(() => { - res(rec); - }); - }; - const reject = (errror: any) => { - setTimeout(() => { - rej(errror); - }); - }; - try { - await transaction.signAndSend(sender, ({events = [], status}) => { - const transactionStatus = getTransactionStatus(events, status); - - // console.log('transactionStatus', transactionStatus, 'events', events); - - if (transactionStatus === TransactionStatus.Success) { - resolve(events); - } else if (transactionStatus === TransactionStatus.Fail) { - reject(events); - } - }); - } catch (e) { - reject(e); - } - }); -} --- a/tests/src/substrate/wait-new-blocks.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {ApiPromise} from '@polkadot/api'; - -/* eslint no-async-promise-executor: "off" */ -export default function waitNewBlocks(api: ApiPromise, blocksCount = 1): Promise { - const promise = new Promise(async (resolve) => { - - const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { - if (blocksCount > 0) { - blocksCount--; - } else { - unsubscribe(); - resolve(); - } - }); - }); - - return promise; -}