--- a/tests/package.json +++ b/tests/package.json @@ -50,6 +50,7 @@ "testContracts": "mocha --timeout 9999999 -r ts-node/register ./**/contracts.test.ts", "testCreateItem": "mocha --timeout 9999999 -r ts-node/register ./**/createItem.test.ts", "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts", + "testCreateMultipleItemsEx": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItemsEx.test.ts", "testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts", "testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts", "testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts", --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -17,7 +17,7 @@ import {expect} from 'chai'; import privateKey from './substrate/privateKey'; import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api'; -import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers'; +import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess} from './util/helpers'; describe('integration test: ext. createCollection():', () => { it('Create new NFT collection', async () => { @@ -38,6 +38,25 @@ it('Create new ReFungible collection', async () => { await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); }); + + it('create new collection with properties #1', async () => { + await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', tokenOwner: true, mutable: false, collectionAdmin: true}]}); + }); + + it('create new collection with properties #2', async () => { + await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', tokenOwner: false, mutable: true, collectionAdmin: false}]}); + }); + + it('create new collection with properties #3', async () => { + await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', tokenOwner: true, mutable: false, collectionAdmin: false}]}); + }); + it('Create new collection with extra fields', async () => { await usingApi(async api => { const alice = privateKey('//Alice'); @@ -96,4 +115,24 @@ await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/); }); }); + + it('(!negative test!) create collection with incorrect property limit (64 elements)', async () => { + const props = []; + + for (let i = 0; i < 65; i++) { + props.push({key: `key${i}`, value: `value${i}`}); + } + + await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props}); + }); + + it('(!negative test!) create collection with incorrect property limit (40 kb)', async () => { + const props = []; + + for (let i = 0; i < 32; i++) { + props.push({key: `key${i}`.repeat(80), value: `value${i}`.repeat(80)}); + } + + await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props}); + }); }); --- a/tests/src/createItem.test.ts +++ b/tests/src/createItem.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {default as usingApi} from './substrate/substrate-api'; +import {default as usingApi, executeTransaction} from './substrate/substrate-api'; import chai from 'chai'; import {Keyring} from '@polkadot/api'; import {IKeyringPair} from '@polkadot/types/types'; @@ -22,6 +22,7 @@ createCollectionExpectSuccess, createItemExpectSuccess, addCollectionAdminExpectSuccess, + createCollectionWithPropsExpectSuccess, } from './util/helpers'; const expect = chai.expect; @@ -70,6 +71,33 @@ await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address); await createItemExpectSuccess(bob, newCollectionID, createMode); }); + + it('Set property Admin', async () => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionWithPropsExpectSuccess({mode: {type: createMode}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: true, tokenOwner: false}]}); + + await createItemExpectSuccess(alice, newCollectionID, createMode); + }); + + it('Set property AdminConst', async () => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionWithPropsExpectSuccess({mode: {type: createMode}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', mutable: false, collectionAdmin: true, tokenOwner: false}]}); + + await createItemExpectSuccess(alice, newCollectionID, createMode); + }); + + it('Set property itemOwnerOrAdmin', async () => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionWithPropsExpectSuccess({mode: {type: createMode}, + properties: [{key: 'key1', value: 'val1'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: true, tokenOwner: true}]}); + + await createItemExpectSuccess(alice, newCollectionID, createMode); + }); }); describe('Negative integration test: ext. createItem():', () => { @@ -96,4 +124,76 @@ const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); await expect(createItemExpectSuccess(bob, newCollectionID, createMode)).to.be.rejected; }); + + it('No editing rights', async () => { + await usingApi(async api => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionWithPropsExpectSuccess({mode: {type: createMode}, + propPerm: [{key: 'key1', mutable: false, collectionAdmin: false, tokenOwner: false}]}); + + const token = await createItemExpectSuccess(alice, newCollectionID, 'NFT'); + await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address); + + await expect(executeTransaction( + api, + alice, + api.tx.unique.setTokenProperties(newCollectionID, token, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + }); + }); + + it('User doesnt have editing rights', async () => { + await usingApi(async api => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionWithPropsExpectSuccess({propPerm: [{key: 'key1', mutable: true, collectionAdmin: false, tokenOwner: false}]}); + const token = await createItemExpectSuccess(alice, newCollectionID, 'NFT'); + + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(newCollectionID, token, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + }); + }); + + it('Adding property without access rights', async () => { + await usingApi(async api => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); + + const token = await createItemExpectSuccess(alice, newCollectionID, 'NFT'); + await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address); + + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(newCollectionID, token, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + }); + }); + + it('Adding more than 64 prps', async () => { + await usingApi(async api => { + const createMode = 'NFT'; + + const prps = []; + + for (let i = 0; i < 65; i++) { + prps.push({key: `key${i}`, value: `value${i}`}); + } + + const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); + + await expect(executeTransaction(api, alice, api.tx.unique.setCollectionProperties(newCollectionID, prps))).to.be.rejectedWith(/common\.PropertyLimitReached/); + }); + }); + + it('Trying to add bigger property than allowed', async () => { + await usingApi(async api => { + const createMode = 'NFT'; + const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); + + await expect(executeTransaction(api, alice, api.tx.unique.setCollectionProperties(newCollectionID, [{key: 'k', value: 'vvvvvv'.repeat(5000)}, {key: 'k2', value: 'vvv'.repeat(5000)}]))).to.be.rejectedWith(/common\.NoSpaceForProperty/); + }); + }); }); --- a/tests/src/createMultipleItems.test.ts +++ b/tests/src/createMultipleItems.test.ts @@ -19,7 +19,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import privateKey from './substrate/privateKey'; -import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api'; +import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync, executeTransaction} from './substrate/substrate-api'; import { createCollectionExpectSuccess, destroyCollectionExpectSuccess, @@ -33,6 +33,8 @@ getVariableMetadata, getConstMetadata, getCreatedCollectionCount, + createCollectionWithPropsExpectSuccess, + getCreateItemsResult, } from './util/helpers'; chai.use(chaiAsPromised); @@ -45,7 +47,9 @@ const itemsListIndexBefore = await getLastTokenId(api, collectionId); expect(itemsListIndexBefore).to.be.equal(0); const alice = privateKey('//Alice'); - const args = [{NFT: ['0x31', '0x31']}, {NFT: ['0x32', '0x32']}, {NFT: ['0x33', '0x33']}]; + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; const createMultipleItemsTx = api.tx.unique .createMultipleItems(collectionId, normalizeAccountId(alice.address), args); await submitTransactionAsync(alice, createMultipleItemsTx); @@ -135,6 +139,114 @@ expect(result.success).to.be.true; }); }); + + it('Create 0x31, 0x32, 0x33 items in active NFT with property Admin', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + const createMultipleItemsTx = api.tx.unique + .createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + await submitTransactionAsync(alice, createMultipleItemsTx); + const itemsListIndexAfter = await getLastTokenId(api, collectionId); + expect(itemsListIndexAfter).to.be.equal(3); + + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collectionId, [{key: 'k', permission: {mutable: true, collectionAdmin: true, tokenOwner: false}}]), + )).to.not.be.rejected; + + expect(await getTokenOwner(api, collectionId, 1)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 2)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 3)).to.be.deep.equal(normalizeAccountId(alice.address)); + + expect(await getConstMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getConstMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getConstMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + + expect(await getVariableMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getVariableMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getVariableMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + }); + }); + + it('Create 0x31, 0x32, 0x33 items in active NFT with property AdminConst', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + const createMultipleItemsTx = api.tx.unique + .createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + await submitTransactionAsync(alice, createMultipleItemsTx); + const itemsListIndexAfter = await getLastTokenId(api, collectionId); + expect(itemsListIndexAfter).to.be.equal(3); + + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collectionId, [{key: 'k', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}}]), + )).to.not.be.rejected; + + expect(await getTokenOwner(api, collectionId, 1)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 2)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 3)).to.be.deep.equal(normalizeAccountId(alice.address)); + + expect(await getConstMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getConstMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getConstMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + + expect(await getVariableMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getVariableMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getVariableMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + }); + }); + + it('Create 0x31, 0x32, 0x33 items in active NFT with property itemOwnerOrAdmin', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + const createMultipleItemsTx = api.tx.unique + .createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + await submitTransactionAsync(alice, createMultipleItemsTx); + const itemsListIndexAfter = await getLastTokenId(api, collectionId); + expect(itemsListIndexAfter).to.be.equal(3); + + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collectionId, [{key: 'k', permission: {mutable: true, collectionAdmin: true, tokenOwner: true}}]), + )).to.not.be.rejected; + + expect(await getTokenOwner(api, collectionId, 1)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 2)).to.be.deep.equal(normalizeAccountId(alice.address)); + expect(await getTokenOwner(api, collectionId, 3)).to.be.deep.equal(normalizeAccountId(alice.address)); + + expect(await getConstMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getConstMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getConstMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + + expect(await getVariableMetadata(api, collectionId, 1)).to.be.deep.equal([0x31]); + expect(await getVariableMetadata(api, collectionId, 2)).to.be.deep.equal([0x32]); + expect(await getVariableMetadata(api, collectionId, 3)).to.be.deep.equal([0x33]); + }); + }); }); describe('Integration Test createMultipleItems(collection_id, owner, items_data) with collection admin permissions:', () => { @@ -155,7 +267,9 @@ const itemsListIndexBefore = await getLastTokenId(api, collectionId); expect(itemsListIndexBefore).to.be.equal(0); await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); - const args = [{NFT: ['0x31', '0x31']}, {NFT: ['0x32', '0x32']}, {NFT: ['0x33', '0x33']}]; + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; const createMultipleItemsTx = api.tx.unique .createMultipleItems(collectionId, normalizeAccountId(bob.address), args); await submitTransactionAsync(bob, createMultipleItemsTx); @@ -245,7 +359,9 @@ const collectionId = await createCollectionExpectSuccess(); const itemsListIndexBefore = await getLastTokenId(api, collectionId); expect(itemsListIndexBefore).to.be.equal(0); - const args = [{NFT: ['0x31', '0x31']}, {NFT: ['0x32', '0x32']}, {NFT: ['0x33', '0x33']}]; + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; const createMultipleItemsTx = api.tx.unique .createMultipleItems(collectionId, normalizeAccountId(alice.address), args); await expect(submitTransactionAsync(bob, createMultipleItemsTx)).to.be.rejected; @@ -361,4 +477,146 @@ await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTx)).to.be.rejected; }); }); + + it('No editing rights', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: false, tokenOwner: false}]}); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + + const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + + const events = await submitTransactionAsync(alice, createMultipleItemsTx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + + // await expect(submitTransactionAsync(bob, createMultipleItemsTx)).to.be.rejected; + }); + }); + + it('User doesnt have editing rights', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: false, collectionAdmin: false, tokenOwner: false}]}); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + + const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + + const events = await submitTransactionAsync(alice, createMultipleItemsTx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + }); + }); + + it('Adding property without access rights', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}]}); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + expect(itemsListIndexBefore).to.be.equal(0); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + + const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + + const events = await submitTransactionAsync(alice, createMultipleItemsTx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + }); + }); + + it('Adding more than 64 prps', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: true, tokenOwner: false}]}); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + + const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + const events = await submitTransactionAsync(alice, createMultipleItemsTx); + + const result = getCreateItemsResult(events); + + const prps = []; + + for (let i = 0; i < 65; i++) { + prps.push({key: `key${i}`, value: `value${i}`}); + } + + await expect(executeTransaction(api, bob, api.tx.unique.setCollectionProperties(collectionId, prps))).to.be.rejectedWith(/common\.PropertyLimitReached/); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, prps), + )).to.be.rejected; + } + }); + }); + + it('Trying to add bigger property than allowed', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: false, tokenOwner: false}]}); + const itemsListIndexBefore = await getLastTokenId(api, collectionId); + expect(itemsListIndexBefore).to.be.equal(0); + const args = [{Nft: {const_data: '0x31', variable_data: '0x31'}}, + {Nft: {const_data: '0x32', variable_data: '0x32'}}, + {Nft: {const_data: '0x33', variable_data: '0x33'}}]; + + const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); + + const events = await submitTransactionAsync(alice, createMultipleItemsTx); + const result = getCreateItemsResult(events); + + + await expect(executeTransaction(api, alice, api.tx.unique.setCollectionProperties(collectionId, [{key: 'k', value: 'vvvvvv'.repeat(5000)}, {key: 'k2', value: 'vvv'.repeat(5000)}]))).to.be.rejectedWith(/common\.NoSpaceForProperty/); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'k', value: 'vvvvvv'.repeat(5000)}, {key: 'k2', value: 'vvv'.repeat(5000)}]), + )).to.be.rejected; + } + }); + }); }); --- a/tests/src/createMultipleItemsEx.test.ts +++ b/tests/src/createMultipleItemsEx.test.ts @@ -16,8 +16,8 @@ import {expect} from 'chai'; import privateKey from './substrate/privateKey'; -import usingApi, {executeTransaction} from './substrate/substrate-api'; -import {createCollectionExpectSuccess} from './util/helpers'; +import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api'; +import {createCollectionExpectSuccess, createCollectionWithPropsExpectSuccess, addCollectionAdminExpectSuccess, getCreateItemsResult} from './util/helpers'; describe('createMultipleItemsEx', () => { it('can initialize multiple NFT with different owners', async () => { @@ -51,6 +51,362 @@ }); }); + it('createMultipleItemsEx with property Admin', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + constData: '0x0000', + variableData: '0x1111', + }, { + owner: {substrate: bob.address}, + constData: '0x2222', + variableData: '0x3333', + }, { + owner: {substrate: charlie.address}, + constData: '0x4444', + variableData: '0x5555', + }, + ]; + + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collection, [{key: 'k', permission: {mutable: true, collectionAdmin: true, tokenOwner: false}}]), + )).to.not.be.rejected; + + await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { + NFT: data, + })); + const tokens = await api.query.nonfungible.tokenData.entries(collection); + const json = tokens.map(([, token]) => token.toJSON()); + expect(json).to.be.deep.equal(data); + }); + }); + + it('createMultipleItemsEx with property AdminConst', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + constData: '0x0000', + variableData: '0x1111', + }, { + owner: {substrate: bob.address}, + constData: '0x2222', + variableData: '0x3333', + }, { + owner: {substrate: charlie.address}, + constData: '0x4444', + variableData: '0x5555', + }, + ]; + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collection, [{key: 'k', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}}]), + )).to.not.be.rejected; + + await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { + NFT: data, + })); + + + const tokens = await api.query.nonfungible.tokenData.entries(collection); + const json = tokens.map(([, token]) => token.toJSON()); + expect(json).to.be.deep.equal(data); + }); + }); + + it('createMultipleItemsEx with property itemOwnerOrAdmin', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + constData: '0x0000', + variableData: '0x1111', + }, { + owner: {substrate: bob.address}, + constData: '0x2222', + variableData: '0x3333', + }, { + owner: {substrate: charlie.address}, + constData: '0x4444', + variableData: '0x5555', + }, + ]; + await expect(executeTransaction( + api, + alice, + api.tx.unique.setPropertyPermissions(collection, [{key: 'k', permission: {mutable: true, collectionAdmin: true, tokenOwner: true}}]), + )).to.not.be.rejected; + + await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { + NFT: data, + })); + + + const tokens = await api.query.nonfungible.tokenData.entries(collection); + const json = tokens.map(([, token]) => token.toJSON()); + expect(json).to.be.deep.equal(data); + }); + }); + + it('No editing rights', async () => { + const collection = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: true, collectionAdmin: false, tokenOwner: false}]}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + }, { + owner: {substrate: bob.address}, + }, { + owner: {substrate: charlie.address}, + }, + ]; + + const tx = api.tx.unique.createMultipleItemsEx(collection, {NFT: data}); + await executeTransaction(api, alice, tx); + + const events = await submitTransactionAsync(alice, tx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + }); + }); + + it('User doesnt have editing rights', async () => { + const collection = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}], + propPerm: [{key: 'key1', mutable: false, collectionAdmin: false, tokenOwner: false}]}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + }, { + owner: {substrate: bob.address}, + }, { + owner: {substrate: charlie.address}, + }, + ]; + + const tx = api.tx.unique.createMultipleItemsEx(collection, {NFT: data}); + await executeTransaction(api, alice, tx); + + const events = await submitTransactionAsync(alice, tx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + }); + }); + + it('Adding property without access rights', async () => { + const collection = await createCollectionWithPropsExpectSuccess({properties: [{key: 'key1', value: 'v'}]}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + }, { + owner: {substrate: bob.address}, + }, { + owner: {substrate: charlie.address}, + }, + ]; + + const tx = api.tx.unique.createMultipleItemsEx(collection, {NFT: data}); + await executeTransaction(api, alice, tx); + + const events = await submitTransactionAsync(alice, tx); + const result = getCreateItemsResult(events); + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, [{key: 'key1', value: 'v2'}]), + )).to.be.rejected; + } + }); + }); + + it('Adding more than 64 prps', async () => { + const collection = await createCollectionWithPropsExpectSuccess(); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + }, { + owner: {substrate: bob.address}, + }, { + owner: {substrate: charlie.address}, + }, + ]; + + const tx = api.tx.unique.createMultipleItemsEx(collection, {NFT: data}); + await executeTransaction(api, alice, tx); + + const events = await submitTransactionAsync(alice, tx); + const result = getCreateItemsResult(events); + + const prps = []; + + for (let i = 0; i < 65; i++) { + prps.push({key: `key${i}`, value: `value${i}`}); + } + + await expect(executeTransaction(api, bob, api.tx.unique.setCollectionProperties(collection, prps))).to.be.rejectedWith(/common\.PropertyLimitReached/); + + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, prps), + )).to.be.rejected; + } + }); + }); + + it('Trying to add bigger property than allowed', async () => { + const collection = await createCollectionWithPropsExpectSuccess(); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + }, { + owner: {substrate: bob.address}, + }, { + owner: {substrate: charlie.address}, + }, + ]; + + const tx = api.tx.unique.createMultipleItemsEx(collection, {NFT: data}); + await executeTransaction(api, alice, tx); + + const events = await submitTransactionAsync(alice, tx); + const result = getCreateItemsResult(events); + + const prps = [{key: 'k', value: 'vvvvvv'.repeat(5000)}, {key: 'k2', value: 'vvv'.repeat(5000)}]; + + await expect(executeTransaction(api, bob, api.tx.unique.setCollectionProperties(collection, prps))).to.be.rejectedWith(/common\.NoSpaceForProperty/); + + + for (const elem of result) { + await expect(executeTransaction( + api, + bob, + api.tx.unique.setTokenProperties(elem.collectionId, elem.itemId, prps), + )).to.be.rejected; + } + }); + }); + + it('can initialize multiple NFT with different owners', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + constData: '0x0000', + variableData: '0x1111', + }, { + owner: {substrate: bob.address}, + constData: '0x2222', + variableData: '0x3333', + }, { + owner: {substrate: charlie.address}, + constData: '0x4444', + variableData: '0x5555', + }, + ]; + + await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { + NFT: data, + })); + const tokens = await api.query.nonfungible.tokenData.entries(collection); + const json = tokens.map(([, token]) => token.toJSON()); + expect(json).to.be.deep.equal(data); + }); + }); + + it('can initialize multiple NFT with different owners', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + await usingApi(async (api) => { + const data = [ + { + owner: {substrate: alice.address}, + constData: '0x0000', + variableData: '0x1111', + }, { + owner: {substrate: bob.address}, + constData: '0x2222', + variableData: '0x3333', + }, { + owner: {substrate: charlie.address}, + constData: '0x4444', + variableData: '0x5555', + }, + ]; + + await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { + NFT: data, + })); + const tokens = await api.query.nonfungible.tokenData.entries(collection); + const json = tokens.map(([, token]) => token.toJSON()); + expect(json).to.be.deep.equal(data); + }); + }); + it('fails when trying to set multiple owners when creating multiple refungibles', async () => { const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const alice = privateKey('//Alice'); @@ -72,3 +428,4 @@ }); }); }); +''; --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -201,6 +201,37 @@ return result; } +export function getCreateItemsResult(events: EventRecord[]): CreateItemResult[] { + let success = false; + let collectionId = 0; + let itemId = 0; + let recipient; + + const results : CreateItemResult[] = []; + + events.forEach(({event: {data, method, section}}) => { + // console.log(` ${phase}: ${section}.${method}:: ${data}`); + if (method == 'ExtrinsicSuccess') { + success = true; + } else if ((section == 'common') && (method == 'ItemCreated')) { + collectionId = parseInt(data[0].toString(), 10); + itemId = parseInt(data[1].toString(), 10); + recipient = normalizeAccountId(data[2].toJSON() as any); + + const itemRes: CreateItemResult = { + success, + collectionId, + itemId, + recipient, + }; + + results.push(itemRes); + } + }); + + return results; +} + export function getCreateItemResult(events: EventRecord[]): CreateItemResult { let success = false; let collectionId = 0; @@ -263,12 +294,26 @@ type CollectionMode = Nft | Fungible | ReFungible; +export type Property = { + key: any, + value: any, +}; + +type PropertyPermission = { + key: any, + mutable: boolean; + collectionAdmin: boolean; + tokenOwner: boolean; +} + export type CreateCollectionParams = { mode: CollectionMode, name: string, description: string, tokenPrefix: string, schemaVersion: string, + properties?: Array, + propPerm?: Array }; const defaultCreateCollectionParams: CreateCollectionParams = { @@ -333,6 +378,86 @@ return collectionId; } +export async function createCollectionWithPropsExpectSuccess(params: Partial = {}): Promise { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let collectionId = 0; + await usingApi(async (api) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKey('//Alice'); + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getCreateCollectionResult(events); + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, result.collectionId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + expect(result.collectionId).to.be.equal(collectionCountAfter); + // tslint:disable-next-line:no-unused-expression + expect(collection).to.be.not.null; + expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); + expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicesPublicKey)); + expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); + expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); + expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); + + + collectionId = result.collectionId; + }); + + return collectionId; +} + +export async function createCollectionWithPropsExpectFailure(params: Partial = {}) { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + const collectionId = 0; + await usingApi(async (api) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKey('//Alice'); + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); + }); +} + export async function createCollectionExpectFailure(params: Partial = {}) { const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};